@xcanwin/manyoyo 5.0.2 → 5.1.3

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/bin/manyoyo.js CHANGED
@@ -14,6 +14,7 @@ const { buildContainerRunArgs, buildContainerRunCommand } = require('../lib/cont
14
14
  const { initAgentConfigs } = require('../lib/init-config');
15
15
  const { buildImage } = require('../lib/image-build');
16
16
  const { resolveAgentResumeArg, buildAgentResumeCommand } = require('../lib/agent-resume');
17
+ const { runPluginCommand } = require('../lib/plugin');
17
18
  const { version: BIN_VERSION, imageVersion: IMAGE_VERSION_DEFAULT } = require('../package.json');
18
19
  const IMAGE_VERSION_BASE = String(IMAGE_VERSION_DEFAULT || '1.0.0').split('-')[0];
19
20
  const IMAGE_VERSION_HELP_EXAMPLE = IMAGE_VERSION_DEFAULT || `${IMAGE_VERSION_BASE}-common`;
@@ -236,6 +237,7 @@ function sanitizeSensitiveData(obj) {
236
237
  * @property {Object.<string, string|number|boolean>} [env] - 环境变量映射
237
238
  * @property {string[]} [envFile] - 环境文件数组
238
239
  * @property {string[]} [volumes] - 挂载卷数组
240
+ * @property {Object.<string, Object>} [plugins] - 可选插件配置映射(如 plugins.playwright)
239
241
  * @property {Object.<string, Object>} [runs] - 运行配置映射(-r <name>)
240
242
  * @property {string} [yolo] - YOLO 模式
241
243
  * @property {string} [containerMode] - 容器模式
@@ -698,11 +700,33 @@ function updateManyoyo() {
698
700
 
699
701
  function getContList() {
700
702
  try {
701
- 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}}"`,
702
- { encoding: 'utf-8' });
703
- console.log(result);
703
+ const output = dockerExecArgs([
704
+ 'ps', '-a', '--size',
705
+ '--format', '{{.Names}}\t{{.Status}}\t{{.Size}}\t{{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Networks}}\t{{.Mounts}}'
706
+ ], { stdio: 'pipe' });
707
+
708
+ const rows = output
709
+ .split('\n')
710
+ .map(line => line.trim())
711
+ .filter(Boolean)
712
+ .filter(line => {
713
+ const cols = line.split('\t');
714
+ const name = cols[0] || '';
715
+ const image = cols[4] || '';
716
+ // include manyoyo runtime containers (image match)
717
+ // and plugin containers (both legacy manyoyo-* and new my-* prefixes)
718
+ return image.includes('manyoyo') || name.startsWith('manyoyo-') || name.startsWith('my-');
719
+ });
720
+
721
+ console.log('NO.\tNAMES\tSTATUS\tSIZE\tCONTAINER ID\tIMAGE\tPORTS\tNETWORKS\tMOUNTS');
722
+ if (rows.length > 0) {
723
+ const numberedRows = rows.map((line, index) => {
724
+ return `${index + 1}.\t${line}`;
725
+ });
726
+ console.log(numberedRows.join('\n'));
727
+ }
704
728
  } catch (e) {
705
- console.log(e.stdout || '');
729
+ console.log((e && e.stdout) || '');
706
730
  }
707
731
  }
708
732
 
@@ -843,6 +867,67 @@ async function setupCommander() {
843
867
  selectedAction = action;
844
868
  selectedOptions = options;
845
869
  };
870
+ const selectPluginAction = (params = {}, options = {}) => {
871
+ selectAction('plugin', {
872
+ ...options,
873
+ pluginAction: params.action || 'ls',
874
+ pluginName: params.pluginName || 'playwright',
875
+ pluginScene: params.scene || 'host-headless',
876
+ pluginHost: params.host || '',
877
+ pluginExtensions: Array.isArray(params.extensions) ? params.extensions : [],
878
+ pluginProdversion: params.prodversion || ''
879
+ });
880
+ };
881
+
882
+ const registerPlaywrightAliasCommands = (command) => {
883
+ command.command('ls')
884
+ .description('列出 playwright 启用场景')
885
+ .option('-r, --run <name>', '加载运行配置 (从 ~/.manyoyo/manyoyo.json 的 runs.<name> 读取)')
886
+ .action(options => selectPluginAction({
887
+ action: 'ls',
888
+ pluginName: 'playwright',
889
+ scene: 'all'
890
+ }, options));
891
+
892
+ const actions = ['up', 'down', 'status', 'health', 'logs'];
893
+ actions.forEach(action => {
894
+ const sceneCommand = command.command(`${action} [scene]`)
895
+ .description(`${action} playwright 场景,scene 默认 host-headless`)
896
+ .option('-r, --run <name>', '加载运行配置 (从 ~/.manyoyo/manyoyo.json 的 runs.<name> 读取)');
897
+
898
+ if (action === 'up') {
899
+ appendArrayOption(sceneCommand, '--ext <path>', '追加浏览器扩展目录(可多次传入;目录需包含 manifest.json)');
900
+ }
901
+
902
+ sceneCommand.action((scene, options) => selectPluginAction({
903
+ action,
904
+ pluginName: 'playwright',
905
+ scene: scene || 'host-headless',
906
+ extensions: action === 'up' ? (options.ext || []) : []
907
+ }, options));
908
+ });
909
+
910
+ command.command('mcp-add')
911
+ .description('输出 playwright 的 MCP 接入命令')
912
+ .option('--host <host>', 'MCP URL 使用的主机名或IP (默认 host.docker.internal)')
913
+ .option('-r, --run <name>', '加载运行配置 (从 ~/.manyoyo/manyoyo.json 的 runs.<name> 读取)')
914
+ .action(options => selectPluginAction({
915
+ action: 'mcp-add',
916
+ pluginName: 'playwright',
917
+ scene: 'all',
918
+ host: options.host || ''
919
+ }, options));
920
+
921
+ command.command('ext-download')
922
+ .description('下载并解压 Playwright 扩展到 ~/.manyoyo/plugin/playwright/extensions/')
923
+ .option('--prodversion <ver>', 'CRX 下载使用的 Chrome 版本号 (默认 132.0.0.0)')
924
+ .action(options => selectPluginAction({
925
+ action: 'ext-download',
926
+ pluginName: 'playwright',
927
+ scene: 'all',
928
+ prodversion: options.prodversion || ''
929
+ }, options));
930
+ };
846
931
 
847
932
  program
848
933
  .name(MANYOYO_NAME)
@@ -870,6 +955,8 @@ async function setupCommander() {
870
955
  ${MANYOYO_NAME} run -x "echo 123" 指定命令执行
871
956
  ${MANYOYO_NAME} serve 3000 -u admin -P 123456 启动带登录认证的网页服务
872
957
  ${MANYOYO_NAME} serve 0.0.0.0:3000 监听全部网卡,便于局域网访问
958
+ ${MANYOYO_NAME} playwright up host-headless 启动 playwright 默认场景(推荐)
959
+ ${MANYOYO_NAME} plugin playwright up host-headless 通过 plugin 命名空间启动
873
960
  ${MANYOYO_NAME} run -n test -q tip -q cmd 多次使用静默选项
874
961
  `);
875
962
 
@@ -910,6 +997,21 @@ async function setupCommander() {
910
997
  });
911
998
  });
912
999
 
1000
+ const playwrightCommand = program.command('playwright').description('管理 playwright 插件服务(推荐)');
1001
+ registerPlaywrightAliasCommands(playwrightCommand);
1002
+
1003
+ const pluginCommand = program.command('plugin').description('管理 manyoyo 插件');
1004
+ pluginCommand.command('ls')
1005
+ .description('列出可用插件与启用场景')
1006
+ .option('-r, --run <name>', '加载运行配置 (从 ~/.manyoyo/manyoyo.json 的 runs.<name> 读取)')
1007
+ .action(options => selectPluginAction({
1008
+ action: 'ls',
1009
+ pluginName: 'playwright',
1010
+ scene: 'all'
1011
+ }, options));
1012
+ const pluginPlaywrightCommand = pluginCommand.command('playwright').description('管理 playwright 插件服务');
1013
+ registerPlaywrightAliasCommands(pluginPlaywrightCommand);
1014
+
913
1015
  const configCommand = program.command('config').description('查看解析后的配置或命令');
914
1016
  const configShowCommand = configCommand.command('show').description('显示最终生效配置并退出');
915
1017
  applyRunStyleOptions(configShowCommand, { includeRmOnExit: false, includeServePreview: true });
@@ -982,7 +1084,7 @@ async function setupCommander() {
982
1084
  const isShowCommandMode = selectedAction === 'config-command';
983
1085
  const isServerMode = options.server !== undefined;
984
1086
 
985
- const noDockerActions = new Set(['init', 'update', 'install', 'config-show']);
1087
+ const noDockerActions = new Set(['init', 'update', 'install', 'config-show', 'plugin']);
986
1088
  if (!noDockerActions.has(selectedAction)) {
987
1089
  ensureDocker();
988
1090
  }
@@ -1003,6 +1105,23 @@ async function setupCommander() {
1003
1105
  process.exit(0);
1004
1106
  }
1005
1107
 
1108
+ if (selectedAction === 'plugin') {
1109
+ const runConfig = options.run ? loadRunConfig(options.run, config) : {};
1110
+ return {
1111
+ isPluginMode: true,
1112
+ pluginRequest: {
1113
+ action: options.pluginAction,
1114
+ pluginName: options.pluginName,
1115
+ scene: options.pluginScene || 'host-headless',
1116
+ host: options.pluginHost || '',
1117
+ extensions: Array.isArray(options.pluginExtensions) ? options.pluginExtensions : [],
1118
+ prodversion: options.pluginProdversion || ''
1119
+ },
1120
+ pluginGlobalConfig: config,
1121
+ pluginRunConfig: runConfig
1122
+ };
1123
+ }
1124
+
1006
1125
  // Load run config if specified
1007
1126
  const runConfig = options.run ? loadRunConfig(options.run, config) : {};
1008
1127
 
@@ -1162,7 +1281,8 @@ async function setupCommander() {
1162
1281
  isBuildMode,
1163
1282
  isRemoveMode,
1164
1283
  isShowCommandMode,
1165
- isServerMode
1284
+ isServerMode,
1285
+ isPluginMode: false
1166
1286
  };
1167
1287
  }
1168
1288
 
@@ -1495,6 +1615,18 @@ async function main() {
1495
1615
  try {
1496
1616
  // 1. Setup commander and parse arguments
1497
1617
  const modeState = await setupCommander();
1618
+
1619
+ if (modeState.isPluginMode) {
1620
+ const exitCode = await runPluginCommand(modeState.pluginRequest, {
1621
+ globalConfig: modeState.pluginGlobalConfig,
1622
+ runConfig: modeState.pluginRunConfig,
1623
+ projectRoot: path.join(__dirname, '..'),
1624
+ stdout: process.stdout,
1625
+ stderr: process.stderr
1626
+ });
1627
+ process.exit(exitCode);
1628
+ }
1629
+
1498
1630
  const runtime = createRuntimeContext(modeState);
1499
1631
 
1500
1632
  // 2. Start web server mode
@@ -25,6 +25,29 @@
25
25
  "imageBuildArgs": [],
26
26
  "quiet": ["tip", "cmd"],
27
27
 
28
+ // 可选插件(manyoyo playwright / manyoyo plugin playwright)
29
+ "plugins": {
30
+ "playwright": {
31
+ // mixed: 支持容器+宿主机;container: 仅容器;host: 仅宿主机
32
+ "runtime": "mixed",
33
+ // 启用场景(可按需裁剪)
34
+ "enabledScenes": ["cont-headless", "cont-headed", "host-headless", "host-headed"],
35
+ // mcp-add 默认 host(可改为 localhost / 127.0.0.1)
36
+ "mcpDefaultHost": "host.docker.internal",
37
+ // cont-headed 场景读取的密码环境变量名(默认 VNC_PASSWORD)
38
+ "vncPasswordEnvKey": "VNC_PASSWORD",
39
+ // playwright ext-download 的 CRX prodversion 参数
40
+ "extensionProdversion": "132.0.0.0",
41
+ "ports": {
42
+ "contHeadless": 8931,
43
+ "contHeaded": 8932,
44
+ "hostHeadless": 8933,
45
+ "hostHeaded": 8934,
46
+ "contHeadedNoVnc": 6080
47
+ }
48
+ }
49
+ },
50
+
28
51
  // 运行配置集合:通过 -r <name> 读取 runs.<name>
29
52
  "runs": {
30
53
  "claude": {
@@ -33,6 +56,11 @@
33
56
  "shell": "claude",
34
57
  "env": {
35
58
  "ANTHROPIC_MODEL": "claude-sonnet-4-5"
59
+ },
60
+ "plugins": {
61
+ "playwright": {
62
+ "runtime": "container"
63
+ }
36
64
  }
37
65
  }
38
66
  }
@@ -0,0 +1,65 @@
1
+ 'use strict';
2
+
3
+ const { PlaywrightPlugin } = require('./playwright');
4
+
5
+ const AVAILABLE_PLUGINS = ['playwright'];
6
+
7
+ function asObject(value) {
8
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
9
+ return {};
10
+ }
11
+ return value;
12
+ }
13
+
14
+ function getPluginConfigs(pluginName, globalConfig, runConfig) {
15
+ const globalPlugins = asObject(asObject(globalConfig).plugins);
16
+ const runPlugins = asObject(asObject(runConfig).plugins);
17
+ return {
18
+ globalPluginConfig: asObject(globalPlugins[pluginName]),
19
+ runPluginConfig: asObject(runPlugins[pluginName])
20
+ };
21
+ }
22
+
23
+ function createPlugin(pluginName, options = {}) {
24
+ if (pluginName !== 'playwright') {
25
+ throw new Error(`未知插件: ${pluginName}`);
26
+ }
27
+
28
+ const cfg = getPluginConfigs(pluginName, options.globalConfig, options.runConfig);
29
+ return new PlaywrightPlugin({
30
+ projectRoot: options.projectRoot,
31
+ stdout: options.stdout,
32
+ stderr: options.stderr,
33
+ globalConfig: cfg.globalPluginConfig,
34
+ runConfig: cfg.runPluginConfig
35
+ });
36
+ }
37
+
38
+ async function runPluginCommand(request, options = {}) {
39
+ const action = String(request && request.action || '').trim();
40
+
41
+ if (action === 'ls') {
42
+ const plugin = createPlugin('playwright', options);
43
+ return await plugin.run({ action: 'ls' });
44
+ }
45
+
46
+ const pluginName = String(request && request.pluginName || '').trim();
47
+ if (!pluginName) {
48
+ throw new Error('plugin 名称不能为空');
49
+ }
50
+
51
+ const plugin = createPlugin(pluginName, options);
52
+ return await plugin.run({
53
+ action,
54
+ scene: request.scene,
55
+ host: request.host,
56
+ extensions: request.extensions,
57
+ prodversion: request.prodversion
58
+ });
59
+ }
60
+
61
+ module.exports = {
62
+ AVAILABLE_PLUGINS,
63
+ createPlugin,
64
+ runPluginCommand
65
+ };
@@ -0,0 +1,19 @@
1
+ services:
2
+ playwright:
3
+ build:
4
+ context: .
5
+ dockerfile: headed.Dockerfile
6
+ args:
7
+ PLAYWRIGHT_MCP_BASE_IMAGE: mcr.microsoft.com/playwright/mcp:${PLAYWRIGHT_MCP_DOCKER_TAG:-latest}
8
+ image: ${PLAYWRIGHT_MCP_IMAGE:-localhost/xcanwin/manyoyo-playwright-headed}
9
+ container_name: ${PLAYWRIGHT_MCP_CONTAINER_NAME:-my-playwright-cont-headed}
10
+ init: true
11
+ stdin_open: true
12
+ ports:
13
+ - "127.0.0.1:${PLAYWRIGHT_MCP_PORT:-8932}:${PLAYWRIGHT_MCP_PORT:-8932}"
14
+ - "127.0.0.1:${PLAYWRIGHT_MCP_NOVNC_PORT:-6080}:6080"
15
+ environment:
16
+ VNC_PASSWORD: "${VNC_PASSWORD:?VNC_PASSWORD is required}"
17
+ volumes:
18
+ - ${PLAYWRIGHT_MCP_CONFIG_PATH:?PLAYWRIGHT_MCP_CONFIG_PATH is required}:/app/config/playwright.json:ro
19
+ command: ["node", "cli.js", "--config", "/app/config/playwright.json"]
@@ -0,0 +1,11 @@
1
+ services:
2
+ playwright:
3
+ image: mcr.microsoft.com/playwright/mcp:${PLAYWRIGHT_MCP_DOCKER_TAG:-latest}
4
+ container_name: ${PLAYWRIGHT_MCP_CONTAINER_NAME:-my-playwright-cont-headless}
5
+ init: true
6
+ stdin_open: true
7
+ ports:
8
+ - "127.0.0.1:${PLAYWRIGHT_MCP_PORT:-8931}:${PLAYWRIGHT_MCP_PORT:-8931}"
9
+ volumes:
10
+ - ${PLAYWRIGHT_MCP_CONFIG_PATH:?PLAYWRIGHT_MCP_CONFIG_PATH is required}:/app/config/playwright.json:ro
11
+ command: ["--config", "/app/config/playwright.json"]
@@ -0,0 +1,15 @@
1
+ ARG PLAYWRIGHT_MCP_BASE_IMAGE=mcr.microsoft.com/playwright/mcp:latest
2
+ FROM ${PLAYWRIGHT_MCP_BASE_IMAGE}
3
+
4
+ USER root
5
+ RUN apt-get update && \
6
+ apt-get install -y --no-install-recommends \
7
+ ca-certificates xvfb x11vnc novnc websockify fluxbox && \
8
+ update-ca-certificates && \
9
+ apt-get clean && \
10
+ rm -rf /tmp/* /var/tmp/* /var/log/apt /var/log/*.log /var/lib/apt/lists/*
11
+
12
+ COPY init-headed.sh /usr/local/bin/init-headed.sh
13
+ RUN chmod +x /usr/local/bin/init-headed.sh
14
+
15
+ ENTRYPOINT ["/usr/local/bin/init-headed.sh"]
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ export DISPLAY=:99
5
+
6
+ Xvfb :99 -screen 0 1920x1080x24 -nolisten tcp &
7
+ fluxbox >/tmp/fluxbox.log 2>&1 &
8
+
9
+ if [[ -z "${VNC_PASSWORD:-}" ]]; then
10
+ echo "VNC_PASSWORD is required."
11
+ exit 1
12
+ fi
13
+
14
+ x11vnc -display :99 -forever -shared -rfbport 5900 -localhost -passwd "$VNC_PASSWORD" >/tmp/x11vnc.log 2>&1 &
15
+ websockify --web=/usr/share/novnc/ 6080 localhost:5900 >/tmp/websockify.log 2>&1 &
16
+
17
+ exec "$@"