qqbrowser-skill 1.0.5__tar.gz

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.
@@ -0,0 +1,68 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ *.pyd
7
+
8
+ # Distribution / packaging
9
+ .Python
10
+ build/
11
+ dist/
12
+ *.egg-info/
13
+ *.egg
14
+ MANIFEST
15
+ wheels/
16
+ *.whl
17
+
18
+ # Virtual environments
19
+ .venv/
20
+ venv/
21
+ env/
22
+ ENV/
23
+
24
+ # uv
25
+ .uv/
26
+
27
+ # Testing
28
+ .tox/
29
+ .nox/
30
+ .coverage
31
+ .coverage.*
32
+ htmlcov/
33
+ .pytest_cache/
34
+ .cache/
35
+ nosetests.xml
36
+ coverage.xml
37
+ *.cover
38
+
39
+ # Type checking
40
+ .mypy_cache/
41
+ .dmypy.json
42
+ dmypy.json
43
+ .pytype/
44
+ .pyre/
45
+
46
+ # IDE / Editor
47
+ .idea/
48
+ .vscode/
49
+ *.swp
50
+ *.swo
51
+ *~
52
+ .DS_Store
53
+
54
+ # Logs
55
+ *.log
56
+ logs/
57
+
58
+ # Environment variables
59
+ .env
60
+ .env.*
61
+ !.env.example
62
+
63
+ # Jupyter Notebook
64
+ .ipynb_checkpoints/
65
+ *.ipynb
66
+
67
+ # pyproject build artifacts
68
+ *.dist-info/
@@ -0,0 +1,6 @@
1
+ Metadata-Version: 2.4
2
+ Name: qqbrowser-skill
3
+ Version: 1.0.5
4
+ Summary: QQ Browser Skill - Browser automation toolkit via WebSocket
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: websockets>=12.0
@@ -0,0 +1 @@
1
+ # QQ Browser Skill
@@ -0,0 +1,199 @@
1
+ ---
2
+ name: qqbrowser-skill
3
+ description: QQ Browser automation CLI for AI agents. Use when the user needs to interact with websites via QQ Browser, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, or automating any browser task. Triggers include requests to "open a website", "fill out a form", "click a button", "take a screenshot", "scrape data from a page", or any task requiring programmatic web interaction.
4
+ ---
5
+
6
+ # QQ Browser Automation with qqbrowser-skill
7
+
8
+ The CLI communicates with the QQ Browser extension through WebSocket. It auto-discovers the browser process and establishes a connection.
9
+
10
+ ## Installation
11
+ ```bash
12
+ # Install QQ Browser (requires root/sudo on Linux)
13
+ qqbrowser-skill install
14
+ ```
15
+
16
+ ## Start the Daemon Service
17
+
18
+ Before executing any browser skill, you must start the persistent daemon service.
19
+ The daemon maintains the WebSocket connection to the browser extension and accepts skill requests via HTTP RPC.
20
+
21
+ ```bash
22
+ # start in background (daemon) mode:
23
+ qqbrowser-skill serve --daemon
24
+
25
+ # Check daemon status:
26
+ qqbrowser-skill status
27
+ ```
28
+
29
+ ## Core Workflow
30
+
31
+ Every browser automation follows this pattern:
32
+
33
+ 1. **Navigate**: `qqbrowser-skill browser_go_to_url --url <url>`
34
+ 2. **Snapshot**: `qqbrowser-skill browser_snapshot` (get indexed element refs)
35
+ 3. **Interact**: Use element index to click, fill, select
36
+ 4. **Re-snapshot**: After navigation or DOM changes, get fresh refs
37
+
38
+ ```bash
39
+ qqbrowser-skill browser_go_to_url --url https://example.com/form
40
+ qqbrowser-skill browser_snapshot
41
+ # Output includes element indices: [1] input "email", [2] input "password", [3] button "Submit"
42
+
43
+ qqbrowser-skill browser_input_text --index 1 --text "user@example.com"
44
+ qqbrowser-skill browser_input_text --index 2 --text "password123"
45
+ qqbrowser-skill browser_click_element --index 3
46
+ qqbrowser-skill browser_wait --seconds 2
47
+ qqbrowser-skill browser_snapshot # Check result
48
+ ```
49
+
50
+ ## Essential Commands
51
+
52
+ ```bash
53
+ # Navigation
54
+ qqbrowser-skill browser_go_to_url --url <url> # Navigate to URL
55
+ qqbrowser-skill browser_go_back # Go back
56
+ qqbrowser-skill browser_wait --seconds 3 # Wait for page load (default 3s)
57
+
58
+ # Snapshot & Screenshot
59
+ qqbrowser-skill browser_snapshot # Get page content with element indices
60
+ qqbrowser-skill browser_screenshot # Take screenshot
61
+ qqbrowser-skill browser_screenshot --full # Full-page screenshot
62
+ qqbrowser-skill browser_screenshot --annotate # Annotated screenshot with element labels
63
+ qqbrowser-skill browser_markdownify # Convert page to markdown
64
+
65
+ # Click & Input (use indices from snapshot)
66
+ qqbrowser-skill browser_click_element --index 1 # Click element
67
+ qqbrowser-skill browser_dblclick_element --index 1 # Double-click element
68
+ qqbrowser-skill browser_focus_element --index 1 # Focus element
69
+ qqbrowser-skill browser_input_text --index 1 --text "hello" # Input text into element
70
+
71
+ # Scroll
72
+ qqbrowser-skill browser_scroll_down # Scroll down one page
73
+ qqbrowser-skill browser_scroll_down --amount 300 # Scroll down 300px
74
+ qqbrowser-skill browser_scroll_up # Scroll up one page
75
+ qqbrowser-skill browser_scroll_up --amount 300 # Scroll up 300px
76
+ qqbrowser-skill browser_scroll_to_text --text "Section 3" # Scroll to text
77
+ qqbrowser-skill browser_scroll_to_top # Scroll to top
78
+ qqbrowser-skill browser_scroll_to_bottom # Scroll to bottom
79
+ qqbrowser-skill browser_scroll_by --direction down --pixels 500 # Scroll page by direction
80
+ qqbrowser-skill browser_scroll_by --direction right --pixels 200 --index 3 # Scroll element by direction
81
+ qqbrowser-skill browser_scroll_into_view --index 5 # Scroll element into view
82
+
83
+ # Keyboard
84
+ qqbrowser-skill browser_keypress --key Enter # Press a key
85
+ qqbrowser-skill browser_keyboard_op --action type --text "hello" # Type text
86
+ qqbrowser-skill browser_keyboard_op --action inserttext --text "hello" # Insert text without key events
87
+ qqbrowser-skill browser_keydown --key Shift # Hold down a key
88
+ qqbrowser-skill browser_keyup --key Shift # Release a key
89
+
90
+ # Dropdown
91
+ qqbrowser-skill browser_get_dropdown_options --index 2 # Get dropdown options
92
+ qqbrowser-skill browser_select_dropdown_option --index 2 --text "Option A" # Select option
93
+
94
+ # Checkbox
95
+ qqbrowser-skill browser_check_op --index 4 --value # Check checkbox
96
+ qqbrowser-skill browser_check_op --index 4 # Uncheck checkbox (omit --value)
97
+
98
+ # Get Information
99
+ qqbrowser-skill browser_get_info --type text --index 1 # Get element text
100
+ qqbrowser-skill browser_get_info --type url # Get current URL
101
+ qqbrowser-skill browser_get_info --type title # Get page title
102
+ qqbrowser-skill browser_get_info --type html --index 1 # Get element HTML
103
+ qqbrowser-skill browser_get_info --type value --index 1 # Get element value
104
+ qqbrowser-skill browser_get_info --type attr --index 1 --attribute href # Get attribute
105
+ qqbrowser-skill browser_get_info --type count # Get element count
106
+ qqbrowser-skill browser_get_info --type box --index 1 # Get bounding box
107
+ qqbrowser-skill browser_get_info --type styles --index 1 # Get computed styles
108
+ qqbrowser-skill browser_check_state --state visible --index 1 # Check visibility
109
+ qqbrowser-skill browser_check_state --state enabled --index 1 # Check if enabled
110
+ qqbrowser-skill browser_check_state --state checked --index 1 # Check if checked
111
+
112
+ # Find and Act (semantic locators)
113
+ qqbrowser-skill browser_find_and_act --by role --value button --action click --name "Submit"
114
+ qqbrowser-skill browser_find_and_act --by text --value "Sign In" --action click
115
+ qqbrowser-skill browser_find_and_act --by label --value "Email" --action fill --actionValue "user@test.com"
116
+ qqbrowser-skill browser_find_and_act --by placeholder --value "Search" --action type --actionValue "query"
117
+ qqbrowser-skill browser_find_and_act --by testid --value "submit-btn" --action click
118
+
119
+ # Download
120
+ qqbrowser-skill browser_download_file --index 5 # Download file by clicking element
121
+ qqbrowser-skill browser_download_url # Download from URL
122
+
123
+ # Tab Management
124
+ qqbrowser-skill browser_tab_open --url <url> # Open URL in new tab
125
+ qqbrowser-skill browser_tab_list # List open tabs
126
+ qqbrowser-skill browser_tab_switch --tabId 2 # Switch to tab
127
+ qqbrowser-skill browser_tab_close --tabId 2 # Close tab
128
+
129
+ # Dialog
130
+ qqbrowser-skill browser_dialog --action accept # Accept dialog
131
+ qqbrowser-skill browser_dialog --action dismiss # Dismiss dialog
132
+ qqbrowser-skill browser_dialog --action accept --text "input text" # Accept prompt with text
133
+
134
+ # Task Completion
135
+ qqbrowser-skill browser_done --success --text "Task completed" # Mark task as done
136
+ qqbrowser-skill browser_done --text "Still in progress" # Mark task as incomplete
137
+
138
+ # Help
139
+ qqbrowser-skill list # List all available skills
140
+ qqbrowser-skill <skill_name> --help # Show help for a specific skill
141
+
142
+ # Daemon service management
143
+ qqbrowser-skill serve # Start daemon (foreground)
144
+ qqbrowser-skill serve --daemon # Start daemon (background)
145
+ qqbrowser-skill status # Check daemon status
146
+ qqbrowser-skill stop # Stop daemon
147
+
148
+ # Install dependencies
149
+ qqbrowser-skill install # Install QQ Browser
150
+ qqbrowser-skill install --dry-run # Preview installation
151
+ ```
152
+
153
+ ## Common Patterns
154
+
155
+ ### Form Submission
156
+
157
+ ```bash
158
+ qqbrowser-skill browser_go_to_url --url https://example.com/signup
159
+ qqbrowser-skill browser_snapshot
160
+ qqbrowser-skill browser_input_text --index 1 --text "Jane Doe"
161
+ qqbrowser-skill browser_input_text --index 2 --text "jane@example.com"
162
+ qqbrowser-skill browser_select_dropdown_option --index 3 --text "California"
163
+ qqbrowser-skill browser_check_op --index 4 --value
164
+ qqbrowser-skill browser_click_element --index 5
165
+ qqbrowser-skill browser_wait --seconds 2
166
+ qqbrowser-skill browser_snapshot # Verify result
167
+ ```
168
+
169
+ ### Data Extraction
170
+
171
+ ```bash
172
+ qqbrowser-skill browser_go_to_url --url https://example.com/products
173
+ qqbrowser-skill browser_snapshot
174
+ qqbrowser-skill browser_get_info --type text --index 5 # Get specific element text
175
+ qqbrowser-skill browser_markdownify # Get full page as markdown
176
+ ```
177
+
178
+ ### Infinite Scroll Pages
179
+
180
+ ```bash
181
+ qqbrowser-skill browser_go_to_url --url https://example.com/feed
182
+ qqbrowser-skill browser_scroll_to_bottom # Trigger lazy loading
183
+ qqbrowser-skill browser_wait --seconds 2 # Wait for content
184
+ qqbrowser-skill browser_snapshot # Get updated content
185
+ ```
186
+
187
+ ## Element Index Lifecycle (Important)
188
+
189
+ Element indices are invalidated when the page changes. Always re-snapshot after:
190
+
191
+ - Clicking links or buttons that navigate
192
+ - Form submissions
193
+ - Dynamic content loading (dropdowns, modals, AJAX)
194
+
195
+ ```bash
196
+ qqbrowser-skill browser_click_element --index 5 # May navigate to new page
197
+ qqbrowser-skill browser_snapshot # MUST re-snapshot
198
+ qqbrowser-skill browser_click_element --index 1 # Use new indices
199
+ ```
@@ -0,0 +1,52 @@
1
+ 工具名,描述,接口描述
2
+ browser_dblclick_element,双击元素,"{""type"":""object"",""properties"":{""index"":{""type"":""integer""}},""required"":[""index""]}"
3
+ browser_focus_element,聚焦元素,"{""type"":""object"",""properties"":{""index"":{""type"":""integer""}},""required"":[""index""]}"
4
+ browser_input_text,向元素输入文本(不清空原内容),"{""type"":""object"",""properties"":{""index"":{""type"":""integer""},""text"":{""type"":""string""}},""required"":[""index"",""text""]}"
5
+ browser_keypress,按下键盘按键,"{""type"":""object"",""properties"":{""key"":{""type"":""string""}},""required"":[""key""]}"
6
+ browser_keyboard_op,在当前焦点元素上执行键盘操作,"{""type"":""object"",""properties"":{""action"":{""type"":""string"",""enum"":[""type"",""inserttext""]},""text"":{""type"":""string""}},""required"":[""action"",""text""]}"
7
+ browser_keydown,按住某个键,"{""type"":""object"",""properties"":{""key"":{""type"":""string""}},""required"":[""key""]}"
8
+ browser_keyup,释放按住的键,"{""type"":""object"",""properties"":{""key"":{""type"":""string""}},""required"":[""key""]}"
9
+ browser_check_op,勾选/取消复选框,"{""type"":""object"",""properties"":{""index"":{""type"":""integer""},""value"":{""type"":""boolean""}},""required"":[""index"",""value""]}"
10
+ browser_scroll_by,滚动页面或指定元素,"{""type"":""object"",""properties"":{""direction"":{""type"":""string"",""enum"":[""up"",""down"",""left"",""right""]},""pixels"":{""type"":""integer""},""index"":{""type"":""integer""}},""required"":[""direction""]}"
11
+ browser_scroll_into_view,将元素滚动到可视区域,"{""type"":""object"",""properties"":{""index"":{""type"":""integer""}},""required"":[""index""]}"
12
+ browser_screenshot,对当前页面截图,"{""type"":""object"",""properties"":{""path"":{""type"":""string""},""full"":{""type"":""boolean""},""annotate"":{""type"":""boolean""}}}"
13
+ browser_get_info,获取页面或元素的信息,"{""type"":""object"",""properties"":{""type"":{""type"":""string"",""enum"":[""text"",""html"",""value"",""attr"",""title"",""url"",""count"",""box"",""styles""]},""index"":{""type"":""integer""},""attribute"":{""type"":""string""},""json"":{""type"":""boolean""}},""required"":[""type""]}"
14
+ browser_check_state,检查元素状态,"{""type"":""object"",""properties"":{""state"":{""type"":""string"",""enum"":[""visible"",""enabled"",""checked""]},""index"":{""type"":""integer""}},""required"":[""state"",""index""]}"
15
+ browser_find_and_act,使用语义定位器查找并操作元素,"{""type"":""object"",""properties"":{""by"":{""type"":""string"",""enum"":[""role"",""text"",""label"",""placeholder"",""alt"",""title"",""testid"",""first"",""last"",""nth""]},""value"":{""type"":""string""},""action"":{""type"":""string"",""enum"":[""click"",""fill"",""type"",""hover"",""focus"",""check"",""uncheck"",""text""]},""actionValue"":{""type"":""string""},""name"":{""type"":""string""},""exact"":{""type"":""boolean""},""nth"":{""type"":""integer""},""index"":{""type"":""integer""}},""required"":[""by"",""value"",""action""]}"
16
+ browser_tab_open,新标签中打开 url,"{
17
+ ""type"": ""object"",
18
+ ""properties"": {
19
+ ""url"": {
20
+ ""type"": ""string"",
21
+ ""description"": ""要打开的 URL 地址""
22
+ }
23
+ },
24
+ ""required"": [""url""]
25
+ }"
26
+ browser_tab_list,列出当前打开的tab列表,"{
27
+ ""type"": ""object"",
28
+ ""properties"": {},
29
+ ""required"": []
30
+ }"
31
+ browser_tab_close,关闭指定标签页,"{
32
+ ""type"": ""object"",
33
+ ""properties"": {
34
+ ""tabId"": {
35
+ ""type"": ""integer"",
36
+ ""description"": ""要关闭的标签页 ID""
37
+ }
38
+ },
39
+ ""required"": [""tabId""]
40
+ }"
41
+ browser_tab_switch,切换到指定标签页,"{
42
+ ""type"": ""object"",
43
+ ""properties"": {
44
+ ""tabId"": {
45
+ ""type"": ""integer"",
46
+ ""description"": ""要切换到的标签页 ID""
47
+ }
48
+ },
49
+ ""required"": [""tabId""]
50
+ }
51
+ "
52
+ browser_dialog,处理浏览器对话框(alert/confirm/prompt),"{""type"":""object"",""properties"":{""action"":{""type"":""string"",""enum"":[""accept"",""dismiss""]},""text"":{""type"":""string""}},""required"":[""action""]}"
@@ -0,0 +1 @@
1
+ qqbrowser-browser-stable --display-invisible-extension=true --disable-infobars --start-maximized --x5use-automatic-in-tab --no-first-run --no-crashed-bubble-view --disable-infobars --no-modal-dialogs --no-external-protocol-dialogs --no-sandbox
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env bash
2
+ # pack.sh
3
+ # Build the wheel package and create a distributable zip archive
4
+ # containing the .whl file and SKILL.md.
5
+ #
6
+ # Usage:
7
+ # bash pack.sh
8
+
9
+ set -euo pipefail
10
+
11
+ # ---------------------------------------------------------------------------
12
+ # Project root directory (where this script lives)
13
+ # ---------------------------------------------------------------------------
14
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
15
+ DIST_DIR="${SCRIPT_DIR}/dist"
16
+ SKILL_MD="${SCRIPT_DIR}/skill.md"
17
+
18
+ # ---------------------------------------------------------------------------
19
+ # Helpers
20
+ # ---------------------------------------------------------------------------
21
+ log_info() { echo "ℹ️ $*"; }
22
+ log_ok() { echo "✅ $*"; }
23
+ log_err() { echo "❌ $*" >&2; }
24
+
25
+ # ---------------------------------------------------------------------------
26
+ # Step 1: Build the wheel with uv
27
+ # ---------------------------------------------------------------------------
28
+ log_info "Building wheel package with uv ..."
29
+ cd "${SCRIPT_DIR}"
30
+ uv build
31
+ log_ok "Build complete."
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # Step 2: Locate the latest .whl file
35
+ # ---------------------------------------------------------------------------
36
+ WHL_FILE="$(ls -t "${DIST_DIR}"/*.whl 2>/dev/null | head -n 1 || true)"
37
+
38
+ if [[ -z "${WHL_FILE}" ]]; then
39
+ log_err "No .whl file found in ${DIST_DIR}."
40
+ exit 1
41
+ fi
42
+ log_ok "Found wheel: ${WHL_FILE}"
43
+
44
+ # ---------------------------------------------------------------------------
45
+ # Step 3: Verify SKILL.md exists
46
+ # ---------------------------------------------------------------------------
47
+ if [[ ! -f "${SKILL_MD}" ]]; then
48
+ log_err "SKILL.md not found at ${SKILL_MD}."
49
+ exit 1
50
+ fi
51
+ log_ok "Found SKILL.md: ${SKILL_MD}"
52
+
53
+ # ---------------------------------------------------------------------------
54
+ # Step 4: Create the zip archive
55
+ # ---------------------------------------------------------------------------
56
+ # Extract package name and version from the whl filename
57
+ # Wheel naming convention: {name}-{version}(-{build})-{python}-{abi}-{platform}.whl
58
+ WHL_BASENAME="$(basename "${WHL_FILE}")"
59
+ PKG_NAME="$(echo "${WHL_BASENAME}" | cut -d'-' -f1)"
60
+ PKG_VERSION="$(echo "${WHL_BASENAME}" | cut -d'-' -f2)"
61
+
62
+ ZIP_NAME="${PKG_NAME}-${PKG_VERSION}.zip"
63
+ ZIP_PATH="${DIST_DIR}/${ZIP_NAME}"
64
+
65
+ # Create a staging directory for the zip contents
66
+ STAGE_DIR="$(mktemp -d -t pack-stage.XXXXXX)"
67
+ trap 'rm -rf "${STAGE_DIR}"' EXIT
68
+
69
+ PACK_DIR="${STAGE_DIR}/${PKG_NAME}-${PKG_VERSION}"
70
+ mkdir -p "${PACK_DIR}"
71
+
72
+ cp -f "${WHL_FILE}" "${PACK_DIR}/"
73
+ cp -f "${SKILL_MD}" "${PACK_DIR}/SKILL.md"
74
+
75
+ log_info "Creating archive ${ZIP_NAME} ..."
76
+ (cd "${STAGE_DIR}" && zip -r "${ZIP_PATH}" "$(basename "${PACK_DIR}")")
77
+ log_ok "Archive created: ${ZIP_PATH}"
78
+
79
+ # ---------------------------------------------------------------------------
80
+ # Done
81
+ # ---------------------------------------------------------------------------
82
+ log_ok "Pack finished! Output: ${ZIP_PATH}"
83
+ echo ""
84
+ echo " 📦 ${ZIP_PATH}"
85
+ echo " Contents:"
86
+ echo " - $(basename "${WHL_FILE}")"
87
+ echo " - SKILL.md"
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env bash
2
+ # publish.sh
3
+ # 构建并上传 qqbrowser-skill 包到 PyPI
4
+ #
5
+ # 用法:
6
+ # bash publish.sh # 上传到正式 PyPI
7
+ # bash publish.sh --test # 上传到 TestPyPI
8
+ #
9
+ # 前置条件:
10
+ # 1. 安装 twine: pip install twine 或 uv pip install twine
11
+ # 2. 配置 PyPI 凭证(任选其一):
12
+ # - 设置环境变量 TWINE_USERNAME / TWINE_PASSWORD
13
+ # - 使用 API Token: TWINE_USERNAME=__token__ TWINE_PASSWORD=pypi-xxx...
14
+ # - 配置 ~/.pypirc 文件
15
+
16
+ set -euo pipefail
17
+
18
+
19
+ export TWINE_USERNAME="__token__"
20
+ export TWINE_PASSWORD="pypi-AgEIcHlwaS5vcmcCJDZkNDRmNWMyLWVkY2ItNDY0NS04ZjE3LTMxZTI0ZGJkNTFhMwACKlszLCI0OGE0YjYwYi1jNTU2LTQwNjgtYjE3ZC05Y2NhYTczOWM0MjMiXQAABiBZDbXYti8K11nsLdCADCNDQ2pO_3M-hzwwunswol6lkA"
21
+
22
+ # ---------------------------------------------------------------------------
23
+ # 项目根目录
24
+ # ---------------------------------------------------------------------------
25
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
26
+ DIST_DIR="${SCRIPT_DIR}/dist"
27
+
28
+ # ---------------------------------------------------------------------------
29
+ # 解析参数
30
+ # ---------------------------------------------------------------------------
31
+ USE_TEST_PYPI=false
32
+
33
+ for arg in "$@"; do
34
+ case "${arg}" in
35
+ --test)
36
+ USE_TEST_PYPI=true
37
+ ;;
38
+ *)
39
+ echo "❌ 未知参数: ${arg}"
40
+ echo "用法: bash publish.sh [--test]"
41
+ exit 1
42
+ ;;
43
+ esac
44
+ done
45
+
46
+ # ---------------------------------------------------------------------------
47
+ # 辅助函数
48
+ # ---------------------------------------------------------------------------
49
+ log_info() { echo "ℹ️ $*"; }
50
+ log_ok() { echo "✅ $*"; }
51
+ log_err() { echo "❌ $*" >&2; }
52
+
53
+ # ---------------------------------------------------------------------------
54
+ # Step 1: 检查依赖工具
55
+ # ---------------------------------------------------------------------------
56
+ if ! command -v uv &>/dev/null; then
57
+ log_err "未找到 uv,请先安装: https://docs.astral.sh/uv/"
58
+ exit 1
59
+ fi
60
+
61
+ if ! command -v twine &>/dev/null; then
62
+ log_info "未找到 twine,尝试自动安装..."
63
+ uv pip install twine
64
+ if ! command -v twine &>/dev/null; then
65
+ log_err "twine 安装失败,请手动安装: pip install twine"
66
+ exit 1
67
+ fi
68
+ log_ok "twine 安装成功"
69
+ fi
70
+
71
+ # ---------------------------------------------------------------------------
72
+ # Step 2: 清理旧的构建产物
73
+ # ---------------------------------------------------------------------------
74
+ log_info "清理旧的构建产物..."
75
+ rm -rf "${DIST_DIR}"
76
+ log_ok "清理完成"
77
+
78
+ # ---------------------------------------------------------------------------
79
+ # Step 3: 构建包(sdist + wheel)
80
+ # ---------------------------------------------------------------------------
81
+ log_info "开始构建包..."
82
+ cd "${SCRIPT_DIR}"
83
+ uv build
84
+ log_ok "构建完成"
85
+
86
+ # ---------------------------------------------------------------------------
87
+ # Step 4: 检查构建产物
88
+ # ---------------------------------------------------------------------------
89
+ if [ -z "$(ls -A "${DIST_DIR}" 2>/dev/null)" ]; then
90
+ log_err "构建失败,${DIST_DIR} 目录为空"
91
+ exit 1
92
+ fi
93
+
94
+ log_info "构建产物:"
95
+ ls -lh "${DIST_DIR}/"
96
+
97
+ # ---------------------------------------------------------------------------
98
+ # Step 5: 使用 twine 检查包的合法性
99
+ # ---------------------------------------------------------------------------
100
+ log_info "检查包的合法性..."
101
+ twine check "${DIST_DIR}"/*
102
+ log_ok "包检查通过"
103
+
104
+ # ---------------------------------------------------------------------------
105
+ # Step 6: 上传到 PyPI
106
+ # ---------------------------------------------------------------------------
107
+ if [ "${USE_TEST_PYPI}" = true ]; then
108
+ log_info "上传到 TestPyPI..."
109
+ twine upload --repository testpypi "${DIST_DIR}"/*
110
+ log_ok "上传到 TestPyPI 成功!"
111
+ echo ""
112
+ echo " 🔗 查看: https://test.pypi.org/project/qqbrowser-skill/"
113
+ echo " 📦 安装: pip install --index-url https://test.pypi.org/simple/ qqbrowser-skill"
114
+ else
115
+ log_info "上传到 PyPI..."
116
+ twine upload "${DIST_DIR}"/*
117
+ log_ok "上传到 PyPI 成功!"
118
+ echo ""
119
+ echo " 🔗 查看: https://pypi.org/project/qqbrowser-skill/"
120
+ echo " 📦 安装: pip install qqbrowser-skill"
121
+ fi
122
+
123
+ echo ""
124
+ log_ok "发布完成!"
@@ -0,0 +1,18 @@
1
+ [project]
2
+ name = "qqbrowser-skill"
3
+ version = "1.0.5"
4
+ description = "QQ Browser Skill - Browser automation toolkit via WebSocket"
5
+ requires-python = ">=3.10"
6
+ dependencies = [
7
+ "websockets>=12.0",
8
+ ]
9
+
10
+ [project.scripts]
11
+ qqbrowser-skill = "x5use.server:main"
12
+
13
+ [build-system]
14
+ requires = ["hatchling"]
15
+ build-backend = "hatchling.build"
16
+
17
+ [tool.hatch.build.targets.wheel]
18
+ packages = ["src/x5use"]
@@ -0,0 +1,103 @@
1
+ #!/bin/sh
2
+ # qqbrowser_skill_install.sh
3
+ # Download, extract, and install the qqbrowser-skill package from a zip archive.
4
+ #
5
+ # Usage:
6
+ # bash qqbrowser_skill_install.sh <zip_name>
7
+ #
8
+ # Example:
9
+ # bash qqbrowser_skill_install.sh qqbrowser_skill_v1.0.0.zip
10
+
11
+
12
+ # ---------------------------------------------------------------------------
13
+ # Constants
14
+ # ---------------------------------------------------------------------------
15
+ BASE_URL="https://dldir1v6.qq.com/invc/tt/QB/Public/ubuntu_qb"
16
+ SKILL_DIR="$HOME/.openclaw/workspace/skills/qqbrowser-skill"
17
+
18
+ # ---------------------------------------------------------------------------
19
+ # Helpers
20
+ # ---------------------------------------------------------------------------
21
+ log_info() { echo "ℹ️ $*"; }
22
+ log_ok() { echo "✅ $*"; }
23
+ log_err() { echo "❌ $*" >&2; }
24
+
25
+ cleanup() {
26
+ if [ -n "${TMP_DIR:-}" ] && [ -d "${TMP_DIR}" ]; then
27
+ rm -rf "${TMP_DIR}"
28
+ log_info "Cleaned up temporary directory."
29
+ fi
30
+ }
31
+ trap cleanup EXIT
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # Argument validation
35
+ # ---------------------------------------------------------------------------
36
+ if [ $# -lt 1 ]; then
37
+ log_err "Missing required argument: <zip_name>"
38
+ echo "Usage: $0 <zip_name>"
39
+ echo "Example: $0 qqbrowser_skill_v1.0.0.zip"
40
+ exit 1
41
+ fi
42
+
43
+ ZIP_NAME="$1"
44
+ DOWNLOAD_URL="${BASE_URL}/${ZIP_NAME}"
45
+
46
+ # ---------------------------------------------------------------------------
47
+ # Step 1: Download the zip file
48
+ # ---------------------------------------------------------------------------
49
+ TMP_DIR="$(mktemp -d -t qqbrowser-skill-install.XXXXXX)"
50
+ ZIP_PATH="${TMP_DIR}/${ZIP_NAME}"
51
+
52
+ log_info "Downloading ${DOWNLOAD_URL} ..."
53
+ if command -v curl >/dev/null 2>&1; then
54
+ curl -fSL -o "${ZIP_PATH}" "${DOWNLOAD_URL}"
55
+ elif command -v wget >/dev/null 2>&1; then
56
+ wget -q -O "${ZIP_PATH}" "${DOWNLOAD_URL}"
57
+ else
58
+ log_err "Neither curl nor wget found. Please install one of them first."
59
+ exit 1
60
+ fi
61
+ log_ok "Download complete: ${ZIP_PATH}"
62
+
63
+ # ---------------------------------------------------------------------------
64
+ # Step 2: Extract the zip file
65
+ # ---------------------------------------------------------------------------
66
+ EXTRACT_DIR="${TMP_DIR}/extracted"
67
+ mkdir -p "${EXTRACT_DIR}"
68
+
69
+ log_info "Extracting ${ZIP_NAME} ..."
70
+ unzip -o -q "${ZIP_PATH}" -d "${EXTRACT_DIR}"
71
+ log_ok "Extraction complete."
72
+
73
+ # ---------------------------------------------------------------------------
74
+ # Step 3: Find and install the .whl file
75
+ # ---------------------------------------------------------------------------
76
+ WHL_FILE="$(find "${EXTRACT_DIR}" -name '*.whl' -type f | head -n 1)"
77
+ if [ -z "${WHL_FILE}" ]; then
78
+ log_err "No .whl file found in the archive."
79
+ exit 1
80
+ fi
81
+
82
+ log_info "Installing ${WHL_FILE} ..."
83
+ pip3 install --break-system-packages --ignore-installed "${WHL_FILE}"
84
+ log_ok "Wheel package installed successfully."
85
+
86
+ # ---------------------------------------------------------------------------
87
+ # Step 4: Copy SKILL.md to the target directory
88
+ # ---------------------------------------------------------------------------
89
+ SKILL_MD="$(find "${EXTRACT_DIR}" -name 'SKILL.md' -type f | head -n 1)"
90
+ if [ -z "${SKILL_MD}" ]; then
91
+ log_err "No SKILL.md file found in the archive."
92
+ exit 1
93
+ fi
94
+
95
+ log_info "Copying SKILL.md to ${SKILL_DIR} ..."
96
+ mkdir -p "${SKILL_DIR}"
97
+ cp -f "${SKILL_MD}" "${SKILL_DIR}/SKILL.md"
98
+ log_ok "SKILL.md copied to ${SKILL_DIR}/SKILL.md"
99
+
100
+ # ---------------------------------------------------------------------------
101
+ # Done
102
+ # ---------------------------------------------------------------------------
103
+ log_ok "qqbrowser-skill installation finished!"