Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,25 @@ on:
branches: [main]

jobs:
plugin-packages:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- run: python tests/test_plugin_packages.py
- name: Verify independent MCP Skill ZIP
if: runner.os != 'Windows'
run: |
./scripts/package-skill.sh lexmount-browser-mcp
unzip -t dist/lexmount-browser-mcp.zip
unzip -Z1 dist/lexmount-browser-mcp.zip | grep -qx SKILL.md
! unzip -Z1 dist/lexmount-browser-mcp.zip | grep -Eq '^(bin|scripts)/'

test:
runs-on: ubuntu-latest
steps:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
/dist/
/skills/lexmount-browser/bin/
*.tmp
__pycache__/
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ Native Rust SDK and command-line client for Lexmount cloud browsers. The binary
named `browser-cli` so existing agent instructions can migrate without changing
their command prefix.

## Agent integrations

See the [integration index](plugins/README.md) for shared CLI/MCP Skills,
client package sources, build commands, review links and release boundaries.
The existing `skills/lexmount-browser` path and default Skill ZIP are unchanged.

## Build

```bash
Expand Down
100 changes: 100 additions & 0 deletions plugins/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# 云浏览器接入索引

本仓库统一维护 browser-cli、共享 Skill 和客户端包装。按调用方式区分
CLI 与远程 MCP;同一种 Skill 不再按客户端复制维护。

本次是源码归档与候选包构建,**不更新已安装插件,不替换市场包,不发布服务**。
下表是 2026-09-22 整理时的来源记录,不代表实时审核状态。

## 目录与修改入口

```text
skills/
lexmount-browser/ CLI Skill,保留既有路径
lexmount-browser-mcp/ 远程 MCP Skill,不调用 CLI
plugins/
assets/ Codex 包共享图标
codex/cli/lexmount-cloud-browser/ CLI 插件清单模板
codex/mcp/lexmount-cloud-browser/ MCP 插件清单与连接配置模板
claude/lexmount-cloud-browser/ Claude Code 清单模板
scripts/
package-skill.sh 独立 Skill ZIP
package-plugins.py 从共享 Skill 生成客户端候选包
```

修改操作说明、认证流程、脚本时改 `skills/`;客户端展示信息与连接声明改
`plugins/`。模板目录没有重复的 Skill,**不能直接作为完整插件安装**,应先打包。
生成包的根目录均为 `lexmount-cloud-browser/`,内部包含清单、共享 Skill 的完整副本
及所需资源,不依赖源码仓库中的相对外部路径或符号链接。

## 各客户端来源与接入边界

| 客户端 / 产物 | 源码或审核入口 | 本次处理 / 后续条件 |
| --- | --- | --- |
| WorkBuddy Skill | [CLI Skill](../skills/lexmount-browser) | 已上架版本保持不变;后续改动从共享 CLI Skill 产生新候选包。 |
| Codex CLI 插件 | [清单](codex/cli/lexmount-cloud-browser/.codex-plugin/plugin.json) · [商店](https://chatgpt.com/plugins/plugins_6aab7b0e7e2481919910e41aa1181a0a) | 汇入 1.1.20 的包装信息;改为共享主线 Skill 后使用 `1.1.21-dev.1`,不是市场原包的字节级副本,须重新验收后发布。 |
| Codex MCP 插件 | [清单与 MCP 配置](codex/mcp/lexmount-cloud-browser) · [MCP Skill](../skills/lexmount-browser-mcp) | 从 `1.2.0-beta.2` 整理为 `1.2.0-beta.2.dev.1` 候选;Skill 标识明确为 `lexmount-browser-mcp`。仍需服务上线与宿主 OAuth、工具调用、接管验收。 |
| OpenClaw 插件 | [PR #33](https://github.com/lexmount/browser-cli-rs/pull/33) · [固定源码](https://github.com/lexmount/browser-cli-rs/tree/6650e9cf29ab5f18d5e74b90e6e5d4cea62cc8cd) · [商店](https://clawhub.ai/tristanisk/plugins/lexmount-cloud-browser) | #33 核对时仍 Open,保留独立审核,不重复导入其安全修复。其 `package.json`、`openclaw.plugin.json` 位于仓库根,入口在 `plugins/openclaw/`,直接引用共享 CLI Skill;合并后沿用该布局。 |
| OpenClaw Skill | [商店](https://clawhub.ai/tristanisk/skills/lexmount-cloud-browser) | 独立 Skill 与原生插件是两种产物;本次不更新既有市场版本,也不迁移其许可承诺。 |
| Hermes Skill | [上游 PR #114031](https://github.com/NousResearch/hermes-agent/pull/114031) | 上游维护 `optional-skills/research/lexmount-cloud-browser`;后续同步共享 CLI Skill,并遵循上游格式要求。该链接表示提交入口,不表示已合并。 |
| Claude Code 插件 | [清单](claude/lexmount-cloud-browser/.claude-plugin/plugin.json) | 汇入原 CLI 包装,接共享 Skill 后版本为 `1.1.18-rc.2`;客户端验收暂停,未作为可发布结果。目标是 Claude Code。 |
| 豆包 Skill | [开发者后台](https://developer.doubao.com/dashboard) | 可准备独立 CLI Skill 包;先前登录后要求企业邀请码,尚未完成市场提交。后台准入与本地安装分开核对。 |
| 千问 Skill | [开放平台](https://open.qianwen.com/home) | 可准备独立 CLI Skill 包;先前页面标注 Skill 接入“即将开放”,未完成公开渠道提交;不能把 Agent 入驻视为 Skill 提审。 |
| DeepSeek Harness 插件 | [lexmount/dsh-browser](https://github.com/lexmount/dsh-browser) | 已有独立仓库,保留原生插件源码与发布流程;本索引聚合入口,不复制一套实现。 |

## 构建与校验

macOS / Linux 上构建独立 Skill(需要 `sh`、`zip`):

```sh
./scripts/package-skill.sh
./scripts/package-skill.sh lexmount-browser-mcp
```

依次输出 `dist/lexmount-browser.zip` 与 `dist/lexmount-browser-mcp.zip`。
两个 ZIP 均以 `SKILL.md` 为根,不含 CLI 二进制或本机凭据。

构建客户端候选包(开发环境 Python 3.9+ 标准库;不是插件运行时依赖):

```sh
python3 scripts/package-plugins.py codex-cli
python3 scripts/package-plugins.py codex-mcp
python3 scripts/package-plugins.py claude-cli
python3 tests/test_plugin_packages.py
```

Windows 使用 `py -3` 替代 `python3`。每个 ZIP 旁附 `.zip.sha256`。
包内 `skills/lexmount-browser/` 或 `skills/lexmount-browser-mcp/` 与共享源码逐字节一致,
构建时排除 `bin/`、缓存与符号链接。固定 ZIP 顺序、时间与文件权限便于复核。
本地固定 Python/zlib 环境中的重复打包应字节相同;不同压缩库版本不保证 ZIP 哈希一致。

CLI 与 MCP 两种 Codex 包沿用同一个插件标识,**是替代版本,不应同时安装**。
切换前保留旧版本来源并在隔离开发安装中验收,不自动覆盖市场版。
CLI Skill 保留主线平台支持范围;存在 Linux CLI 二进制不代表当前主线 Skill 的
Linux 引导已完成,相关扩展见 [PR #27](https://github.com/lexmount/browser-cli-rs/pull/27)。

## MCP 服务与发布依赖

远程入口:`https://browser.lexmount.cn/chatgpt-app/mcp`。
`.mcp.json` 只含服务地址与 OAuth resource;不写 client secret、Token、
固定 client_id、猜测的回调地址或本地测试回调。
宿主负责发起授权及保存凭据;固定客户端需登记宿主实际回调,CIMD 路径需独立核验。

| 依赖 | 代码审核入口 |
| --- | --- |
| OAuth 应用与回调配置入口 | [lex-home #352(内部仓库)](https://code.lexmount.net/feixiang/lex-home/pulls/352) |
| MCP 服务及内嵌接管契约 | [lexmount-nodejs-backend #447](https://github.com/lexmount/lexmount-nodejs-backend/pull/447) |
| MCP 测试客户端 | [mcp-test-client #18](https://github.com/lexmount/mcp-test-client/pull/18) |

这些依赖由各自 PR 审核与部署。本仓库合并不等于服务已部署,也不等于客户端已通过验收。
MCP Skill 的 `browser_control`、`browser_view` 与 App 内部 `browser_handoff` 依赖该候选契约。
发布前先按部署版本复核真实工具 Schema,再验证首次授权、网页读取、填写后读回、
登录接管后继续操作及会话清理;保留实际客户端截图。格式与打包检查只能证明包可构建。

## 来源与许可

- Codex CLI 包装来源:2026-09-21 的 1.1.20 修订包;本次使用主线 CLI Skill,旧市场包不变。
- MCP Skill、配置、图标和包装来源:2026-09-22 的 1.2.0-beta.2 开发候选;只调整 Skill 标识与对应默认提示,不宣称部署或验收完成。
- Claude 包装来源:2026-09-16 的 1.1.18-rc.1 CLI 候选;本次仅整理包装并接共享源码。
- 本仓库与新生成包带 [MIT LICENSE](../LICENSE)。ClawHub 独立 Skill 的 MIT-0 承诺仅针对当时提交的 Skill 文件,不能推广到托管浏览器服务。服务使用继续受服务条款约束。
- 不收集本机凭据、客户资料、历史聊天或原始授权日志;不将旧 ZIP、安装缓存和二进制作为第二份源码长期维护。
Binary file added plugins/assets/lexmount-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions plugins/claude/lexmount-cloud-browser/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "lexmount-cloud-browser",
"version": "1.1.18-rc.2",
"description": "通过 Skill 和 browser-cli 使用 LexMount 云浏览器读取网页、填写表单和截图。",
"author": {
"name": "LexMount"
},
"keywords": [
"browser",
"cloud-browser",
"skill",
"cli"
]
}
29 changes: 29 additions & 0 deletions plugins/codex/cli/lexmount-cloud-browser/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "lexmount-cloud-browser",
"version": "1.1.21-dev.1",
"description": "使用 LexMount 云端浏览器完成网页浏览、信息提取、点击交互、表单填写及登录后的自动化任务。",
"author": {
"name": "LexMount"
},
"skills": "./skills/",
"interface": {
"displayName": "LexMount 云浏览器",
"shortDescription": "云端浏览器自动化:浏览、提取与交互",
"longDescription": "通过 LexMount 云端浏览器浏览网站、提取信息、点击交互、填写并核对表单,在用户授权后处理已登录的网站;按需截图、导出 PDF 或下载文件。根据用户目标交付信息、操作结果或文件,完成后清理临时会话。首次使用需要 LexMount 账号及服务授权,网站登录单独完成。",
"developerName": "LexMount",
"category": "Productivity",
"capabilities": [
"Read",
"Write"
],
"defaultPrompt": [
"打开 https://example.com,告诉我页面的主要内容,并列出页面上的链接。",
"打开 https://www.selenium.dev/selenium/web/web-form.html,把 Text input 填成“浏览器测试”,确认填写正确,不要提交。"
],
"composerIcon": "./assets/lexmount-icon.png",
"logo": "./assets/lexmount-icon.png",
"websiteURL": "https://browser.lexmount.cn/",
"privacyPolicyURL": "https://browser.lexmount.cn/privacy",
"termsOfServiceURL": "https://browser.lexmount.cn/terms"
}
}
30 changes: 30 additions & 0 deletions plugins/codex/mcp/lexmount-cloud-browser/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "lexmount-cloud-browser",
"version": "1.2.0-beta.2.dev.1",
"description": "使用 LexMount 云端浏览器完成网页浏览、信息提取、点击交互、表单填写及登录后的自动化任务。",
"author": {
"name": "LexMount"
},
"skills": "./skills/",
"interface": {
"displayName": "LexMount 云浏览器",
"shortDescription": "云端浏览器自动化:浏览、提取与交互",
"longDescription": "通过 LexMount 云端浏览器浏览网站、提取信息、点击交互及填写并核对表单;按需截图、导出 PDF 和下载文件。首次使用通过宿主提供的网页授权连接 LexMount,网站自身的登录单独完成。根据用户目标交付结果,完成后清理临时会话。内嵌浏览器视图取决于宿主支持。",
"developerName": "LexMount",
"category": "Productivity",
"capabilities": [
"Read",
"Write"
],
"defaultPrompt": [
"打开 https://example.com,告诉我页面的主要内容,并列出页面上的链接。",
"打开 https://www.selenium.dev/selenium/web/web-form.html,把 Text input 填成“浏览器测试”,确认填写正确,不要提交。"
],
"composerIcon": "./assets/lexmount-icon.png",
"logo": "./assets/lexmount-icon.png",
"websiteURL": "https://browser.lexmount.cn/",
"privacyPolicyURL": "https://browser.lexmount.cn/privacy",
"termsOfServiceURL": "https://browser.lexmount.cn/terms"
},
"mcpServers": "./.mcp.json"
}
9 changes: 9 additions & 0 deletions plugins/codex/mcp/lexmount-cloud-browser/.mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"mcpServers": {
"lexmount-cloud-browser": {
"type": "http",
"url": "https://browser.lexmount.cn/chatgpt-app/mcp",
"oauth_resource": "https://browser.lexmount.cn/chatgpt-app/mcp"
}
}
}
67 changes: 67 additions & 0 deletions scripts/package-plugins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env python3
"""Build client candidates from shared Skills; Python is build-time only."""
import argparse
import hashlib
import json
from pathlib import Path
import re
import zipfile

ROOT = Path(__file__).resolve().parents[1]
PROFILES = {
"codex-cli": ("codex/cli", ".codex-plugin", "lexmount-browser"),
"codex-mcp": ("codex/mcp", ".codex-plugin", "lexmount-browser-mcp"),
"claude-cli": ("claude", ".claude-plugin", "lexmount-browser"),
}


def source_files(directory):
for path in sorted(directory.rglob("*")):
relative = path.relative_to(directory)
if any(part in {"bin", ".DS_Store", "__pycache__"} for part in relative.parts):
continue
if path.is_symlink():
raise ValueError(f"Symlink cannot be packaged: {path}")
if path.is_file():
yield relative.as_posix(), path.read_bytes()


def build(profile, output):
template, manifest_dir, skill = PROFILES[profile]
name = "lexmount-cloud-browser"
files = dict(source_files(ROOT / "plugins" / template / name))
manifest = json.loads(files[f"{manifest_dir}/plugin.json"])
version = manifest["version"]
if manifest["name"] != name or not re.fullmatch(r"[0-9A-Za-z][0-9A-Za-z.+-]*", version):
raise ValueError("Invalid plugin name or version")
files.update((f"skills/{skill}/{path}", data)
for path, data in source_files(ROOT / "skills" / skill))
files["LICENSE"] = (ROOT / "LICENSE").read_bytes()
if manifest_dir == ".codex-plugin":
files["assets/lexmount-icon.png"] = (ROOT / "plugins/assets/lexmount-icon.png").read_bytes()
if profile == "codex-mcp":
if manifest.get("mcpServers") != "./.mcp.json" or ".mcp.json" not in files:
raise ValueError("Remote MCP manifest must reference its bundled config")
elif "mcpServers" in manifest or ".mcp.json" in files:
raise ValueError("CLI profile must not acquire an MCP connection")

output.mkdir(parents=True, exist_ok=True)
archive = output / f"{name}-{profile}-{version}.zip"
with zipfile.ZipFile(archive, "w", compression=zipfile.ZIP_DEFLATED) as bundle:
for path, data in sorted(files.items()):
entry = zipfile.ZipInfo(f"{name}/{path}", (1980, 1, 1, 0, 0, 0))
entry.create_system = 3
entry.external_attr = (0o100755 if path.endswith((".sh", ".ps1")) else 0o100644) << 16
entry.compress_type = zipfile.ZIP_DEFLATED
bundle.writestr(entry, data)
digest = hashlib.sha256(archive.read_bytes()).hexdigest()
archive.with_suffix(".zip.sha256").write_text(f"{digest} {archive.name}\n", encoding="utf-8")
return archive


if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("profile", choices=PROFILES)
parser.add_argument("--output", type=Path, default=ROOT / "dist")
args = parser.parse_args()
print(build(args.profile, args.output))
17 changes: 12 additions & 5 deletions scripts/package-skill.sh
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
#!/bin/sh
set -eu
repo_dir="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)"
skill_dir="$repo_dir/skills/lexmount-browser"
skill_name="${1:-lexmount-browser}"
case "$skill_name" in
lexmount-browser|lexmount-browser-mcp) ;;
*) echo 'Usage: package-skill.sh [lexmount-browser|lexmount-browser-mcp]' >&2; exit 2 ;;
esac
skill_dir="$repo_dir/skills/$skill_name"
dist_dir="$repo_dir/dist"
staging_dir="$(mktemp -d)"
trap 'rm -rf "$staging_dir"' EXIT INT TERM
mkdir -p "$dist_dir"
rm -f "$dist_dir/lexmount-browser.zip"
rm -f "$dist_dir/$skill_name.zip"

(
cd "$skill_dir"
Expand All @@ -20,12 +25,14 @@ rm -f "$dist_dir/lexmount-browser.zip"

find "$staging_dir" -type d -exec chmod 0755 {} +
find "$staging_dir" -type f -exec chmod 0644 {} +
find "$staging_dir/scripts" -type f \( -name '*.sh' -o -name '*.ps1' \) -exec chmod 0755 {} +
if [ -d "$staging_dir/scripts" ]; then
find "$staging_dir/scripts" -type f \( -name '*.sh' -o -name '*.ps1' \) -exec chmod 0755 {} +
fi
find "$staging_dir" -exec touch -t 198001010000 {} +

(
cd "$staging_dir"
find . -type f -print | LC_ALL=C sort | sed 's|^\./||' |
zip -X -9 -q "$dist_dir/lexmount-browser.zip" -@
zip -X -9 -q "$dist_dir/$skill_name.zip" -@
)
echo "$dist_dir/lexmount-browser.zip"
echo "$dist_dir/$skill_name.zip"
Loading
Loading