@xcanwin/manyoyo 4.2.8 → 5.0.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 +20 -19
- package/bin/manyoyo.js +232 -104
- package/config.example.json +1 -0
- package/docker/manyoyo.Dockerfile +34 -16
- package/lib/container-run.js +1 -0
- package/lib/init-config.js +1 -1
- package/lib/web/server.js +33 -5
- package/package.json +8 -3
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,26 @@ 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
|
|
127
|
-
manyoyo -n my-dev
|
|
128
|
-
manyoyo
|
|
129
|
-
manyoyo
|
|
125
|
+
manyoyo ps
|
|
126
|
+
manyoyo images
|
|
127
|
+
manyoyo run -n my-dev -x /bin/bash
|
|
128
|
+
manyoyo rm my-dev
|
|
129
|
+
manyoyo serve 3000
|
|
130
|
+
manyoyo serve 3000 -u admin -P 123456
|
|
130
131
|
|
|
131
132
|
# 调试配置与命令拼装
|
|
132
|
-
manyoyo
|
|
133
|
-
manyoyo
|
|
133
|
+
manyoyo config show
|
|
134
|
+
manyoyo config command
|
|
134
135
|
```
|
|
135
136
|
|
|
136
137
|
## 配置
|
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 = {}) {
|
|
@@ -706,6 +706,22 @@ function getContList() {
|
|
|
706
706
|
}
|
|
707
707
|
}
|
|
708
708
|
|
|
709
|
+
function getImageList() {
|
|
710
|
+
try {
|
|
711
|
+
const output = dockerExecArgs(['images', '-a', '--format', '{{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedSince}}\t{{.Size}}']);
|
|
712
|
+
const lines = output
|
|
713
|
+
.split('\n')
|
|
714
|
+
.map(line => line.trim())
|
|
715
|
+
.filter(line => line && line.includes('manyoyo'));
|
|
716
|
+
console.log('REPOSITORY\tTAG\tIMAGE ID\tCREATED\tSIZE');
|
|
717
|
+
if (lines.length > 0) {
|
|
718
|
+
console.log(lines.join('\n'));
|
|
719
|
+
}
|
|
720
|
+
} catch (e) {
|
|
721
|
+
console.log((e && e.stdout) || '');
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
709
725
|
function pruneDanglingImages() {
|
|
710
726
|
console.log(`\n${YELLOW}清理悬空镜像...${NC}`);
|
|
711
727
|
dockerExecArgs(['image', 'prune', '-f'], { stdio: 'inherit' });
|
|
@@ -758,15 +774,79 @@ function normalizeShellFullArgv(argv) {
|
|
|
758
774
|
}
|
|
759
775
|
}
|
|
760
776
|
|
|
777
|
+
function appendArrayOption(command, flags, description) {
|
|
778
|
+
return command.option(
|
|
779
|
+
flags,
|
|
780
|
+
description,
|
|
781
|
+
(value, previous) => [...(previous || []), value],
|
|
782
|
+
[]
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
function applyRunStyleOptions(command, options = {}) {
|
|
787
|
+
const includeRmOnExit = options.includeRmOnExit !== false;
|
|
788
|
+
const includeServePreview = options.includeServePreview === true;
|
|
789
|
+
const includeWebAuthOptions = options.includeWebAuthOptions === true;
|
|
790
|
+
|
|
791
|
+
command
|
|
792
|
+
.option('-r, --run <name>', '加载运行配置 (从 ~/.manyoyo/manyoyo.json 的 runs.<name> 读取)')
|
|
793
|
+
.option('--hp, --host-path <path>', '设置宿主机工作目录 (默认当前路径)')
|
|
794
|
+
.option('-n, --cont-name <name>', '设置容器名称')
|
|
795
|
+
.option('--cp, --cont-path <path>', '设置容器工作目录')
|
|
796
|
+
.option('-m, --cont-mode <mode>', '设置容器嵌套容器模式 (common, dind, sock)')
|
|
797
|
+
.option('--in, --image-name <name>', '指定镜像名称')
|
|
798
|
+
.option('--iv, --image-ver <version>', '指定镜像版本 (格式: x.y.z-后缀,如 1.7.4-common)');
|
|
799
|
+
|
|
800
|
+
appendArrayOption(command, '-e, --env <env>', '设置环境变量 XXX=YYY (可多次使用)');
|
|
801
|
+
appendArrayOption(command, '--ef, --env-file <file>', '设置环境变量通过文件 (仅支持绝对路径,如 /abs/path.env)');
|
|
802
|
+
appendArrayOption(command, '-v, --volume <volume>', '绑定挂载卷 XXX:YYY (可多次使用)');
|
|
803
|
+
appendArrayOption(command, '-p, --port <port>', '设置端口映射 XXX:YYY (可多次使用)');
|
|
804
|
+
|
|
805
|
+
command
|
|
806
|
+
.option('--sp, --shell-prefix <command>', '临时环境变量 (作为-s前缀)')
|
|
807
|
+
.option('-s, --shell <command>', '指定命令执行')
|
|
808
|
+
.option('--ss, --shell-suffix <command>', '指定命令后缀 (追加到-s之后,等价于 -- <args>)')
|
|
809
|
+
.option('-x, --shell-full <command...>', '指定完整命令执行 (代替--sp和-s和--命令)')
|
|
810
|
+
.option('-y, --yolo <cli>', '使AGENT无需确认 (claude/c, gemini/gm, codex/cx, opencode/oc)');
|
|
811
|
+
|
|
812
|
+
if (includeRmOnExit) {
|
|
813
|
+
command.option('--rm-on-exit', '退出后自动删除容器 (一次性模式)');
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
appendArrayOption(command, '-q, --quiet <item>', '静默显示 (可多次使用: cnew,crm,tip,cmd,full)');
|
|
817
|
+
|
|
818
|
+
if (includeServePreview) {
|
|
819
|
+
command
|
|
820
|
+
.option('--serve [listen]', '按 serve 模式解析配置 (支持 port 或 host:port)')
|
|
821
|
+
.option('-u, --user <username>', '网页服务登录用户名 (默认 admin)')
|
|
822
|
+
.option('-P, --pass <password>', '网页服务登录密码 (默认自动生成随机密码)');
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
if (includeWebAuthOptions) {
|
|
826
|
+
command
|
|
827
|
+
.option('-u, --user <username>', '网页服务登录用户名 (默认 admin)')
|
|
828
|
+
.option('-P, --pass <password>', '网页服务登录密码 (默认自动生成随机密码)');
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
return command;
|
|
832
|
+
}
|
|
833
|
+
|
|
761
834
|
async function setupCommander() {
|
|
762
835
|
// Load config file
|
|
763
836
|
const config = loadConfig();
|
|
764
837
|
|
|
765
838
|
const program = new Command();
|
|
839
|
+
program.enablePositionalOptions();
|
|
840
|
+
let selectedAction = '';
|
|
841
|
+
let selectedOptions = {};
|
|
842
|
+
const selectAction = (action, options = {}) => {
|
|
843
|
+
selectedAction = action;
|
|
844
|
+
selectedOptions = options;
|
|
845
|
+
};
|
|
766
846
|
|
|
767
847
|
program
|
|
768
848
|
.name(MANYOYO_NAME)
|
|
769
|
-
.version(BIN_VERSION, '-
|
|
849
|
+
.version(BIN_VERSION, '-v, --version', '显示版本')
|
|
770
850
|
.description('MANYOYO - AI Agent CLI Sandbox\nhttps://github.com/xcanwin/manyoyo')
|
|
771
851
|
.addHelpText('after', `
|
|
772
852
|
配置文件:
|
|
@@ -774,59 +854,98 @@ async function setupCommander() {
|
|
|
774
854
|
~/.manyoyo/run/c.json 运行配置示例
|
|
775
855
|
|
|
776
856
|
路径规则:
|
|
777
|
-
-r name
|
|
778
|
-
--ef /abs/path.env → 绝对路径环境文件
|
|
779
|
-
--ss "<args>"
|
|
780
|
-
-- <args...>
|
|
857
|
+
run -r name → ~/.manyoyo/manyoyo.json 的 runs.name
|
|
858
|
+
run --ef /abs/path.env → 绝对路径环境文件
|
|
859
|
+
run --ss "<args>" → 显式设置命令后缀
|
|
860
|
+
run -- <args...> → 直接透传命令后缀(优先级最高)
|
|
781
861
|
|
|
782
862
|
示例:
|
|
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 多次使用静默选项
|
|
863
|
+
${MANYOYO_NAME} update 更新 MANYOYO 到最新版本
|
|
864
|
+
${MANYOYO_NAME} build --iv ${IMAGE_VERSION_HELP_EXAMPLE} --yes 构建镜像
|
|
865
|
+
${MANYOYO_NAME} init all 从本机 Agent 配置初始化 ~/.manyoyo
|
|
866
|
+
${MANYOYO_NAME} run -r claude 使用 manyoyo.json 的 runs.claude 快速启动
|
|
867
|
+
${MANYOYO_NAME} run -r codex --ss "resume --last" 使用命令后缀
|
|
868
|
+
${MANYOYO_NAME} run -n test --ef /abs/path/myenv.env -y c 使用绝对路径环境变量文件
|
|
869
|
+
${MANYOYO_NAME} run -n test -- -c 恢复之前会话
|
|
870
|
+
${MANYOYO_NAME} run -x "echo 123" 指定命令执行
|
|
871
|
+
${MANYOYO_NAME} serve 3000 -u admin -P 123456 启动带登录认证的网页服务
|
|
872
|
+
${MANYOYO_NAME} serve 0.0.0.0:3000 监听全部网卡,便于局域网访问
|
|
873
|
+
${MANYOYO_NAME} run -n test -q tip -q cmd 多次使用静默选项
|
|
795
874
|
`);
|
|
796
875
|
|
|
797
|
-
|
|
798
|
-
|
|
876
|
+
const runCommand = program.command('run').description('启动或连接容器并执行命令');
|
|
877
|
+
applyRunStyleOptions(runCommand);
|
|
878
|
+
runCommand.action(options => selectAction('run', options));
|
|
879
|
+
|
|
880
|
+
const buildCommand = program.command('build').description('构建 manyoyo 沙箱镜像');
|
|
881
|
+
buildCommand
|
|
799
882
|
.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
883
|
.option('--in, --image-name <name>', '指定镜像名称')
|
|
807
884
|
.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
|
-
|
|
885
|
+
.option('--yes', '所有提示自动确认 (用于CI/脚本)');
|
|
886
|
+
appendArrayOption(buildCommand, '--iba, --image-build-arg <arg>', '构建镜像时传参给dockerfile (可多次使用)');
|
|
887
|
+
buildCommand.action(options => selectAction('build', options));
|
|
888
|
+
|
|
889
|
+
const removeCommand = program.command('rm <name>').description('删除指定容器');
|
|
890
|
+
removeCommand
|
|
891
|
+
.option('-r, --run <name>', '加载运行配置 (从 ~/.manyoyo/manyoyo.json 的 runs.<name> 读取)')
|
|
892
|
+
.action((name, options) => selectAction('rm', { ...options, contName: name }));
|
|
893
|
+
|
|
894
|
+
program.command('ps')
|
|
895
|
+
.description('列举容器')
|
|
896
|
+
.action(() => selectAction('ps', { contList: true }));
|
|
897
|
+
|
|
898
|
+
program.command('images')
|
|
899
|
+
.description('列举镜像')
|
|
900
|
+
.action(() => selectAction('images', { imageList: true }));
|
|
901
|
+
|
|
902
|
+
const serveCommand = program.command('serve [listen]').description('启动网页交互服务 (默认 127.0.0.1:3000)');
|
|
903
|
+
applyRunStyleOptions(serveCommand, { includeRmOnExit: false, includeWebAuthOptions: true });
|
|
904
|
+
serveCommand.action((listen, options) => {
|
|
905
|
+
selectAction('serve', {
|
|
906
|
+
...options,
|
|
907
|
+
server: listen === undefined ? true : listen,
|
|
908
|
+
serverUser: options.user,
|
|
909
|
+
serverPass: options.pass
|
|
910
|
+
});
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
const configCommand = program.command('config').description('查看解析后的配置或命令');
|
|
914
|
+
const configShowCommand = configCommand.command('show').description('显示最终生效配置并退出');
|
|
915
|
+
applyRunStyleOptions(configShowCommand, { includeRmOnExit: false, includeServePreview: true });
|
|
916
|
+
configShowCommand.action(options => {
|
|
917
|
+
const finalOptions = {
|
|
918
|
+
...options,
|
|
919
|
+
showConfig: true
|
|
920
|
+
};
|
|
921
|
+
if (options.serve !== undefined) {
|
|
922
|
+
finalOptions.server = options.serve;
|
|
923
|
+
finalOptions.serverUser = options.user;
|
|
924
|
+
finalOptions.serverPass = options.pass;
|
|
925
|
+
}
|
|
926
|
+
selectAction('config-show', finalOptions);
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
const configRunCommand = configCommand.command('command').description('显示将执行的 docker run 命令并退出');
|
|
930
|
+
applyRunStyleOptions(configRunCommand, { includeRmOnExit: false });
|
|
931
|
+
configRunCommand.action(options => selectAction('config-command', options));
|
|
932
|
+
|
|
933
|
+
const initCommand = program.command('init [agents]').description('初始化 Agent 配置到 ~/.manyoyo');
|
|
934
|
+
initCommand
|
|
827
935
|
.option('--yes', '所有提示自动确认 (用于CI/脚本)')
|
|
828
|
-
.
|
|
829
|
-
|
|
936
|
+
.action((agents, options) => selectAction('init', { ...options, initConfig: agents === undefined ? 'all' : agents }));
|
|
937
|
+
|
|
938
|
+
program.command('update')
|
|
939
|
+
.description('更新 MANYOYO(若检测为本地 file 安装则跳过)')
|
|
940
|
+
.action(() => selectAction('update', { update: true }));
|
|
941
|
+
|
|
942
|
+
program.command('install <name>')
|
|
943
|
+
.description(`安装${MANYOYO_NAME}命令 (docker-cli-plugin)`)
|
|
944
|
+
.action(name => selectAction('install', { install: name }));
|
|
945
|
+
|
|
946
|
+
program.command('prune')
|
|
947
|
+
.description('清理悬空镜像和 <none> 镜像')
|
|
948
|
+
.action(() => selectAction('prune', { imageRemove: true }));
|
|
830
949
|
|
|
831
950
|
// Docker CLI plugin metadata check
|
|
832
951
|
if (maybeHandleDockerPluginMetadata(process.argv)) {
|
|
@@ -841,25 +960,31 @@ async function setupCommander() {
|
|
|
841
960
|
program.help();
|
|
842
961
|
}
|
|
843
962
|
|
|
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
963
|
// Pre-handle -x/--shell-full: treat all following args as a single command
|
|
853
964
|
normalizeShellFullArgv(process.argv);
|
|
854
965
|
|
|
855
966
|
// Parse arguments
|
|
856
967
|
program.allowUnknownOption(false);
|
|
857
|
-
program.
|
|
968
|
+
await program.parseAsync(process.argv);
|
|
858
969
|
|
|
859
|
-
|
|
970
|
+
if (!selectedAction) {
|
|
971
|
+
program.help();
|
|
972
|
+
}
|
|
860
973
|
|
|
861
|
-
|
|
862
|
-
|
|
974
|
+
const options = selectedOptions;
|
|
975
|
+
const yesMode = Boolean(options.yes);
|
|
976
|
+
const isBuildMode = selectedAction === 'build';
|
|
977
|
+
const isRemoveMode = selectedAction === 'rm';
|
|
978
|
+
const isPsMode = selectedAction === 'ps';
|
|
979
|
+
const isImagesMode = selectedAction === 'images';
|
|
980
|
+
const isPruneMode = selectedAction === 'prune';
|
|
981
|
+
const isShowConfigMode = selectedAction === 'config-show';
|
|
982
|
+
const isShowCommandMode = selectedAction === 'config-command';
|
|
983
|
+
const isServerMode = options.server !== undefined;
|
|
984
|
+
|
|
985
|
+
const noDockerActions = new Set(['init', 'update', 'install', 'config-show']);
|
|
986
|
+
if (!noDockerActions.has(selectedAction)) {
|
|
987
|
+
ensureDocker();
|
|
863
988
|
}
|
|
864
989
|
|
|
865
990
|
if (options.update) {
|
|
@@ -869,7 +994,7 @@ async function setupCommander() {
|
|
|
869
994
|
|
|
870
995
|
if (options.initConfig !== undefined) {
|
|
871
996
|
await initAgentConfigs(options.initConfig, {
|
|
872
|
-
yesMode
|
|
997
|
+
yesMode,
|
|
873
998
|
askQuestion,
|
|
874
999
|
loadConfig,
|
|
875
1000
|
supportedAgents: SUPPORTED_INIT_AGENTS,
|
|
@@ -936,6 +1061,9 @@ async function setupCommander() {
|
|
|
936
1061
|
const volumeList = mergeArrayConfig(config.volumes, runConfig.volumes, options.volume);
|
|
937
1062
|
volumeList.forEach(v => addVolume(v));
|
|
938
1063
|
|
|
1064
|
+
const portList = mergeArrayConfig(config.ports, runConfig.ports, options.port);
|
|
1065
|
+
portList.forEach(p => addPort(p));
|
|
1066
|
+
|
|
939
1067
|
const buildArgList = mergeArrayConfig(config.imageBuildArgs, runConfig.imageBuildArgs, options.imageBuildArg);
|
|
940
1068
|
buildArgList.forEach(arg => addImageBuildArg(arg));
|
|
941
1069
|
|
|
@@ -968,8 +1096,7 @@ async function setupCommander() {
|
|
|
968
1096
|
RM_ON_EXIT = true;
|
|
969
1097
|
}
|
|
970
1098
|
|
|
971
|
-
if (
|
|
972
|
-
SERVER_MODE = true;
|
|
1099
|
+
if (isServerMode) {
|
|
973
1100
|
const serverListen = parseServerListen(options.server);
|
|
974
1101
|
SERVER_HOST = serverListen.host;
|
|
975
1102
|
SERVER_PORT = serverListen.port;
|
|
@@ -986,11 +1113,11 @@ async function setupCommander() {
|
|
|
986
1113
|
SERVER_AUTH_PASS_AUTO = false;
|
|
987
1114
|
}
|
|
988
1115
|
|
|
989
|
-
if (
|
|
1116
|
+
if (isServerMode) {
|
|
990
1117
|
ensureWebServerAuthCredentials();
|
|
991
1118
|
}
|
|
992
1119
|
|
|
993
|
-
if (
|
|
1120
|
+
if (isShowConfigMode) {
|
|
994
1121
|
const finalConfig = {
|
|
995
1122
|
hostPath: HOST_PATH,
|
|
996
1123
|
containerName: CONTAINER_NAME,
|
|
@@ -1000,6 +1127,7 @@ async function setupCommander() {
|
|
|
1000
1127
|
envFile: envFileList,
|
|
1001
1128
|
env: envMap,
|
|
1002
1129
|
volumes: volumeList,
|
|
1130
|
+
ports: portList,
|
|
1003
1131
|
imageBuildArgs: buildArgList,
|
|
1004
1132
|
containerMode: contModeValue || "",
|
|
1005
1133
|
shellPrefix: EXEC_COMMAND_PREFIX.trim(),
|
|
@@ -1007,9 +1135,9 @@ async function setupCommander() {
|
|
|
1007
1135
|
shellSuffix: EXEC_COMMAND_SUFFIX || "",
|
|
1008
1136
|
yolo: yoloValue || "",
|
|
1009
1137
|
quiet: quietValue || [],
|
|
1010
|
-
server:
|
|
1011
|
-
serverHost:
|
|
1012
|
-
serverPort:
|
|
1138
|
+
server: isServerMode,
|
|
1139
|
+
serverHost: isServerMode ? SERVER_HOST : null,
|
|
1140
|
+
serverPort: isServerMode ? SERVER_PORT : null,
|
|
1013
1141
|
serverUser: SERVER_AUTH_USER || "",
|
|
1014
1142
|
serverPass: SERVER_AUTH_PASS || "",
|
|
1015
1143
|
exec: {
|
|
@@ -1024,20 +1152,21 @@ async function setupCommander() {
|
|
|
1024
1152
|
process.exit(0);
|
|
1025
1153
|
}
|
|
1026
1154
|
|
|
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); }
|
|
1155
|
+
if (isPsMode) { getContList(); process.exit(0); }
|
|
1156
|
+
if (isImagesMode) { getImageList(); process.exit(0); }
|
|
1157
|
+
if (isPruneMode) { pruneDanglingImages(); process.exit(0); }
|
|
1158
|
+
if (selectedAction === 'install') { installManyoyo(options.install); process.exit(0); }
|
|
1036
1159
|
|
|
1037
|
-
return
|
|
1160
|
+
return {
|
|
1161
|
+
yesMode,
|
|
1162
|
+
isBuildMode,
|
|
1163
|
+
isRemoveMode,
|
|
1164
|
+
isShowCommandMode,
|
|
1165
|
+
isServerMode
|
|
1166
|
+
};
|
|
1038
1167
|
}
|
|
1039
1168
|
|
|
1040
|
-
function createRuntimeContext() {
|
|
1169
|
+
function createRuntimeContext(modeState = {}) {
|
|
1041
1170
|
return {
|
|
1042
1171
|
containerName: CONTAINER_NAME,
|
|
1043
1172
|
hostPath: HOST_PATH,
|
|
@@ -1050,10 +1179,11 @@ function createRuntimeContext() {
|
|
|
1050
1179
|
contModeArgs: CONT_MODE_ARGS,
|
|
1051
1180
|
containerEnvs: CONTAINER_ENVS,
|
|
1052
1181
|
containerVolumes: CONTAINER_VOLUMES,
|
|
1182
|
+
containerPorts: CONTAINER_PORTS,
|
|
1053
1183
|
quiet: QUIET,
|
|
1054
|
-
showCommand:
|
|
1184
|
+
showCommand: Boolean(modeState.isShowCommandMode),
|
|
1055
1185
|
rmOnExit: RM_ON_EXIT,
|
|
1056
|
-
serverMode:
|
|
1186
|
+
serverMode: Boolean(modeState.isServerMode),
|
|
1057
1187
|
serverHost: SERVER_HOST,
|
|
1058
1188
|
serverPort: SERVER_PORT,
|
|
1059
1189
|
serverAuthUser: SERVER_AUTH_USER,
|
|
@@ -1063,10 +1193,6 @@ function createRuntimeContext() {
|
|
|
1063
1193
|
}
|
|
1064
1194
|
|
|
1065
1195
|
function handleRemoveContainer(runtime) {
|
|
1066
|
-
if (!SHOULD_REMOVE) {
|
|
1067
|
-
return false;
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
1196
|
try {
|
|
1071
1197
|
if (containerExists(runtime.containerName)) {
|
|
1072
1198
|
removeContainer(runtime.containerName);
|
|
@@ -1076,7 +1202,6 @@ function handleRemoveContainer(runtime) {
|
|
|
1076
1202
|
} catch (e) {
|
|
1077
1203
|
console.log(`${RED}⚠️ 错误: 未找到名为 ${runtime.containerName} 的容器。${NC}`);
|
|
1078
1204
|
}
|
|
1079
|
-
return true;
|
|
1080
1205
|
}
|
|
1081
1206
|
|
|
1082
1207
|
function validateHostPath(runtime) {
|
|
@@ -1180,6 +1305,7 @@ function buildDockerRunArgs(runtime) {
|
|
|
1180
1305
|
contModeArgs: runtime.contModeArgs,
|
|
1181
1306
|
containerEnvs: runtime.containerEnvs,
|
|
1182
1307
|
containerVolumes: runtime.containerVolumes,
|
|
1308
|
+
containerPorts: runtime.containerPorts,
|
|
1183
1309
|
defaultCommand: runtime.execCommand
|
|
1184
1310
|
});
|
|
1185
1311
|
}
|
|
@@ -1343,6 +1469,7 @@ async function runWebServerMode(runtime) {
|
|
|
1343
1469
|
contModeArgs: runtime.contModeArgs,
|
|
1344
1470
|
containerEnvs: runtime.containerEnvs,
|
|
1345
1471
|
containerVolumes: runtime.containerVolumes,
|
|
1472
|
+
containerPorts: runtime.containerPorts,
|
|
1346
1473
|
validateHostPath: () => validateHostPath(runtime),
|
|
1347
1474
|
formatDate,
|
|
1348
1475
|
isValidContainerName,
|
|
@@ -1367,8 +1494,8 @@ async function runWebServerMode(runtime) {
|
|
|
1367
1494
|
async function main() {
|
|
1368
1495
|
try {
|
|
1369
1496
|
// 1. Setup commander and parse arguments
|
|
1370
|
-
await setupCommander();
|
|
1371
|
-
const runtime = createRuntimeContext();
|
|
1497
|
+
const modeState = await setupCommander();
|
|
1498
|
+
const runtime = createRuntimeContext(modeState);
|
|
1372
1499
|
|
|
1373
1500
|
// 2. Start web server mode
|
|
1374
1501
|
if (runtime.serverMode) {
|
|
@@ -1377,7 +1504,7 @@ async function main() {
|
|
|
1377
1504
|
}
|
|
1378
1505
|
|
|
1379
1506
|
// 3. Handle image build operation
|
|
1380
|
-
if (
|
|
1507
|
+
if (modeState.isBuildMode) {
|
|
1381
1508
|
await buildImage({
|
|
1382
1509
|
imageBuildArgs: IMAGE_BUILD_ARGS,
|
|
1383
1510
|
imageName: runtime.imageName,
|
|
@@ -1386,7 +1513,7 @@ async function main() {
|
|
|
1386
1513
|
imageVersionBase: IMAGE_VERSION_BASE,
|
|
1387
1514
|
parseImageVersionTag,
|
|
1388
1515
|
manyoyoName: MANYOYO_NAME,
|
|
1389
|
-
yesMode:
|
|
1516
|
+
yesMode: Boolean(modeState.yesMode),
|
|
1390
1517
|
dockerCmd: DOCKER_CMD,
|
|
1391
1518
|
rootDir: path.join(__dirname, '..'),
|
|
1392
1519
|
loadConfig,
|
|
@@ -1399,7 +1526,8 @@ async function main() {
|
|
|
1399
1526
|
}
|
|
1400
1527
|
|
|
1401
1528
|
// 4. Handle remove container operation
|
|
1402
|
-
if (
|
|
1529
|
+
if (modeState.isRemoveMode) {
|
|
1530
|
+
handleRemoveContainer(runtime);
|
|
1403
1531
|
return;
|
|
1404
1532
|
}
|
|
1405
1533
|
|
package/config.example.json
CHANGED
|
@@ -136,6 +136,8 @@ RUN <<EOX
|
|
|
136
136
|
# 配置 node.js
|
|
137
137
|
npm config set registry=${NPM_REGISTRY}
|
|
138
138
|
|
|
139
|
+
export GIT_SSL_NO_VERIFY=$GIT_SSL_NO_VERIFY
|
|
140
|
+
|
|
139
141
|
# 安装 LSP服务(python、typescript)
|
|
140
142
|
npm install -g pyright typescript-language-server typescript
|
|
141
143
|
|
|
@@ -153,7 +155,7 @@ RUN <<EOX
|
|
|
153
155
|
}
|
|
154
156
|
}
|
|
155
157
|
EOF
|
|
156
|
-
|
|
158
|
+
claude plugin marketplace add https://github.com/anthropics/claude-plugins-official
|
|
157
159
|
claude plugin install ralph-loop@claude-plugins-official
|
|
158
160
|
claude plugin install typescript-lsp@claude-plugins-official
|
|
159
161
|
claude plugin install pyright-lsp@claude-plugins-official
|
|
@@ -163,11 +165,39 @@ EOF
|
|
|
163
165
|
case ",$TOOL," in *,full,*|*,java,*)
|
|
164
166
|
claude plugin install jdtls-lsp@claude-plugins-official
|
|
165
167
|
;; esac
|
|
166
|
-
|
|
167
|
-
GIT_SSL_NO_VERIFY=$GIT_SSL_NO_VERIFY claude plugin marketplace add https://github.com/anthropics/skills
|
|
168
|
-
claude plugin install example-skills@anthropic-agent-skills
|
|
168
|
+
claude plugin marketplace add https://github.com/anthropics/skills
|
|
169
169
|
claude plugin install document-skills@anthropic-agent-skills
|
|
170
170
|
|
|
171
|
+
# 安装 Codex CLI
|
|
172
|
+
npm install -g @openai/codex
|
|
173
|
+
mkdir -p ~/.codex
|
|
174
|
+
cat > ~/.codex/config.toml <<EOF
|
|
175
|
+
check_for_update_on_startup = false
|
|
176
|
+
|
|
177
|
+
[analytics]
|
|
178
|
+
enabled = false
|
|
179
|
+
EOF
|
|
180
|
+
mkdir -p "$HOME/.codex/skills"
|
|
181
|
+
git clone --depth 1 https://github.com/openai/skills.git /tmp/openai-skills
|
|
182
|
+
cp -a /tmp/openai-skills/skills/.system "$HOME/.codex/skills/.system"
|
|
183
|
+
rm -rf /tmp/openai-skills
|
|
184
|
+
CODEX_INSTALLER="$HOME/.codex/skills/.system/skill-installer/scripts/install-skill-from-github.py"
|
|
185
|
+
python "$CODEX_INSTALLER" --repo openai/skills --path \
|
|
186
|
+
skills/.curated/doc \
|
|
187
|
+
skills/.curated/spreadsheet \
|
|
188
|
+
skills/.curated/pdf \
|
|
189
|
+
skills/.curated/security-best-practices \
|
|
190
|
+
skills/.curated/security-threat-model
|
|
191
|
+
python "$CODEX_INSTALLER" --repo anthropics/skills --path \
|
|
192
|
+
skills/pptx \
|
|
193
|
+
skills/theme-factory \
|
|
194
|
+
skills/frontend-design \
|
|
195
|
+
skills/canvas-design \
|
|
196
|
+
skills/doc-coauthoring \
|
|
197
|
+
skills/internal-comms \
|
|
198
|
+
skills/web-artifacts-builder \
|
|
199
|
+
skills/webapp-testing
|
|
200
|
+
|
|
171
201
|
# 安装 Gemini CLI
|
|
172
202
|
case ",$TOOL," in *,full,*|*,gemini,*)
|
|
173
203
|
npm install -g @google/gemini-cli
|
|
@@ -198,18 +228,6 @@ EOF
|
|
|
198
228
|
EOF
|
|
199
229
|
;; esac
|
|
200
230
|
|
|
201
|
-
# 安装 Codex CLI
|
|
202
|
-
case ",$TOOL," in *,full,*|*,codex,*)
|
|
203
|
-
npm install -g @openai/codex
|
|
204
|
-
mkdir -p ~/.codex
|
|
205
|
-
cat > ~/.codex/config.toml <<EOF
|
|
206
|
-
check_for_update_on_startup = false
|
|
207
|
-
|
|
208
|
-
[analytics]
|
|
209
|
-
enabled = false
|
|
210
|
-
EOF
|
|
211
|
-
;; esac
|
|
212
|
-
|
|
213
231
|
# 安装 OpenCode CLI
|
|
214
232
|
case ",$TOOL," in *,full,*|*,opencode,*)
|
|
215
233
|
npm install -g opencode-ai
|
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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xcanwin/manyoyo",
|
|
3
|
-
"version": "
|
|
4
|
-
"imageVersion": "1.8.
|
|
3
|
+
"version": "5.0.2",
|
|
4
|
+
"imageVersion": "1.8.1-common",
|
|
5
5
|
"description": "AI Agent CLI Security Sandbox for Docker and Podman",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"manyoyo",
|
|
@@ -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"
|