From 6be786e25cca5302ccc1c6b8410183fa028b7e5e Mon Sep 17 00:00:00 2001 From: yu-xin-c <2182712990@qq.com> Date: Tue, 29 Sep 2026 17:11:45 +0800 Subject: [PATCH 1/2] fix(video2code): resolve Python across platforms --- marketplace.json | 2 +- plugins/video2code/.claude-plugin/plugin.json | 8 +- plugins/video2code/.mcp.json | 14 +++- plugins/video2code/.zcode-plugin/plugin.json | 8 +- plugins/video2code/README.md | 12 ++- plugins/video2code/README_CN.md | 11 ++- plugins/video2code/hooks/check_closeout.py | 5 +- plugins/video2code/hooks/hooks.json | 10 +-- plugins/video2code/hooks/run_python.mjs | 63 +++++++++++++++ plugins/video2code/mcp/v2c_tools/deploy.py | 3 +- plugins/video2code/skills/env-setup/SKILL.md | 4 +- .../skills/env-setup/scripts/env_doctor.py | 17 ++-- plugins/video2code/skills/video2code/SKILL.md | 4 +- plugins/video2code/tests/test_env_doctor.py | 39 ++++++++++ .../video2code/tests/test_python_launcher.py | 78 +++++++++++++++++++ 15 files changed, 244 insertions(+), 34 deletions(-) create mode 100644 plugins/video2code/hooks/run_python.mjs create mode 100644 plugins/video2code/tests/test_python_launcher.py diff --git a/marketplace.json b/marketplace.json index b988fe5..2a64d15 100644 --- a/marketplace.json +++ b/marketplace.json @@ -117,7 +117,7 @@ "en": "Records WebM through ZCode's built-in Browser Use WebView and converts it to MP4 with ffmpeg for video/URL replication; no Playwright or external Chromium required.", "zh-CN": "基于 ZCode 内置 Browser Use WebView 录制 WebM 并用 ffmpeg 转成 MP4/URL 复刻;无需 Playwright 或外部 Chromium" }, - "version": "0.6.0", + "version": "0.6.1", "author": { "name": "Z.ai", "url": "https://z.ai" diff --git a/plugins/video2code/.claude-plugin/plugin.json b/plugins/video2code/.claude-plugin/plugin.json index cfc189f..4d55433 100644 --- a/plugins/video2code/.claude-plugin/plugin.json +++ b/plugins/video2code/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "video2code", - "version": "0.6.0", + "version": "0.6.1", "description": "基于 ZCode 内置 Browser Use WebView 录制 WebM 并用 ffmpeg 转成 MP4/URL 复刻;无需 Playwright 或外部 Chromium", "author": { "name": "Z.ai", @@ -34,8 +34,9 @@ "mcpServers": { "runtime": { "type": "stdio", - "command": "python3", + "command": "node", "args": [ + "${CLAUDE_PLUGIN_ROOT}/hooks/run_python.mjs", "${CLAUDE_PLUGIN_ROOT}/mcp/runtime_server.py" ], "cwd": "${CLAUDE_PROJECT_DIR}", @@ -47,8 +48,9 @@ }, "video": { "type": "stdio", - "command": "python3", + "command": "node", "args": [ + "${CLAUDE_PLUGIN_ROOT}/hooks/run_python.mjs", "${CLAUDE_PLUGIN_ROOT}/mcp/video_server.py" ], "cwd": "${CLAUDE_PROJECT_DIR}", diff --git a/plugins/video2code/.mcp.json b/plugins/video2code/.mcp.json index 0bb0ccc..9b4d5e3 100644 --- a/plugins/video2code/.mcp.json +++ b/plugins/video2code/.mcp.json @@ -2,8 +2,11 @@ "mcpServers": { "runtime": { "type": "stdio", - "command": "python3", - "args": ["${CLAUDE_PLUGIN_ROOT}/mcp/runtime_server.py"], + "command": "node", + "args": [ + "${CLAUDE_PLUGIN_ROOT}/hooks/run_python.mjs", + "${CLAUDE_PLUGIN_ROOT}/mcp/runtime_server.py" + ], "cwd": "${CLAUDE_PROJECT_DIR}", "env": { "V2C_PLUGIN_ROOT": "${CLAUDE_PLUGIN_ROOT}", @@ -13,8 +16,11 @@ }, "video": { "type": "stdio", - "command": "python3", - "args": ["${CLAUDE_PLUGIN_ROOT}/mcp/video_server.py"], + "command": "node", + "args": [ + "${CLAUDE_PLUGIN_ROOT}/hooks/run_python.mjs", + "${CLAUDE_PLUGIN_ROOT}/mcp/video_server.py" + ], "cwd": "${CLAUDE_PROJECT_DIR}", "env": { "V2C_PLUGIN_ROOT": "${CLAUDE_PLUGIN_ROOT}", diff --git a/plugins/video2code/.zcode-plugin/plugin.json b/plugins/video2code/.zcode-plugin/plugin.json index cfc189f..4d55433 100644 --- a/plugins/video2code/.zcode-plugin/plugin.json +++ b/plugins/video2code/.zcode-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "video2code", - "version": "0.6.0", + "version": "0.6.1", "description": "基于 ZCode 内置 Browser Use WebView 录制 WebM 并用 ffmpeg 转成 MP4/URL 复刻;无需 Playwright 或外部 Chromium", "author": { "name": "Z.ai", @@ -34,8 +34,9 @@ "mcpServers": { "runtime": { "type": "stdio", - "command": "python3", + "command": "node", "args": [ + "${CLAUDE_PLUGIN_ROOT}/hooks/run_python.mjs", "${CLAUDE_PLUGIN_ROOT}/mcp/runtime_server.py" ], "cwd": "${CLAUDE_PROJECT_DIR}", @@ -47,8 +48,9 @@ }, "video": { "type": "stdio", - "command": "python3", + "command": "node", "args": [ + "${CLAUDE_PLUGIN_ROOT}/hooks/run_python.mjs", "${CLAUDE_PLUGIN_ROOT}/mcp/video_server.py" ], "cwd": "${CLAUDE_PROJECT_DIR}", diff --git a/plugins/video2code/README.md b/plugins/video2code/README.md index 99b8c77..db21643 100644 --- a/plugins/video2code/README.md +++ b/plugins/video2code/README.md @@ -69,7 +69,13 @@ Two stdio MCP servers, declared in both [`.mcp.json`](./.mcp.json) and the plugi ## Hooks -All four are `command` hooks running `python3` against scripts in [`hooks/`](./hooks). +All four are `command` hooks. They and both MCP servers use the bundled +[`run_python.mjs`](./hooks/run_python.mjs) launcher instead of assuming the host +provides a `python3` executable. On Windows it tries `py -3`, `python`, then +`python3`; on macOS and Linux it tries `python3`, then `python`. The launcher +preserves the selected interpreter's exit status and defaults Python stdio to +UTF-8 so localized Hook output is not limited by the Windows console code page. +Existing `PYTHONUTF8` and `PYTHONIOENCODING` values are respected. | Event | Script | Behavior | | --- | --- | --- | @@ -90,8 +96,8 @@ All four are `command` hooks running `python3` against scripts in [`hooks/`](./h Run the doctor rather than checking by hand: ```bash -python3 skills/env-setup/scripts/env_doctor.py -python3 skills/env-setup/scripts/env_doctor.py --fix +node hooks/run_python.mjs skills/env-setup/scripts/env_doctor.py +node hooks/run_python.mjs skills/env-setup/scripts/env_doctor.py --fix ``` ## Side effects, network access, and data diff --git a/plugins/video2code/README_CN.md b/plugins/video2code/README_CN.md index 895863f..a5b0638 100644 --- a/plugins/video2code/README_CN.md +++ b/plugins/video2code/README_CN.md @@ -69,7 +69,12 @@ ## Hooks -四个都是 `command` 类型,用 `python3` 执行 [`hooks/`](./hooks) 下的脚本。 +四个都是 `command` 类型。它们和两个 MCP 服务都通过插件自带的 +[`run_python.mjs`](./hooks/run_python.mjs) 启动 Python,不再假设宿主一定提供 +`python3` 命令。Windows 依次尝试 `py -3`、`python`、`python3`,macOS 和 Linux +依次尝试 `python3`、`python`;选中解释器后会原样传递其退出码,并默认把 Python +标准输入输出设为 UTF-8,避免中文 Hook 输出受 Windows 控制台代码页限制。调用方已经 +设置的 `PYTHONUTF8` 或 `PYTHONIOENCODING` 会被保留。 | 事件 | 脚本 | 行为 | | --- | --- | --- | @@ -90,8 +95,8 @@ 不要手工逐项确认,直接跑体检脚本: ```bash -python3 skills/env-setup/scripts/env_doctor.py -python3 skills/env-setup/scripts/env_doctor.py --fix +node hooks/run_python.mjs skills/env-setup/scripts/env_doctor.py +node hooks/run_python.mjs skills/env-setup/scripts/env_doctor.py --fix ``` ## 副作用、网络访问与数据 diff --git a/plugins/video2code/hooks/check_closeout.py b/plugins/video2code/hooks/check_closeout.py index 65b662e..a9d2ceb 100644 --- a/plugins/video2code/hooks/check_closeout.py +++ b/plugins/video2code/hooks/check_closeout.py @@ -127,8 +127,9 @@ def main() -> None: allow() # 三振/会话预算用尽/已顶回过 → 放行并已记录, 防死循环 block_stop( "复刻契约未闭环, 不能收尾:\n- " + "\n- ".join(res["gaps"]) + - "\n先自己跑审计脚本对账: python3 /skills/video2code/scripts/" - "contract_audit.py (plugin_root 见会话开头横幅, 兜底 cat .v2c/plugin_root), " + "\n先自己跑审计脚本对账: node \"/hooks/run_python.mjs\" " + "\"/skills/video2code/scripts/contract_audit.py\" " + "(plugin_root 见会话开头横幅, 兜底 cat .v2c/plugin_root), " "按缺口逐条补完 (D 项成对证据用 composite_view 的 beats 视频输入一轮即出), " "全绿后再收尾。\n" "若用户本轮要的其实不是这个复刻任务 (契约是本会话早前认领的, 但用户已经转去别的事, " diff --git a/plugins/video2code/hooks/hooks.json b/plugins/video2code/hooks/hooks.json index fff69b8..2012170 100644 --- a/plugins/video2code/hooks/hooks.json +++ b/plugins/video2code/hooks/hooks.json @@ -6,7 +6,7 @@ "hooks": [ { "type": "command", - "command": "python3 \"${CLAUDE_PLUGIN_ROOT}/hooks/env_check.py\"", + "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/run_python.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/env_check.py\"", "timeout": 30, "statusMessage": "video2code 环境检查…" } @@ -18,12 +18,12 @@ "hooks": [ { "type": "command", - "command": "python3 \"${CLAUDE_PLUGIN_ROOT}/hooks/check_video_input.py\"", + "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/run_python.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/check_video_input.py\"", "timeout": 15 }, { "type": "command", - "command": "python3 \"${CLAUDE_PLUGIN_ROOT}/hooks/check_url_input.py\"", + "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/run_python.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/check_url_input.py\"", "timeout": 10 } ] @@ -35,7 +35,7 @@ "hooks": [ { "type": "command", - "command": "python3 \"${CLAUDE_PLUGIN_ROOT}/hooks/check_plan_first.py\"", + "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/run_python.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/check_plan_first.py\"", "timeout": 10 } ] @@ -46,7 +46,7 @@ "hooks": [ { "type": "command", - "command": "python3 \"${CLAUDE_PLUGIN_ROOT}/hooks/check_closeout.py\"", + "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/run_python.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/check_closeout.py\"", "timeout": 15 } ] diff --git a/plugins/video2code/hooks/run_python.mjs b/plugins/video2code/hooks/run_python.mjs new file mode 100644 index 0000000..96fde44 --- /dev/null +++ b/plugins/video2code/hooks/run_python.mjs @@ -0,0 +1,63 @@ +#!/usr/bin/env node +/** Run a plugin Python entrypoint with platform-appropriate interpreter names. */ + +import { spawnSync } from "node:child_process"; + +const [script, ...scriptArgs] = process.argv.slice(2); + +if (!script) { + process.stderr.write("Usage: run_python.mjs