Metadata-Version: 2.4
Name: cvm-vnc
Version: 0.4.0
Summary: CLI and optional MCP server for Tencent Cloud CVM Web VNC, OCR, and keyboard recovery
Author: Curu
License: MIT
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Provides-Extra: dotenv
Requires-Dist: python-dotenv; extra == "dotenv"
Provides-Extra: mcp
Requires-Dist: mcp[cli]>=1.0.0; extra == "mcp"

# cvm-vnc

通过 `agent-browser` 对 Web VNC 页面进行程序化控制的 Python 工具，支持屏幕截图、文本输入、图片 OCR 和页面状态检测，并提供 MCP (Model Context Protocol) Server 供 AI Agent 集成。

## 功能特性

- **VNC 页面控制** — 打开并操作 Web VNC 界面，兼容腾讯云临时 VNC URL 和 Workbench VNC 页面
- **屏幕截图** — 将 VNC Canvas 捕获为 PNG 图片
- **智能文本输入** — 自动识别输入方式（远程命令对话框 / noVNC 键盘 / Canvas 直接输入等）
- **LLM OCR** — 通过 `cvm-vnc ocr-read` 调用 OpenAI 兼容视觉模型，从本地图片提取文本
- **MCP Server** — 以 stdio 模式运行，供 AI Agent 调用
- **登录检测** — 自动识别 VNC 页面跳转至登录页的场景
- **终端网格估算** — 根据 Canvas 尺寸启发式推算终端行列数

## 安装

### 从 PyPI 安装

```bash
# 基础安装（CLI 功能：open / capture / type / close / ocr-read 等）
pip install cvm-vnc

# 安装 MCP Server 支持（AI Agent 集成必需）
pip install cvm-vnc[mcp]

# 安装全部可选依赖
pip install cvm-vnc[mcp,dotenv]
```

### 从源码安装（开发模式）

```bash
git clone <repo-url>
cd cvm-vnc
pip install -e '.[mcp]'
```

`-e`（editable）模式会将当前源码目录链接为已安装包，修改代码后无需重新安装即可生效。

### 可选依赖说明

| Extra | 包 | 说明 |
|-------|-----|------|
| `mcp` | `mcp[cli]>=1.0.0` | MCP Server 功能（`cvm-vnc mcp` / `cvm-vnc-mcp` 命令必需） |
| `dotenv` | `python-dotenv` | 本地开发时加载可选的 `.env` 配置 |

> **注意**：不安装 `mcp` extra 时，CLI 基础命令（`open`、`capture`、`type`、`type-sequence`、`close`、`ocr-read`）仍可正常使用，无需任何第三方依赖。

### 前置依赖

需要系统中安装支持 `--init-script` 的 [agent-browser](https://www.npmjs.com/package/agent-browser)；本轮验证使用 0.41.0：

```bash
npm install -g agent-browser
# 首次使用且尚未下载浏览器时
agent-browser install
```

## 使用方式

### CLI 命令

```bash
# 打开 VNC 页面
cvm-vnc open <url> [--wait-ms MS] [--session NAME] [--headed]

# 截取 VNC 屏幕
cvm-vnc capture [output] [--session NAME] [--headed]

# 发送文本输入
cvm-vnc type <text> [--enter] [--mode stable|fast] [--session NAME] [--headed]

# 批量顺序输入（避免 AI 思考间隙导致登录超时）
cvm-vnc type-sequence --steps-json '<JSON>' [--mode MODE] [--session NAME]

# 关闭浏览器会话
cvm-vnc close [--session NAME]

# 从本地图片读取文本（OpenAI 兼容视觉模型）
cvm-vnc ocr-read <image>

# 仅检查 OCR 配置是否齐全，不输出配置值或发起网络请求
cvm-vnc ocr-check

# 启动 MCP Server
cvm-vnc mcp [--session-default NAME] [--mode-default MODE] [--headed]
```

`open` 只有在 VNC 连接就绪后才成功；连接失败、地址过期或页面没有可用 Canvas 时返回非零退出码，`type` 和 `capture` 也会拒绝操作断开的连接。

### 配合 tccli 打开 CVM

腾讯云 `DescribeInstanceVncUrl` 返回的是编码后的连接参数，需要拼接网页地址。[官方文档](https://cloud.tencent.com/document/product/213/18150)说明临时地址仅有效 15 秒且只能使用一次；重连时必须重新获取。以下示例先启动浏览器，再获取地址并立即打开：

```bash
agent-browser --session vnc-test open about:blank
python3 - <<'PY'
import json
import subprocess

response = subprocess.check_output([
    "tccli", "cvm", "DescribeInstanceVncUrl",
    "--region", "ap-guangzhou", "--InstanceId", "ins-xxxxxxxx",
], text=True)
url = "https://img.qcloud.com/qcloud/app/active_vnc/index.html?InstanceVncUrl=" + json.loads(response)["InstanceVncUrl"]
subprocess.run(["cvm-vnc", "open", url, "--session", "vnc-test"], check=True)
PY
```

VNC 连接就绪与系统登录是两步：看到系统 `login:` 后，再使用 `type-sequence` 登录。腾讯云传统页面会跟踪实际粘贴队列，发送完最后一个字符后才按 Enter；其他页面保留随字符数增加的等待时间。`fast` 模式也会等待发送完成。若发送中断，先检查终端中已输入的内容，再决定是否重试。

传统页面的连续粘贴会限制按键速率，避免丢字或残留 Shift 状态：fast 最少间隔 50 ms，stable 最少间隔 75 ms。因此数千字符的脚本需要数分钟，请为调用设置足够的超时时间。

默认使用无界面浏览器，无需图形桌面。`cvm-vnc open` 会在临时 `/active_vnc/` 页面和 Workbench `/vnc` 页面创建 RFB 连接前安装键盘补丁：大写字母显式发送 Shift，粘贴前释放残留修饰键，避免服务端反复用 Caps Lock 补偿大小写、切换 Linux 控制台后发生大小写反转。补丁仅在工具打开的浏览器页面内生效；旧会话需要用当前版本重新打开。`vnc_status` 的 `keyboardInput.uppercaseShift` 可确认补丁是否生效。Workbench 创建 Canvas 不代表连接成功，工具会继续等待连接状态。

Workbench 的 CLI 输入默认通过 Canvas 逐键发送；实测原生批量粘贴在锁键恢复后仍存在兼容性异常，因此不作为默认路径。已打开的粘贴对话框会先关闭。临时 VNC 页面继续使用粘贴队列；诊断原生粘贴可用测试脚本的 `--remote-dialog` 选项。

此补丁预防上述状态漂移，但重新连接不会自动清除已存在的异常。确认当前 Linux 控制台大小写反转后，可以直接通过 VNC 恢复，不需要登录、SSH 或重启实例：

```bash
cvm-vnc repair-keyboard --case-inverted --session vnc-test
```

大小写恢复是对已确认异常的校正动作，会改变锁键状态差；在正常输入时执行或恢复后盲目重复执行，会再次造成反转。它只作用于当前活动控制台，不会逐一切换或修改其他 VT。先确认输入回显，修复后再验证一次；响应中的 `status=applied` 表示序列已发送，`verified=false` 表示工具未自动识别客户机最终回显。`uppercaseShift=true` 也只表示发送补丁已安装，不表示客户机状态已经校准。

如果只有 Shift、Ctrl、Alt 等修饰键卡住，可省略 `--case-inverted`；默认操作只释放修饰键，不修改大小写锁定状态，可重复执行：

```bash
cvm-vnc repair-keyboard --session vnc-test
```

对应 MCP 工具为 `vnc_repair_keyboard(case_inverted=true, session="vnc-test")`。该恢复方法针对腾讯云临时 VNC 页面、Workbench VNC 页面与 QEMU/Linux 控制台组合；工具不会在每次连接时盲目翻转状态。`keyboardInput.repairAvailable` 表示已支持恢复操作。使用旧版本打开的会话先关闭再用当前版本打开；临时 URL 需要重新获取。Workbench 需要保留同一 session 完成网页登录；更新浏览器启动参数可能重建浏览器并需要再次登录。

源码更新后，已有的长驻 MCP 服务需要重启，再重新打开 VNC 页面。

### 操作 Skill

`cvm-vnc-ops` 位于 `skills/cvm-vnc-ops/`，提供 Agent 可复用的操作流程，支持用户提供的 VNC URL、按需通过 tccli 获取地址、OCR 优先核验与键盘恢复。将该目录复制到客户端的技能目录即可使用，运行时通过 CLI 操作，不需要启动 MCP Server。

辅助脚本会核对实例状态、预热浏览器，再获取临时地址并立即打开，返回会话名和截图路径；脚本输出不包含临时 URL：

```bash
python3 skills/cvm-vnc-ops/scripts/open_instance.py \
  --instance-id ins-xxxxxxxx --region ap-guangzhou --session vnc-task
```

可用 `--profile NAME` 选择已有 tccli 配置，`--screenshot PATH` 指定截图位置。脚本会重新打开指定 session，后续操作使用返回的同一 session。

### OCR 识图命令

Agent 可先执行 `cvm-vnc ocr-check`，该命令只输出配置状态 JSON，例如 `{"configured":true,"missing":[]}`。配置齐全时退出码为 0，否则为 1；缺少项以 `endpoint`、`credential`、`model` 类别表示。不输出服务地址、模型名、密钥或其片段，也不验证网络连通性。Agent 无需直接读取环境变量或配置文件。

`ocr-read` 和 `ocr-check` 优先使用进程环境中的配置。生产环境可由容器、服务管理器或部署平台直接注入以下变量，无需创建配置文件：

- `LLM_OCR_BASE_URL`
- `LLM_OCR_API_KEY`
- `LLM_OCR_MODEL`

若缺少任意一项，`ocr-read` 会在发起请求前报错；`ocr-check` 只返回缺少项和非零退出码。

本地开发也支持可选的当前目录 `.env`，仅补充尚未设置的变量，不覆盖已有环境变量。

```bash
# 运行环境注入（此处用 shell 导出示意）
export LLM_OCR_BASE_URL="https://api.example.com"
export LLM_OCR_API_KEY="<your-key>"
export LLM_OCR_MODEL="gpt-4.1-mini"

cvm-vnc ocr-read ./invoice.png

# 本地开发可选：通过 .env 补充未设置的变量
cat > .env <<'EOF'
LLM_OCR_BASE_URL=https://api.example.com
LLM_OCR_API_KEY=<your-key>
LLM_OCR_MODEL=gpt-4.1-mini
EOF

cvm-vnc ocr-read ./screenshot.png
```

成功时只会在 stdout 输出 OCR 文本，便于管道串联；失败时会在 stderr 输出简洁错误信息。

按操作 skill 使用时，OCR 失败、结果不清或与预期不符，由 Agent 读取同一截图核验；`ocr-read` CLI 本身不调用宿主模型做读图回退。

也可通过独立入口直接启动 MCP Server：

```bash
cvm-vnc-mcp
```

### MCP Server Tools

MCP Server 对外暴露以下工具：

| 工具 | 说明 |
|------|------|
| `vnc_open(url, wait_ms, session)` | 打开 VNC 页面 |
| `vnc_status(session)` | 检查页面状态（Canvas 元数据、登录检测等） |
| `vnc_capture(output_path, session)` | 截取 Canvas 为 PNG |
| `vnc_type(text, press_enter, session, mode)` | 发送文本输入 |
| `vnc_type_sequence(steps, session, mode)` | 批量顺序输入（适合系统登录等需要连续输入的场景） |
| `vnc_close(session)` | 关闭浏览器会话，释放资源 |
| `vnc_ocr_read(image_path)` | 读取本地图片文字，返回 OCR 文本 |

### 示例

```bash
# 打开 VNC 并等待页面加载
cvm-vnc open https://vnc.example.com --wait-ms 3000

# 截图保存到文件
cvm-vnc capture /tmp/screen.png

# 执行命令
cvm-vnc type "ls -la" --enter

# 一次性完成系统登录（用户名 + 密码）
cvm-vnc type-sequence --steps-json '[{"text":"root","press_enter":true,"wait_ms":500},{"text":"password","press_enter":true}]'

# 关闭浏览器会话
cvm-vnc close

# 从图片中提取文本
export LLM_OCR_BASE_URL="https://api.example.com"
export LLM_OCR_API_KEY="<your-key>"
export LLM_OCR_MODEL="gpt-4.1-mini"
cvm-vnc ocr-read ./receipt.png
```

## 项目结构

```
src/cvm_vnc/
├── __init__.py        # 版本信息
├── __main__.py        # CLI 入口
├── llm_ocr.py         # OCR 核心实现（配置解析、图片编码、兼容接口调用）
└── vnc_browser.py     # 核心实现（浏览器控制、CLI 分发、MCP Server、JS 注入脚本）
```

## MCP Server 配置

使用 MCP 功能前，请确保已安装 mcp extra：`pip install cvm-vnc[mcp]`

安装完成后，可将 cvm-vnc 作为 MCP Server 接入 AI 客户端。

### Claude Code

```bash
claude mcp add cvm-vnc -- cvm-vnc-mcp
```

添加后可通过以下命令验证：

```bash
claude mcp list
```

### Codebuddy

```bash
codebuddy mcp add cvm-vnc -- cvm-vnc-mcp
```

或手动编辑 `~/.codebuddy/settings.json`：

```json
{
  "mcpServers": {
    "cvm-vnc": {
      "command": "cvm-vnc-mcp"
    }
  }
}
```

> **提示**：如果 `cvm-vnc-mcp` 不在 PATH 中，需使用完整路径，例如 `/path/to/venv/bin/cvm-vnc-mcp`。

## 配置

| 环境变量 | 说明 | 默认值 |
|----------|------|--------|
| `AGENT_BROWSER_BIN` | agent-browser 可执行文件路径 | 自动查找 |
| `VNC_SESSION_LOCK_TIMEOUT_SECONDS` | 等待其他命令释放会话锁的超时时间（秒）；不限制当前命令的执行时长 | 120 |
| `LLM_OCR_BASE_URL` | `ocr-read` 使用的 OpenAI 兼容接口地址 | 无 |
| `LLM_OCR_API_KEY` | `ocr-read` 使用的 API Key | 无 |
| `LLM_OCR_MODEL` | `ocr-read` 使用的视觉模型名 | 无 |

## 开发验证

以下命令在 Git 源码仓库目录执行。

```bash
# 不依赖云实例的单元测试
python3 -m unittest discover -s tests -p 'test_*.py'

# 真实浏览器 + 本地 HTML，验证粘贴入口和对话框提交
python3 tests/browser_dom_regression.py

# 大小写反转、控制台切换、修饰键释放及 Canvas 键盘事件回归
python3 tests/browser_case_regression.py

# 已登录的真实 VNC 会话：CPU、内存、磁盘、网络、日志管道、长脚本、特殊字符
python3 tests/live_scenarios.py --session vnc-test --mode fast --output-dir /tmp/vnc-results

# 已登录控制台且有 SSH 密钥访问：反复获取新 URL，通过 VNC 写脚本，SSH 只读核对源码哈希
python3 tests/live_roundtrip.py --session vnc-test --instance-id ins-xxxxxxxx \
  --region ap-guangzhou --ssh-host root@<instance-ip> --rounds 2

# 实机专项：需要 root SSH、Node.js 22+ 和两个未分配的 Linux VT（默认 48、49）
# 原生网页复现；结果记录中 reproduced_case_flip=true 表示成功复现
python3 tests/live_case_regression.py --instance-id ins-xxxxxxxx \
  --ssh-host root@<instance-ip> --native --rounds 2

# 相同场景验证修复；也可加 --canvas 验证回退路径
python3 tests/live_case_regression.py --instance-id ins-xxxxxxxx \
  --ssh-host root@<instance-ip> --rounds 5

# 已完成网页登录的 Workbench 会话：保留认证，在当前页面连接中核验
python3 tests/live_case_regression.py --instance-id ins-xxxxxxxx \
  --ssh-host root@<instance-ip> --session vnc-task \
  --workbench-url 'https://workbench.cloud.tencent.com/vnc?regionId=<region-id>&instanceId=ins-xxxxxxxx&source=cvm' \
  --rounds 2
```

`live_scenarios.py` 在独立 `/tmp/cvm-vnc-validation.*` 目录创建日志，并在最后清理；输出截图和 `run.json` 供核验。命令发送成功不等于远端执行成功，需要检查截图中的结果标记。长脚本包含 4320 字节特殊字符文本的 SHA-256 校验，完整输入约 7 KB，执行需要数分钟。真实 LLM OCR 测试另需配置 `LLM_OCR_*`。

`live_roundtrip.py` 默认交替使用 fast/stable 模式，每轮创建独立的 `/tmp/cvm-vnc-roundtrip-<uuid>.py` 及退出码标记，并在测试结束清理；完整脚本约 7 KB，通过 `results.json` 记录本地和远端源码 SHA-256、退出码。SSH 只读取哈希和完成状态（失败时保存文件用于比较），命令输入和脚本执行均通过 VNC 完成。测试会等待执行完成后再截图，避免将发送完成误认为执行完成。

`live_case_regression.py` 在未分配的空闲控制台中直接读取 TTY 输入字节，不依赖 OCR 或 shell 回显；结束后恢复原控制台、原始服务端锁定状态并释放测试 VT。原生网页可复现 `AaAbBzZ → aAaBbZz`，修复后的相同输入应逐字节一致。脚本不会使用已有的登录控制台作为测试接收器。

## Changelog

### v0.4.0

- 新增 `repair-keyboard` 和 MCP `vnc_repair_keyboard`，释放残留修饰键，并按需校正已确认的 Linux 控制台大小写反转。
- 为腾讯云临时 VNC 与 Workbench 页面加载共用键盘补丁，大写输入显式发送 Shift；Workbench CLI 默认采用已验证的 Canvas 逐键输入路径，规避原生批量粘贴的已知异常。
- 跟踪临时页面的实际粘贴队列并限制按键速率，改进连接就绪判断、失败处理和会话锁，避免部分输入后自动重发命令。
- 新增 `ocr-check`，仅输出本地配置状态；明确环境变量注入优先，可选 `.env` 不覆盖已有变量。
- 更新操作 skill 的 URL 获取、OCR 优先与 Agent 读图回退流程。

### v0.3.0

- **兼容 Workbench VNC 页面** — 同时支持腾讯云临时 VNC URL（`img.qcloud.com`）和 Workbench VNC 页面（`workbench.cloud.tencent.com`），两种模式均支持粘贴（快速）输入
- **修复粘贴模式选择器** — 远程命令对话框启动链接选择器从硬编码 `a.copyBtn` 改为通用选择器，适配不同页面变体
- **修复 Shift 符号字符输入** — Canvas 键盘输入路径中 `!@#$%^&*()` 等 Shift 组合符号不再丢失
- **重构粘贴命令模式** — 解决 canvas 渲染未完成就返回的问题，通过 canvas 指纹轮询等待稳定
- **新增 LLM OCR** — 通过 `cvm-vnc ocr-read` 调用 OpenAI 兼容视觉模型从图片提取文本

## License

MIT
