@xcanwin/manyoyo 5.3.6 → 5.3.9

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
@@ -3,7 +3,8 @@
3
3
  </p>
4
4
 
5
5
  # <p align="center"><a href="https://github.com/xcanwin/manyoyo">MANYOYO(慢悠悠)</a></p>
6
- <p align="center">一款 AI Agent CLI 安全沙箱,基于 Docker/Podman 保护宿主机,支持 YOLO/SOLO 模式。</p>
6
+ <p align="center">面向 AI Agent CLI Docker / Podman 安全沙箱。</p>
7
+ <p align="center">用于隔离 Claude Code、Codex、Gemini、OpenCode 等命令行智能体,降低宿主机风险,并保持可复现的运行环境。</p>
7
8
  <p align="center">
8
9
  <a href="https://www.npmjs.com/package/@xcanwin/manyoyo"><img alt="npm" src="https://img.shields.io/npm/v/@xcanwin/manyoyo?style=flat-square" /></a>
9
10
  <a href="https://github.com/xcanwin/manyoyo/actions/workflows/npm-publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/xcanwin/manyoyo/npm-publish.yml?style=flat-square" /></a>
@@ -15,157 +16,185 @@
15
16
  <a href="https://xcanwin.github.io/manyoyo/en/">English</a>
16
17
  </p>
17
18
  <p align="center">
18
- 📚 在线文档:<a href="https://xcanwin.github.io/manyoyo/">https://xcanwin.github.io/manyoyo/</a>
19
+ 文档:<a href="https://xcanwin.github.io/manyoyo/">https://xcanwin.github.io/manyoyo/</a>
19
20
  </p>
20
21
 
21
22
  ---
22
23
 
23
- ## 项目简介
24
+ ## 为什么是 MANYOYO
24
25
 
25
- **MANYOYO** 是一款 AI 智能体提效安全沙箱,安全、高效、省 token,专为 Agent YOLO 模式设计,保障宿主机安全。
26
+ AI Agent CLI 往往需要:
26
27
 
27
- 预装常见 Agent 与工具,进一步节省 token。循环自由切换 Agent 和 `/bin/bash`,进一步提效。
28
+ - 访问代码仓库
29
+ - 执行 shell 命令
30
+ - 读写文件
31
+ - 安装依赖或调用容器能力
28
32
 
29
- **MANYOYO** 提供隔离的 Docker/Podman 容器环境,用于安全运行 AI 智能体命令行工具。
33
+ 直接在宿主机上裸跑这些工具,风险边界通常不清晰。**MANYOYO** 的目标不是替代容器平台,而是把常见 Agent CLI 的运行方式收敛到一个更清晰、可复现、可审计的沙箱入口。
30
34
 
31
- ## 功能亮点
35
+ 你可以把它理解为:
32
36
 
33
- - **多智能体支持**:支持 claude code, gemini, codex, opencode
34
- - **安全隔离**:保护宿主机,支持安全容器嵌套(Docker-in-Docker)
35
- - **快速启动**:快捷开启常见 Agent YOLO / SOLO 模式(例如 claude --dangerously-skip-permissions)
36
- - **便捷操作**:快速进入 `/bin/bash`
37
- - **会话恢复**:安装 Skills Marketplace 可快速恢复会话
38
- - **灵活自定义**:支持配置各 CLI 的 `*_BASE_URL` / `*_AUTH_TOKEN` / `*_API_KEY` 等变量
39
- - **配置管理**:快捷导入配置文件
40
- - **高级模式**:支持危险容器嵌套(mount-docker-socket)、自定义沙箱镜像
37
+ - 面向 Agent CLI 的运行包装层
38
+ - 面向团队协作的配置与镜像约定
39
+ - 面向高风险模式的显式边界说明
41
40
 
42
- ---
41
+ ## 核心能力
42
+
43
+ - **多 Agent 支持**:支持 `claude`、`gemini`、`codex`、`opencode`
44
+ - **容器隔离**:基于 Docker / Podman 运行,降低宿主机暴露面
45
+ - **YOLO / SOLO 工作流**:适配跳过权限确认的高效率模式
46
+ - **统一配置入口**:集中管理 `runs.<name>`、环境变量、挂载与镜像参数
47
+ - **命令可预览**:支持查看配置合并结果与最终命令拼装
48
+ - **会话与 Web 模式**:支持容器会话管理与网页访问入口
49
+ - **镜像可定制**:支持 common / full / 自定义工具集镜像
43
50
 
44
51
  ## 快速开始
45
52
 
46
53
  ```bash
47
- npm install -g @xcanwin/manyoyo # 安装
54
+ npm install -g @xcanwin/manyoyo
48
55
  podman pull ubuntu:24.04 # 仅 Podman 需要
49
- manyoyo build --iv 1.8.4-common # 构建镜像
50
- manyoyo init all # 从本机 Agent 配置迁移到 ~/.manyoyo
51
- manyoyo run -r claude # 使用 manyoyo.json 的 runs.claude 启动
56
+ manyoyo build --iv 1.8.4-common
57
+ manyoyo init all
58
+ manyoyo run -r claude
52
59
  ```
53
60
 
54
- 注意:YOLO/SOLO 会跳过权限确认,请确保在可控环境中使用。
61
+ 系统要求:
55
62
 
56
- ---
63
+ - Node.js >= 22
64
+ - Podman(推荐)或 Docker
57
65
 
58
- ## 适用场景(高频)
66
+ 注意:
59
67
 
60
- - 安全运行 **Claude Code YOLO** / **SOLO** 模式
61
- - 在容器中运行 **Codex CLI**,降低宿主机风险
62
- - 使用 **Gemini CLI / OpenCode** 做代码任务隔离
63
- - 用 **Docker/Podman sandbox** 统一团队 Agent 运行环境
68
+ - `YOLO / SOLO` 会跳过权限确认,只适合在可控环境中使用
69
+ - `sock` 模式会暴露宿主机 Docker socket,不属于强隔离
70
+
71
+ ## 适合什么场景
72
+
73
+ - 在容器中运行 **Claude Code YOLO / SOLO**
74
+ - 为 **Codex CLI** 提供独立于宿主机的运行边界
75
+ - 隔离运行 **Gemini CLI / OpenCode** 的代码任务
76
+ - 用统一镜像和配置管理团队 Agent 环境
77
+ - 在调试和自动化任务中快速切换 Agent 与 `/bin/bash`
64
78
 
65
79
  ## 裸跑 vs MANYOYO
66
80
 
67
81
  | 对比项 | 裸跑 Agent CLI | MANYOYO |
68
82
  | --- | --- | --- |
69
- | 宿主机风险 | 高 | 低(容器隔离) |
70
- | 环境复用 | | 强(镜像 + 配置) |
71
- | 会话恢复 | 依赖工具自身 | 支持统一会话管理 |
72
- | 切换效率 | 一般 | 快捷(`-y` / `-x`) |
83
+ | 宿主机暴露面 | 高 | 更低 |
84
+ | 运行边界 | 分散 | 集中到容器与配置 |
85
+ | 环境复现 | | 强(镜像 + 配置) |
86
+ | 高风险模式说明 | 通常依赖工具自身 | 明确提示 YOLO / SOLO / sock 风险 |
87
+ | 团队统一性 | 弱 | 更强 |
73
88
 
74
- ---
89
+ ## 安全边界
75
90
 
76
- ## 安全提示
91
+ MANYOYO 可以降低风险,但不是“绝对安全”:
77
92
 
78
- - **YOLO/SOLO 模式**:跳过权限确认,存在误删或执行危险命令风险。详见:[AI 智能体](https://xcanwin.github.io/manyoyo/zh/reference/agents)
79
- - **sock 容器模式**:挂载宿主机 Docker socket,容器可完全控制宿主机容器。详见:[容器模式](https://xcanwin.github.io/manyoyo/zh/reference/container-modes)
93
+ - 它的主要隔离手段是容器,不是虚拟机
94
+ - `YOLO / SOLO` 仍然可能执行危险命令
95
+ - `sock` 模式本质上会把宿主机容器控制权暴露给容器
96
+ - 自定义挂载、环境变量和网络访问会直接影响实际安全边界
80
97
 
81
- ## 安装
98
+ 相关文档:
82
99
 
83
- ### 全局安装(推荐)
100
+ - [AI 智能体说明](https://xcanwin.github.io/manyoyo/zh/reference/agents)
101
+ - [容器模式说明](https://xcanwin.github.io/manyoyo/zh/reference/container-modes)
102
+ - [网页认证与安全](https://xcanwin.github.io/manyoyo/zh/advanced/web-server-auth)
103
+
104
+ ## 常用命令
84
105
 
85
106
  ```bash
86
- npm install -g @xcanwin/manyoyo
87
- ```
107
+ # 初始化与迁移
108
+ manyoyo init all
109
+
110
+ # 启动常见 Agent
111
+ manyoyo run -y c
112
+ manyoyo run -y gm
113
+ manyoyo run -y cx
114
+ manyoyo run -y oc
88
115
 
89
- ### 系统要求
116
+ # 更新
117
+ manyoyo update
118
+
119
+ # 容器与调试
120
+ manyoyo ps
121
+ manyoyo images
122
+ manyoyo run -n my-dev -x /bin/bash
123
+ manyoyo rm my-dev
90
124
 
91
- - Node.js >= 22.0.0
92
- - Podman(推荐) 或 Docker
125
+ # Web 模式
126
+ manyoyo serve 127.0.0.1:3000
127
+ manyoyo serve 127.0.0.1:3000 -U admin -P 123456
128
+ manyoyo serve 127.0.0.1:3000 -U admin -P 123456 -d
129
+ manyoyo serve 127.0.0.1:3000 -d # 未设置密码时会打印本次随机密码
93
130
 
94
- 详细安装指南请参考:[安装详解](https://xcanwin.github.io/manyoyo/zh/guide/installation)
131
+ # 查看配置与命令拼装
132
+ manyoyo config show
133
+ manyoyo config command
134
+ ```
95
135
 
96
- ## 构建镜像
136
+ ## 镜像构建
97
137
 
98
138
  ```bash
99
- # 构建 common 版本(推荐)
139
+ # common 版本
100
140
  manyoyo build --iv 1.8.4-common
101
141
 
102
- # 构建 full 版本
142
+ # full 版本
103
143
  manyoyo build --iv 1.8.4-full
104
144
 
105
- # 构建自定义版本
145
+ # 自定义工具集
106
146
  manyoyo build --iba TOOL=go,codex,java,gemini
107
147
  ```
108
148
 
109
- - 首次构建会自动下载依赖到 `docker/cache/`,2天内再次构建会使用缓存,速度提升约 **5 倍**
110
-
111
- ## 常用命令
149
+ 说明:
112
150
 
113
- ```bash
114
- # 配置迁移(推荐首步)
115
- manyoyo init all
151
+ - 首次构建会把依赖缓存到 `docker/cache/`
152
+ - 在缓存有效期内重复构建,通常会更快
153
+ - `imageVersion` 格式必须为 `x.y.z-后缀`
116
154
 
117
- # 启动常见智能体
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 安装场景)
155
+ ## 配置模型
123
156
 
124
- # 容器管理
125
- manyoyo ps
126
- manyoyo images
127
- manyoyo run -n my-dev -x /bin/bash
128
- manyoyo rm my-dev
129
- manyoyo serve 127.0.0.1:3000
130
- manyoyo serve 127.0.0.1:3000 -U admin -P 123456
157
+ MANYOYO 的配置重点不是“多”,而是“可预测”:
131
158
 
132
- # 调试配置与命令拼装
133
- manyoyo config show
134
- manyoyo config command
135
- ```
159
+ - 标量值按 `命令行参数 > runs.<name> > 全局配置 > 默认值` 覆盖
160
+ - 数组值按 `全局配置 -> runs.<name> -> 命令行参数` 追加合并
161
+ - `env` 使用 map 合并,按 key 覆盖
136
162
 
137
- ## 配置
163
+ 相关文档:
138
164
 
139
- 配置优先级:命令行参数 > runs.<name> > 全局配置 > 默认值
140
- 详细说明请参考:
141
165
  - [配置系统概览](https://xcanwin.github.io/manyoyo/zh/configuration/)
142
- - [环境变量详解](https://xcanwin.github.io/manyoyo/zh/configuration/environment)
143
166
  - [配置文件详解](https://xcanwin.github.io/manyoyo/zh/configuration/config-files)
167
+ - [环境变量详解](https://xcanwin.github.io/manyoyo/zh/configuration/environment)
144
168
 
145
- ## 📚 完整文档
169
+ ## 文档入口
146
170
 
147
- 在线文档:**https://xcanwin.github.io/manyoyo/**
171
+ 中文文档:
148
172
 
149
- **中文文档:**
150
173
  - [快速开始](https://xcanwin.github.io/manyoyo/zh/guide/quick-start)
151
174
  - [安装详解](https://xcanwin.github.io/manyoyo/zh/guide/installation)
152
- - [配置系统](https://xcanwin.github.io/manyoyo/zh/configuration/)
175
+ - [CLI 选项](https://xcanwin.github.io/manyoyo/zh/reference/cli-options)
153
176
  - [故障排查](https://xcanwin.github.io/manyoyo/zh/troubleshooting/)
154
177
 
155
- **English Documentation:**
178
+ English Documentation:
179
+
156
180
  - [Quick Start](https://xcanwin.github.io/manyoyo/en/guide/quick-start)
157
181
  - [Installation](https://xcanwin.github.io/manyoyo/en/guide/installation)
158
- - [Configuration](https://xcanwin.github.io/manyoyo/en/configuration/)
182
+ - [CLI Options](https://xcanwin.github.io/manyoyo/en/reference/cli-options)
159
183
  - [Troubleshooting](https://xcanwin.github.io/manyoyo/en/troubleshooting/)
160
184
 
161
- ## 卸载
185
+ ## 安装与卸载
186
+
187
+ 安装:
162
188
 
163
189
  ```bash
164
- # 卸载全局安装
165
- npm uninstall -g @xcanwin/manyoyo
190
+ npm install -g @xcanwin/manyoyo
191
+ ```
166
192
 
167
- # 清理配置(可选)
168
- rm -rf ~/.manyoyo/
193
+ 卸载:
194
+
195
+ ```bash
196
+ npm uninstall -g @xcanwin/manyoyo
197
+ rm -rf ~/.manyoyo/ # 可选
169
198
  ```
170
199
 
171
200
  ## 许可证
@@ -174,6 +203,7 @@ MIT
174
203
 
175
204
  ## 贡献
176
205
 
177
- 欢迎提交 Issue 和 Pull Request
206
+ 欢迎提交 Issue 和 Pull Request
178
207
 
179
- 访问 [GitHub Issues](https://github.com/xcanwin/manyoyo/issues) 报告问题或提出建议。
208
+ - Issues: <https://github.com/xcanwin/manyoyo/issues>
209
+ - Repository: <https://github.com/xcanwin/manyoyo>
package/bin/manyoyo.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { spawnSync } = require('child_process');
3
+ const { spawn, spawnSync } = require('child_process');
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
6
  const os = require('os');
@@ -16,6 +16,12 @@ const { buildImage } = require('../lib/image-build');
16
16
  const { resolveAgentResumeArg, buildAgentResumeCommand } = require('../lib/agent-resume');
17
17
  const { runPluginCommand } = require('../lib/plugin');
18
18
  const { buildManyoyoLogPath } = require('../lib/log-path');
19
+ const {
20
+ sanitizeSensitiveData,
21
+ sanitizeServeLogText,
22
+ formatServeLogValue,
23
+ getServeProcessSnapshot
24
+ } = require('../lib/serve-log');
19
25
  const { version: BIN_VERSION, imageVersion: IMAGE_VERSION_DEFAULT } = require('../package.json');
20
26
  const IMAGE_VERSION_BASE = String(IMAGE_VERSION_DEFAULT || '1.0.0').split('-')[0];
21
27
  const IMAGE_VERSION_HELP_EXAMPLE = IMAGE_VERSION_DEFAULT || `${IMAGE_VERSION_BASE}-common`;
@@ -190,93 +196,6 @@ function ensureWebServerAuthCredentials() {
190
196
  }
191
197
  }
192
198
 
193
- /**
194
- * 敏感信息脱敏(用于 config show 输出)
195
- * @param {Object} obj - 配置对象
196
- * @returns {Object} 脱敏后的配置对象
197
- */
198
- function sanitizeSensitiveData(obj) {
199
- const sensitiveKeys = ['KEY', 'TOKEN', 'SECRET', 'PASSWORD', 'PASS', 'AUTH', 'CREDENTIAL'];
200
-
201
- function sanitizeValue(key, value) {
202
- if (typeof value !== 'string') return value;
203
- const upperKey = key.toUpperCase();
204
- if (sensitiveKeys.some(k => upperKey.includes(k))) {
205
- if (value.length <= 8) return '****';
206
- return value.slice(0, 4) + '****' + value.slice(-4);
207
- }
208
- return value;
209
- }
210
-
211
- function sanitizeArray(arr) {
212
- return arr.map(item => {
213
- if (typeof item === 'string' && item.includes('=')) {
214
- const idx = item.indexOf('=');
215
- const key = item.slice(0, idx);
216
- const value = item.slice(idx + 1);
217
- return `${key}=${sanitizeValue(key, value)}`;
218
- }
219
- return item;
220
- });
221
- }
222
-
223
- const result = {};
224
- for (const [key, value] of Object.entries(obj)) {
225
- if (Array.isArray(value)) {
226
- result[key] = sanitizeArray(value);
227
- } else if (typeof value === 'object' && value !== null) {
228
- result[key] = sanitizeSensitiveData(value);
229
- } else {
230
- result[key] = sanitizeValue(key, value);
231
- }
232
- }
233
- return result;
234
- }
235
-
236
- function stripAnsi(text) {
237
- if (typeof text !== 'string') return '';
238
- return text.replace(/\x1b\[[0-9;]*m/g, '');
239
- }
240
-
241
- function sanitizeServeLogText(input) {
242
- let text = stripAnsi(String(input || ''));
243
- if (!text) return text;
244
-
245
- text = text.replace(/(--pass|-P)\s+\S+/gi, '$1 ****');
246
- text = text.replace(
247
- /\b(MANYOYO_SERVER_PASS|OPENAI_API_KEY|ANTHROPIC_AUTH_TOKEN|GEMINI_API_KEY|GOOGLE_API_KEY|OPENCODE_API_KEY)\s*=\s*([^\s'"]+)/gi,
248
- '$1=****'
249
- );
250
- text = text.replace(
251
- /("?(?:password|pass|token|api[_-]?key|authorization|cookie)"?\s*[:=]\s*)("[^"]*"|'[^']*'|[^,\s]+)/gi,
252
- '$1"****"'
253
- );
254
- return text;
255
- }
256
-
257
- function formatServeLogValue(value) {
258
- if (value instanceof Error) {
259
- return sanitizeServeLogText(value.stack || value.message || String(value));
260
- }
261
- if (typeof value === 'object' && value !== null) {
262
- try {
263
- return sanitizeServeLogText(JSON.stringify(sanitizeSensitiveData(value)));
264
- } catch (e) {
265
- return sanitizeServeLogText(String(value));
266
- }
267
- }
268
- return sanitizeServeLogText(String(value));
269
- }
270
-
271
- function getServeProcessSnapshot() {
272
- return {
273
- pid: process.pid,
274
- ppid: process.ppid,
275
- cwd: process.cwd(),
276
- argv: Array.isArray(process.argv) ? process.argv.slice() : []
277
- };
278
- }
279
-
280
199
  function createServeLogger() {
281
200
  function formatLocalTimestamp(date = new Date()) {
282
201
  const y = date.getFullYear();
@@ -1120,7 +1039,8 @@ async function setupCommander() {
1120
1039
  ${MANYOYO_NAME} run -n test -- -c 恢复之前会话
1121
1040
  ${MANYOYO_NAME} run -x "echo 123" 使用完整命令
1122
1041
  ${MANYOYO_NAME} serve 127.0.0.1:3000 启动本机网页服务
1123
- ${MANYOYO_NAME} serve 0.0.0.0:3000 -U admin -P 123 &>/dev/null & 后台启动并监听全部网卡
1042
+ ${MANYOYO_NAME} serve 127.0.0.1:3000 -d 后台启动;未设密码时会打印本次随机密码
1043
+ ${MANYOYO_NAME} serve 0.0.0.0:3000 -U admin -P 123 -d 后台启动并监听全部网卡
1124
1044
  ${MANYOYO_NAME} playwright up host-headless 启动 playwright 默认场景(推荐)
1125
1045
  ${MANYOYO_NAME} plugin playwright up host-headless 通过 plugin 命名空间启动
1126
1046
  ${MANYOYO_NAME} run -n test -q tip -q cmd 多次使用静默选项
@@ -1163,6 +1083,7 @@ Notes:
1163
1083
 
1164
1084
  const serveCommand = program.command('serve [listen]').description('启动网页交互服务 (默认 127.0.0.1:3000)');
1165
1085
  applyRunStyleOptions(serveCommand, { includeRmOnExit: false, includeWebAuthOptions: true });
1086
+ serveCommand.option('-d, --detach', '后台启动网页服务并立即返回');
1166
1087
  serveCommand.action((listen, options) => {
1167
1088
  selectAction('serve', {
1168
1089
  ...options,
@@ -1498,6 +1419,7 @@ Notes:
1498
1419
  isRemoveMode,
1499
1420
  isShowCommandMode,
1500
1421
  isServerMode,
1422
+ isServerDetach: Boolean(selectedAction === 'serve' && options.detach),
1501
1423
  isPluginMode: false
1502
1424
  };
1503
1425
  }
@@ -1524,6 +1446,7 @@ function createRuntimeContext(modeState = {}) {
1524
1446
  showCommand: Boolean(modeState.isShowCommandMode),
1525
1447
  rmOnExit: RM_ON_EXIT,
1526
1448
  serverMode: Boolean(modeState.isServerMode),
1449
+ serverDetach: Boolean(modeState.isServerDetach),
1527
1450
  serverHost: SERVER_HOST,
1528
1451
  serverPort: SERVER_PORT,
1529
1452
  serverAuthUser: SERVER_AUTH_USER,
@@ -1558,6 +1481,62 @@ function validateHostPath(runtime) {
1558
1481
  }
1559
1482
  }
1560
1483
 
1484
+ function validateHostPathOrThrow(hostPath) {
1485
+ if (!fs.existsSync(hostPath)) {
1486
+ throw new Error(`宿主机路径不存在: ${hostPath}`);
1487
+ }
1488
+ const realHostPath = fs.realpathSync(hostPath);
1489
+ const homeDir = process.env.HOME || '/home';
1490
+ if (realHostPath === '/' || realHostPath === '/home' || realHostPath === homeDir) {
1491
+ throw new Error('不允许挂载根目录或home目录。');
1492
+ }
1493
+ }
1494
+
1495
+ function buildDetachedServeArgv(argv) {
1496
+ const result = [];
1497
+ for (let i = 0; i < argv.length; i++) {
1498
+ const arg = String(argv[i] || '');
1499
+ if (arg === '-d' || arg === '--detach') {
1500
+ continue;
1501
+ }
1502
+ result.push(arg);
1503
+ }
1504
+ return result;
1505
+ }
1506
+
1507
+ function buildDetachedServeEnv(runtime) {
1508
+ const env = { ...process.env };
1509
+ if (runtime.serverAuthUser) {
1510
+ env.MANYOYO_SERVER_USER = runtime.serverAuthUser;
1511
+ }
1512
+ if (runtime.serverAuthPass) {
1513
+ env.MANYOYO_SERVER_PASS = runtime.serverAuthPass;
1514
+ }
1515
+ return env;
1516
+ }
1517
+
1518
+ function relaunchServeDetached(runtime) {
1519
+ const serveLog = buildManyoyoLogPath('serve');
1520
+ fs.mkdirSync(serveLog.dir, { recursive: true });
1521
+
1522
+ const child = spawn(process.argv[0], buildDetachedServeArgv(process.argv.slice(1)), {
1523
+ detached: true,
1524
+ stdio: 'ignore',
1525
+ env: buildDetachedServeEnv(runtime)
1526
+ });
1527
+ child.unref();
1528
+
1529
+ console.log(`${GREEN}✅ serve 已转入后台运行${NC}`);
1530
+ console.log(`PID: ${child.pid}`);
1531
+ console.log(`日志: ${serveLog.path}`);
1532
+ console.log(`登录用户名: ${runtime.serverAuthUser}`);
1533
+ if (runtime.serverAuthPassAuto) {
1534
+ console.log(`登录密码(本次随机): ${runtime.serverAuthPass}`);
1535
+ } else {
1536
+ console.log('登录密码: 使用你配置的 serve -P / serverPass / MANYOYO_SERVER_PASS');
1537
+ }
1538
+ }
1539
+
1561
1540
  /**
1562
1541
  * 等待容器就绪(使用指数退避算法)
1563
1542
  * @param {string} containerName - 容器名称
@@ -1850,7 +1829,7 @@ async function runWebServerMode(runtime) {
1850
1829
  containerEnvs: runtime.containerEnvs,
1851
1830
  containerVolumes: runtime.containerVolumes,
1852
1831
  containerPorts: runtime.containerPorts,
1853
- validateHostPath: () => validateHostPath(runtime),
1832
+ validateHostPath: value => validateHostPathOrThrow(value),
1854
1833
  formatDate,
1855
1834
  isValidContainerName,
1856
1835
  containerExists,
@@ -1892,6 +1871,10 @@ async function main() {
1892
1871
 
1893
1872
  // 2. Start web server mode
1894
1873
  if (runtime.serverMode) {
1874
+ if (runtime.serverDetach) {
1875
+ relaunchServeDetached(runtime);
1876
+ return;
1877
+ }
1895
1878
  const serveLogger = createServeLogger();
1896
1879
  runtime.logger = serveLogger;
1897
1880
  installServeProcessDiagnostics(serveLogger);
@@ -0,0 +1,116 @@
1
+ function stripAnsi(text) {
2
+ if (typeof text !== 'string') return '';
3
+ return text.replace(/\x1b\[[0-9;]*m/g, '');
4
+ }
5
+
6
+ function sanitizeProcessArgv(argv) {
7
+ if (!Array.isArray(argv)) {
8
+ return [];
9
+ }
10
+
11
+ const result = [];
12
+ for (let i = 0; i < argv.length; i++) {
13
+ const arg = String(argv[i] || '');
14
+ if (arg === '--pass' || arg === '-P') {
15
+ result.push(arg);
16
+ if (i + 1 < argv.length) {
17
+ result.push('****');
18
+ i += 1;
19
+ }
20
+ continue;
21
+ }
22
+ if (arg.startsWith('--pass=')) {
23
+ result.push('--pass=****');
24
+ continue;
25
+ }
26
+ result.push(arg);
27
+ }
28
+ return result;
29
+ }
30
+
31
+ function sanitizeServeLogText(input) {
32
+ let text = stripAnsi(String(input || ''));
33
+ if (!text) return text;
34
+
35
+ text = text.replace(/(--pass|-P)\s+\S+/gi, '$1 ****');
36
+ text = text.replace(/("--pass"|"-P")\s*,\s*"[^"]*"/gi, '$1,"****"');
37
+ text = text.replace(/--pass=([^\s'"]+)/gi, '--pass=****');
38
+ text = text.replace(
39
+ /\b(MANYOYO_SERVER_PASS|OPENAI_API_KEY|ANTHROPIC_AUTH_TOKEN|GEMINI_API_KEY|GOOGLE_API_KEY|OPENCODE_API_KEY)\s*=\s*([^\s'"]+)/gi,
40
+ '$1=****'
41
+ );
42
+ text = text.replace(
43
+ /(?<![-\w])("?(?:password|pass|token|api[_-]?key|authorization|cookie)"?\s*[:=]\s*)("[^"]*"|'[^']*'|[^,\s]+)/gi,
44
+ '$1"****"'
45
+ );
46
+ return text;
47
+ }
48
+
49
+ function sanitizeSensitiveData(obj) {
50
+ const sensitiveKeys = ['KEY', 'TOKEN', 'SECRET', 'PASSWORD', 'PASS', 'AUTH', 'CREDENTIAL'];
51
+
52
+ function sanitizeValue(key, value) {
53
+ if (typeof value !== 'string') return value;
54
+ const upperKey = key.toUpperCase();
55
+ if (sensitiveKeys.some(k => upperKey.includes(k))) {
56
+ if (value.length <= 8) return '****';
57
+ return value.slice(0, 4) + '****' + value.slice(-4);
58
+ }
59
+ return value;
60
+ }
61
+
62
+ function sanitizeArray(arr) {
63
+ return arr.map(item => {
64
+ if (typeof item === 'string' && item.includes('=')) {
65
+ const idx = item.indexOf('=');
66
+ const key = item.slice(0, idx);
67
+ const value = item.slice(idx + 1);
68
+ return `${key}=${sanitizeValue(key, value)}`;
69
+ }
70
+ return item;
71
+ });
72
+ }
73
+
74
+ const result = {};
75
+ for (const [key, value] of Object.entries(obj || {})) {
76
+ if (Array.isArray(value)) {
77
+ result[key] = sanitizeArray(value);
78
+ } else if (typeof value === 'object' && value !== null) {
79
+ result[key] = sanitizeSensitiveData(value);
80
+ } else {
81
+ result[key] = sanitizeValue(key, value);
82
+ }
83
+ }
84
+ return result;
85
+ }
86
+
87
+ function formatServeLogValue(value) {
88
+ if (value instanceof Error) {
89
+ return sanitizeServeLogText(value.stack || value.message || String(value));
90
+ }
91
+ if (typeof value === 'object' && value !== null) {
92
+ try {
93
+ return sanitizeServeLogText(JSON.stringify(sanitizeSensitiveData(value)));
94
+ } catch (e) {
95
+ return sanitizeServeLogText(String(value));
96
+ }
97
+ }
98
+ return sanitizeServeLogText(String(value));
99
+ }
100
+
101
+ function getServeProcessSnapshot(processRef = process) {
102
+ return {
103
+ pid: processRef.pid,
104
+ ppid: processRef.ppid,
105
+ cwd: typeof processRef.cwd === 'function' ? processRef.cwd() : '',
106
+ argv: sanitizeProcessArgv(Array.isArray(processRef.argv) ? processRef.argv.slice() : [])
107
+ };
108
+ }
109
+
110
+ module.exports = {
111
+ sanitizeProcessArgv,
112
+ sanitizeServeLogText,
113
+ sanitizeSensitiveData,
114
+ formatServeLogValue,
115
+ getServeProcessSnapshot
116
+ };
package/lib/web/server.js CHANGED
@@ -812,7 +812,11 @@ function buildCreateRuntime(ctx, state, payload) {
812
812
  validateContainerNameStrict(containerName);
813
813
 
814
814
  const hostPath = pickFirstString(requestOptions.hostPath, config.hostPath, ctx.hostPath);
815
- validateWebHostPath(hostPath);
815
+ if (typeof ctx.validateHostPath === 'function') {
816
+ ctx.validateHostPath(hostPath);
817
+ } else {
818
+ validateWebHostPath(hostPath);
819
+ }
816
820
 
817
821
  const containerPath = pickFirstString(requestOptions.containerPath, config.containerPath, ctx.containerPath, hostPath) || hostPath;
818
822
  const imageName = pickFirstString(requestOptions.imageName, config.imageName, ctx.imageName);
@@ -1788,7 +1792,7 @@ async function startWebServer(options) {
1788
1792
  };
1789
1793
 
1790
1794
  if (!ctx.authUser || !ctx.authPass) {
1791
- throw new Error('Web 认证配置缺失,请设置 serve -u / serve -P');
1795
+ throw new Error('Web 认证配置缺失,请设置 serve -U / serve -P');
1792
1796
  }
1793
1797
 
1794
1798
  const state = {
@@ -1798,7 +1802,6 @@ async function startWebServer(options) {
1798
1802
  terminalSessions: new Map()
1799
1803
  };
1800
1804
 
1801
- ctx.validateHostPath();
1802
1805
  ensureWebHistoryDir(state.webHistoryDir);
1803
1806
 
1804
1807
  const wsServer = new WebSocket.Server({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "5.3.6",
3
+ "version": "5.3.9",
4
4
  "imageVersion": "1.8.4-common",
5
5
  "description": "AI Agent CLI Security Sandbox for Docker and Podman",
6
6
  "keywords": [