@xcanwin/manyoyo 4.2.6 → 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 +236 -104
- package/config.example.json +1 -0
- package/lib/container-run.js +1 -0
- package/lib/image-build.js +9 -8
- 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 = {}) {
|
|
@@ -642,10 +642,18 @@ function installManyoyo(name) {
|
|
|
642
642
|
|
|
643
643
|
function updateManyoyo() {
|
|
644
644
|
let isLocalFileInstall = false;
|
|
645
|
+
let currentVersion = 'unknown';
|
|
646
|
+
|
|
645
647
|
try {
|
|
646
648
|
const listOutput = runCmd('npm', ['ls', '-g', '@xcanwin/manyoyo', '--json', '--long'], { stdio: 'pipe' });
|
|
647
649
|
const listJson = JSON.parse(listOutput || '{}');
|
|
648
650
|
const dep = listJson && listJson.dependencies && listJson.dependencies['@xcanwin/manyoyo'];
|
|
651
|
+
|
|
652
|
+
// 获取当前版本
|
|
653
|
+
if (dep && dep.version) {
|
|
654
|
+
currentVersion = dep.version;
|
|
655
|
+
}
|
|
656
|
+
|
|
649
657
|
const resolved = dep && typeof dep.resolved === 'string' ? dep.resolved : '';
|
|
650
658
|
const depPath = dep && typeof dep.path === 'string' ? dep.path : '';
|
|
651
659
|
|
|
@@ -664,9 +672,28 @@ function updateManyoyo() {
|
|
|
664
672
|
return;
|
|
665
673
|
}
|
|
666
674
|
|
|
675
|
+
console.log(`${CYAN}🔄 当前版本: ${currentVersion}${NC}`);
|
|
667
676
|
console.log(`${CYAN}🔄 正在更新 ${MANYOYO_NAME} 到最新版本...${NC}`);
|
|
668
677
|
runCmd('npm', ['update', '-g', '@xcanwin/manyoyo'], { stdio: 'inherit' });
|
|
669
|
-
|
|
678
|
+
|
|
679
|
+
// 升级后获取新版本
|
|
680
|
+
let newVersion = 'unknown';
|
|
681
|
+
try {
|
|
682
|
+
const listOutput = runCmd('npm', ['ls', '-g', '@xcanwin/manyoyo', '--json'], { stdio: 'pipe' });
|
|
683
|
+
const listJson = JSON.parse(listOutput || '{}');
|
|
684
|
+
const dep = listJson && listJson.dependencies && listJson.dependencies['@xcanwin/manyoyo'];
|
|
685
|
+
if (dep && dep.version) {
|
|
686
|
+
newVersion = dep.version;
|
|
687
|
+
}
|
|
688
|
+
} catch (e) {
|
|
689
|
+
// ignore
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
if (currentVersion === newVersion) {
|
|
693
|
+
console.log(`${GREEN}✅ 已是最新版本 ${newVersion}${NC}`);
|
|
694
|
+
} else {
|
|
695
|
+
console.log(`${GREEN}✅ 更新完成: ${currentVersion} → ${newVersion}${NC}`);
|
|
696
|
+
}
|
|
670
697
|
}
|
|
671
698
|
|
|
672
699
|
function getContList() {
|
|
@@ -731,11 +758,74 @@ function normalizeShellFullArgv(argv) {
|
|
|
731
758
|
}
|
|
732
759
|
}
|
|
733
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
|
+
|
|
734
818
|
async function setupCommander() {
|
|
735
819
|
// Load config file
|
|
736
820
|
const config = loadConfig();
|
|
737
821
|
|
|
738
822
|
const program = new Command();
|
|
823
|
+
let selectedAction = '';
|
|
824
|
+
let selectedOptions = {};
|
|
825
|
+
const selectAction = (action, options = {}) => {
|
|
826
|
+
selectedAction = action;
|
|
827
|
+
selectedOptions = options;
|
|
828
|
+
};
|
|
739
829
|
|
|
740
830
|
program
|
|
741
831
|
.name(MANYOYO_NAME)
|
|
@@ -747,59 +837,94 @@ async function setupCommander() {
|
|
|
747
837
|
~/.manyoyo/run/c.json 运行配置示例
|
|
748
838
|
|
|
749
839
|
路径规则:
|
|
750
|
-
-r name
|
|
751
|
-
--ef /abs/path.env → 绝对路径环境文件
|
|
752
|
-
--ss "<args>"
|
|
753
|
-
-- <args...>
|
|
840
|
+
run -r name → ~/.manyoyo/manyoyo.json 的 runs.name
|
|
841
|
+
run --ef /abs/path.env → 绝对路径环境文件
|
|
842
|
+
run --ss "<args>" → 显式设置命令后缀
|
|
843
|
+
run -- <args...> → 直接透传命令后缀(优先级最高)
|
|
754
844
|
|
|
755
845
|
示例:
|
|
756
|
-
${MANYOYO_NAME}
|
|
757
|
-
${MANYOYO_NAME}
|
|
758
|
-
${MANYOYO_NAME}
|
|
759
|
-
${MANYOYO_NAME} -r claude
|
|
760
|
-
${MANYOYO_NAME} -r codex --ss "resume --last"
|
|
761
|
-
${MANYOYO_NAME} -n test --ef /abs/path/myenv.env -y c 使用绝对路径环境变量文件
|
|
762
|
-
${MANYOYO_NAME} -n test -- -c
|
|
763
|
-
${MANYOYO_NAME} -x echo 123
|
|
764
|
-
${MANYOYO_NAME}
|
|
765
|
-
${MANYOYO_NAME}
|
|
766
|
-
${MANYOYO_NAME}
|
|
767
|
-
${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 多次使用静默选项
|
|
768
857
|
`);
|
|
769
858
|
|
|
770
|
-
|
|
771
|
-
|
|
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
|
|
772
865
|
.option('-r, --run <name>', '加载运行配置 (从 ~/.manyoyo/manyoyo.json 的 runs.<name> 读取)')
|
|
773
|
-
.option('--hp, --host-path <path>', '设置宿主机工作目录 (默认当前路径)')
|
|
774
|
-
.option('-n, --cont-name <name>', '设置容器名称')
|
|
775
|
-
.option('--cp, --cont-path <path>', '设置容器工作目录')
|
|
776
|
-
.option('-l, --cont-list', '列举容器')
|
|
777
|
-
.option('--crm, --cont-remove', '删除-n指定容器')
|
|
778
|
-
.option('-m, --cont-mode <mode>', '设置容器嵌套容器模式 (common, dind, sock)')
|
|
779
866
|
.option('--in, --image-name <name>', '指定镜像名称')
|
|
780
867
|
.option('--iv, --image-ver <version>', '指定镜像版本 (格式: x.y.z-后缀,如 1.7.4-common)')
|
|
781
|
-
.option('--
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
.option('
|
|
788
|
-
.
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
.
|
|
792
|
-
.
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
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
|
|
800
914
|
.option('--yes', '所有提示自动确认 (用于CI/脚本)')
|
|
801
|
-
.
|
|
802
|
-
|
|
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 }));
|
|
803
928
|
|
|
804
929
|
// Docker CLI plugin metadata check
|
|
805
930
|
if (maybeHandleDockerPluginMetadata(process.argv)) {
|
|
@@ -814,25 +939,30 @@ async function setupCommander() {
|
|
|
814
939
|
program.help();
|
|
815
940
|
}
|
|
816
941
|
|
|
817
|
-
const isInitConfigMode = process.argv.some(arg => arg === '--init-config' || arg.startsWith('--init-config='));
|
|
818
|
-
const isUpdateMode = process.argv.includes('--update');
|
|
819
|
-
// init-config/update 只处理本地文件或 npm,不依赖 docker/podman
|
|
820
|
-
if (!isInitConfigMode && !isUpdateMode) {
|
|
821
|
-
// Ensure docker/podman is available
|
|
822
|
-
ensureDocker();
|
|
823
|
-
}
|
|
824
|
-
|
|
825
942
|
// Pre-handle -x/--shell-full: treat all following args as a single command
|
|
826
943
|
normalizeShellFullArgv(process.argv);
|
|
827
944
|
|
|
828
945
|
// Parse arguments
|
|
829
946
|
program.allowUnknownOption(false);
|
|
830
|
-
program.
|
|
947
|
+
await program.parseAsync(process.argv);
|
|
948
|
+
|
|
949
|
+
if (!selectedAction) {
|
|
950
|
+
program.help();
|
|
951
|
+
}
|
|
831
952
|
|
|
832
|
-
const options =
|
|
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;
|
|
833
962
|
|
|
834
|
-
|
|
835
|
-
|
|
963
|
+
const noDockerActions = new Set(['init', 'update', 'install', 'config-show']);
|
|
964
|
+
if (!noDockerActions.has(selectedAction)) {
|
|
965
|
+
ensureDocker();
|
|
836
966
|
}
|
|
837
967
|
|
|
838
968
|
if (options.update) {
|
|
@@ -842,7 +972,7 @@ async function setupCommander() {
|
|
|
842
972
|
|
|
843
973
|
if (options.initConfig !== undefined) {
|
|
844
974
|
await initAgentConfigs(options.initConfig, {
|
|
845
|
-
yesMode
|
|
975
|
+
yesMode,
|
|
846
976
|
askQuestion,
|
|
847
977
|
loadConfig,
|
|
848
978
|
supportedAgents: SUPPORTED_INIT_AGENTS,
|
|
@@ -909,6 +1039,9 @@ async function setupCommander() {
|
|
|
909
1039
|
const volumeList = mergeArrayConfig(config.volumes, runConfig.volumes, options.volume);
|
|
910
1040
|
volumeList.forEach(v => addVolume(v));
|
|
911
1041
|
|
|
1042
|
+
const portList = mergeArrayConfig(config.ports, runConfig.ports, options.port);
|
|
1043
|
+
portList.forEach(p => addPort(p));
|
|
1044
|
+
|
|
912
1045
|
const buildArgList = mergeArrayConfig(config.imageBuildArgs, runConfig.imageBuildArgs, options.imageBuildArg);
|
|
913
1046
|
buildArgList.forEach(arg => addImageBuildArg(arg));
|
|
914
1047
|
|
|
@@ -941,8 +1074,7 @@ async function setupCommander() {
|
|
|
941
1074
|
RM_ON_EXIT = true;
|
|
942
1075
|
}
|
|
943
1076
|
|
|
944
|
-
if (
|
|
945
|
-
SERVER_MODE = true;
|
|
1077
|
+
if (isServerMode) {
|
|
946
1078
|
const serverListen = parseServerListen(options.server);
|
|
947
1079
|
SERVER_HOST = serverListen.host;
|
|
948
1080
|
SERVER_PORT = serverListen.port;
|
|
@@ -959,11 +1091,11 @@ async function setupCommander() {
|
|
|
959
1091
|
SERVER_AUTH_PASS_AUTO = false;
|
|
960
1092
|
}
|
|
961
1093
|
|
|
962
|
-
if (
|
|
1094
|
+
if (isServerMode) {
|
|
963
1095
|
ensureWebServerAuthCredentials();
|
|
964
1096
|
}
|
|
965
1097
|
|
|
966
|
-
if (
|
|
1098
|
+
if (isShowConfigMode) {
|
|
967
1099
|
const finalConfig = {
|
|
968
1100
|
hostPath: HOST_PATH,
|
|
969
1101
|
containerName: CONTAINER_NAME,
|
|
@@ -973,6 +1105,7 @@ async function setupCommander() {
|
|
|
973
1105
|
envFile: envFileList,
|
|
974
1106
|
env: envMap,
|
|
975
1107
|
volumes: volumeList,
|
|
1108
|
+
ports: portList,
|
|
976
1109
|
imageBuildArgs: buildArgList,
|
|
977
1110
|
containerMode: contModeValue || "",
|
|
978
1111
|
shellPrefix: EXEC_COMMAND_PREFIX.trim(),
|
|
@@ -980,9 +1113,9 @@ async function setupCommander() {
|
|
|
980
1113
|
shellSuffix: EXEC_COMMAND_SUFFIX || "",
|
|
981
1114
|
yolo: yoloValue || "",
|
|
982
1115
|
quiet: quietValue || [],
|
|
983
|
-
server:
|
|
984
|
-
serverHost:
|
|
985
|
-
serverPort:
|
|
1116
|
+
server: isServerMode,
|
|
1117
|
+
serverHost: isServerMode ? SERVER_HOST : null,
|
|
1118
|
+
serverPort: isServerMode ? SERVER_PORT : null,
|
|
986
1119
|
serverUser: SERVER_AUTH_USER || "",
|
|
987
1120
|
serverPass: SERVER_AUTH_PASS || "",
|
|
988
1121
|
exec: {
|
|
@@ -997,20 +1130,20 @@ async function setupCommander() {
|
|
|
997
1130
|
process.exit(0);
|
|
998
1131
|
}
|
|
999
1132
|
|
|
1000
|
-
if (
|
|
1001
|
-
|
|
1002
|
-
}
|
|
1133
|
+
if (isListMode) { getContList(); process.exit(0); }
|
|
1134
|
+
if (isPruneMode) { pruneDanglingImages(); process.exit(0); }
|
|
1135
|
+
if (selectedAction === 'install') { installManyoyo(options.install); process.exit(0); }
|
|
1003
1136
|
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1137
|
+
return {
|
|
1138
|
+
yesMode,
|
|
1139
|
+
isBuildMode,
|
|
1140
|
+
isRemoveMode,
|
|
1141
|
+
isShowCommandMode,
|
|
1142
|
+
isServerMode
|
|
1143
|
+
};
|
|
1011
1144
|
}
|
|
1012
1145
|
|
|
1013
|
-
function createRuntimeContext() {
|
|
1146
|
+
function createRuntimeContext(modeState = {}) {
|
|
1014
1147
|
return {
|
|
1015
1148
|
containerName: CONTAINER_NAME,
|
|
1016
1149
|
hostPath: HOST_PATH,
|
|
@@ -1023,10 +1156,11 @@ function createRuntimeContext() {
|
|
|
1023
1156
|
contModeArgs: CONT_MODE_ARGS,
|
|
1024
1157
|
containerEnvs: CONTAINER_ENVS,
|
|
1025
1158
|
containerVolumes: CONTAINER_VOLUMES,
|
|
1159
|
+
containerPorts: CONTAINER_PORTS,
|
|
1026
1160
|
quiet: QUIET,
|
|
1027
|
-
showCommand:
|
|
1161
|
+
showCommand: Boolean(modeState.isShowCommandMode),
|
|
1028
1162
|
rmOnExit: RM_ON_EXIT,
|
|
1029
|
-
serverMode:
|
|
1163
|
+
serverMode: Boolean(modeState.isServerMode),
|
|
1030
1164
|
serverHost: SERVER_HOST,
|
|
1031
1165
|
serverPort: SERVER_PORT,
|
|
1032
1166
|
serverAuthUser: SERVER_AUTH_USER,
|
|
@@ -1036,10 +1170,6 @@ function createRuntimeContext() {
|
|
|
1036
1170
|
}
|
|
1037
1171
|
|
|
1038
1172
|
function handleRemoveContainer(runtime) {
|
|
1039
|
-
if (!SHOULD_REMOVE) {
|
|
1040
|
-
return false;
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
1173
|
try {
|
|
1044
1174
|
if (containerExists(runtime.containerName)) {
|
|
1045
1175
|
removeContainer(runtime.containerName);
|
|
@@ -1049,7 +1179,6 @@ function handleRemoveContainer(runtime) {
|
|
|
1049
1179
|
} catch (e) {
|
|
1050
1180
|
console.log(`${RED}⚠️ 错误: 未找到名为 ${runtime.containerName} 的容器。${NC}`);
|
|
1051
1181
|
}
|
|
1052
|
-
return true;
|
|
1053
1182
|
}
|
|
1054
1183
|
|
|
1055
1184
|
function validateHostPath(runtime) {
|
|
@@ -1153,6 +1282,7 @@ function buildDockerRunArgs(runtime) {
|
|
|
1153
1282
|
contModeArgs: runtime.contModeArgs,
|
|
1154
1283
|
containerEnvs: runtime.containerEnvs,
|
|
1155
1284
|
containerVolumes: runtime.containerVolumes,
|
|
1285
|
+
containerPorts: runtime.containerPorts,
|
|
1156
1286
|
defaultCommand: runtime.execCommand
|
|
1157
1287
|
});
|
|
1158
1288
|
}
|
|
@@ -1316,6 +1446,7 @@ async function runWebServerMode(runtime) {
|
|
|
1316
1446
|
contModeArgs: runtime.contModeArgs,
|
|
1317
1447
|
containerEnvs: runtime.containerEnvs,
|
|
1318
1448
|
containerVolumes: runtime.containerVolumes,
|
|
1449
|
+
containerPorts: runtime.containerPorts,
|
|
1319
1450
|
validateHostPath: () => validateHostPath(runtime),
|
|
1320
1451
|
formatDate,
|
|
1321
1452
|
isValidContainerName,
|
|
@@ -1340,8 +1471,8 @@ async function runWebServerMode(runtime) {
|
|
|
1340
1471
|
async function main() {
|
|
1341
1472
|
try {
|
|
1342
1473
|
// 1. Setup commander and parse arguments
|
|
1343
|
-
await setupCommander();
|
|
1344
|
-
const runtime = createRuntimeContext();
|
|
1474
|
+
const modeState = await setupCommander();
|
|
1475
|
+
const runtime = createRuntimeContext(modeState);
|
|
1345
1476
|
|
|
1346
1477
|
// 2. Start web server mode
|
|
1347
1478
|
if (runtime.serverMode) {
|
|
@@ -1350,7 +1481,7 @@ async function main() {
|
|
|
1350
1481
|
}
|
|
1351
1482
|
|
|
1352
1483
|
// 3. Handle image build operation
|
|
1353
|
-
if (
|
|
1484
|
+
if (modeState.isBuildMode) {
|
|
1354
1485
|
await buildImage({
|
|
1355
1486
|
imageBuildArgs: IMAGE_BUILD_ARGS,
|
|
1356
1487
|
imageName: runtime.imageName,
|
|
@@ -1359,7 +1490,7 @@ async function main() {
|
|
|
1359
1490
|
imageVersionBase: IMAGE_VERSION_BASE,
|
|
1360
1491
|
parseImageVersionTag,
|
|
1361
1492
|
manyoyoName: MANYOYO_NAME,
|
|
1362
|
-
yesMode:
|
|
1493
|
+
yesMode: Boolean(modeState.yesMode),
|
|
1363
1494
|
dockerCmd: DOCKER_CMD,
|
|
1364
1495
|
rootDir: path.join(__dirname, '..'),
|
|
1365
1496
|
loadConfig,
|
|
@@ -1372,7 +1503,8 @@ async function main() {
|
|
|
1372
1503
|
}
|
|
1373
1504
|
|
|
1374
1505
|
// 4. Handle remove container operation
|
|
1375
|
-
if (
|
|
1506
|
+
if (modeState.isRemoveMode) {
|
|
1507
|
+
handleRemoveContainer(runtime);
|
|
1376
1508
|
return;
|
|
1377
1509
|
}
|
|
1378
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/image-build.js
CHANGED
|
@@ -301,10 +301,6 @@ function buildBuildkitRunArgs(ctx, dockerfilePath, fullImageTag, imageBuildArgs)
|
|
|
301
301
|
const args = [
|
|
302
302
|
'run', '--rm', '--privileged',
|
|
303
303
|
'--network', `host`,
|
|
304
|
-
'-e', `HTTP_PROXY=$HTTP_PROXY`,
|
|
305
|
-
'-e', `HTTPS_PROXY=$HTTPS_PROXY`,
|
|
306
|
-
'-e', `ALL_PROXY=$ALL_PROXY`,
|
|
307
|
-
'-e', `NO_PROXY=$NO_PROXY`,
|
|
308
304
|
'--volume', `${ctx.rootDir}:/workspace`,
|
|
309
305
|
'--entrypoint', 'buildctl-daemonless.sh',
|
|
310
306
|
'docker.io/moby/buildkit:latest',
|
|
@@ -314,10 +310,10 @@ function buildBuildkitRunArgs(ctx, dockerfilePath, fullImageTag, imageBuildArgs)
|
|
|
314
310
|
'--local', 'dockerfile=/workspace',
|
|
315
311
|
'--opt', `filename=${dockerfileRelativePath}`,
|
|
316
312
|
'--opt', 'network=host',
|
|
317
|
-
'--opt', `build-arg:HTTP_PROXY=$HTTP_PROXY`,
|
|
318
|
-
'--opt', `build-arg:HTTPS_PROXY=$HTTPS_PROXY`,
|
|
319
|
-
'--opt', `build-arg:ALL_PROXY=$ALL_PROXY`,
|
|
320
|
-
'--opt', `build-arg:NO_PROXY=$NO_PROXY`
|
|
313
|
+
'--opt', `build-arg:HTTP_PROXY=${process.env.HTTP_PROXY}`,
|
|
314
|
+
'--opt', `build-arg:HTTPS_PROXY=${process.env.HTTPS_PROXY}`,
|
|
315
|
+
'--opt', `build-arg:ALL_PROXY=${process.env.ALL_PROXY}`,
|
|
316
|
+
'--opt', `build-arg:NO_PROXY=${process.env.NO_PROXY}`
|
|
321
317
|
];
|
|
322
318
|
|
|
323
319
|
for (const value of buildArgs) {
|
|
@@ -433,6 +429,9 @@ async function buildImage(options = {}) {
|
|
|
433
429
|
|
|
434
430
|
await prepareBuildCache(ctx, imageTool);
|
|
435
431
|
|
|
432
|
+
// 记录构建开始时间
|
|
433
|
+
const buildStartTime = Date.now();
|
|
434
|
+
|
|
436
435
|
const dockerfilePath = path.join(ctx.rootDir, 'docker', 'manyoyo.Dockerfile');
|
|
437
436
|
if (!fs.existsSync(dockerfilePath)) {
|
|
438
437
|
ctx.error(`${RED}错误: 找不到 Dockerfile: ${dockerfilePath}${NC}`);
|
|
@@ -452,7 +451,9 @@ async function buildImage(options = {}) {
|
|
|
452
451
|
const buildkitRunArgs = buildBuildkitRunArgs(ctx, dockerfilePath, fullImageTag, imageBuildArgs);
|
|
453
452
|
|
|
454
453
|
function logBuildSuccess() {
|
|
454
|
+
const buildDuration = ((Date.now() - buildStartTime) / 1000).toFixed(1);
|
|
455
455
|
ctx.log(`\n${GREEN}✅ 镜像构建成功: ${fullImageTag}${NC}`);
|
|
456
|
+
ctx.log(`${GREEN}⏱️ 构建耗时: ${buildDuration} 秒${NC}`);
|
|
456
457
|
ctx.log(`${BLUE}使用镜像:${NC}`);
|
|
457
458
|
ctx.log(` ${ctx.manyoyoName} -n test --in ${ctx.imageName} --iv ${version}-${imageTool} -y c`);
|
|
458
459
|
ctx.pruneDanglingImages();
|
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"
|