@xcanwin/manyoyo 5.4.6 → 5.4.14

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
@@ -570,6 +570,42 @@ function addEnvFile(envFile) {
570
570
  return addEnvFileTo(CONTAINER_ENVS, envFile);
571
571
  }
572
572
 
573
+ function expandHomeAliasPath(filePath) {
574
+ const text = String(filePath || '').trim();
575
+ const homeDir = process.env.HOME || os.homedir();
576
+
577
+ if (text === '~') {
578
+ return homeDir;
579
+ }
580
+ if (text.startsWith('~/')) {
581
+ return path.join(homeDir, text.slice(2));
582
+ }
583
+ if (text === '$HOME') {
584
+ return homeDir;
585
+ }
586
+ if (text.startsWith('$HOME/')) {
587
+ return path.join(homeDir, text.slice('$HOME/'.length));
588
+ }
589
+
590
+ return text;
591
+ }
592
+
593
+ function normalizeVolume(volume) {
594
+ const text = String(volume || '').trim();
595
+ if (!text.startsWith('~') && !text.startsWith('$HOME')) {
596
+ return text;
597
+ }
598
+
599
+ const separatorIndex = text.indexOf(':');
600
+ if (separatorIndex === -1) {
601
+ return expandHomeAliasPath(text);
602
+ }
603
+
604
+ const hostPath = text.slice(0, separatorIndex);
605
+ const rest = text.slice(separatorIndex);
606
+ return `${expandHomeAliasPath(hostPath)}${rest}`;
607
+ }
608
+
573
609
  function hasEnvKey(targetEnvs, key) {
574
610
  for (let i = 0; i < targetEnvs.length; i += 2) {
575
611
  if (targetEnvs[i] !== '--env') {
@@ -1071,6 +1107,14 @@ async function setupCommander() {
1071
1107
  host: options.host || ''
1072
1108
  }, options));
1073
1109
 
1110
+ command.command('cli-add')
1111
+ .description('输出 playwright-cli skill 安装命令')
1112
+ .action(() => selectPluginAction({
1113
+ action: 'cli-add',
1114
+ pluginName: 'playwright',
1115
+ scene: 'all'
1116
+ }));
1117
+
1074
1118
  command.command('ext-download')
1075
1119
  .description('下载并解压 Playwright 扩展到 ~/.manyoyo/plugin/playwright/extensions/')
1076
1120
  .option('--prodversion <ver>', 'CRX 下载使用的 Chrome 版本号 (默认 132.0.0.0)')
@@ -1109,8 +1153,8 @@ async function setupCommander() {
1109
1153
  ${MANYOYO_NAME} serve 127.0.0.1:3000 启动本机网页服务
1110
1154
  ${MANYOYO_NAME} serve 127.0.0.1:3000 -d 后台启动;未设密码时会打印本次随机密码
1111
1155
  ${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 命名空间启动
1156
+ ${MANYOYO_NAME} playwright up mcp-host-headless 启动 playwright MCP 宿主场景(默认/推荐)
1157
+ ${MANYOYO_NAME} playwright up cli-host-headless 启动 playwright CLI 宿主场景(供容器内 playwright-cli 附着)
1114
1158
  ${MANYOYO_NAME} run -n test -q tip -q cmd 多次使用静默选项
1115
1159
  `);
1116
1160
 
@@ -1372,7 +1416,8 @@ Notes:
1372
1416
 
1373
1417
  applyPlaywrightCliSessionIntegration(config, runConfig);
1374
1418
 
1375
- const volumeList = mergeArrayConfig(config.volumes, runConfig.volumes, options.volume);
1419
+ const volumeList = mergeArrayConfig(config.volumes, runConfig.volumes, options.volume)
1420
+ .map(normalizeVolume);
1376
1421
  volumeList.forEach(v => addVolume(v));
1377
1422
 
1378
1423
  const portList = mergeArrayConfig(config.ports, runConfig.ports, options.port);
@@ -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,32 +203,30 @@ RUN <<EOX
202
203
  ;; esac
203
204
 
204
205
  # 安装 Playwright CLI skills(不在镜像构建阶段下载浏览器)
205
- npm install -g @playwright/cli@latest
206
206
  PLAYWRIGHT_CLI_INSTALL_DIR=/tmp/playwright-cli-install
207
- 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
218
- cd "${PLAYWRIGHT_CLI_INSTALL_DIR}"
219
- playwright-cli install --skills
220
- mkdir -p "$HOME/.codex/skills/playwright-cli" ~/.gemini/skills/playwright-cli
221
- cp -R "${PLAYWRIGHT_CLI_INSTALL_DIR}/.claude/skills/playwright-cli/." "$HOME/.codex/skills/playwright-cli/"
222
- cp -R "${PLAYWRIGHT_CLI_INSTALL_DIR}/.claude/skills/playwright-cli/." "$HOME/.gemini/skills/playwright-cli/"
223
- cd $OLDPWD
224
- rm -rf "${PLAYWRIGHT_CLI_INSTALL_DIR}"
207
+ mkdir -p "$PLAYWRIGHT_CLI_INSTALL_DIR/.playwright"
208
+ echo '{"browser":{"browserName":"chromium","launchOptions":{"channel":"chromium"}}}' > "${PLAYWRIGHT_CLI_INSTALL_DIR}/.playwright/cli.config.json"
209
+ cd "$PLAYWRIGHT_CLI_INSTALL_DIR"
210
+ 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")
211
+ npm install -g "@playwright/cli@${PLAYWRIGHT_CLI_VERSION}"
212
+ playwright-cli --config="${PLAYWRIGHT_CLI_INSTALL_DIR}/.playwright/cli.config.json" install --skills
213
+ PLAYWRIGHT_CLI_SKILL_SOURCE="$PLAYWRIGHT_CLI_INSTALL_DIR/.claude/skills/playwright-cli"
214
+ for target in ~/.claude/skills/playwright-cli ~/.codex/skills/playwright-cli ~/.gemini/skills/playwright-cli; do
215
+ mkdir -p "$target"
216
+ cp -R "$PLAYWRIGHT_CLI_SKILL_SOURCE/." "$target/"
217
+ done
218
+ cd "$OLDPWD"
219
+ rm -rf "$PLAYWRIGHT_CLI_INSTALL_DIR"
225
220
 
226
221
  # 清理
227
222
  npm cache clean --force
223
+ rm -f /tmp/manyoyo-package.json
228
224
  rm -rf /tmp/* /var/tmp/* /var/log/apt /var/log/*.log /var/lib/apt/lists/* ~/.npm ~/go/pkg/mod/cache
229
225
  EOX
230
226
 
227
+ COPY ./docker/res/playwright/playwright-cli-wrapper.sh /usr/local/bin/playwright-cli
228
+ RUN chmod +x /usr/local/bin/playwright-cli
229
+
231
230
  # 从 cache-stage 复制 JDT LSP(缓存或下载)
232
231
  COPY --from=cache-stage /opt/jdtls /tmp/jdtls-cache
233
232
 
@@ -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'],
@@ -290,6 +291,7 @@ class PlaywrightPlugin {
290
291
  const homeDir = os.homedir();
291
292
  const pluginRootDir = path.join(homeDir, '.manyoyo', 'plugin', 'playwright');
292
293
  const defaultConfig = {
294
+ homeDir,
293
295
  runtime: 'mixed',
294
296
  enabledScenes: [...SCENE_ORDER],
295
297
  cliSessionScene: 'cli-host-headless',
@@ -379,6 +381,29 @@ class PlaywrightPlugin {
379
381
  this.stderr.write(`${line}\n`);
380
382
  }
381
383
 
384
+ remindCliSessionScene(sceneName) {
385
+ if (!isCliScene(sceneName)) {
386
+ return;
387
+ }
388
+ if (sceneName !== 'cli-host-headed') {
389
+ return;
390
+ }
391
+ if (this.config.cliSessionScene === sceneName) {
392
+ return;
393
+ }
394
+ this.writeStdout('[tip] 如果希望容器内 manyoyo run 自动附着到当前 CLI 宿主场景,请在 ~/.manyoyo/manyoyo.json 中设置:');
395
+ this.writeStdout('{');
396
+ this.writeStdout(' "volumes": [');
397
+ this.writeStdout(' "~/.manyoyo/.cache/ms-playwright:/root/.cache/ms-playwright"');
398
+ this.writeStdout(' ],');
399
+ this.writeStdout(' "plugins": {');
400
+ this.writeStdout(' "playwright": {');
401
+ this.writeStdout(' "cliSessionScene": "cli-host-headed"');
402
+ this.writeStdout(' }');
403
+ this.writeStdout(' }');
404
+ this.writeStdout('}');
405
+ }
406
+
382
407
  randomAlnum(length = 16) {
383
408
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
384
409
  let out = '';
@@ -605,11 +630,22 @@ class PlaywrightPlugin {
605
630
  }
606
631
 
607
632
  extensionDirPath() {
608
- return path.join(os.homedir(), '.manyoyo', 'plugin', 'playwright', 'extensions');
633
+ return path.join(this.config.homeDir, '.manyoyo', 'plugin', 'playwright', 'extensions');
609
634
  }
610
635
 
611
636
  extensionTmpDirPath() {
612
- return path.join(os.homedir(), '.manyoyo', 'plugin', 'playwright', 'tmp-crx');
637
+ return path.join(this.config.homeDir, '.manyoyo', 'plugin', 'playwright', 'tmp-crx');
638
+ }
639
+
640
+ cliBrowserCacheDirPath() {
641
+ return path.join(this.config.homeDir, '.manyoyo', '.cache', 'ms-playwright');
642
+ }
643
+
644
+ ensureCliHostHeadedCacheDir(sceneName) {
645
+ if (sceneName !== 'cli-host-headed') {
646
+ return;
647
+ }
648
+ fs.mkdirSync(this.cliBrowserCacheDirPath(), { recursive: true });
613
649
  }
614
650
 
615
651
  resolveTargets(sceneName = 'all') {
@@ -1204,6 +1240,7 @@ class PlaywrightPlugin {
1204
1240
 
1205
1241
  async startHost(sceneName, options = {}) {
1206
1242
  try {
1243
+ this.ensureCliHostHeadedCacheDir(sceneName);
1207
1244
  this.ensureHostScenePrerequisites(sceneName);
1208
1245
  } catch (error) {
1209
1246
  this.writeStderr(`[up] ${sceneName} failed: ${error.message || String(error)}`);
@@ -1259,6 +1296,7 @@ class PlaywrightPlugin {
1259
1296
  if (managedPids.length > 0) {
1260
1297
  fs.writeFileSync(pidFile, `${managedPids[0]}`, 'utf8');
1261
1298
  this.writeStdout(`[up] ${sceneName} ready on 127.0.0.1:${port} (pid(s) ${managedPids.join(' ')})`);
1299
+ this.remindCliSessionScene(sceneName);
1262
1300
  return 0;
1263
1301
  }
1264
1302
  }
@@ -1539,6 +1577,26 @@ class PlaywrightPlugin {
1539
1577
  return 0;
1540
1578
  }
1541
1579
 
1580
+ printCliAdd() {
1581
+ const lines = [
1582
+ 'PLAYWRIGHT_CLI_INSTALL_DIR="${TMPDIR:-/tmp}/manyoyo-playwright-cli-install-$$"',
1583
+ 'mkdir -p "$PLAYWRIGHT_CLI_INSTALL_DIR/.playwright"',
1584
+ 'echo \'{"browser":{"browserName":"chromium","launchOptions":{"channel":"chromium"}}}\' > "$PLAYWRIGHT_CLI_INSTALL_DIR/.playwright/cli.config.json"',
1585
+ 'cd "$PLAYWRIGHT_CLI_INSTALL_DIR"',
1586
+ `npm install -g @playwright/cli@${PLAYWRIGHT_CLI_VERSION}`,
1587
+ 'playwright-cli install --skills',
1588
+ 'PLAYWRIGHT_CLI_SKILL_SOURCE="$PLAYWRIGHT_CLI_INSTALL_DIR/.claude/skills/playwright-cli"',
1589
+ 'for target in ~/.claude/skills/playwright-cli ~/.codex/skills/playwright-cli ~/.gemini/skills/playwright-cli; do',
1590
+ ' mkdir -p "$target"',
1591
+ ' cp -R "$PLAYWRIGHT_CLI_SKILL_SOURCE/." "$target/"',
1592
+ 'done',
1593
+ 'cd "$OLDPWD"',
1594
+ 'rm -rf "$PLAYWRIGHT_CLI_INSTALL_DIR"'
1595
+ ];
1596
+ this.writeStdout(lines.join('\n'));
1597
+ return 0;
1598
+ }
1599
+
1542
1600
  printSummary() {
1543
1601
  const scenes = this.resolveTargets('all');
1544
1602
  this.writeStdout(`playwright\truntime=${this.config.runtime}\tscenes=${scenes.join(',')}`);
@@ -1583,6 +1641,10 @@ class PlaywrightPlugin {
1583
1641
  return this.printMcpAdd(host);
1584
1642
  }
1585
1643
 
1644
+ if (action === 'cli-add') {
1645
+ return this.printCliAdd();
1646
+ }
1647
+
1586
1648
  if (action === 'ext-download') {
1587
1649
  return await this.downloadExtensions({ prodversion });
1588
1650
  }
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.14",
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",