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.
- qqbrowser_skill-1.0.5/.gitignore +68 -0
- qqbrowser_skill-1.0.5/PKG-INFO +6 -0
- qqbrowser_skill-1.0.5/README.md +1 -0
- qqbrowser_skill-1.0.5/SKILL.md +199 -0
- qqbrowser_skill-1.0.5/funciton_list.csv +52 -0
- qqbrowser_skill-1.0.5/launch_qb_non_header.sh +1 -0
- qqbrowser_skill-1.0.5/pack.sh +87 -0
- qqbrowser_skill-1.0.5/publish.sh +124 -0
- qqbrowser_skill-1.0.5/pyproject.toml +18 -0
- qqbrowser_skill-1.0.5/qqbrowser_skill_install.sh +103 -0
- qqbrowser_skill-1.0.5/src/x5use/__init__.py +26 -0
- qqbrowser_skill-1.0.5/src/x5use/constants.py +58 -0
- qqbrowser_skill-1.0.5/src/x5use/daemon_server.py +560 -0
- qqbrowser_skill-1.0.5/src/x5use/installer.py +296 -0
- qqbrowser_skill-1.0.5/src/x5use/rpc_client.py +194 -0
- qqbrowser_skill-1.0.5/src/x5use/server.py +405 -0
- qqbrowser_skill-1.0.5/src/x5use/skill_registry.py +535 -0
- qqbrowser_skill-1.0.5/src/x5use/vnc_proxy.py +36 -0
- qqbrowser_skill-1.0.5/src/x5use/vnc_util.py +259 -0
- qqbrowser_skill-1.0.5/src/x5use/websocket_manager.py +359 -0
- qqbrowser_skill-1.0.5/test_client.py +192 -0
- qqbrowser_skill-1.0.5/uv.lock +82 -0
|
@@ -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 @@
|
|
|
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!"
|