@xcanwin/manyoyo 3.5.7 → 3.7.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 CHANGED
@@ -40,6 +40,7 @@ npm install -g .
40
40
  ## 2. 安装 podman
41
41
 
42
42
  2.1 安装 [podman](https://podman.io/docs/installation)
43
+
43
44
  2.2 拉取基础镜像
44
45
 
45
46
  ```bash
@@ -100,31 +101,40 @@ manyoyo --irm
100
101
 
101
102
  # 静默显示执行命令
102
103
  manyoyo -q full -x echo "hello world"
104
+ manyoyo -q tip -q cmd -x echo "hello world" # 多次使用静默选项
103
105
  ```
104
106
 
105
107
  ### 环境变量
106
108
 
107
- #### 字符串格式
109
+ 给容器内CLI传递BASE_URL和TOKEN等。
108
110
 
109
- ```bash
110
- # 直接传递
111
- manyoyo -e "VAR=value" -x env
111
+ #### 字符串形式
112
112
 
113
- # 多个变量
114
- manyoyo -e "A=1" -e "B=2" -x env
113
+ ```bash
114
+ manyoyo -e "ANTHROPIC_BASE_URL=https://xxxx" -e "ANTHROPIC_AUTH_TOKEN=your-key" -x claude
115
115
  ```
116
116
 
117
- #### 文件格式
117
+ #### 文件形式
118
+
119
+ 环境文件使用 `.env` 格式,支持注释(以 `#` 开头的行):
118
120
 
119
121
  ```bash
120
- # 从文件加载
121
- manyoyo --ef .env -x env
122
+ export ANTHROPIC_BASE_URL="https://xxxx"
123
+ AUTH_TOANTHROPIC_AUTH_TOKENKEN=your-key
124
+ # MESSAGE="Hello World" # 注释会被忽略
125
+ TESTPATH='/usr/local/bin'
122
126
  ```
123
127
 
124
- 环境文件(`.env`)支持以下格式:
128
+ **环境文件路径规则**:
129
+ - `manyoyo --ef myconfig` → 加载 `~/.manyoyo/env/myconfig.env`
130
+ - `manyoyo --ef ./myconfig.env` → 加载当前目录的 `myconfig.env`
125
131
 
126
132
  ```bash
127
- # 使用 export 语句
133
+ # 创建环境文件目录
134
+ mkdir -p ~/.manyoyo/env/
135
+
136
+ # 示例:创建 Claude 环境文件
137
+ cat > ~/.manyoyo/env/claude.env << 'EOF'
128
138
  export ANTHROPIC_BASE_URL="https://api.anthropic.com"
129
139
  # export CLAUDE_CODE_OAUTH_TOKEN="sk-xxxxxxxx"
130
140
  export ANTHROPIC_AUTH_TOKEN="sk-xxxxxxxx"
@@ -134,13 +144,82 @@ export ANTHROPIC_DEFAULT_OPUS_MODEL="claude-opus-4-5"
134
144
  export ANTHROPIC_DEFAULT_SONNET_MODEL="claude-sonnet-4-5"
135
145
  export ANTHROPIC_DEFAULT_HAIKU_MODEL="claude-haiku-4-5"
136
146
  export CLAUDE_CODE_SUBAGENT_MODEL="claude-sonnet-4-5"
147
+ EOF
148
+
149
+ # 在任意目录下使用环境文件
150
+ manyoyo --ef claude -x claude
151
+ ```
152
+
153
+ ### 配置文件
154
+
155
+ 简化MANYOYO命令行操作。配置文件使用 **JSON5 格式**,支持注释、尾随逗号等特性。
156
+
157
+ #### 配置文件路径规则
158
+
159
+ - `manyoyo -r myconfig` → 加载 `~/.manyoyo/run/myconfig.json`
160
+ - `manyoyo -r ./myconfig.json` → 加载当前目录的 `myconfig.json`
161
+ - `manyoyo [任何选项]` → 始终会加载全局配置 `~/.manyoyo/manyoyo.json`
137
162
 
138
- # 简单的键值对
139
- API_KEY=your-api-key-here
163
+ #### 配置选项
140
164
 
141
- # 带引号的值(引号会被移除)
142
- MESSAGE="Hello World"
143
- PATH='/usr/local/bin'
165
+ 参考 `config.example.json` 文件查看所有可配置项:
166
+
167
+ ```json5
168
+ {
169
+ // 容器基础配置
170
+ "containerName": "myy-dev", // 默认容器名称
171
+ "hostPath": "/path/to/project", // 默认宿主机工作目录
172
+ "containerPath": "/path/to/project", // 默认容器工作目录
173
+ "imageName": "localhost/xcanwin/manyoyo", // 默认镜像名称
174
+ "imageVersion": "1.6.3-full", // 默认镜像版本
175
+ "containerMode": "common", // 容器嵌套模式 (common, dind, sock)
176
+
177
+ // 环境变量配置
178
+ "envFile": [
179
+ "claude" // 对应 ~/.manyoyo/env/claude.env
180
+ ],
181
+ "env": [], // 默认环境变量数组
182
+
183
+ // 其他配置
184
+ "volumes": [], // 默认挂载卷数组
185
+ "shellPrefix": "", // 默认命令前缀
186
+ "shell": "", // 默认执行命令
187
+ "yolo": "", // 默认 YOLO 模式 (c, gm, cx, oc)
188
+ "quiet": [], // 默认静默选项数组 (支持 ["tip", "cmd"] 格式)
189
+ "imageBuildArgs": [] // 默认镜像构建参数
190
+ }
191
+ ```
192
+
193
+ #### 优先级
194
+
195
+ - **覆盖型参数**:命令行 > 运行配置 > 全局配置 > 默认值
196
+ - **合并型参数**:全局配置 + 运行配置 + 命令行(按顺序累加)
197
+
198
+ 覆盖型参数包括:`containerName`, `hostPath`, `containerPath`, `imageName`, `imageVersion`, `containerMode`, `shellPrefix`, `shell`, `yolo`, `quiet`
199
+
200
+ 合并型参数包括:`envFile`, `env`, `volumes`, `imageBuildArgs`
201
+
202
+ #### 常用样例
203
+
204
+ ```bash
205
+ # 创建运行配置目录
206
+ mkdir -p ~/.manyoyo/run/
207
+
208
+ # 创建 Claude 运行配置
209
+ cat > ~/.manyoyo/run/c.json << 'EOF'
210
+ {
211
+ // Claude Code 快捷配置
212
+ "imageName": "localhost/xcanwin/manyoyo",
213
+ "imageVersion": "1.6.3-full",
214
+ "envFile": [
215
+ "claude" // 自动加载 ~/.manyoyo/env/claude.env
216
+ ],
217
+ "yolo": "c"
218
+ }
219
+ EOF
220
+
221
+ # 在任意目录下使用运行配置
222
+ manyoyo -r c
144
223
  ```
145
224
 
146
225
  ### AI CLI 快捷方式(跳过权限确认)
@@ -212,7 +291,7 @@ docker ps -a # 现在可以在容器内使用 docker 命令
212
291
  | `--iba XXX=YYY` | `--image-build-arg` | 构建镜像时传参给dockerfile |
213
292
  | `--irm` | `--image-remove` | 清理悬空镜像和 `<none>` 镜像 |
214
293
  | `-e STRING` | `--env` | 设置环境变量 |
215
- | `--ef FILE` | `--env-file` | 从文件加载环境变量 |
294
+ | `--ef FILE` | `--env-file` | 从文件加载环境变量(支持 `name` 或 `./path.env`) |
216
295
  | `-v STRING` | `--volume` | 绑定挂载卷 |
217
296
  | `--sp CMD` | `--shell-prefix` | 临时环境变量(作为 -s 的前缀) |
218
297
  | `-s CMD` | `--shell` | 指定要执行的命令 |
@@ -221,6 +300,7 @@ docker ps -a # 现在可以在容器内使用 docker 命令
221
300
  | `-y CLI` | `--yolo` | 无需确认运行 AI 智能体 |
222
301
  | `--install NAME` | | 安装 manyoyo 命令 |
223
302
  | `-q LIST` | `--quiet` | 静默显示 |
303
+ | `-r NAME` | `--run` | 加载运行配置(支持 `name` 或 `./path.json`) |
224
304
  | `-V` | `--version` | 显示版本 |
225
305
  | `-h` | `--help` | 显示帮助 |
226
306
 
package/bin/manyoyo.js CHANGED
@@ -7,7 +7,10 @@
7
7
  const { execSync, spawnSync } = require('child_process');
8
8
  const fs = require('fs');
9
9
  const path = require('path');
10
+ const os = require('os');
10
11
  const readline = require('readline');
12
+ const { Command } = require('commander');
13
+ const JSON5 = require('json5');
11
14
  const { version: BIN_VERSION, imageVersion: IMAGE_VERSION_BASE } = require('../package.json');
12
15
 
13
16
  // Helper function to format date like bash $(date +%m%d-%H%M)
@@ -59,59 +62,61 @@ function sleep(ms) {
59
62
  }
60
63
 
61
64
  // ==============================================================================
62
- // UI Functions
65
+ // Configuration File Functions
63
66
  // ==============================================================================
64
67
 
65
- function showHelp() {
66
- console.log(`MANYOYO - AI Agent CLI Sandbox`);
67
- console.log(`https://github.com/xcanwin/manyoyo`);
68
- console.log("");
69
- console.log(`${BLUE}Usage:${NC}`);
70
- console.log(` ${MANYOYO_NAME} [OPTIONS]`);
71
- console.log(` ${MANYOYO_NAME} [--hp HOST_PATH] [-n CONTAINER_NAME] [--cp CONTAINER_PATH] [--ef ENV_FILE] [--sp COMMAND] [-s COMMAND] [-- COMMAND]`);
72
- console.log("");
73
- console.log(`${BLUE}Options:${NC}`);
74
- console.log(" --hp|--host-path PATH 设置宿主机工作目录 (默认当前路径)");
75
- console.log(" -n|--cn|--cont-name NAME 设置容器名称");
76
- console.log(" --cp|--cont-path PATH 设置容器工作目录");
77
- console.log(" -l|--cl|--cont-list 列举容器");
78
- console.log(" --crm|--cont-remove 删除-n指定容器");
79
- console.log(" -m|--cm|--cont-mode STRING 设置容器嵌套容器模式");
80
- console.log(" 例如 common, dind, sock");
81
- console.log(" --in|--image-name NAME 指定镜像名称");
82
- console.log(" --iv|--image-ver VERSION 指定镜像版本");
83
- console.log(" --ib|--image-build 构建镜像");
84
- console.log(" --iba|--image-build-arg XXX=YYY 构建镜像时传参给dockerfile");
85
- console.log(" --irm|--image-remove 清理悬空镜像和 <none> 镜像");
86
- console.log(" -e|--env XXX=YYY 设置环境变量");
87
- console.log(" --ef|--env-file ENV_FILE 设置环境变量通过文件");
88
- console.log(" -v|--volume XXX:YYY 绑定挂载卷");
89
- console.log(" --sp|--shell-prefix COMMAND 临时环境变量 (作为-s前缀)");
90
- console.log(" -s|--shell COMMAND 指定命令执行");
91
- console.log(" --|--shell-suffix COMMAND 指定命令参数, --后面全部直传 (作为-s后缀)");
92
- console.log(" -x|--shell-full COMMAND 指定完整命令执行, -x后面全部直传 (代替--sp和-s和--命令)");
93
- console.log(" -y|--yolo CLI 使AGENT无需确认 (代替-s命令)");
94
- console.log(" 例如 claude / c, gemini / gm, codex / cx, opencode / oc");
95
- console.log(" --install NAME 安装manyoyo命令");
96
- console.log(" 例如 docker-cli-plugin");
97
- console.log(" -q|--quiet LIST 静默显示");
98
- console.log(" 例如 cnew,crm,tip,cmd,full");
99
- console.log(" -V|--version 显示版本");
100
- console.log(" -h|--help 显示帮助");
101
- console.log("");
102
- console.log(`${BLUE}Example:${NC}`);
103
- console.log(` ${MANYOYO_NAME} --ib 构建镜像`);
104
- console.log(` ${MANYOYO_NAME} -n test --ef ./xxx.env -y c 设置环境变量并运行无需确认的AGENT`);
105
- console.log(` ${MANYOYO_NAME} -n test -- -c 恢复之前会话`);
106
- console.log(` ${MANYOYO_NAME} -x echo 123 指定命令执行`);
107
- console.log(` ${MANYOYO_NAME} -n test --ef ./xxx.env -x claude 设置环境变量并运行`);
108
- console.log(` ${MANYOYO_NAME} -n test -x claude -c 恢复之前会话`);
68
+ function loadConfig() {
69
+ const configPath = path.join(os.homedir(), '.manyoyo', 'manyoyo.json');
70
+ if (fs.existsSync(configPath)) {
71
+ try {
72
+ const config = JSON5.parse(fs.readFileSync(configPath, 'utf-8'));
73
+ return config;
74
+ } catch (e) {
75
+ console.error(`${YELLOW}⚠️ 配置文件格式错误: ${configPath}${NC}`);
76
+ return {};
77
+ }
78
+ }
79
+ return {};
109
80
  }
110
81
 
111
- function showVersion() {
112
- console.log(`manyoyo by xcanwin, ${BIN_VERSION}`);
82
+ function loadRunConfig(name) {
83
+ // Check if name is a file path (contains path separator or extension)
84
+ const isFilePath = name.includes('/') || name.includes('\\') || path.extname(name);
85
+
86
+ if (isFilePath) {
87
+ // If it's a file path, only check that exact path
88
+ if (fs.existsSync(name)) {
89
+ try {
90
+ const config = JSON5.parse(fs.readFileSync(name, 'utf-8'));
91
+ return config;
92
+ } catch (e) {
93
+ console.error(`${YELLOW}⚠️ 运行配置文件格式错误: ${name}${NC}`);
94
+ return {};
95
+ }
96
+ }
97
+ } else {
98
+ // If it's just a name, only check ~/.manyoyo/run/name.json
99
+ const configPath = path.join(os.homedir(), '.manyoyo', 'run', `${name}.json`);
100
+ if (fs.existsSync(configPath)) {
101
+ try {
102
+ const config = JSON5.parse(fs.readFileSync(configPath, 'utf-8'));
103
+ return config;
104
+ } catch (e) {
105
+ console.error(`${YELLOW}⚠️ 运行配置文件格式错误: ${configPath}${NC}`);
106
+ return {};
107
+ }
108
+ }
109
+ }
110
+
111
+ console.error(`${RED}⚠️ 未找到运行配置: ${name}${NC}`);
112
+ return {};
113
113
  }
114
114
 
115
+ // ==============================================================================
116
+ // UI Functions
117
+ // ==============================================================================
118
+
119
+
115
120
  function getHelloTip(containerName, defaultCommand) {
116
121
  if ( !(QUIET.tip || QUIET.full) ) {
117
122
  console.log("");
@@ -126,8 +131,12 @@ function getHelloTip(containerName, defaultCommand) {
126
131
  }
127
132
  }
128
133
 
129
- function setQuiet(action) {
130
- action.split(',').forEach(ac => {
134
+ function setQuiet(actions) {
135
+ // Support both string and array input
136
+ const actionArray = Array.isArray(actions) ? actions : [actions];
137
+ actionArray.forEach(action => {
138
+ // Remove comma splitting - each action should be a single quiet option
139
+ const ac = action.trim();
131
140
  switch (ac) {
132
141
  case 'cnew':
133
142
  QUIET.cnew = 1;
@@ -149,7 +158,6 @@ function setQuiet(action) {
149
158
  break;
150
159
  }
151
160
  });
152
- // process.exit(0);
153
161
  }
154
162
 
155
163
  async function askQuestion(prompt) {
@@ -175,9 +183,21 @@ function addEnv(env) {
175
183
  }
176
184
 
177
185
  function addEnvFile(envFile) {
178
- ENV_FILE = envFile;
179
- if (ENV_FILE && fs.existsSync(ENV_FILE)) {
180
- const content = fs.readFileSync(ENV_FILE, 'utf-8');
186
+ // Check if envFile is a file path (contains path separator)
187
+ const isFilePath = envFile.includes('/') || envFile.includes('\\');
188
+
189
+ let filePath;
190
+ if (isFilePath) {
191
+ // If it's a file path, only check that exact path
192
+ filePath = envFile;
193
+ } else {
194
+ // If it's just a name, only check ~/.manyoyo/env/name.env
195
+ filePath = path.join(os.homedir(), '.manyoyo', 'env', `${envFile}.env`);
196
+ }
197
+
198
+ ENV_FILE = filePath;
199
+ if (fs.existsSync(filePath)) {
200
+ const content = fs.readFileSync(filePath, 'utf-8');
181
201
  const lines = content.split('\n');
182
202
 
183
203
  for (let line of lines) {
@@ -203,7 +223,10 @@ function addEnvFile(envFile) {
203
223
  }
204
224
  }
205
225
  }
226
+ return {};
206
227
  }
228
+ console.error(`${RED}⚠️ 未找到环境文件: ${envFile}${NC}`);
229
+ return {};
207
230
  }
208
231
 
209
232
  function addVolume(volume) {
@@ -231,7 +254,7 @@ function setYolo(cli) {
231
254
  EXEC_COMMAND = "opencode";
232
255
  break;
233
256
  default:
234
- console.log(`${RED}⚠️ 未知LLM CLI: ${cli}${NC}`);
257
+ console.log(`${RED}⚠️ 未知LLM CLI: ${cli}${NC}`);
235
258
  process.exit(0);
236
259
  }
237
260
  }
@@ -251,10 +274,10 @@ function setContMode(mode) {
251
274
  case 'sock':
252
275
  case 's':
253
276
  CONT_MODE = "--privileged --volume /var/run/docker.sock:/var/run/docker.sock --env DOCKER_HOST=unix:///var/run/docker.sock --env CONTAINER_HOST=unix:///var/run/docker.sock";
254
- console.log(`${RED}⚠️ 开启危险的容器嵌套容器模式, 危害: 容器可访问宿主机文件${NC}`);
277
+ console.log(`${RED}⚠️ 开启危险的容器嵌套容器模式, 危害: 容器可访问宿主机文件${NC}`);
255
278
  break;
256
279
  default:
257
- console.log(`${RED}⚠️ 未知模式: ${mode}${NC}`);
280
+ console.log(`${RED}⚠️ 未知模式: ${mode}${NC}`);
258
281
  process.exit(0);
259
282
  }
260
283
  }
@@ -565,188 +588,157 @@ async function buildImage(IMAGE_BUILD_ARGS, imageName, imageVersion) {
565
588
  // Main Function Helpers
566
589
  // ==============================================================================
567
590
 
568
- function validateAndInitialize() {
569
- // Check if no arguments provided
570
- if (process.argv.length <= 2) {
571
- showHelp();
572
- process.exit(1);
573
- }
574
-
575
- // Ensure docker/podman is available
576
- ensureDocker();
577
-
578
- // Docker CLI plugin metadata
591
+ function setupCommander() {
592
+ // Load config file
593
+ const config = loadConfig();
594
+
595
+ const program = new Command();
596
+
597
+ program
598
+ .name(MANYOYO_NAME)
599
+ .version(BIN_VERSION, '-V, --version', '显示版本')
600
+ .description('MANYOYO - AI Agent CLI Sandbox\nhttps://github.com/xcanwin/manyoyo')
601
+ .addHelpText('after', `
602
+ 配置文件:
603
+ ~/.manyoyo/manyoyo.json 全局配置文件 (JSON5格式,支持注释)
604
+ ~/.manyoyo/run/c.json 运行配置示例
605
+
606
+ 路径规则:
607
+ -r name → ~/.manyoyo/run/name.json
608
+ -r ./file.json → 当前目录的 file.json
609
+ --ef name → ~/.manyoyo/env/name.env
610
+ --ef ./file.env → 当前目录的 file.env
611
+
612
+ 示例:
613
+ ${MANYOYO_NAME} --ib 构建镜像
614
+ ${MANYOYO_NAME} -r c 使用 ~/.manyoyo/run/c.json 配置
615
+ ${MANYOYO_NAME} -r ./myconfig.json 使用当前目录 ./myconfig.json 配置
616
+ ${MANYOYO_NAME} -n test --ef claude -y c 使用 ~/.manyoyo/env/claude.env 环境变量文件
617
+ ${MANYOYO_NAME} -n test --ef ./myenv.env -y c 使用当前目录 ./myenv.env 环境变量文件
618
+ ${MANYOYO_NAME} -n test -- -c 恢复之前会话
619
+ ${MANYOYO_NAME} -x echo 123 指定命令执行
620
+ ${MANYOYO_NAME} -n test -q tip -q cmd 多次使用静默选项
621
+ `);
622
+
623
+ // Options
624
+ program
625
+ .option('-r, --run <name>', '加载运行配置 (name → ~/.manyoyo/run/name.json, ./file.json → 当前目录文件)')
626
+ .option('--hp, --host-path <path>', '设置宿主机工作目录 (默认当前路径)')
627
+ .option('-n, --cont-name <name>', '设置容器名称')
628
+ .option('--cp, --cont-path <path>', '设置容器工作目录')
629
+ .option('-l, --cont-list', '列举容器')
630
+ .option('--crm, --cont-remove', '删除-n指定容器')
631
+ .option('-m, --cont-mode <mode>', '设置容器嵌套容器模式 (common, dind, sock)')
632
+ .option('--in, --image-name <name>', '指定镜像名称')
633
+ .option('--iv, --image-ver <version>', '指定镜像版本')
634
+ .option('--ib, --image-build', '构建镜像')
635
+ .option('--iba, --image-build-arg <arg>', '构建镜像时传参给dockerfile (可多次使用)', (value, previous) => [...(previous || []), value], [])
636
+ .option('--irm, --image-remove', '清理悬空镜像和 <none> 镜像')
637
+ .option('-e, --env <env>', '设置环境变量 XXX=YYY (可多次使用)', (value, previous) => [...(previous || []), value], [])
638
+ .option('--ef, --env-file <file>', '设置环境变量通过文件 (name → ~/.manyoyo/env/name.env, ./file.env → 当前目录文件)', (value, previous) => [...(previous || []), value], [])
639
+ .option('-v, --volume <volume>', '绑定挂载卷 XXX:YYY (可多次使用)', (value, previous) => [...(previous || []), value], [])
640
+ .option('--sp, --shell-prefix <command>', '临时环境变量 (作为-s前缀)')
641
+ .option('-s, --shell <command>', '指定命令执行')
642
+ .option('-x, --shell-full <command...>', '指定完整命令执行 (代替--sp和-s和--命令)')
643
+ .option('-y, --yolo <cli>', '使AGENT无需确认 (claude/c, gemini/gm, codex/cx, opencode/oc)')
644
+ .option('--install <name>', '安装manyoyo命令 (docker-cli-plugin)')
645
+ .option('-q, --quiet <item>', '静默显示 (可多次使用: cnew,crm,tip,cmd,full)', (value, previous) => [...(previous || []), value], []);
646
+
647
+ // Docker CLI plugin metadata check
579
648
  if (process.argv[2] === 'docker-cli-plugin-metadata') {
580
- const metadata = {
649
+ console.log(JSON.stringify({
581
650
  "SchemaVersion": "0.1.0",
582
651
  "Vendor": "xcanwin",
583
652
  "Version": "v1.0.0",
584
653
  "Description": "AI Agent CLI Sandbox"
585
- };
586
- console.log(JSON.stringify(metadata, null, 2));
654
+ }, null, 2));
587
655
  process.exit(0);
588
656
  }
589
- }
590
-
591
- function parseArguments(argv) {
592
- // Parse arguments
593
- let args = argv.slice(2);
594
657
 
595
658
  // Docker CLI plugin mode - remove first arg if running as plugin
596
659
  const dockerPluginPath = path.join(process.env.HOME || '', '.docker/cli-plugins/docker-manyoyo');
597
- if (argv[1] === dockerPluginPath && args[0] === 'manyoyo') {
598
- args.shift();
660
+ if (process.argv[1] === dockerPluginPath && process.argv[2] === 'manyoyo') {
661
+ process.argv.splice(2, 1);
599
662
  }
600
663
 
601
- // Parse command-line arguments
602
- let i = 0;
603
- while (i < args.length) {
604
- const arg = args[i];
605
-
606
- switch (arg) {
607
- case '-q':
608
- case '--quiet':
609
- setQuiet(args[i + 1]);
610
- i += 2;
611
- break;
612
-
613
- case '--hp':
614
- case '--host-path':
615
- HOST_PATH = args[i + 1];
616
- i += 2;
617
- break;
618
-
619
- case '-n':
620
- case '--cn':
621
- case '--cont-name':
622
- CONTAINER_NAME = args[i + 1];
623
- i += 2;
624
- break;
625
-
626
- case '--cp':
627
- case '--cont-path':
628
- CONTAINER_PATH = args[i + 1];
629
- i += 2;
630
- break;
631
-
632
- case '-l':
633
- case '--cl':
634
- case '--cont-list':
635
- getContList();
636
- process.exit(0);
637
-
638
- case '--crm':
639
- case '--cont-remove':
640
- SHOULD_REMOVE = true;
641
- i += 1;
642
- break;
643
-
644
- case '--in':
645
- case '--image-name':
646
- IMAGE_NAME = args[i + 1];
647
- i += 2;
648
- break;
649
-
650
- case '--iv':
651
- case '--image-ver':
652
- IMAGE_VERSION = args[i + 1];
653
- i += 2;
654
- break;
655
-
656
- case '-e':
657
- case '--env':
658
- addEnv(args[i + 1]);
659
- i += 2;
660
- break;
661
-
662
- case '--ef':
663
- case '--env-file':
664
- addEnvFile(args[i + 1]);
665
- i += 2;
666
- break;
664
+ // Ensure docker/podman is available
665
+ ensureDocker();
667
666
 
668
- case '-v':
669
- case '--volume':
670
- addVolume(args[i + 1]);
671
- i += 2;
672
- break;
667
+ // Parse arguments
668
+ program.allowUnknownOption(false);
669
+ program.parse(process.argv);
673
670
 
674
- case '--sp':
675
- case '--shell-prefix':
676
- EXEC_COMMAND_PREFIX = args[i + 1] + " ";
677
- i += 2;
678
- break;
671
+ const options = program.opts();
679
672
 
680
- case '-s':
681
- case '--shell':
682
- EXEC_COMMAND = args[i + 1];
683
- i += 2;
684
- break;
673
+ // Load run config if specified
674
+ const runConfig = options.run ? loadRunConfig(options.run) : {};
685
675
 
686
- case '--':
687
- case '--ss':
688
- case '--shell-suffix':
689
- EXEC_COMMAND_SUFFIX = " " + args.slice(i + 1).join(' ');
690
- i = args.length;
691
- break;
676
+ // Merge configs: command line > run config > global config > defaults
677
+ // Override mode (scalar values): use first defined value
678
+ HOST_PATH = options.hostPath || runConfig.hostPath || config.hostPath || HOST_PATH;
679
+ if (options.contName || runConfig.containerName || config.containerName) {
680
+ CONTAINER_NAME = options.contName || runConfig.containerName || config.containerName;
681
+ }
682
+ if (options.contPath || runConfig.containerPath || config.containerPath) {
683
+ CONTAINER_PATH = options.contPath || runConfig.containerPath || config.containerPath;
684
+ }
685
+ IMAGE_NAME = options.imageName || runConfig.imageName || config.imageName || IMAGE_NAME;
686
+ if (options.imageVer || runConfig.imageVersion || config.imageVersion) {
687
+ IMAGE_VERSION = options.imageVer || runConfig.imageVersion || config.imageVersion;
688
+ }
689
+ if (options.shellPrefix || runConfig.shellPrefix || config.shellPrefix) {
690
+ EXEC_COMMAND_PREFIX = (options.shellPrefix || runConfig.shellPrefix || config.shellPrefix) + " ";
691
+ }
692
+ if (options.shell || runConfig.shell || config.shell) {
693
+ EXEC_COMMAND = options.shell || runConfig.shell || config.shell;
694
+ }
692
695
 
693
- case '-x':
694
- case '--sf':
695
- case '--shell-full':
696
- EXEC_COMMAND = args.slice(i + 1).join(' ');
697
- i = args.length;
698
- break;
696
+ // Merge mode (array values): concatenate all sources
697
+ const toArray = (val) => Array.isArray(val) ? val : (val ? [val] : []);
698
+ const envFileList = [
699
+ ...toArray(config.envFile),
700
+ ...toArray(runConfig.envFile),
701
+ ...(options.envFile || [])
702
+ ].filter(Boolean);
703
+ envFileList.forEach(ef => addEnvFile(ef));
699
704
 
700
- case '-y':
701
- case '--yolo':
702
- setYolo(args[i + 1]);
703
- i += 2;
704
- break;
705
+ const envList = [...(config.env || []), ...(runConfig.env || []), ...(options.env || [])];
706
+ envList.forEach(e => addEnv(e));
705
707
 
706
- case '-m':
707
- case '--cm':
708
- case '--cont-mode':
709
- setContMode(args[i + 1]);
710
- i += 2;
711
- break;
708
+ const volumeList = [...(config.volumes || []), ...(runConfig.volumes || []), ...(options.volume || [])];
709
+ volumeList.forEach(v => addVolume(v));
712
710
 
713
- case '--ib':
714
- case '--image-build':
715
- IMAGE_BUILD_NEED = true;
716
- i += 1;
717
- break;
711
+ const buildArgList = [...(config.imageBuildArgs || []), ...(runConfig.imageBuildArgs || []), ...(options.imageBuildArg || [])];
712
+ buildArgList.forEach(arg => addImageBuildArg(arg));
718
713
 
719
- case '--iba':
720
- case '--image-build-arg':
721
- addImageBuildArg(args[i + 1]);
722
- i += 2;
723
- break;
714
+ // Override mode for special options
715
+ const yoloValue = options.yolo || runConfig.yolo || config.yolo;
716
+ if (yoloValue) setYolo(yoloValue);
724
717
 
725
- case '--irm':
726
- case '--image-remove':
727
- pruneDanglingImages();
728
- process.exit(0);
718
+ const contModeValue = options.contMode || runConfig.containerMode || config.containerMode;
719
+ if (contModeValue) setContMode(contModeValue);
729
720
 
730
- case '--install':
731
- installManyoyo(args[i + 1]);
732
- process.exit(0);
721
+ const quietValue = options.quiet || runConfig.quiet || config.quiet;
722
+ if (quietValue) setQuiet(quietValue);
733
723
 
734
- case '-V':
735
- case '--version':
736
- showVersion();
737
- process.exit(0);
724
+ if (options.contList) { getContList(); process.exit(0); }
725
+ if (options.contRemove) SHOULD_REMOVE = true;
726
+ if (options.imageBuild) IMAGE_BUILD_NEED = true;
727
+ if (options.imageRemove) { pruneDanglingImages(); process.exit(0); }
728
+ if (options.install) { installManyoyo(options.install); process.exit(0); }
738
729
 
739
- case '-h':
740
- case '--help':
741
- showHelp();
742
- process.exit(0);
730
+ // Handle shell-full (variadic arguments)
731
+ if (options.shellFull) {
732
+ EXEC_COMMAND = options.shellFull.join(' ');
733
+ }
743
734
 
744
- default:
745
- console.log(`${RED}⚠️ 未知参数: ${arg}${NC}`);
746
- showHelp();
747
- process.exit(1);
748
- }
735
+ // Handle -- suffix arguments
736
+ const doubleDashIndex = process.argv.indexOf('--');
737
+ if (doubleDashIndex !== -1 && doubleDashIndex < process.argv.length - 1) {
738
+ EXEC_COMMAND_SUFFIX = " " + process.argv.slice(doubleDashIndex + 1).join(' ');
749
739
  }
740
+
741
+ return program;
750
742
  }
751
743
 
752
744
  function handleRemoveContainer() {
@@ -755,10 +747,10 @@ function handleRemoveContainer() {
755
747
  if (containerExists(CONTAINER_NAME)) {
756
748
  removeContainer(CONTAINER_NAME);
757
749
  } else {
758
- console.log(`${RED}⚠️ 错误: 未找到名为 ${CONTAINER_NAME} 的容器。${NC}`);
750
+ console.log(`${RED}⚠️ 错误: 未找到名为 ${CONTAINER_NAME} 的容器。${NC}`);
759
751
  }
760
752
  } catch (e) {
761
- console.log(`${RED}⚠️ 错误: 未找到名为 ${CONTAINER_NAME} 的容器。${NC}`);
753
+ console.log(`${RED}⚠️ 错误: 未找到名为 ${CONTAINER_NAME} 的容器。${NC}`);
762
754
  }
763
755
  process.exit(0);
764
756
  }
@@ -768,7 +760,7 @@ function validateHostPath() {
768
760
  const realHostPath = fs.realpathSync(HOST_PATH);
769
761
  const homeDir = process.env.HOME || '/home';
770
762
  if (realHostPath === '/' || realHostPath === '/home' || realHostPath === homeDir) {
771
- console.log(`${RED}⚠️ 错误: 不允许挂载根目录或home目录。${NC}`);
763
+ console.log(`${RED}⚠️ 错误: 不允许挂载根目录或home目录。${NC}`);
772
764
  process.exit(1);
773
765
  }
774
766
  }
@@ -785,7 +777,7 @@ async function waitForContainerReady(containerName) {
785
777
  }
786
778
 
787
779
  if (status === 'exited') {
788
- console.log(`${RED}⚠️ 错误: 容器启动后立即退出。${NC}`);
780
+ console.log(`${RED}⚠️ 错误: 容器启动后立即退出。${NC}`);
789
781
  dockerExec(`${DOCKER_CMD} logs "${containerName}"`, { stdio: 'inherit' });
790
782
  process.exit(1);
791
783
  }
@@ -794,7 +786,7 @@ async function waitForContainerReady(containerName) {
794
786
  count++;
795
787
 
796
788
  if (count >= MAX_RETRIES) {
797
- console.log(`${RED}⚠️ 错误: 容器启动超时(当前状态: ${status})。${NC}`);
789
+ console.log(`${RED}⚠️ 错误: 容器启动超时(当前状态: ${status})。${NC}`);
798
790
  dockerExec(`${DOCKER_CMD} logs "${containerName}"`, { stdio: 'inherit' });
799
791
  process.exit(1);
800
792
  }
@@ -802,7 +794,7 @@ async function waitForContainerReady(containerName) {
802
794
  await sleep(100);
803
795
  count++;
804
796
  if (count >= MAX_RETRIES) {
805
- console.log(`${RED}⚠️ 错误: 容器启动超时。${NC}`);
797
+ console.log(`${RED}⚠️ 错误: 容器启动超时。${NC}`);
806
798
  process.exit(1);
807
799
  }
808
800
  }
@@ -917,31 +909,28 @@ async function handlePostExit(defaultCommand) {
917
909
 
918
910
  async function main() {
919
911
  try {
920
- // 1. Validate and initialize
921
- validateAndInitialize();
922
-
923
- // 2. Parse command-line arguments
924
- parseArguments(process.argv);
912
+ // 1. Setup commander and parse arguments
913
+ setupCommander();
925
914
 
926
- // 3. Handle image build operation
915
+ // 2. Handle image build operation
927
916
  if (IMAGE_BUILD_NEED) {
928
917
  await buildImage(IMAGE_BUILD_ARGS, IMAGE_NAME, IMAGE_VERSION.split('-')[0]);
929
918
  process.exit(0);
930
919
  }
931
920
 
932
- // 4. Handle remove container operation
921
+ // 3. Handle remove container operation
933
922
  handleRemoveContainer();
934
923
 
935
- // 5. Validate host path safety
924
+ // 4. Validate host path safety
936
925
  validateHostPath();
937
926
 
938
- // 6. Setup container (create or connect)
927
+ // 5. Setup container (create or connect)
939
928
  const defaultCommand = await setupContainer();
940
929
 
941
- // 7. Execute command in container
930
+ // 6. Execute command in container
942
931
  executeInContainer(defaultCommand);
943
932
 
944
- // 8. Handle post-exit interactions
933
+ // 7. Handle post-exit interactions
945
934
  await handlePostExit(defaultCommand);
946
935
 
947
936
  } catch (e) {
@@ -0,0 +1,21 @@
1
+ {
2
+ // MANYOYO 全局配置文件,保存到 ~/.manyoyo/manyoyo.json
3
+
4
+ // 覆盖型参数(命令行 > 运行配置 > 全局配置)
5
+ "containerName": "myy-dev",
6
+ "hostPath": "/path/to/your/project",
7
+ "containerPath": "/path/to/your/project",
8
+ "imageName": "localhost/xcanwin/manyoyo",
9
+ "imageVersion": "1.6.4-full",
10
+ "containerMode": "common",
11
+ "shellPrefix": "",
12
+ "shell": "",
13
+ "yolo": "",
14
+
15
+ // 合并型参数(配置文件和命令行会累加)
16
+ "env": ["IS_SANDBOX=1"],
17
+ "envFile": [],
18
+ "volumes": [],
19
+ "imageBuildArgs": [],
20
+ "quiet": ["tip", "cmd"]
21
+ }
@@ -192,7 +192,7 @@ EOF
192
192
  mkdir -p ~/.config/opencode/
193
193
  cat > ~/.config/opencode/opencode.json <<EOF
194
194
  {
195
- "$schema": "https://opencode.ai/config.json",
195
+ "\$schema": "https://opencode.ai/config.json",
196
196
  "autoupdate": false,
197
197
  "permission": "allow",
198
198
  "model": "myprovider/{env:ANTHROPIC_MODEL}",
package/docs/README_EN.md CHANGED
@@ -40,6 +40,7 @@ npm install -g .
40
40
  ## 2. Install podman
41
41
 
42
42
  2.1 Install [podman](https://podman.io/docs/installation)
43
+
43
44
  2.2 Pull base image
44
45
 
45
46
  ```bash
@@ -100,31 +101,40 @@ manyoyo --irm
100
101
 
101
102
  # Execute custom command with quiet output
102
103
  manyoyo -q full -x echo "hello world"
104
+ manyoyo -q tip -q cmd -x echo "hello world" # Multiple quiet options
103
105
  ```
104
106
 
105
107
  ### Environment Variables
106
108
 
109
+ 给容器内CLI传递BASE_URL和TOKEN等。
110
+
107
111
  #### String Format
108
112
 
109
113
  ```bash
110
- # Direct
111
- manyoyo -e "VAR=value" -x env
112
-
113
- # Multiple
114
- manyoyo -e "A=1" -e "B=2" -x env
114
+ manyoyo -e "ANTHROPIC_BASE_URL=https://xxxx" -e "ANTHROPIC_AUTH_TOKEN=your-key" -x claude
115
115
  ```
116
116
 
117
117
  #### File Format
118
118
 
119
+ Environment files use `.env` format and support comments (lines starting with `#`):
120
+
119
121
  ```bash
120
- # From file
121
- manyoyo --ef .env -x env
122
+ export ANTHROPIC_BASE_URL="https://xxxx"
123
+ ANTHROPIC_AUTH_TOKEN=your-key
124
+ # MESSAGE="Hello World" # Comments will be ignored
125
+ TESTPATH='/usr/local/bin'
122
126
  ```
123
127
 
124
- Environment files (`.env`) support the following formats:
128
+ **Environment file path rules**:
129
+ - `manyoyo --ef myconfig` → loads `~/.manyoyo/env/myconfig.env`
130
+ - `manyoyo --ef ./myconfig.env` → loads `myconfig.env` from current directory
125
131
 
126
132
  ```bash
127
- # With export statement
133
+ # Create environment file directory
134
+ mkdir -p ~/.manyoyo/env/
135
+
136
+ # Example: Create Claude environment file
137
+ cat > ~/.manyoyo/env/claude.env << 'EOF'
128
138
  export ANTHROPIC_BASE_URL="https://api.anthropic.com"
129
139
  # export CLAUDE_CODE_OAUTH_TOKEN="sk-xxxxxxxx"
130
140
  export ANTHROPIC_AUTH_TOKEN="sk-xxxxxxxx"
@@ -134,13 +144,82 @@ export ANTHROPIC_DEFAULT_OPUS_MODEL="claude-opus-4-5"
134
144
  export ANTHROPIC_DEFAULT_SONNET_MODEL="claude-sonnet-4-5"
135
145
  export ANTHROPIC_DEFAULT_HAIKU_MODEL="claude-haiku-4-5"
136
146
  export CLAUDE_CODE_SUBAGENT_MODEL="claude-sonnet-4-5"
147
+ EOF
148
+
149
+ # Use environment file from any directory
150
+ manyoyo --ef claude -x claude
151
+ ```
152
+
153
+ ### Configuration Files
154
+
155
+ Simplify MANYOYO command-line operations. Configuration files use **JSON5 format**, which supports comments, trailing commas, and other features.
156
+
157
+ #### Configuration file path rules
158
+
159
+ - `manyoyo -r myconfig` → loads `~/.manyoyo/run/myconfig.json`
160
+ - `manyoyo -r ./myconfig.json` → loads `myconfig.json` from current directory
161
+ - `manyoyo [any option]` → always loads global configuration `~/.manyoyo/manyoyo.json`
137
162
 
138
- # Simple key=value
139
- API_KEY=your-api-key-here
163
+ #### Configuration Options
140
164
 
141
- # Quoted values (quotes will be stripped)
142
- MESSAGE="Hello World"
143
- PATH='/usr/local/bin'
165
+ Refer to `config.example.json` for all available options:
166
+
167
+ ```json5
168
+ {
169
+ // Container basic configuration
170
+ "containerName": "myy-dev", // Default container name
171
+ "hostPath": "/path/to/project", // Default host working directory
172
+ "containerPath": "/path/to/project", // Default container working directory
173
+ "imageName": "localhost/xcanwin/manyoyo", // Default image name
174
+ "imageVersion": "1.6.3-full", // Default image version
175
+ "containerMode": "common", // Container nesting mode (common, dind, sock)
176
+
177
+ // Environment variable configuration
178
+ "envFile": [
179
+ "claude" // Corresponds to ~/.manyoyo/env/claude.env
180
+ ],
181
+ "env": [], // Default environment variables array
182
+
183
+ // Other configuration
184
+ "volumes": [], // Default volume mounts array
185
+ "shellPrefix": "", // Default command prefix
186
+ "shell": "", // Default execute command
187
+ "yolo": "", // Default YOLO mode (c, gm, cx, oc)
188
+ "quiet": [], // Default quiet options array (supports ["tip", "cmd"] format)
189
+ "imageBuildArgs": [] // Default image build arguments
190
+ }
191
+ ```
192
+
193
+ #### Priority
194
+
195
+ - **Override parameters**: Command line > Run config > Global config > Defaults
196
+ - **Merge parameters**: Global config + Run config + Command line (concatenated in order)
197
+
198
+ Override parameters include: `containerName`, `hostPath`, `containerPath`, `imageName`, `imageVersion`, `containerMode`, `shellPrefix`, `shell`, `yolo`, `quiet`
199
+
200
+ Merge parameters include: `envFile`, `env`, `volumes`, `imageBuildArgs`
201
+
202
+ #### Common Examples
203
+
204
+ ```bash
205
+ # Create run configuration directory
206
+ mkdir -p ~/.manyoyo/run/
207
+
208
+ # Create Claude run configuration
209
+ cat > ~/.manyoyo/run/c.json << 'EOF'
210
+ {
211
+ // Claude Code quick configuration
212
+ "imageName": "localhost/xcanwin/manyoyo",
213
+ "imageVersion": "1.6.3-full",
214
+ "envFile": [
215
+ "claude" // Automatically loads ~/.manyoyo/env/claude.env
216
+ ],
217
+ "yolo": "c"
218
+ }
219
+ EOF
220
+
221
+ # Use run configuration from any directory
222
+ manyoyo -r c
144
223
  ```
145
224
 
146
225
  ### AI CLI Shortcuts (skip permissions)
@@ -212,7 +291,7 @@ docker ps -a # Now you can use docker commands inside the container
212
291
  | `--iba` | `--image-build-arg` | Pass arguments to a Dockerfile during image build |
213
292
  | `--irm` | `--image-remove` | Clean dangling images and `<none>` images |
214
293
  | `-e STRING` | `--env` | Set environment variable |
215
- | `--ef FILE` | `--env-file` | Load environment variables from file |
294
+ | `--ef FILE` | `--env-file` | Load environment variables from file (supports `name` or `./path.env`) |
216
295
  | `-v STRING` | `--volume` | Bind mount volume |
217
296
  | `--sp CMD` | `--shell-prefix` | Temporary environment variable (prefix for -s) |
218
297
  | `-s CMD` | `--shell` | Specify command to execute |
@@ -221,6 +300,7 @@ docker ps -a # Now you can use docker commands inside the container
221
300
  | `-y CLI` | `--yolo` | Run AI agent without confirmation |
222
301
  | `--install NAME` | | Install manyoyo command |
223
302
  | `-q LIST` | `--quiet` | Quiet output |
303
+ | `-r NAME` | `--run` | Load run configuration (supports `name` or `./path.json`) |
224
304
  | `-V` | `--version` | Show version |
225
305
  | `-h` | `--help` | Show help |
226
306
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "3.5.7",
4
- "imageVersion": "1.6.3",
3
+ "version": "3.7.0",
4
+ "imageVersion": "1.6.4",
5
5
  "description": "AI Agent CLI Security Sandbox",
6
6
  "keywords": [
7
7
  "ai", "agent", "sandbox", "docker", "cli", "container", "development"
@@ -31,6 +31,11 @@
31
31
  "README.md",
32
32
  "docs/README_EN.md",
33
33
  "LICENSE",
34
- "docker/manyoyo.Dockerfile"
35
- ]
34
+ "docker/manyoyo.Dockerfile",
35
+ "config.example.json"
36
+ ],
37
+ "dependencies": {
38
+ "commander": "^12.0.0",
39
+ "json5": "^2.2.3"
40
+ }
36
41
  }