@xcanwin/manyoyo 5.4.6 → 5.4.12

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
@@ -53,7 +53,7 @@ AI Agent CLI 往往需要:
53
53
  ```bash
54
54
  npm install -g @xcanwin/manyoyo
55
55
  podman pull ubuntu:24.04 # 仅 Podman 需要
56
- manyoyo build --iv 1.8.8-common
56
+ manyoyo build --iv 1.8.12-common
57
57
  manyoyo init all
58
58
  manyoyo run -r claude
59
59
  ```
@@ -137,10 +137,10 @@ manyoyo config command
137
137
 
138
138
  ```bash
139
139
  # common 版本
140
- manyoyo build --iv 1.8.8-common
140
+ manyoyo build --iv 1.8.12-common
141
141
 
142
142
  # full 版本
143
- manyoyo build --iv 1.8.8-full
143
+ manyoyo build --iv 1.8.12-full
144
144
 
145
145
  # 自定义工具集
146
146
  manyoyo build --iba TOOL=go,codex,java,gemini
package/bin/manyoyo.js CHANGED
@@ -1071,6 +1071,14 @@ async function setupCommander() {
1071
1071
  host: options.host || ''
1072
1072
  }, options));
1073
1073
 
1074
+ command.command('cli-add')
1075
+ .description('输出 playwright-cli skill 安装命令')
1076
+ .action(() => selectPluginAction({
1077
+ action: 'cli-add',
1078
+ pluginName: 'playwright',
1079
+ scene: 'all'
1080
+ }));
1081
+
1074
1082
  command.command('ext-download')
1075
1083
  .description('下载并解压 Playwright 扩展到 ~/.manyoyo/plugin/playwright/extensions/')
1076
1084
  .option('--prodversion <ver>', 'CRX 下载使用的 Chrome 版本号 (默认 132.0.0.0)')
@@ -1109,8 +1117,8 @@ async function setupCommander() {
1109
1117
  ${MANYOYO_NAME} serve 127.0.0.1:3000 启动本机网页服务
1110
1118
  ${MANYOYO_NAME} serve 127.0.0.1:3000 -d 后台启动;未设密码时会打印本次随机密码
1111
1119
  ${MANYOYO_NAME} serve 0.0.0.0:3000 -U admin -P 123 -d 后台启动并监听全部网卡
1112
- ${MANYOYO_NAME} playwright up mcp-host-headless 启动 playwright 默认场景(推荐)
1113
- ${MANYOYO_NAME} plugin playwright up mcp-host-headless 通过 plugin 命名空间启动
1120
+ ${MANYOYO_NAME} playwright up mcp-host-headless 启动 playwright MCP 宿主场景(默认/推荐)
1121
+ ${MANYOYO_NAME} playwright up cli-host-headless 启动 playwright CLI 宿主场景(供容器内 playwright-cli 附着)
1114
1122
  ${MANYOYO_NAME} run -n test -q tip -q cmd 多次使用静默选项
1115
1123
  `);
1116
1124
 
@@ -127,6 +127,7 @@ EOX
127
127
 
128
128
  # 从 cache-stage 复制 Node.js(缓存或下载)
129
129
  COPY --from=cache-stage /opt/node /usr/local
130
+ COPY ./package.json /tmp/manyoyo-package.json
130
131
  COPY ./docker/res/playwright/cli-cont-headless.init.js /app/config/cli-cont-headless.init.js
131
132
  COPY ./docker/res/playwright/cli-cont-headless.json /app/config/cli-cont-headless.json
132
133
  COPY ./docker/res/ /tmp/docker-res/
@@ -202,22 +203,15 @@ RUN <<EOX
202
203
  ;; esac
203
204
 
204
205
  # 安装 Playwright CLI skills(不在镜像构建阶段下载浏览器)
205
- npm install -g @playwright/cli@latest
206
+ PLAYWRIGHT_CLI_VERSION=$(node -p "const pkg = require('/tmp/manyoyo-package.json'); const value = String(pkg.playwrightCliVersion || '').trim(); if (!value) { throw new Error('package.json.playwrightCliVersion is required'); } value")
207
+ npm install -g "@playwright/cli@${PLAYWRIGHT_CLI_VERSION}"
206
208
  PLAYWRIGHT_CLI_INSTALL_DIR=/tmp/playwright-cli-install
207
209
  mkdir -p "${PLAYWRIGHT_CLI_INSTALL_DIR}/.playwright"
208
- cat > "${PLAYWRIGHT_CLI_INSTALL_DIR}/.playwright/cli.config.json" <<'EOF'
209
- {
210
- "browser": {
211
- "browserName": "chromium",
212
- "launchOptions": {
213
- "channel": "chrome"
214
- }
215
- }
216
- }
217
- EOF
210
+ echo '{"browser":{"browserName":"chromium","launchOptions":{"channel":"chromium"}}}' > "${PLAYWRIGHT_CLI_INSTALL_DIR}/.playwright/cli.config.json"
218
211
  cd "${PLAYWRIGHT_CLI_INSTALL_DIR}"
219
- playwright-cli install --skills
212
+ playwright-cli --config="${PLAYWRIGHT_CLI_INSTALL_DIR}/.playwright/cli.config.json" install --skills
220
213
  mkdir -p "$HOME/.codex/skills/playwright-cli" ~/.gemini/skills/playwright-cli
214
+ cp -R "${PLAYWRIGHT_CLI_INSTALL_DIR}/.claude/skills/playwright-cli/." "$HOME/.claude/skills/playwright-cli/"
221
215
  cp -R "${PLAYWRIGHT_CLI_INSTALL_DIR}/.claude/skills/playwright-cli/." "$HOME/.codex/skills/playwright-cli/"
222
216
  cp -R "${PLAYWRIGHT_CLI_INSTALL_DIR}/.claude/skills/playwright-cli/." "$HOME/.gemini/skills/playwright-cli/"
223
217
  cd $OLDPWD
@@ -225,9 +219,13 @@ EOF
225
219
 
226
220
  # 清理
227
221
  npm cache clean --force
222
+ rm -f /tmp/manyoyo-package.json
228
223
  rm -rf /tmp/* /var/tmp/* /var/log/apt /var/log/*.log /var/lib/apt/lists/* ~/.npm ~/go/pkg/mod/cache
229
224
  EOX
230
225
 
226
+ COPY ./docker/res/playwright/playwright-cli-wrapper.sh /usr/local/bin/playwright-cli
227
+ RUN chmod +x /usr/local/bin/playwright-cli
228
+
231
229
  # 从 cache-stage 复制 JDT LSP(缓存或下载)
232
230
  COPY --from=cache-stage /opt/jdtls /tmp/jdtls-cache
233
231
 
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env sh
2
+ set -eu
3
+
4
+ PLAYWRIGHT_NODE_BIN="${PLAYWRIGHT_NODE_BIN:-node}"
5
+ PLAYWRIGHT_CLI_ROOT="/usr/local/lib/node_modules/@playwright/cli/node_modules/playwright"
6
+ PLAYWRIGHT_CLI_PROGRAM="${PLAYWRIGHT_CLI_ROOT}/lib/cli/client/program.js"
7
+ PLAYWRIGHT_REAL_CLI="${PLAYWRIGHT_CLI_ROOT}/cli.js"
8
+
9
+ resolve_browser() {
10
+ browser=""
11
+
12
+ while [ "$#" -gt 0 ]; do
13
+ case "$1" in
14
+ --browser=*)
15
+ browser="${1#--browser=}"
16
+ ;;
17
+ --browser)
18
+ shift
19
+ if [ "$#" -gt 0 ]; then
20
+ browser="$1"
21
+ shift
22
+ fi
23
+ continue
24
+ ;;
25
+ --)
26
+ break
27
+ ;;
28
+ -*)
29
+ ;;
30
+ *)
31
+ if [ -z "$browser" ]; then
32
+ browser="$1"
33
+ fi
34
+ ;;
35
+ esac
36
+ shift
37
+ done
38
+
39
+ if [ -n "$browser" ]; then
40
+ printf '%s\n' "$browser"
41
+ return 0
42
+ fi
43
+
44
+ "$PLAYWRIGHT_NODE_BIN" <<'NODE'
45
+ const fs = require('fs');
46
+ const path = require('path');
47
+
48
+ const candidates = [
49
+ process.env.PLAYWRIGHT_MCP_CONFIG,
50
+ path.resolve('.playwright/cli.config.json')
51
+ ].filter(Boolean);
52
+
53
+ for (const filePath of candidates) {
54
+ try {
55
+ const config = JSON.parse(fs.readFileSync(filePath, 'utf8'));
56
+ const browser = config && config.browser ? config.browser : {};
57
+ const launchOptions = browser.launchOptions || {};
58
+ const channel = String(launchOptions.channel || browser.browserName || '').trim();
59
+ if (channel) {
60
+ process.stdout.write(channel);
61
+ process.exit(0);
62
+ }
63
+ } catch (_) {}
64
+ }
65
+
66
+ process.stdout.write('chromium');
67
+ NODE
68
+ }
69
+
70
+ if [ "${1-}" = "install-browser" ]; then
71
+ shift
72
+ for arg in "$@"; do
73
+ if [ "$arg" = "--help" ] || [ "$arg" = "-h" ]; then
74
+ exec "$PLAYWRIGHT_NODE_BIN" "$PLAYWRIGHT_CLI_PROGRAM" install-browser "$@"
75
+ fi
76
+ done
77
+
78
+ BROWSER="$(resolve_browser "$@")"
79
+ exec "$PLAYWRIGHT_NODE_BIN" "$PLAYWRIGHT_REAL_CLI" install "$BROWSER"
80
+ fi
81
+
82
+ exec "$PLAYWRIGHT_NODE_BIN" "$PLAYWRIGHT_CLI_PROGRAM" "$@"
@@ -36,6 +36,223 @@ function readManyoyoConfig(homeDir = os.homedir()) {
36
36
  }
37
37
  }
38
38
 
39
+ function readQuotedString(text, startIndex) {
40
+ const quote = text[startIndex];
41
+ let value = '';
42
+
43
+ for (let i = startIndex + 1; i < text.length; i += 1) {
44
+ const ch = text[i];
45
+ if (ch === '\\') {
46
+ value += ch;
47
+ if (i + 1 < text.length) {
48
+ value += text[i + 1];
49
+ i += 1;
50
+ }
51
+ continue;
52
+ }
53
+ if (ch === quote) {
54
+ return {
55
+ value,
56
+ end: i + 1
57
+ };
58
+ }
59
+ value += ch;
60
+ }
61
+
62
+ return null;
63
+ }
64
+
65
+ function isIdentifierStart(ch) {
66
+ return /[A-Za-z_$]/.test(ch);
67
+ }
68
+
69
+ function isIdentifierPart(ch) {
70
+ return /[A-Za-z0-9_$]/.test(ch);
71
+ }
72
+
73
+ function skipWhitespace(text, index) {
74
+ let i = index;
75
+ while (i < text.length && /\s/.test(text[i])) {
76
+ i += 1;
77
+ }
78
+ return i;
79
+ }
80
+
81
+ function findTopLevelPropertyValueRange(text, propertyName) {
82
+ let depth = 0;
83
+ let inString = '';
84
+ let inLineComment = false;
85
+ let inBlockComment = false;
86
+
87
+ for (let i = 0; i < text.length; i += 1) {
88
+ const ch = text[i];
89
+ const next = text[i + 1];
90
+
91
+ if (inLineComment) {
92
+ if (ch === '\n') inLineComment = false;
93
+ continue;
94
+ }
95
+ if (inBlockComment) {
96
+ if (ch === '*' && next === '/') {
97
+ inBlockComment = false;
98
+ i += 1;
99
+ }
100
+ continue;
101
+ }
102
+ if (inString) {
103
+ if (ch === '\\') {
104
+ i += 1;
105
+ continue;
106
+ }
107
+ if (ch === inString) inString = '';
108
+ continue;
109
+ }
110
+
111
+ if (ch === '/' && next === '/') {
112
+ inLineComment = true;
113
+ i += 1;
114
+ continue;
115
+ }
116
+ if (ch === '/' && next === '*') {
117
+ inBlockComment = true;
118
+ i += 1;
119
+ continue;
120
+ }
121
+ if (depth === 1 && !/\s|,/.test(ch)) {
122
+ let property = '';
123
+ let cursor = i;
124
+ if (ch === '"' || ch === '\'') {
125
+ const token = readQuotedString(text, i);
126
+ if (!token) {
127
+ return null;
128
+ }
129
+ property = token.value;
130
+ cursor = token.end;
131
+ } else if (isIdentifierStart(ch)) {
132
+ cursor = i + 1;
133
+ while (cursor < text.length && isIdentifierPart(text[cursor])) {
134
+ cursor += 1;
135
+ }
136
+ property = text.slice(i, cursor);
137
+ }
138
+
139
+ if (property) {
140
+ const colonIndex = skipWhitespace(text, cursor);
141
+ if (text[colonIndex] === ':') {
142
+ if (property !== propertyName) {
143
+ i = colonIndex;
144
+ continue;
145
+ }
146
+
147
+ let valueStart = skipWhitespace(text, colonIndex + 1);
148
+ let valueEnd = valueStart;
149
+ let valueString = '';
150
+ let valueLineComment = false;
151
+ let valueBlockComment = false;
152
+ let valueDepth = 0;
153
+
154
+ for (; valueEnd < text.length; valueEnd += 1) {
155
+ const valueCh = text[valueEnd];
156
+ const valueNext = text[valueEnd + 1];
157
+
158
+ if (valueLineComment) {
159
+ if (valueCh === '\n') valueLineComment = false;
160
+ continue;
161
+ }
162
+ if (valueBlockComment) {
163
+ if (valueCh === '*' && valueNext === '/') {
164
+ valueBlockComment = false;
165
+ valueEnd += 1;
166
+ }
167
+ continue;
168
+ }
169
+ if (valueString) {
170
+ if (valueCh === '\\') {
171
+ valueEnd += 1;
172
+ continue;
173
+ }
174
+ if (valueCh === valueString) valueString = '';
175
+ continue;
176
+ }
177
+
178
+ if (valueCh === '/' && valueNext === '/') {
179
+ valueLineComment = true;
180
+ valueEnd += 1;
181
+ continue;
182
+ }
183
+ if (valueCh === '/' && valueNext === '*') {
184
+ valueBlockComment = true;
185
+ valueEnd += 1;
186
+ continue;
187
+ }
188
+ if (valueCh === '"' || valueCh === '\'') {
189
+ valueString = valueCh;
190
+ continue;
191
+ }
192
+ if (valueCh === '{' || valueCh === '[' || valueCh === '(') {
193
+ valueDepth += 1;
194
+ continue;
195
+ }
196
+ if (valueCh === '}' || valueCh === ']' || valueCh === ')') {
197
+ if (valueDepth === 0) {
198
+ break;
199
+ }
200
+ valueDepth -= 1;
201
+ continue;
202
+ }
203
+ if (valueDepth === 0 && valueCh === ',') {
204
+ break;
205
+ }
206
+ }
207
+
208
+ while (valueEnd > valueStart && /\s/.test(text[valueEnd - 1])) {
209
+ valueEnd -= 1;
210
+ }
211
+
212
+ return {
213
+ start: valueStart,
214
+ end: valueEnd
215
+ };
216
+ }
217
+ }
218
+ }
219
+
220
+ if (ch === '"' || ch === '\'') {
221
+ inString = ch;
222
+ continue;
223
+ }
224
+ if (ch === '{' || ch === '[') {
225
+ depth += 1;
226
+ continue;
227
+ }
228
+ if (ch === '}' || ch === ']') {
229
+ depth -= 1;
230
+ continue;
231
+ }
232
+ }
233
+
234
+ return null;
235
+ }
236
+
237
+ function insertTopLevelImageVersion(text, imageVersion) {
238
+ const openBraceIndex = text.indexOf('{');
239
+ if (openBraceIndex === -1) {
240
+ return null;
241
+ }
242
+
243
+ const newlineIndex = text.indexOf('\n', openBraceIndex);
244
+ const insertIndex = newlineIndex === -1 ? openBraceIndex + 1 : newlineIndex + 1;
245
+ return `${text.slice(0, insertIndex)} imageVersion: ${JSON.stringify(imageVersion)},\n${text.slice(insertIndex)}`;
246
+ }
247
+
248
+ function updateImageVersionText(text, imageVersion) {
249
+ const range = findTopLevelPropertyValueRange(text, 'imageVersion');
250
+ if (range) {
251
+ return `${text.slice(0, range.start)}${JSON.stringify(imageVersion)}${text.slice(range.end)}`;
252
+ }
253
+ return insertTopLevelImageVersion(text, imageVersion);
254
+ }
255
+
39
256
  function syncGlobalImageVersion(imageVersion, options = {}) {
40
257
  const homeDir = options.homeDir || os.homedir();
41
258
  const result = readManyoyoConfig(homeDir);
@@ -72,7 +289,17 @@ function syncGlobalImageVersion(imageVersion, options = {}) {
72
289
  };
73
290
 
74
291
  fs.mkdirSync(path.dirname(configPath), { recursive: true });
75
- fs.writeFileSync(configPath, `${JSON.stringify(nextConfig, null, 4)}\n`);
292
+ if (result.exists) {
293
+ const currentText = fs.readFileSync(configPath, 'utf-8');
294
+ const updatedText = updateImageVersionText(currentText, imageVersion);
295
+ if (updatedText) {
296
+ fs.writeFileSync(configPath, updatedText.endsWith('\n') ? updatedText : `${updatedText}\n`);
297
+ } else {
298
+ fs.writeFileSync(configPath, `${JSON.stringify(nextConfig, null, 4)}\n`);
299
+ }
300
+ } else {
301
+ fs.writeFileSync(configPath, `${JSON.stringify(nextConfig, null, 4)}\n`);
302
+ }
76
303
 
77
304
  return {
78
305
  updated: true,
@@ -6,6 +6,7 @@ const os = require('os');
6
6
  const path = require('path');
7
7
  const crypto = require('crypto');
8
8
  const { spawn, spawnSync } = require('child_process');
9
+ const { playwrightCliVersion: PLAYWRIGHT_CLI_VERSION } = require('../../package.json');
9
10
 
10
11
  const EXTENSIONS = [
11
12
  ['ublock-origin-lite', 'ddkjiahejlhfcafbddmgiahcphecmpfh'],
@@ -379,6 +380,24 @@ class PlaywrightPlugin {
379
380
  this.stderr.write(`${line}\n`);
380
381
  }
381
382
 
383
+ remindCliSessionScene(sceneName) {
384
+ if (!isCliScene(sceneName)) {
385
+ return;
386
+ }
387
+ if (sceneName !== 'cli-host-headed') {
388
+ return;
389
+ }
390
+ if (this.config.cliSessionScene === sceneName) {
391
+ return;
392
+ }
393
+ this.writeStdout('[tip] 如果希望容器内 manyoyo run 自动附着到当前 CLI 宿主场景,请在 ~/.manyoyo/manyoyo.json 中设置:');
394
+ this.writeStdout(' "plugins": {');
395
+ this.writeStdout(' "playwright": {');
396
+ this.writeStdout(' "cliSessionScene": "cli-host-headed"');
397
+ this.writeStdout(' }');
398
+ this.writeStdout(' }');
399
+ }
400
+
382
401
  randomAlnum(length = 16) {
383
402
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
384
403
  let out = '';
@@ -1259,6 +1278,7 @@ class PlaywrightPlugin {
1259
1278
  if (managedPids.length > 0) {
1260
1279
  fs.writeFileSync(pidFile, `${managedPids[0]}`, 'utf8');
1261
1280
  this.writeStdout(`[up] ${sceneName} ready on 127.0.0.1:${port} (pid(s) ${managedPids.join(' ')})`);
1281
+ this.remindCliSessionScene(sceneName);
1262
1282
  return 0;
1263
1283
  }
1264
1284
  }
@@ -1539,6 +1559,25 @@ class PlaywrightPlugin {
1539
1559
  return 0;
1540
1560
  }
1541
1561
 
1562
+ printCliAdd() {
1563
+ const lines = [
1564
+ 'PLAYWRIGHT_CLI_INSTALL_DIR="${TMPDIR:-/tmp}/manyoyo-playwright-cli-install-$$"',
1565
+ 'mkdir -p "$PLAYWRIGHT_CLI_INSTALL_DIR/.playwright"',
1566
+ 'echo \'{"browser":{"browserName":"chromium","launchOptions":{"channel":"chromium"}}}\' > "$PLAYWRIGHT_CLI_INSTALL_DIR/.playwright/cli.config.json"',
1567
+ 'cd "$PLAYWRIGHT_CLI_INSTALL_DIR"',
1568
+ `npm install -g @playwright/cli@${PLAYWRIGHT_CLI_VERSION}`,
1569
+ 'playwright-cli install --skills',
1570
+ 'mkdir -p ~/.codex/skills/playwright-cli ~/.gemini/skills/playwright-cli',
1571
+ 'cp -R "$PLAYWRIGHT_CLI_INSTALL_DIR/.claude/skills/playwright-cli/." ~/.claude/skills/playwright-cli/',
1572
+ 'cp -R "$PLAYWRIGHT_CLI_INSTALL_DIR/.claude/skills/playwright-cli/." ~/.codex/skills/playwright-cli/',
1573
+ 'cp -R "$PLAYWRIGHT_CLI_INSTALL_DIR/.claude/skills/playwright-cli/." ~/.gemini/skills/playwright-cli/',
1574
+ 'cd $OLDPWD',
1575
+ 'rm -rf "$PLAYWRIGHT_CLI_INSTALL_DIR"'
1576
+ ];
1577
+ this.writeStdout(lines.join('\n'));
1578
+ return 0;
1579
+ }
1580
+
1542
1581
  printSummary() {
1543
1582
  const scenes = this.resolveTargets('all');
1544
1583
  this.writeStdout(`playwright\truntime=${this.config.runtime}\tscenes=${scenes.join(',')}`);
@@ -1583,6 +1622,10 @@ class PlaywrightPlugin {
1583
1622
  return this.printMcpAdd(host);
1584
1623
  }
1585
1624
 
1625
+ if (action === 'cli-add') {
1626
+ return this.printCliAdd();
1627
+ }
1628
+
1586
1629
  if (action === 'ext-download') {
1587
1630
  return await this.downloadExtensions({ prodversion });
1588
1631
  }
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "5.4.6",
4
- "imageVersion": "1.8.8-common",
3
+ "version": "5.4.12",
4
+ "imageVersion": "1.8.12-common",
5
+ "playwrightCliVersion": "0.1.1",
5
6
  "description": "AI Agent CLI Security Sandbox for Docker and Podman",
6
7
  "keywords": [
7
8
  "manyoyo",