@xcanwin/manyoyo 2.2.1 → 3.0.1

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
@@ -1,19 +1,23 @@
1
+ [English](docs/README_EN.md) | [ [中文](README.md) ]
2
+
3
+ ---
4
+
1
5
  # MANYOYO(慢悠悠)
2
6
 
3
7
  **MANYOYO** 是一款 AI 智能体提效安全沙箱,安全、高效、省 token,专为 Agent YOLO 模式设计,保障宿主机安全。
4
8
 
5
- 预装常见 Agent 与工具,进一步节省 token。循环自由切换 Agent 和 /bin/bash,进一步提效。
9
+ 预装常见 Agent 与工具,进一步节省 token。循环自由切换 Agent 和 `/bin/bash`,进一步提效。
6
10
 
7
- MANYOYO provides an isolated Docker/Podman container environment for running AI agent CLIs safely.
11
+ **MANYOYO** 提供隔离的 Docker/Podman 容器环境,用于安全运行 AI 智能体命令行工具。
8
12
 
9
13
  ## 功能亮点
10
14
 
11
- - **多Agent**:支持 claude code, gemini, codex, opencode
15
+ - **多智能体支持**:支持 claude code, gemini, codex, opencode
12
16
  - **安全隔离**:保护宿主机,支持安全容器嵌套(Docker-in-Docker)
13
- - **高效启动**:快捷开启常见 Agent YOLO / SOLO 模式(例如 claude --dangerously-skip-permissions)
17
+ - **快速启动**:快捷开启常见 Agent YOLO / SOLO 模式(例如 claude --dangerously-skip-permissions)
14
18
  - **便捷操作**:快速进入 `/bin/bash`
15
19
  - **会话恢复**:安装 Skills Marketplace 可快速恢复会话
16
- - **自定义灵活**:支持自定义 `BASEURL`、`AUTH_TOKEN` 等变量
20
+ - **灵活自定义**:支持自定义 `BASEURL`、`AUTH_TOKEN` 等变量
17
21
  - **配置管理**:快捷导入配置文件
18
22
  - **高级模式**:支持危险容器嵌套(mount-docker-socket)、自定义沙箱镜像
19
23
 
@@ -25,7 +29,7 @@ MANYOYO provides an isolated Docker/Podman container environment for running AI
25
29
 
26
30
  ## 2. 编译镜像
27
31
 
28
- ```
32
+ ```bash
29
33
  podman pull ubuntu:24.04
30
34
  iv=1.4.0-all && podman build -t localhost/xcanwin/manyoyo:$iv -f docker/manyoyo.Dockerfile . --build-arg EXT=all --no-cache
31
35
  podman image prune -f
@@ -33,13 +37,13 @@ podman image prune -f
33
37
 
34
38
  ## 3. 安装 manyoyo(选一种)
35
39
 
36
- ### Global Installation (Recommended)
40
+ ### 全局安装(推荐)
37
41
 
38
42
  ```bash
39
43
  npm install -g @xcanwin/manyoyo
40
44
  ```
41
45
 
42
- ### Local Development
46
+ ### 本地开发
43
47
 
44
48
  ```bash
45
49
  npm install -g .
@@ -47,57 +51,57 @@ npm install -g .
47
51
 
48
52
  ## 4. 使用方法
49
53
 
50
- ### Basic Commands
54
+ ### 基础命令
51
55
 
52
56
  ```bash
53
- # Show help
57
+ # 显示帮助
54
58
  manyoyo -h
55
59
 
56
- # Show version
60
+ # 显示版本
57
61
  manyoyo -V
58
62
 
59
- # List all containers
63
+ # 列出所有容器
60
64
  manyoyo -l
61
65
 
62
- # Create new container with environment file
66
+ # 创建新容器并使用环境文件
63
67
  manyoyo -n test --ef .env -y c
64
68
 
65
- # Resume existing session
69
+ # 恢复现有会话
66
70
  manyoyo -n test -- -c
67
71
 
68
- # Execute command in interactive shell
72
+ # 在交互式 shell 中执行命令
69
73
  manyoyo -n test -x /bin/bash
70
74
 
71
- # Execute custom command
75
+ # 执行自定义命令
72
76
  manyoyo -n test -x echo "hello world"
73
77
 
74
- # Remove container
78
+ # 删除容器
75
79
  manyoyo -n test --rm
76
80
  ```
77
81
 
78
- ### Environment Variables
82
+ ### 环境变量
79
83
 
80
- #### String Format
84
+ #### 字符串格式
81
85
 
82
86
  ```bash
83
- # Direct
87
+ # 直接传递
84
88
  manyoyo -e "VAR=value" -x env
85
89
 
86
- # Multiple
90
+ # 多个变量
87
91
  manyoyo -e "A=1" -e "B=2" -x env
88
92
  ```
89
93
 
90
- #### File Format
94
+ #### 文件格式
91
95
 
92
96
  ```bash
93
- # From file
97
+ # 从文件加载
94
98
  manyoyo --ef .env -x env
95
99
  ```
96
100
 
97
- Environment files (`.env`) support the following formats:
101
+ 环境文件(`.env`)支持以下格式:
98
102
 
99
103
  ```bash
100
- # With export statement
104
+ # 使用 export 语句
101
105
  export ANTHROPIC_BASE_URL="https://api.anthropic.com"
102
106
  # export CLAUDE_CODE_OAUTH_TOKEN="sk-xxxxxxxx"
103
107
  export ANTHROPIC_AUTH_TOKEN="sk-xxxxxxxx"
@@ -108,107 +112,107 @@ export ANTHROPIC_DEFAULT_SONNET_MODEL="claude-sonnet-4-5"
108
112
  export ANTHROPIC_DEFAULT_HAIKU_MODEL="claude-haiku-4-5"
109
113
  export CLAUDE_CODE_SUBAGENT_MODEL="claude-sonnet-4-5"
110
114
 
111
- # Simple key=value
115
+ # 简单的键值对
112
116
  API_KEY=your-api-key-here
113
117
 
114
- # Quoted values (quotes will be stripped)
118
+ # 带引号的值(引号会被移除)
115
119
  MESSAGE="Hello World"
116
120
  PATH='/usr/local/bin'
117
121
  ```
118
122
 
119
- ### AI CLI Shortcuts (skip permissions)
123
+ ### AI CLI 快捷方式(跳过权限确认)
120
124
 
121
125
  ```bash
122
126
  # Claude Code
123
- manyoyo -y c # or: claude, cc
127
+ manyoyo -y c # 或: claude, cc
124
128
 
125
129
  # Gemini
126
- manyoyo -y gm # or: gemini, g
130
+ manyoyo -y gm # 或: gemini, g
127
131
 
128
132
  # Codex
129
- manyoyo -y cx # or: codex
133
+ manyoyo -y cx # 或: codex
130
134
 
131
135
  # OpenCode
132
- manyoyo -y oc # or: opencode
136
+ manyoyo -y oc # 或: opencode
133
137
  ```
134
138
 
135
- ### Interactive Session Management
139
+ ### 交互式会话管理
136
140
 
137
- After exiting a container session, you'll be prompted with options:
141
+ 退出容器会话后,系统将提示您选择操作:
138
142
 
139
- - `y` - Keep container running in background (default)
140
- - `n` - Delete the container
141
- - `1` - Re-enter with the original command
142
- - `s` - Execute a new command
143
- - `i` - Enter interactive shell
143
+ - `y` - 保持容器在后台运行(默认)
144
+ - `n` - 删除容器
145
+ - `1` - 使用首次命令重新进入
146
+ - `x` - 执行新命令
147
+ - `i` - 进入交互式 shell
144
148
 
145
- ### Container Modes
149
+ ### 容器模式
146
150
 
147
- #### Docker-in-Docker Development
151
+ #### Docker-in-Docker 开发
148
152
 
149
153
  ```bash
150
- # Docker-in-Docker (safe nested containers)
151
- # Create a container with Docker-in-Docker support
154
+ # Docker-in-Docker(安全的嵌套容器)
155
+ # 创建支持 Docker-in-Docker 的容器
152
156
  manyoyo -n docker-dev -m dind -x /bin/bash
153
157
 
154
- # Inside the container, start dockerd
158
+ # 在容器内启动 dockerd
155
159
  nohup dockerd &
156
160
 
157
- # Now you can use docker commands inside the container
161
+ # 现在可以在容器内使用 docker 命令
158
162
  docker run hello-world
159
163
  ```
160
164
 
161
- #### Mount Docker socket Development
165
+ #### 挂载 Docker Socket 开发
162
166
 
163
167
  ```bash
164
- # Mount Docker socket (dangerous - container can access host)
168
+ # 挂载 Docker Socket(危险 - 容器可以访问宿主机)
165
169
  manyoyo -n socket-dev -m mdsock -x docker ps
166
170
  ```
167
171
 
168
- ### Command-Line Options
169
-
170
- | Option | Aliases | Description |
171
- |--------|---------|-------------|
172
- | `-l` | `--ls`, `--list` | List all manyoyo containers |
173
- | `--hp PATH` | `--host-path` | Set host working directory (default: current path) |
174
- | `-n NAME` | `--cn`, `--cont-name` | Set container name |
175
- | `--cp PATH` | `--cont-path` | Set container working directory |
176
- | `--in NAME` | `--image-name` | Specify image name |
177
- | `--iv VERSION` | `--image-ver` | Specify image version |
178
- | `-e STRING` | `--env` | Set environment variable |
179
- | `--ef FILE` | `--env-file` | Load environment variables from file |
180
- | `-v STRING` | `--volume` | Bind mount volume |
181
- | `--rm` | `--rmc`, `--remove-cont` | Remove container |
182
- | `--sp CMD` | `--shell-prefix` | Temporary environment variable (prefix for -s) |
183
- | `-s CMD` | `--shell` | Specify command to execute |
184
- | `--` | `--ss`, `--shell-suffix` | Command arguments (suffix for -s) |
185
- | `-x CMD` | `--sf`, `--shell-full` | Full command (replaces --sp, -s, and --) |
186
- | `-y CLI` | `--yolo` | Run AI agent without confirmation |
187
- | `-m MODE` | `--cm`, `--cont-mode` | Set container mode (common, dind, mdsock) |
188
- | `--install NAME` | | Install manyoyo command |
189
- | `-V` | `--version` | Show version |
190
- | `-h` | `--help` | Show help |
172
+ ### 命令行选项
173
+
174
+ | 选项 | 别名 | 描述 |
175
+ |------|------|------|
176
+ | `-l` | `--ls`, `--list` | 列出所有 manyoyo 容器 |
177
+ | `--hp PATH` | `--host-path` | 设置宿主机工作目录(默认:当前路径) |
178
+ | `-n NAME` | `--cn`, `--cont-name` | 设置容器名称 |
179
+ | `--cp PATH` | `--cont-path` | 设置容器工作目录 |
180
+ | `--in NAME` | `--image-name` | 指定镜像名称 |
181
+ | `--iv VERSION` | `--image-ver` | 指定镜像版本 |
182
+ | `-e STRING` | `--env` | 设置环境变量 |
183
+ | `--ef FILE` | `--env-file` | 从文件加载环境变量 |
184
+ | `-v STRING` | `--volume` | 绑定挂载卷 |
185
+ | `--rm` | `--rmc`, `--remove-cont` | 删除容器 |
186
+ | `--sp CMD` | `--shell-prefix` | 临时环境变量(作为 -s 的前缀) |
187
+ | `-s CMD` | `--shell` | 指定要执行的命令 |
188
+ | `--` | `--ss`, `--shell-suffix` | 命令参数(作为 -s 的后缀) |
189
+ | `-x CMD` | `--sf`, `--shell-full` | 完整命令(替代 --sp, -s --) |
190
+ | `-y CLI` | `--yolo` | 无需确认运行 AI 智能体 |
191
+ | `-m MODE` | `--cm`, `--cont-mode` | 设置容器模式(common, dind, mdsock |
192
+ | `--install NAME` | | 安装 manyoyo 命令 |
193
+ | `-V` | `--version` | 显示版本 |
194
+ | `-h` | `--help` | 显示帮助 |
191
195
 
192
196
  ## 其他说明
193
197
 
194
- ### Default Configuration
198
+ ### 默认配置
195
199
 
196
- - **Container Name**: `myy-{MMDD-HHMM}` (auto-generated based on current time)
197
- - **Host Path**: Current working directory
198
- - **Container Path**: Same as host path
199
- - **Image**: `localhost/xcanwin/manyoyo:xxx`
200
+ - **容器名称**:`myy-{月日-时分}`(基于当前时间自动生成)
201
+ - **宿主机路径**:当前工作目录
202
+ - **容器路径**:与宿主机路径相同
203
+ - **镜像**:`localhost/xcanwin/manyoyo:xxx`
200
204
 
201
- ### Requirements
205
+ ### 系统要求
202
206
 
203
- - Node.js >= 14.0.0
204
- - Docker or Podman
207
+ - Node.js >= 24.0.0
208
+ - Docker Podman
205
209
 
206
- ### Remove
210
+ ### 卸载
207
211
 
208
212
  ```bash
209
213
  npm uninstall -g @xcanwin/manyoyo
210
214
  ```
211
215
 
212
- ## License
216
+ ## 许可证
213
217
 
214
218
  MIT
package/bin/manyoyo.js CHANGED
@@ -47,7 +47,19 @@ const NC = '\x1b[0m'; // No Color
47
47
  // Docker command (will be set by ensure_docker)
48
48
  let DOCKER_CMD = 'docker';
49
49
 
50
- function show_help() {
50
+ // ==============================================================================
51
+ // Utility Functions
52
+ // ==============================================================================
53
+
54
+ function sleep(ms) {
55
+ return new Promise(resolve => setTimeout(resolve, ms));
56
+ }
57
+
58
+ // ==============================================================================
59
+ // UI Functions
60
+ // ==============================================================================
61
+
62
+ function showHelp() {
51
63
  console.log(`${BLUE}Usage:${NC}`);
52
64
  console.log(` ${MANYOYO_NAME} [OPTIONS]`);
53
65
  console.log(` ${MANYOYO_NAME} [--hp HOST_PATH] [-n CONTAINER_NAME] [--cp CONTAINER_PATH] [--ef ENV_FILE] [--sp COMMAND] [-s COMMAND] [-- COMMAND]`);
@@ -84,41 +96,80 @@ function show_help() {
84
96
  console.log(` ${MANYOYO_NAME} -n test -x claude -c 恢复之前会话`);
85
97
  }
86
98
 
87
- function ensure_docker() {
88
- try {
89
- execSync('docker --version', { stdio: 'pipe' });
90
- DOCKER_CMD = 'docker';
91
- return true;
92
- } catch (e) {
93
- try {
94
- execSync('podman --version', { stdio: 'pipe' });
95
- DOCKER_CMD = 'podman';
96
- return true;
97
- } catch (e2) {
98
- console.error("docker/podman not found");
99
- process.exit(1);
100
- }
101
- }
99
+ function showVersion() {
100
+ console.log(`manyoyo by xcanwin, ${BIN_VERSION}`);
102
101
  }
103
102
 
104
- function install_manyoyo(name) {
105
- const MANYOYO_FILE = fs.realpathSync(__filename);
106
- switch (name) {
107
- case 'docker-cli-plugin':
108
- execSync(`mkdir -p "$HOME/.docker/cli-plugins/"`, { stdio: 'inherit' });
109
- execSync(`ln -f -s "${MANYOYO_FILE}" "$HOME/.docker/cli-plugins/docker-manyoyo"`, { stdio: 'inherit' });
110
- break;
111
- default:
112
- console.log("");
103
+ function getHelloTip(containerName, defaultCommand) {
104
+ console.log(`${BLUE}----------------------------------------${NC}`);
105
+ console.log(`📦 首次命令 : ${defaultCommand}`);
106
+ console.log(`⚫ 恢复首次命令会话: ${CYAN}${MANYOYO_NAME} -n ${containerName} -- -c${NC}`);
107
+ console.log(`⚫ 执行首次命令 : ${GREEN}${MANYOYO_NAME} -n ${containerName}${NC}`);
108
+ console.log(`⚫ 执行指定命令 : ${GREEN}${MANYOYO_NAME} -n ${containerName} -x /bin/bash${NC}`);
109
+ console.log(`⚫ 执行指定命令 : ${GREEN}docker exec -it ${containerName} /bin/bash${NC}`);
110
+ console.log(`⚫ 删除容器 : ${MANYOYO_NAME} -n ${containerName} --rm`);
111
+ console.log("");
112
+ }
113
+
114
+ async function askQuestion(prompt) {
115
+ const rl = readline.createInterface({
116
+ input: process.stdin,
117
+ output: process.stdout
118
+ });
119
+
120
+ return new Promise((resolve) => {
121
+ rl.question(prompt, (answer) => {
122
+ rl.close();
123
+ resolve(answer);
124
+ });
125
+ });
126
+ }
127
+
128
+ // ==============================================================================
129
+ // Configuration Functions
130
+ // ==============================================================================
131
+
132
+ function addEnv(env) {
133
+ CONTAINER_ENVS.push("--env", env);
134
+ }
135
+
136
+ function addEnvFile(envFile) {
137
+ ENV_FILE = envFile;
138
+ if (ENV_FILE && fs.existsSync(ENV_FILE)) {
139
+ const content = fs.readFileSync(ENV_FILE, 'utf-8');
140
+ const lines = content.split('\n');
141
+
142
+ for (let line of lines) {
143
+ // Match pattern: (export )?(KEY)=(VALUE)
144
+ const match = line.match(/^(?:export\s+)?([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.*)$/);
145
+ if (match) {
146
+ let key = match[1];
147
+ let value = match[2].trim();
148
+
149
+ // Filter malicious characters
150
+ if (/[\$\(\)\`\|\&\*\{\}]/.test(value)) continue;
151
+ if (/^\(/.test(value)) continue;
152
+
153
+ // Remove quotes
154
+ if (/^"(.*)"$/.test(value)) {
155
+ value = value.slice(1, -1);
156
+ } else if (/^'(.*)'$/.test(value)) {
157
+ value = value.slice(1, -1);
158
+ }
159
+
160
+ if (key) {
161
+ CONTAINER_ENVS.push("--env", `${key}=${value}`);
162
+ }
163
+ }
164
+ }
113
165
  }
114
- process.exit(0);
115
166
  }
116
167
 
117
- function show_version() {
118
- console.log(`manyoyo by xcanwin, ${BIN_VERSION}`);
168
+ function addVolume(volume) {
169
+ CONTAINER_VOLUMES.push("--volume", volume);
119
170
  }
120
171
 
121
- function set_yolo(cli) {
172
+ function setYolo(cli) {
122
173
  switch (cli) {
123
174
  case 'claude':
124
175
  case 'cc':
@@ -144,7 +195,7 @@ function set_yolo(cli) {
144
195
  }
145
196
  }
146
197
 
147
- function set_cont_mode(mode) {
198
+ function setContMode(mode) {
148
199
  switch (mode) {
149
200
  case 'common':
150
201
  CONT_MODE = "";
@@ -167,105 +218,96 @@ function set_cont_mode(mode) {
167
218
  }
168
219
  }
169
220
 
170
- function get_cont_list() {
221
+ // ==============================================================================
222
+ // Docker Helper Functions
223
+ // ==============================================================================
224
+
225
+ function dockerExec(cmd, options = {}) {
171
226
  try {
172
- const result = execSync(`${DOCKER_CMD} ps -a --size --filter "ancestor=manyoyo" --filter "ancestor=$(${DOCKER_CMD} images -a --format '{{.Repository}}:{{.Tag}}' | grep manyoyo)" --format "table {{.Names}}\\t{{.Status}}\\t{{.Size}}\\t{{.ID}}\\t{{.Image}}\\t{{.Ports}}\\t{{.Networks}}\\t{{.Mounts}}"`,
173
- { encoding: 'utf-8' });
174
- console.log(result);
227
+ return execSync(cmd, { encoding: 'utf-8', ...options });
175
228
  } catch (e) {
176
- console.log(e.stdout || '');
229
+ if (options.ignoreError) {
230
+ return e.stdout || '';
231
+ }
232
+ throw e;
177
233
  }
178
234
  }
179
235
 
180
- function add_volume(volume) {
181
- CONTAINER_VOLUMES.push("--volume", volume);
236
+ function containerExists(name) {
237
+ const containers = dockerExec(`${DOCKER_CMD} ps -a --format '{{.Names}}'`);
238
+ return containers.split('\n').some(n => n.trim() === name);
182
239
  }
183
240
 
184
- function add_env(env) {
185
- CONTAINER_ENVS.push("--env", env);
241
+ function getContainerStatus(name) {
242
+ return dockerExec(`${DOCKER_CMD} inspect -f '{{.State.Status}}' "${name}"`).trim();
186
243
  }
187
244
 
188
- function add_env_file(envFile) {
189
- ENV_FILE = envFile;
190
- if (ENV_FILE && fs.existsSync(ENV_FILE)) {
191
- const content = fs.readFileSync(ENV_FILE, 'utf-8');
192
- const lines = content.split('\n');
193
-
194
- for (let line of lines) {
195
- // Match pattern: (export )?(KEY)=(VALUE)
196
- const match = line.match(/^(?:export\s+)?([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.*)$/);
197
- if (match) {
198
- let key = match[1];
199
- let value = match[2].trim();
200
-
201
- // Filter malicious characters
202
- if (/[\$\(\)\`\|\&\*\{\}]/.test(value)) continue;
203
- if (/^\(/.test(value)) continue;
245
+ function removeContainer(name) {
246
+ console.log(`${YELLOW}🗑️ 正在删除容器: ${name}...${NC}`);
247
+ dockerExec(`${DOCKER_CMD} rm -f "${name}"`, { stdio: 'pipe' });
248
+ console.log(`${GREEN}✅ 已彻底删除。${NC}`);
249
+ }
204
250
 
205
- // Remove quotes
206
- if (/^"(.*)"$/.test(value)) {
207
- value = value.slice(1, -1);
208
- } else if (/^'(.*)'$/.test(value)) {
209
- value = value.slice(1, -1);
210
- }
251
+ // ==============================================================================
252
+ // Docker Operations
253
+ // ==============================================================================
211
254
 
212
- if (key) {
213
- CONTAINER_ENVS.push("--env", `${key}=${value}`);
214
- }
215
- }
255
+ function ensureDocker() {
256
+ const commands = ['docker', 'podman'];
257
+ for (const cmd of commands) {
258
+ try {
259
+ execSync(`${cmd} --version`, { stdio: 'pipe' });
260
+ DOCKER_CMD = cmd;
261
+ return true;
262
+ } catch (e) {
263
+ // Try next command
216
264
  }
217
265
  }
266
+ console.error("docker/podman not found");
267
+ process.exit(1);
218
268
  }
219
269
 
220
- function get_hello_tip(containerName, defaultCommand) {
221
- console.log(`${BLUE}----------------------------------------${NC}`);
222
- console.log(`📦 首次命令 : ${defaultCommand}`);
223
- console.log(`⚫ 恢复首次命令会话: ${CYAN}${MANYOYO_NAME} -n ${containerName} -- -c${NC}`);
224
- console.log(`⚫ 执行首次命令 : ${GREEN}${MANYOYO_NAME} -n ${containerName}${NC}`);
225
- console.log(`⚫ 执行指定命令 : ${GREEN}${MANYOYO_NAME} -n ${containerName} -x /bin/bash${NC}`);
226
- console.log(`⚫ 执行指定命令 : ${GREEN}docker exec -it ${containerName} /bin/bash${NC}`);
227
- console.log(`⚫ 删除容器 : ${MANYOYO_NAME} -n ${containerName} --rm`);
228
- console.log("");
270
+ function installManyoyo(name) {
271
+ const MANYOYO_FILE = fs.realpathSync(__filename);
272
+ switch (name) {
273
+ case 'docker-cli-plugin':
274
+ const pluginDir = path.join(process.env.HOME, '.docker/cli-plugins');
275
+ fs.mkdirSync(pluginDir, { recursive: true });
276
+ const targetPath = path.join(pluginDir, 'docker-manyoyo');
277
+ if (fs.existsSync(targetPath)) {
278
+ fs.unlinkSync(targetPath);
279
+ }
280
+ fs.symlinkSync(MANYOYO_FILE, targetPath);
281
+ break;
282
+ default:
283
+ console.log("");
284
+ }
285
+ process.exit(0);
229
286
  }
230
287
 
231
- function docker_exec(cmd, options = {}) {
288
+ function getContList() {
232
289
  try {
233
- return execSync(cmd, { encoding: 'utf-8', ...options });
290
+ const result = execSync(`${DOCKER_CMD} ps -a --size --filter "ancestor=manyoyo" --filter "ancestor=$(${DOCKER_CMD} images -a --format '{{.Repository}}:{{.Tag}}' | grep manyoyo)" --format "table {{.Names}}\\t{{.Status}}\\t{{.Size}}\\t{{.ID}}\\t{{.Image}}\\t{{.Ports}}\\t{{.Networks}}\\t{{.Mounts}}"`,
291
+ { encoding: 'utf-8' });
292
+ console.log(result);
234
293
  } catch (e) {
235
- if (options.ignoreError) {
236
- return e.stdout || '';
237
- }
238
- throw e;
294
+ console.log(e.stdout || '');
239
295
  }
240
296
  }
241
297
 
242
- function sleep(ms) {
243
- return new Promise(resolve => setTimeout(resolve, ms));
244
- }
245
-
246
- async function askQuestion(prompt) {
247
- const rl = readline.createInterface({
248
- input: process.stdin,
249
- output: process.stdout
250
- });
251
-
252
- return new Promise((resolve) => {
253
- rl.question(prompt, (answer) => {
254
- rl.close();
255
- resolve(answer);
256
- });
257
- });
258
- }
298
+ // ==============================================================================
299
+ // Main Function Helpers
300
+ // ==============================================================================
259
301
 
260
- async function main() {
302
+ function validateAndInitialize() {
261
303
  // Check if no arguments provided
262
304
  if (process.argv.length <= 2) {
263
- show_help();
305
+ showHelp();
264
306
  process.exit(1);
265
307
  }
266
308
 
267
309
  // Ensure docker/podman is available
268
- ensure_docker();
310
+ ensureDocker();
269
311
 
270
312
  // Docker CLI plugin metadata
271
313
  if (process.argv[2] === 'docker-cli-plugin-metadata') {
@@ -278,13 +320,15 @@ async function main() {
278
320
  console.log(JSON.stringify(metadata, null, 2));
279
321
  process.exit(0);
280
322
  }
323
+ }
281
324
 
325
+ function parseArguments(argv) {
282
326
  // Parse arguments
283
- let args = process.argv.slice(2);
327
+ let args = argv.slice(2);
284
328
 
285
329
  // Docker CLI plugin mode - remove first arg if running as plugin
286
330
  const dockerPluginPath = path.join(process.env.HOME || '', '.docker/cli-plugins/docker-manyoyo');
287
- if (process.argv[1] === dockerPluginPath && args[0] === 'manyoyo') {
331
+ if (argv[1] === dockerPluginPath && args[0] === 'manyoyo') {
288
332
  args.shift();
289
333
  }
290
334
 
@@ -297,7 +341,7 @@ async function main() {
297
341
  case '-l':
298
342
  case '--ls':
299
343
  case '--list':
300
- get_cont_list();
344
+ getContList();
301
345
  process.exit(0);
302
346
 
303
347
  case '--hp':
@@ -333,19 +377,19 @@ async function main() {
333
377
 
334
378
  case '-e':
335
379
  case '--env':
336
- add_env(args[i + 1]);
380
+ addEnv(args[i + 1]);
337
381
  i += 2;
338
382
  break;
339
383
 
340
384
  case '--ef':
341
385
  case '--env-file':
342
- add_env_file(args[i + 1]);
386
+ addEnvFile(args[i + 1]);
343
387
  i += 2;
344
388
  break;
345
389
 
346
390
  case '-v':
347
391
  case '--volume':
348
- add_volume(args[i + 1]);
392
+ addVolume(args[i + 1]);
349
393
  i += 2;
350
394
  break;
351
395
 
@@ -384,46 +428,44 @@ async function main() {
384
428
 
385
429
  case '-y':
386
430
  case '--yolo':
387
- set_yolo(args[i + 1]);
431
+ setYolo(args[i + 1]);
388
432
  i += 2;
389
433
  break;
390
434
 
391
435
  case '-m':
392
436
  case '--cm':
393
437
  case '--cont-mode':
394
- set_cont_mode(args[i + 1]);
438
+ setContMode(args[i + 1]);
395
439
  i += 2;
396
440
  break;
397
441
 
398
442
  case '--install':
399
- install_manyoyo(args[i + 1]);
443
+ installManyoyo(args[i + 1]);
400
444
  process.exit(0);
401
445
 
402
446
  case '-V':
403
447
  case '--version':
404
- show_version();
448
+ showVersion();
405
449
  process.exit(0);
406
450
 
407
451
  case '-h':
408
452
  case '--help':
409
- show_help();
453
+ showHelp();
410
454
  process.exit(0);
411
455
 
412
456
  default:
413
457
  console.log(`${RED}⚠️ 未知参数: ${arg}${NC}`);
414
- show_help();
458
+ showHelp();
415
459
  process.exit(1);
416
460
  }
417
461
  }
462
+ }
418
463
 
419
- // Handle remove logic
464
+ function handleRemoveContainer() {
420
465
  if (SHOULD_REMOVE) {
421
466
  try {
422
- const containers = docker_exec(`${DOCKER_CMD} ps -a --format '{{.Names}}'`);
423
- if (containers.split('\n').some(name => name.trim() === CONTAINER_NAME)) {
424
- console.log(`${YELLOW}🗑️ 正在删除容器: ${CONTAINER_NAME}...${NC}`);
425
- docker_exec(`${DOCKER_CMD} rm -f "${CONTAINER_NAME}"`, { stdio: 'pipe' });
426
- console.log(`${GREEN}✅ 已彻底删除。${NC}`);
467
+ if (containerExists(CONTAINER_NAME)) {
468
+ removeContainer(CONTAINER_NAME);
427
469
  } else {
428
470
  console.log(`${RED}⚠️ 错误: 未找到名为 ${CONTAINER_NAME} 的容器。${NC}`);
429
471
  }
@@ -432,136 +474,179 @@ async function main() {
432
474
  }
433
475
  process.exit(0);
434
476
  }
477
+ }
435
478
 
436
- // Safety check
479
+ function validateHostPath() {
437
480
  const realHostPath = fs.realpathSync(HOST_PATH);
438
481
  const homeDir = process.env.HOME || '/home';
439
482
  if (realHostPath === '/' || realHostPath === '/home' || realHostPath === homeDir) {
440
483
  console.log(`${RED}⚠️ 错误: 不允许挂载根目录或home目录。${NC}`);
441
484
  process.exit(1);
442
485
  }
486
+ }
443
487
 
444
- const FULL_IMAGE = `${IMAGE_NAME}:${IMAGE_VERSION}`;
445
- let DEFAULT_COMMAND = "";
488
+ async function waitForContainerReady(containerName) {
489
+ const MAX_RETRIES = 50;
490
+ let count = 0;
491
+ while (true) {
492
+ try {
493
+ const status = getContainerStatus(containerName);
446
494
 
447
- // Check if container exists
448
- try {
449
- const containers = docker_exec(`${DOCKER_CMD} ps -a --format '{{.Names}}'`);
450
- const containerExists = containers.split('\n').some(name => name.trim() === CONTAINER_NAME);
451
-
452
- if (!containerExists) {
453
- // Create new container
454
- console.log(`${CYAN}📦 manyoyo by xcanwin 正在创建新容器: ${YELLOW}${CONTAINER_NAME}${NC}\n`);
455
- EXEC_COMMAND = `${EXEC_COMMAND_PREFIX}${EXEC_COMMAND}${EXEC_COMMAND_SUFFIX}`;
456
- DEFAULT_COMMAND = EXEC_COMMAND;
457
-
458
- // Build docker run command
459
- const envArgs = CONTAINER_ENVS.join(' ');
460
- const volumeArgs = CONTAINER_VOLUMES.join(' ');
461
- const contModeArg = CONT_MODE ? CONT_MODE : '';
462
-
463
- const dockerRunCmd = `${DOCKER_CMD} run -d --name "${CONTAINER_NAME}" --entrypoint "" ${contModeArg} ${envArgs} ${volumeArgs} --volume "${HOST_PATH}:${CONTAINER_PATH}" --workdir "${CONTAINER_PATH}" --label "manyoyo.default_cmd=${EXEC_COMMAND}" "${FULL_IMAGE}" tail -f /dev/null`;
464
-
465
- docker_exec(dockerRunCmd, { stdio: 'pipe' });
466
-
467
- // Wait for container to be ready
468
- const MAX_RETRIES = 50;
469
- let count = 0;
470
- while (true) {
471
- try {
472
- const status = docker_exec(`${DOCKER_CMD} inspect -f '{{.State.Status}}' "${CONTAINER_NAME}"`).trim();
473
-
474
- if (status === 'running') {
475
- break;
476
- }
477
-
478
- if (status === 'exited') {
479
- console.log(`${RED}⚠️ 错误: 容器启动后立即退出。${NC}`);
480
- docker_exec(`${DOCKER_CMD} logs "${CONTAINER_NAME}"`, { stdio: 'inherit' });
481
- process.exit(1);
482
- }
483
-
484
- await sleep(100);
485
- count++;
486
-
487
- if (count >= MAX_RETRIES) {
488
- console.log(`${RED}⚠️ 错误: 容器启动超时(当前状态: ${status})。${NC}`);
489
- docker_exec(`${DOCKER_CMD} logs "${CONTAINER_NAME}"`, { stdio: 'inherit' });
490
- process.exit(1);
491
- }
492
- } catch (e) {
493
- await sleep(100);
494
- count++;
495
- if (count >= MAX_RETRIES) {
496
- console.log(`${RED}⚠️ 错误: 容器启动超时。${NC}`);
497
- process.exit(1);
498
- }
499
- }
495
+ if (status === 'running') {
496
+ break;
500
497
  }
501
- } else {
502
- // Container exists
503
- console.log(`${CYAN}🔄 manyoyo by xcanwin 正在连接到现有容器: ${YELLOW}${CONTAINER_NAME}${NC}`);
504
-
505
- // Start container if stopped
506
- const status = docker_exec(`${DOCKER_CMD} inspect -f '{{.State.Status}}' "${CONTAINER_NAME}"`).trim();
507
- if (status !== 'running') {
508
- docker_exec(`${DOCKER_CMD} start "${CONTAINER_NAME}"`, { stdio: 'pipe' });
498
+
499
+ if (status === 'exited') {
500
+ console.log(`${RED}⚠️ 错误: 容器启动后立即退出。${NC}`);
501
+ dockerExec(`${DOCKER_CMD} logs "${containerName}"`, { stdio: 'inherit' });
502
+ process.exit(1);
509
503
  }
510
504
 
511
- // Get default command from label
512
- DEFAULT_COMMAND = docker_exec(`${DOCKER_CMD} inspect -f '{{index .Config.Labels "manyoyo.default_cmd"}}' "${CONTAINER_NAME}"`).trim();
505
+ await sleep(100);
506
+ count++;
513
507
 
514
- if (!EXEC_COMMAND) {
515
- EXEC_COMMAND = `${EXEC_COMMAND_PREFIX}${DEFAULT_COMMAND}${EXEC_COMMAND_SUFFIX}`;
516
- } else {
517
- EXEC_COMMAND = `${EXEC_COMMAND_PREFIX}${EXEC_COMMAND}${EXEC_COMMAND_SUFFIX}`;
508
+ if (count >= MAX_RETRIES) {
509
+ console.log(`${RED}⚠️ 错误: 容器启动超时(当前状态: ${status})。${NC}`);
510
+ dockerExec(`${DOCKER_CMD} logs "${containerName}"`, { stdio: 'inherit' });
511
+ process.exit(1);
512
+ }
513
+ } catch (e) {
514
+ await sleep(100);
515
+ count++;
516
+ if (count >= MAX_RETRIES) {
517
+ console.log(`${RED}⚠️ 错误: 容器启动超时。${NC}`);
518
+ process.exit(1);
518
519
  }
519
520
  }
521
+ }
522
+ }
520
523
 
521
- get_hello_tip(CONTAINER_NAME, DEFAULT_COMMAND);
522
- console.log(`${BLUE}----------------------------------------${NC}`);
523
- console.log(`💻 执行命令: ${YELLOW}${EXEC_COMMAND || '交互式 Shell'}${NC}`);
524
+ async function createNewContainer() {
525
+ console.log(`${CYAN}📦 manyoyo by xcanwin 正在创建新容器: ${YELLOW}${CONTAINER_NAME}${NC}\n`);
524
526
 
525
- // Execute command in container
526
- if (EXEC_COMMAND) {
527
- spawnSync(`${DOCKER_CMD}`, ['exec', '-it', CONTAINER_NAME, '/bin/bash', '-c', EXEC_COMMAND], { stdio: 'inherit' });
528
- } else {
529
- spawnSync(`${DOCKER_CMD}`, ['exec', '-it', CONTAINER_NAME, '/bin/bash'], { stdio: 'inherit' });
530
- }
527
+ EXEC_COMMAND = `${EXEC_COMMAND_PREFIX}${EXEC_COMMAND}${EXEC_COMMAND_SUFFIX}`;
528
+ const defaultCommand = EXEC_COMMAND;
531
529
 
532
- // Post-exit prompt
533
- console.log("");
534
- get_hello_tip(CONTAINER_NAME, DEFAULT_COMMAND);
535
-
536
- const reply = await askQuestion(`❔ 会话已结束。是否保留此后台容器 ${CONTAINER_NAME}? [ y=默认保留, n=删除, 1=首次命令进入, s=执行命令, i=交互式SHELL ]: `);
537
- console.log("");
538
-
539
- const firstChar = reply.trim().toLowerCase()[0];
540
-
541
- if (firstChar === 'n') {
542
- console.log(`${YELLOW}🗑️ 正在删除容器...${NC}`);
543
- docker_exec(`${DOCKER_CMD} rm -f "${CONTAINER_NAME}"`, { stdio: 'pipe' });
544
- console.log(`${GREEN}✅ 已彻底删除。${NC}`);
545
- } else if (firstChar === '1') {
546
- console.log(`${GREEN}✅ 离开当前连接,用首次命令进入。${NC}`);
547
- // Reconstruct the command and execute recursively
548
- const newArgs = ['-n', CONTAINER_NAME];
549
- process.argv = [process.argv[0], process.argv[1], ...newArgs];
550
- await main();
551
- } else if (firstChar === 's') {
552
- const command = await askQuestion('❔ 输入要执行的命令: ');
553
- console.log(`${GREEN}✅ 离开当前连接,执行命令。${NC}`);
554
- const newArgs = ['-n', CONTAINER_NAME, '-x', command];
555
- process.argv = [process.argv[0], process.argv[1], ...newArgs];
556
- await main();
557
- } else if (firstChar === 'i') {
558
- console.log(`${GREEN} 离开当前连接,进入容器交互式SHELL。${NC}`);
559
- const newArgs = ['-n', CONTAINER_NAME, '-x', '/bin/bash'];
560
- process.argv = [process.argv[0], process.argv[1], ...newArgs];
561
- await main();
562
- } else {
563
- console.log(`${GREEN}✅ 已退出连接。容器 ${CONTAINER_NAME} 仍在后台运行。${NC}`);
564
- }
530
+ // Build docker run command
531
+ const fullImage = `${IMAGE_NAME}:${IMAGE_VERSION}`;
532
+ const envArgs = CONTAINER_ENVS.join(' ');
533
+ const volumeArgs = CONTAINER_VOLUMES.join(' ');
534
+ const contModeArg = CONT_MODE || '';
535
+
536
+ const dockerRunCmd = `${DOCKER_CMD} run -d --name "${CONTAINER_NAME}" --entrypoint "" ${contModeArg} ${envArgs} ${volumeArgs} --volume "${HOST_PATH}:${CONTAINER_PATH}" --workdir "${CONTAINER_PATH}" --label "manyoyo.default_cmd=${EXEC_COMMAND}" "${fullImage}" tail -f /dev/null`;
537
+
538
+ dockerExec(dockerRunCmd, { stdio: 'pipe' });
539
+
540
+ // Wait for container to be ready
541
+ await waitForContainerReady(CONTAINER_NAME);
542
+
543
+ return defaultCommand;
544
+ }
545
+
546
+ async function connectExistingContainer() {
547
+ console.log(`${CYAN}🔄 manyoyo by xcanwin 正在连接到现有容器: ${YELLOW}${CONTAINER_NAME}${NC}`);
548
+
549
+ // Start container if stopped
550
+ const status = getContainerStatus(CONTAINER_NAME);
551
+ if (status !== 'running') {
552
+ dockerExec(`${DOCKER_CMD} start "${CONTAINER_NAME}"`, { stdio: 'pipe' });
553
+ }
554
+
555
+ // Get default command from label
556
+ const defaultCommand = dockerExec(`${DOCKER_CMD} inspect -f '{{index .Config.Labels "manyoyo.default_cmd"}}' "${CONTAINER_NAME}"`).trim();
557
+
558
+ if (!EXEC_COMMAND) {
559
+ EXEC_COMMAND = `${EXEC_COMMAND_PREFIX}${defaultCommand}${EXEC_COMMAND_SUFFIX}`;
560
+ } else {
561
+ EXEC_COMMAND = `${EXEC_COMMAND_PREFIX}${EXEC_COMMAND}${EXEC_COMMAND_SUFFIX}`;
562
+ }
563
+
564
+ return defaultCommand;
565
+ }
566
+
567
+ async function setupContainer() {
568
+ if (!containerExists(CONTAINER_NAME)) {
569
+ return await createNewContainer();
570
+ } else {
571
+ return await connectExistingContainer();
572
+ }
573
+ }
574
+
575
+ function executeInContainer(defaultCommand) {
576
+ getHelloTip(CONTAINER_NAME, defaultCommand);
577
+ console.log(`${BLUE}----------------------------------------${NC}`);
578
+ console.log(`💻 执行命令: ${YELLOW}${EXEC_COMMAND || '交互式 Shell'}${NC}`);
579
+
580
+ // Execute command in container
581
+ if (EXEC_COMMAND) {
582
+ spawnSync(`${DOCKER_CMD}`, ['exec', '-it', CONTAINER_NAME, '/bin/bash', '-c', EXEC_COMMAND], { stdio: 'inherit' });
583
+ } else {
584
+ spawnSync(`${DOCKER_CMD}`, ['exec', '-it', CONTAINER_NAME, '/bin/bash'], { stdio: 'inherit' });
585
+ }
586
+ }
587
+
588
+ async function handlePostExit(defaultCommand) {
589
+ console.log("");
590
+ getHelloTip(CONTAINER_NAME, defaultCommand);
591
+
592
+ const reply = await askQuestion(`❔ 会话已结束。是否保留此后台容器 ${CONTAINER_NAME}? [ y=默认保留, n=删除, 1=首次命令进入, x=执行命令, i=交互式SHELL ]: `);
593
+ console.log("");
594
+
595
+ const firstChar = reply.trim().toLowerCase()[0];
596
+
597
+ if (firstChar === 'n') {
598
+ removeContainer(CONTAINER_NAME);
599
+ } else if (firstChar === '1') {
600
+ console.log(`${GREEN}✅ 离开当前连接,用首次命令进入。${NC}`);
601
+ // Reset command variables to use default command
602
+ EXEC_COMMAND = "";
603
+ EXEC_COMMAND_PREFIX = "";
604
+ EXEC_COMMAND_SUFFIX = "";
605
+ const newArgs = ['-n', CONTAINER_NAME];
606
+ process.argv = [process.argv[0], process.argv[1], ...newArgs];
607
+ await main();
608
+ } else if (firstChar === 'x') {
609
+ const command = await askQuestion('❔ 输入要执行的命令: ');
610
+ console.log(`${GREEN}✅ 离开当前连接,执行命令。${NC}`);
611
+ const newArgs = ['-n', CONTAINER_NAME, '-x', command];
612
+ process.argv = [process.argv[0], process.argv[1], ...newArgs];
613
+ await main();
614
+ } else if (firstChar === 'i') {
615
+ console.log(`${GREEN}✅ 离开当前连接,进入容器交互式SHELL。${NC}`);
616
+ const newArgs = ['-n', CONTAINER_NAME, '-x', '/bin/bash'];
617
+ process.argv = [process.argv[0], process.argv[1], ...newArgs];
618
+ await main();
619
+ } else {
620
+ console.log(`${GREEN}✅ 已退出连接。容器 ${CONTAINER_NAME} 仍在后台运行。${NC}`);
621
+ }
622
+ }
623
+
624
+ // ==============================================================================
625
+ // Main Function
626
+ // ==============================================================================
627
+
628
+ async function main() {
629
+ try {
630
+ // 1. Validate and initialize
631
+ validateAndInitialize();
632
+
633
+ // 2. Parse command-line arguments
634
+ parseArguments(process.argv);
635
+
636
+ // 3. Handle remove container operation
637
+ handleRemoveContainer();
638
+
639
+ // 4. Validate host path safety
640
+ validateHostPath();
641
+
642
+ // 5. Setup container (create or connect)
643
+ const defaultCommand = await setupContainer();
644
+
645
+ // 6. Execute command in container
646
+ executeInContainer(defaultCommand);
647
+
648
+ // 7. Handle post-exit interactions
649
+ await handlePostExit(defaultCommand);
565
650
 
566
651
  } catch (e) {
567
652
  console.error(`${RED}Error: ${e.message}${NC}`);
@@ -0,0 +1,218 @@
1
+ [ [English](README_EN.md) ] | [中文](../README.md)
2
+
3
+ ---
4
+
5
+ # MANYOYO (Man-Yo-Yo)
6
+
7
+ **MANYOYO** is an AI agent security sandbox that is safe, efficient, and token-saving. Designed specifically for Agent YOLO mode to protect the host machine.
8
+
9
+ Pre-installed with common agents and tools to further save tokens. Freely switch between agents and `/bin/bash` in a loop for enhanced efficiency.
10
+
11
+ **MANYOYO** provides an isolated Docker/Podman container environment for running AI agent CLIs safely.
12
+
13
+ ## Key Features
14
+
15
+ - **Multi-Agent Support**: Supports claude code, gemini, codex, opencode
16
+ - **Security Isolation**: Protects host machine, supports safe nested containers (Docker-in-Docker)
17
+ - **Quick Launch**: Quickly enable common Agent YOLO / SOLO mode (e.g., claude --dangerously-skip-permissions)
18
+ - **Convenient Operations**: Quick access to `/bin/bash`
19
+ - **Session Recovery**: Install Skills Marketplace to quickly resume sessions
20
+ - **Flexible Customization**: Support custom `BASEURL`, `AUTH_TOKEN`, and other variables
21
+ - **Configuration Management**: Quick import of configuration files
22
+ - **Advanced Mode**: Supports dangerous nested containers (mount-docker-socket), custom sandbox images
23
+
24
+ # Usage
25
+
26
+ ## 1. Install podman
27
+
28
+ - Install [podman](https://podman.io/docs/installation)
29
+
30
+ ## 2. Build Image
31
+
32
+ ```
33
+ podman pull ubuntu:24.04
34
+ iv=1.4.0-all && podman build -t localhost/xcanwin/manyoyo:$iv -f docker/manyoyo.Dockerfile . --build-arg EXT=all --no-cache
35
+ podman image prune -f
36
+ ```
37
+
38
+ ## 3. Install manyoyo (Choose One)
39
+
40
+ ### Global Installation (Recommended)
41
+
42
+ ```bash
43
+ npm install -g @xcanwin/manyoyo
44
+ ```
45
+
46
+ ### Local Development
47
+
48
+ ```bash
49
+ npm install -g .
50
+ ```
51
+
52
+ ## 4. Usage
53
+
54
+ ### Basic Commands
55
+
56
+ ```bash
57
+ # Show help
58
+ manyoyo -h
59
+
60
+ # Show version
61
+ manyoyo -V
62
+
63
+ # List all containers
64
+ manyoyo -l
65
+
66
+ # Create new container with environment file
67
+ manyoyo -n test --ef .env -y c
68
+
69
+ # Resume existing session
70
+ manyoyo -n test -- -c
71
+
72
+ # Execute command in interactive shell
73
+ manyoyo -n test -x /bin/bash
74
+
75
+ # Execute custom command
76
+ manyoyo -n test -x echo "hello world"
77
+
78
+ # Remove container
79
+ manyoyo -n test --rm
80
+ ```
81
+
82
+ ### Environment Variables
83
+
84
+ #### String Format
85
+
86
+ ```bash
87
+ # Direct
88
+ manyoyo -e "VAR=value" -x env
89
+
90
+ # Multiple
91
+ manyoyo -e "A=1" -e "B=2" -x env
92
+ ```
93
+
94
+ #### File Format
95
+
96
+ ```bash
97
+ # From file
98
+ manyoyo --ef .env -x env
99
+ ```
100
+
101
+ Environment files (`.env`) support the following formats:
102
+
103
+ ```bash
104
+ # With export statement
105
+ export ANTHROPIC_BASE_URL="https://api.anthropic.com"
106
+ # export CLAUDE_CODE_OAUTH_TOKEN="sk-xxxxxxxx"
107
+ export ANTHROPIC_AUTH_TOKEN="sk-xxxxxxxx"
108
+ export API_TIMEOUT_MS=3000000
109
+ export ANTHROPIC_MODEL="claude-sonnet-4-5"
110
+ export ANTHROPIC_DEFAULT_OPUS_MODEL="claude-opus-4-5"
111
+ export ANTHROPIC_DEFAULT_SONNET_MODEL="claude-sonnet-4-5"
112
+ export ANTHROPIC_DEFAULT_HAIKU_MODEL="claude-haiku-4-5"
113
+ export CLAUDE_CODE_SUBAGENT_MODEL="claude-sonnet-4-5"
114
+
115
+ # Simple key=value
116
+ API_KEY=your-api-key-here
117
+
118
+ # Quoted values (quotes will be stripped)
119
+ MESSAGE="Hello World"
120
+ PATH='/usr/local/bin'
121
+ ```
122
+
123
+ ### AI CLI Shortcuts (skip permissions)
124
+
125
+ ```bash
126
+ # Claude Code
127
+ manyoyo -y c # or: claude, cc
128
+
129
+ # Gemini
130
+ manyoyo -y gm # or: gemini, g
131
+
132
+ # Codex
133
+ manyoyo -y cx # or: codex
134
+
135
+ # OpenCode
136
+ manyoyo -y oc # or: opencode
137
+ ```
138
+
139
+ ### Interactive Session Management
140
+
141
+ After exiting a container session, you'll be prompted with options:
142
+
143
+ - `y` - Keep container running in background (default)
144
+ - `n` - Delete the container
145
+ - `1` - Re-enter with the original command
146
+ - `x` - Execute a new command
147
+ - `i` - Enter interactive shell
148
+
149
+ ### Container Modes
150
+
151
+ #### Docker-in-Docker Development
152
+
153
+ ```bash
154
+ # Docker-in-Docker (safe nested containers)
155
+ # Create a container with Docker-in-Docker support
156
+ manyoyo -n docker-dev -m dind -x /bin/bash
157
+
158
+ # Inside the container, start dockerd
159
+ nohup dockerd &
160
+
161
+ # Now you can use docker commands inside the container
162
+ docker run hello-world
163
+ ```
164
+
165
+ #### Mount Docker socket Development
166
+
167
+ ```bash
168
+ # Mount Docker socket (dangerous - container can access host)
169
+ manyoyo -n socket-dev -m mdsock -x docker ps
170
+ ```
171
+
172
+ ### Command-Line Options
173
+
174
+ | Option | Aliases | Description |
175
+ |--------|---------|-------------|
176
+ | `-l` | `--ls`, `--list` | List all manyoyo containers |
177
+ | `--hp PATH` | `--host-path` | Set host working directory (default: current path) |
178
+ | `-n NAME` | `--cn`, `--cont-name` | Set container name |
179
+ | `--cp PATH` | `--cont-path` | Set container working directory |
180
+ | `--in NAME` | `--image-name` | Specify image name |
181
+ | `--iv VERSION` | `--image-ver` | Specify image version |
182
+ | `-e STRING` | `--env` | Set environment variable |
183
+ | `--ef FILE` | `--env-file` | Load environment variables from file |
184
+ | `-v STRING` | `--volume` | Bind mount volume |
185
+ | `--rm` | `--rmc`, `--remove-cont` | Remove container |
186
+ | `--sp CMD` | `--shell-prefix` | Temporary environment variable (prefix for -s) |
187
+ | `-s CMD` | `--shell` | Specify command to execute |
188
+ | `--` | `--ss`, `--shell-suffix` | Command arguments (suffix for -s) |
189
+ | `-x CMD` | `--sf`, `--shell-full` | Full command (replaces --sp, -s, and --) |
190
+ | `-y CLI` | `--yolo` | Run AI agent without confirmation |
191
+ | `-m MODE` | `--cm`, `--cont-mode` | Set container mode (common, dind, mdsock) |
192
+ | `--install NAME` | | Install manyoyo command |
193
+ | `-V` | `--version` | Show version |
194
+ | `-h` | `--help` | Show help |
195
+
196
+ ## Additional Information
197
+
198
+ ### Default Configuration
199
+
200
+ - **Container Name**: `myy-{MMDD-HHMM}` (auto-generated based on current time)
201
+ - **Host Path**: Current working directory
202
+ - **Container Path**: Same as host path
203
+ - **Image**: `localhost/xcanwin/manyoyo:xxx`
204
+
205
+ ### Requirements
206
+
207
+ - Node.js >= 24.0.0
208
+ - Docker or Podman
209
+
210
+ ### Uninstall
211
+
212
+ ```bash
213
+ npm uninstall -g @xcanwin/manyoyo
214
+ ```
215
+
216
+ ## License
217
+
218
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "2.2.1",
3
+ "version": "3.0.1",
4
4
  "description": "AI Agent CLI Security Sandbox",
5
5
  "keywords": [
6
6
  "ai", "agent", "sandbox", "docker", "cli", "container", "development"
@@ -26,8 +26,9 @@
26
26
  "node": ">=14.0.0"
27
27
  },
28
28
  "files": [
29
- "manyoyo.js",
29
+ "bin/manyoyo.js",
30
30
  "README.md",
31
+ "docs/README_EN.md",
31
32
  "LICENSE",
32
33
  "docker/manyoyo.Dockerfile"
33
34
  ]