crawlpro-win 1.0.0

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.
package/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Proprietary License
2
+
3
+ Copyright (c) 2026 Xi'an Wenyao Network Information Technology Co., Ltd.
4
+ All rights reserved.
5
+
6
+ CrawlPro and its accompanying software, binaries, documentation, and other
7
+ materials are proprietary and confidential. Possession of a copy does not
8
+ grant ownership or any intellectual-property rights.
9
+
10
+ Use of CrawlPro is permitted only under a valid license or written agreement
11
+ issued by Xi'an Wenyao Network Information Technology Co., Ltd. Except where
12
+ that license or agreement expressly permits it, copying, modification,
13
+ distribution, sublicensing, sale, reverse engineering, decompilation, or
14
+ creation of derivative works is prohibited.
15
+
16
+ Third-party components remain subject to their respective license terms.
17
+
18
+ For licensing enquiries, contact Xi'an Wenyao Network Information Technology
19
+ Co., Ltd. through https://www.wenyaotech.com/.
package/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # CrawlPro
2
+
3
+ English | Simplified Chinese is included as `README_CN.md` in the npm package.
4
+
5
+ CrawlPro is a proprietary Agent Skill for finding and organizing publicly
6
+ visible Douyin customer signals. It supports keyword-based collection, customer
7
+ workbooks, assisted follow-up, user-authorized replies, and Excel/HTML analysis.
8
+
9
+ Developed by **Xi'an Wenyao Network Information Technology Co., Ltd.**
10
+
11
+ [Company Website](https://www.wenyaotech.com/)
12
+
13
+ ## Main Capabilities
14
+
15
+ - Collect public videos, comments, and profiles by keyword.
16
+ - Filter comments into customer-intent levels and export customer workbooks.
17
+ - Draft replies or send them only after explicit user authorization.
18
+ - Support human-confirmed assisted replies and follow-up status updates.
19
+ - Generate cleaned Excel workbooks and HTML customer reports.
20
+ - Keep keyword catalogs and runtime settings local to the user's computer.
21
+
22
+ ## Supported Platforms
23
+
24
+ - macOS on Apple Silicon (`arm64`)
25
+ - Windows 10 or 11 (`x64`)
26
+
27
+ Intel Mac, Windows ARM64, and Linux are not currently supported.
28
+
29
+ ## Requirements
30
+
31
+ - Node.js 18 or later and npm 9 or later for installation
32
+ - Hermes, OpenClaw, Claude Code, or Codex
33
+ - Microsoft Edge or Google Chrome; Safari is not supported
34
+ - A local MongoDB or Redis instance for keyword catalogs
35
+ - A valid CrawlPro License Key
36
+
37
+ Python is not required when using the packaged executable.
38
+
39
+ ## Install
40
+
41
+ ```bash
42
+ npx --yes crawlpro
43
+ ```
44
+
45
+ The unified installer detects the operating system and architecture, installs
46
+ only the matching platform package, detects installed agents, and asks where to
47
+ install CrawlPro.
48
+
49
+ Install into one, several, or all detected agents:
50
+
51
+ ```bash
52
+ npx --yes crawlpro --agent hermes
53
+ npx --yes crawlpro --agent claude,codex
54
+ npx --yes crawlpro --all --yes
55
+ ```
56
+
57
+ Inspect detection or preview changes without writing files:
58
+
59
+ ```bash
60
+ npx --yes crawlpro --list
61
+ npx --yes crawlpro --all --dry-run
62
+ ```
63
+
64
+ The platform-specific commands remain available:
65
+
66
+ ```bash
67
+ npx --yes crawlpro-mac
68
+ npx --yes crawlpro-win
69
+ ```
70
+
71
+ Updates replace only packaged release files. Existing `license.json`,
72
+ `app/settings.json`, output files, browser profiles, and local databases are
73
+ preserved.
74
+
75
+ ## First Run
76
+
77
+ Start a new Agent session after installation, then ask the Agent to use
78
+ CrawlPro. Before browser workflows, CrawlPro checks whether Edge or Chrome is
79
+ available and whether Douyin is logged in.
80
+
81
+ The packaged commands are:
82
+
83
+ ```text
84
+ macOS: ./main check
85
+ Windows: .\main.exe check
86
+ ```
87
+
88
+ If the browser cannot be started, use `start_chrome_debug.sh` on macOS or
89
+ `start_chrome_debug.bat` on Windows, complete login, and run the check again.
90
+
91
+ ## License Activation
92
+
93
+ CrawlPro requires a valid License Key. To purchase or request one, contact the
94
+ CrawlPro team through the [company website](https://www.wenyaotech.com/).
95
+
96
+ The License Key is not included in the npm package. Enter it only in the local
97
+ CrawlPro activation prompt. The resulting device-bound `license.json` must not
98
+ be shared or committed to source control.
99
+
100
+ ## Responsible Use and Security
101
+
102
+ - Process only publicly visible information and follow applicable laws and
103
+ platform terms.
104
+ - Sending replies requires explicit user authorization and user-defined limits.
105
+ - Never place License Keys, API keys, customer data, or local configuration in
106
+ Agent conversations, logs, or source control.
107
+ - Do not modify or bypass CrawlPro license validation or platform safeguards.
108
+ - The npm packages contain no user credentials or runtime customer data.
109
+
110
+ ## Maintainer Commands
111
+
112
+ ```bash
113
+ npm test
114
+ npm run pack:check
115
+
116
+ npm publish --workspace packages/macos --registry=https://registry.npmjs.org/
117
+ npm publish --workspace packages/windows --registry=https://registry.npmjs.org/
118
+ npm publish --workspace packages/cli --registry=https://registry.npmjs.org/
119
+ ```
120
+
121
+ Publish both platform packages before publishing the unified `crawlpro` entry
122
+ package.
123
+
124
+ ## License
125
+
126
+ CrawlPro is distributed under a Proprietary License. The complete license is
127
+ included in the npm package as the `LICENSE` file.
128
+
129
+ Copyright © 2026 Xi'an Wenyao Network Information Technology Co., Ltd.
package/README_CN.md ADDED
@@ -0,0 +1,123 @@
1
+ # CrawlPro
2
+
3
+ [English](https://www.npmjs.com/package/crawlpro) | 中文
4
+
5
+ CrawlPro 是一套专有 Agent Skill,用于从抖音公开可见内容中查找和整理客户
6
+ 信号,支持关键词采集、客户表格、辅助跟进、用户授权回复以及 Excel/HTML
7
+ 客户分析。
8
+
9
+ 本项目由 **西安文鳐网络信息科技有限责任公司** 开发。
10
+
11
+ [公司官网](https://www.wenyaotech.com/)
12
+
13
+ ## 核心能力
14
+
15
+ - 按关键词采集公开可见的视频、评论和用户资料。
16
+ - 对评论进行客户意向分级,并导出客户工作簿。
17
+ - 草拟回复;只有获得用户明确授权后才执行发送。
18
+ - 支持人工确认的辅助回复和客户跟进状态更新。
19
+ - 生成清洗后的 Excel 工作簿和 HTML 客户报告。
20
+ - 关键词库和运行配置保存在用户本机。
21
+
22
+ ## 支持平台
23
+
24
+ - Apple Silicon(`arm64`)macOS
25
+ - Windows 10 或 Windows 11(`x64`)
26
+
27
+ 暂不支持 Intel Mac、Windows ARM64 和 Linux。
28
+
29
+ ## 使用要求
30
+
31
+ - 安装时需要 Node.js 18 或更高版本、npm 9 或更高版本
32
+ - Hermes、OpenClaw、Claude Code 或 Codex
33
+ - Microsoft Edge 或 Google Chrome;不支持 Safari
34
+ - 本地 MongoDB 或 Redis,用于保存关键词库
35
+ - 有效的 CrawlPro License Key
36
+
37
+ 使用预编译程序时不需要安装 Python。
38
+
39
+ ## 安装
40
+
41
+ ```bash
42
+ npx --yes crawlpro
43
+ ```
44
+
45
+ 统一安装器会判断操作系统和 CPU 架构,只安装匹配的平台包,然后检测已经
46
+ 安装的 Agent,并询问需要把 CrawlPro 安装到哪里。
47
+
48
+ 安装到一个、多个或全部 Agent:
49
+
50
+ ```bash
51
+ npx --yes crawlpro --agent hermes
52
+ npx --yes crawlpro --agent claude,codex
53
+ npx --yes crawlpro --all --yes
54
+ ```
55
+
56
+ 查看检测结果或预演安装,不写入文件:
57
+
58
+ ```bash
59
+ npx --yes crawlpro --list
60
+ npx --yes crawlpro --all --dry-run
61
+ ```
62
+
63
+ 仍然可以直接使用平台包:
64
+
65
+ ```bash
66
+ npx --yes crawlpro-mac
67
+ npx --yes crawlpro-win
68
+ ```
69
+
70
+ 更新只会替换发布文件,已有的 `license.json`、`app/settings.json`、输出文件、
71
+ 浏览器资料和本地数据库不会被删除。
72
+
73
+ ## 首次使用
74
+
75
+ 安装后请在对应 Agent 中新建会话,并要求 Agent 使用 CrawlPro。开始浏览器
76
+ 工作流前,CrawlPro 会检查 Edge 或 Chrome 是否可用,以及抖音是否已登录。
77
+
78
+ 程序命令为:
79
+
80
+ ```text
81
+ macOS: ./main check
82
+ Windows: .\main.exe check
83
+ ```
84
+
85
+ 浏览器无法启动时,macOS 运行 `start_chrome_debug.sh`,Windows 运行
86
+ `start_chrome_debug.bat`;完成登录后再次执行检查。
87
+
88
+ ## License 激活
89
+
90
+ 使用 CrawlPro 需要有效的 License Key。如需购买或申请,请通过
91
+ [公司官网](https://www.wenyaotech.com/)联系 CrawlPro 团队。
92
+
93
+ License Key 不包含在 npm 安装包中。请仅在本机 CrawlPro 激活提示中输入。
94
+ 激活后生成的 `license.json` 与设备绑定,不要转发或提交到源代码仓库。
95
+
96
+ ## 合规和安全说明
97
+
98
+ - 只处理公开可见的信息,并遵守适用法律和平台规则。
99
+ - 发送回复必须获得用户明确授权,并使用用户设定的数量限制。
100
+ - 不要在 Agent 对话、日志或源码中提供 License Key、API Key、客户数据或
101
+ 本地配置。
102
+ - 不要修改或绕过 CrawlPro License 校验及平台安全限制。
103
+ - npm 安装包不包含用户凭证或运行时客户数据。
104
+
105
+ ## 维护者命令
106
+
107
+ ```bash
108
+ npm test
109
+ npm run pack:check
110
+
111
+ npm publish --workspace packages/macos --registry=https://registry.npmjs.org/
112
+ npm publish --workspace packages/windows --registry=https://registry.npmjs.org/
113
+ npm publish --workspace packages/cli --registry=https://registry.npmjs.org/
114
+ ```
115
+
116
+ 必须先发布两个平台包,再发布统一入口包 `crawlpro`。
117
+
118
+ ## License
119
+
120
+ CrawlPro 使用 Proprietary License,完整许可证条款包含在 npm 安装包的
121
+ `LICENSE` 文件中。
122
+
123
+ Copyright © 2026 西安文鳐网络信息科技有限责任公司
package/SKILL.md ADDED
@@ -0,0 +1,279 @@
1
+ ---
2
+ name: crawlpro
3
+ description: Operate the packaged CrawlPro application for Douyin login check, customer collection, keyword catalogs, follow-up status, and Excel/HTML customer analysis, plus drafting comment replies within user-set limits. Use when the user asks to run CrawlPro workflows such as check, collect, reply, semi-reply, follow-up, analyze, or config. Verifies browser login before browser workflows, confirms required limits before execution, processes only publicly visible data, and never delivers private settings or internal state files.
4
+ ---
5
+
6
+ # CrawlPro agent workflow
7
+
8
+ Use only the packaged application in the current release directory:
9
+
10
+ ```text
11
+ Windows: ./main.exe
12
+ macOS: ./main
13
+ ```
14
+
15
+ For source development, the equivalent command is `uv run python main.py`.
16
+
17
+ The included browser launcher is also platform-specific:
18
+
19
+ ```text
20
+ Windows: start_chrome_debug.bat
21
+ macOS: ./start_chrome_debug.sh
22
+ ```
23
+
24
+ ## Core rules
25
+
26
+ 1. Determine the requested workflow before running commands.
27
+ 2. Ask for missing keywords, limits and send mode.
28
+ 3. Run `APP check` before login, collection or reply workflows.
29
+ 4. Do not continue browser work unless `APP check` exits `0`.
30
+ 5. Never claim a reply was sent unless the application confirms it.
31
+ 6. Never claim a file exists without checking it.
32
+ 7. Never attach `app/settings.json`, `license.json`, `active_collection.json`, `reply_tracking.json`, browser profiles or API keys.
33
+ 8. Deliver only generated `.xlsx`, `.html`, `.log` when explicitly requested, or the application-generated ZIP.
34
+
35
+ `APP` below means the platform command shown above.
36
+
37
+ ## Command router
38
+
39
+ | Request | Command |
40
+ |---|---|
41
+ | Browser/login check | `APP check` |
42
+ | Guided login | `APP login` |
43
+ | Collect target customers | `APP collect` |
44
+ | Draft/send replies within limits | `APP reply` |
45
+ | Assisted reply (manual confirm) | `APP semi-reply` |
46
+ | Follow-up status | `APP follow-up` |
47
+ | Customer analysis | `APP analyze` |
48
+ | Settings and keywords | `APP config` |
49
+ | View model-generated keyword content | `APP keywords` |
50
+ | Initialize or verify database | `APP init-db` |
51
+
52
+ Interactive commands require a real terminal. If no PTY is available, ask the user to open the application and select the matching menu item.
53
+
54
+ ## Browser pre-flight
55
+
56
+ Run before every browser workflow:
57
+
58
+ ```text
59
+ APP check
60
+ ```
61
+
62
+ | Exit | Meaning | Action |
63
+ |---|---|---|
64
+ | `0` | Browser connected and logged in | Continue |
65
+ | `1` | Browser connected but logged out | Ask the user to scan QR, then rerun |
66
+ | `2` | Browser unavailable | Ask the user to run the included browser launcher, then rerun |
67
+
68
+ CrawlPro uses Edge first and Chrome as fallback on CDP port `19553`.
69
+ Safari cannot be used because this workflow requires the Chromium CDP interface.
70
+ On macOS, ask the user to install Edge or Chrome when neither is available.
71
+
72
+ ## Customer collection
73
+
74
+ Ask for:
75
+
76
+ 1. Search keyword or comma-separated keywords;
77
+ 2. latest-first search: `yes` or `no` (`no` means hot/most-liked);
78
+ 3. videos per keyword;
79
+ 4. comments to retain per video (default: all; a positive number sets a cap);
80
+ 5. maximum target customers;
81
+ 6. maximum profiles;
82
+ 7. whether to use an existing keyword bundle or generate one with the model.
83
+
84
+ If existing defaults are requested:
85
+
86
+ - read numeric limits from `app/settings.json` when it exists;
87
+ - obtain active keywords and generated content through `APP keywords`;
88
+ - do not expect keywords inside `settings.json`.
89
+
90
+ Run:
91
+
92
+ ```text
93
+ APP collect "keywordA,keywordB" --videos 10 --leads 50 --profiles 50
94
+ APP collect "keywordA" --comments 20 # optional per-video cap
95
+ APP collect "keywordA" --sort hot
96
+ ```
97
+
98
+ Every keyword is searched independently. Duplicate video IDs are removed.
99
+ Only publicly visible comments and profile data are processed. Intent grading
100
+ prioritizes explicit request actions and rejects praise-only comments. Count
101
+ repeated values by distinct users, not raw comment rows. Any number used by at
102
+ least three users under one video is treated as that video's spoken code; two
103
+ users require surrounding request evidence. Normalize repeated text for
104
+ punctuation and platform emoji tags. Repeated request phrases are high intent,
105
+ ambiguous repeated phrases are medium-intent candidates, and repeated
106
+ praise/reactions remain low. All signals are scoped to one video.
107
+
108
+ Treat store/address, on-site service, delivery coverage, and supported-city
109
+ questions as high intent; bare creator-location questions as medium; and
110
+ filming-location or scenery questions as low.
111
+
112
+ Expected primary output:
113
+
114
+ ```text
115
+ output/Test/collections/<keyword-group>/target_customs.xlsx
116
+ ```
117
+
118
+ The collection ZIP contains only final customer workbooks, cleaned analysis workbooks, HTML reports, and `.log` files. Runtime JSON, search tables, per-video comment tables, and per-user detail tables stay out of the bundle.
119
+
120
+ ## Reply
121
+
122
+ Ask for:
123
+
124
+ 1. keyword set;
125
+ 2. live search or `target_customs.xlsx` source;
126
+ 3. latest-first search (`yes`/`no`), videos per keyword, and comments per video when live search is selected;
127
+ 4. whether to draft replies only or send them (sending requires explicit user authorization and confirmation);
128
+ 5. model use: `yes` or `no`;
129
+ 6. daily reply limit;
130
+ 7. allowed comment age;
131
+ 8. whether to collect profiles.
132
+
133
+ Commands:
134
+
135
+ ```text
136
+ APP reply "keywordA"
137
+ APP reply "keywordA" --no-reply
138
+ APP reply "keywordA" --profile
139
+ APP reply "keywordA" --from-leads
140
+ APP reply "keywordA" --sort hot
141
+ ```
142
+
143
+ Before sending, state the selected keyword, source and reply limit, and confirm the user has authorized sending. Sending happens only within the user-set daily limit and must not bypass platform interaction limits. Live search is the default and has no Excel input. `--from-leads` reads only `target_customs.xlsx`. `activate_customs.xlsx` is an optional profile/reply-status output, and `all_comment_users.xlsx` is never a sending source. Stop if selectors are missing or sending cannot be confirmed.
144
+
145
+ ## Assisted reply
146
+
147
+ Run:
148
+
149
+ ```text
150
+ APP semi-reply
151
+ ```
152
+
153
+ This workflow requires a person at the computer. It reads only the active keyword group's `target_customs.xlsx`, filters customers, opens the related comment and either sends after confirmation or fills the text for manual sending. It does not read `activate_customs.xlsx` or `all_comment_users.xlsx`.
154
+
155
+ ## Follow-up
156
+
157
+ Run:
158
+
159
+ ```text
160
+ APP follow-up
161
+ ```
162
+
163
+ The user marks rows as 未跟进 (not followed up), 已私信 (DM sent), 已加微信 (WeChat added) or 已成交 (deal closed). The workflow writes back to Excel. If Excel/WPS locks the file, ask the user to close it and retry.
164
+
165
+ ## Analysis
166
+
167
+ Ask for:
168
+
169
+ 1. `target_customs.xlsx`, `activate_customs.xlsx`, `all_comment_users.xlsx`, or an explicit Excel path;
170
+ 2. brief or full terminal statistics;
171
+ 3. whether the generated Excel/HTML should be delivered.
172
+
173
+ Commands:
174
+
175
+ ```text
176
+ APP analyze
177
+ APP analyze --source target
178
+ APP analyze --source profiles
179
+ APP analyze --source comments
180
+ APP analyze --brief
181
+ APP analyze --file "path/to/customer_table.xlsx" --full
182
+ APP evaluate-intent "path/to/labeled_comments.xlsx"
183
+ ```
184
+
185
+ Without a source flag, an interactive terminal offers all three customer tables. `--daily` remains a compatibility alias for `--source profiles`; `--csv` remains a compatibility alias for `--file`, but the input is an Excel workbook.
186
+
187
+ Each schema is normalized from its real fields. The formal `activate_customs.xlsx` includes intent and reply status; compatible older internal profile data may omit intent, which stays unlabelled. Comment-user workbooks map highest intent, aggregated comments, latest comment time and source videos.
188
+
189
+ Generated files are placed beside the source workbook under `analysis/`:
190
+
191
+ ```text
192
+ analysis/<source>_clean.xlsx
193
+ analysis/<source>_report.html
194
+ ```
195
+
196
+ The cleaned workbook and HTML contain only customer delivery fields: user identity, contact information, intent, source video, intent comment, comment time and IP region. The HTML is a simple customer table (user, Douyin ID, user-homepage link, contact method, contact detail, intent, video link, video title, intent comment, comment time, IP region), sorted by intent level (high → mid) then newest comment first, with a region filter dropdown — not a dashboard.
197
+
198
+ The analysis ZIP contains the cleaned workbook, the HTML report, and the same main tables/logs found in the keyword directory.
199
+
200
+ ## Intent evaluation
201
+
202
+ Use `APP evaluate-intent <labeled_comments.xlsx>` only when the user supplies a
203
+ manually labeled workbook with `评论内容` and `预期意向` columns. The command
204
+ uses the production classifier, including video-scoped repeated codes when
205
+ `视频ID` is present. It makes no model call, spends no tokens, and writes no
206
+ copy of the labeled data.
207
+
208
+ ## Settings and keyword catalog
209
+
210
+ `app/settings.json` contains only English application settings:
211
+
212
+ - `llm`;
213
+ - `database`;
214
+ - `parameters`.
215
+
216
+ Keywords, intent words, reply-intent words and reply templates are stored with English field names in the password-free MongoDB/Redis `keywords` collection. `APP init-db` also migrates compatible legacy Chinese field names without regenerating content.
217
+
218
+ Commands:
219
+
220
+ ```text
221
+ APP init-db
222
+ APP config
223
+ APP config "keywordA,keywordB"
224
+ APP config --reset
225
+ APP keywords
226
+ ```
227
+
228
+ Ask for confirmation before `APP config --reset` because it overwrites application settings. API keys must be entered privately in the application, never in chat.
229
+
230
+ ## Output and delivery
231
+
232
+ Main customer outputs:
233
+
234
+ ```text
235
+ target_customs.xlsx
236
+ activate_customs.xlsx
237
+ all_comment_users.xlsx
238
+ analysis/<source>_clean.xlsx
239
+ analysis/<source>_report.html
240
+ deliverables/*.zip
241
+ ```
242
+
243
+ Internal files such as `active_collection.json` and `reply_tracking.json` are necessary runtime state, not delivery files.
244
+
245
+ After success, report:
246
+
247
+ 1. command and keyword set;
248
+ 2. approved limits;
249
+ 3. processed/success/failure counts;
250
+ 4. whether real replies were sent;
251
+ 5. exact existing output paths;
252
+ 6. remaining user action.
253
+
254
+ Use the printed `DELIVERABLE_PATH=...` value for ZIP delivery. Do not search for or construct a guessed ZIP path.
255
+
256
+ ## Recovery
257
+
258
+ | Problem | Action |
259
+ |---|---|
260
+ | Browser exit `1` | Ask for QR login and rerun check |
261
+ | Browser exit `2` | Run platform browser launcher and retry |
262
+ | Search returns no data | Verify login and keywords |
263
+ | Page disconnect | Keep browser open, rerun check, retry once |
264
+ | Model failure | Ask whether to retry or continue without generated content |
265
+ | Reply selector missing | Stop sending and report probable layout change |
266
+ | `send_unconfirmed` | Do not count as sent |
267
+ | Excel locked | Ask user to close Excel/WPS and retry |
268
+ | Analysis source missing/empty | Stop and report the exact expected path |
269
+ | Database unavailable | Verify the local service and URL, run `APP init-db`, and report its exact error |
270
+ | macOS exit `137` / `Killed: 9` | Stop retrying; compare `uname -m` with `file ./main`, then report the output of `codesign --verify --deep --strict --verbose=4 ./main` |
271
+
272
+ ## Safety
273
+
274
+ - Follow Douyin terms of service and applicable privacy and data-protection laws.
275
+ - Process only publicly visible comment and profile data, and only for user-authorized private-domain communication.
276
+ - Keep collection and reply volume conservative; never bypass platform interaction limits or anti-automation measures.
277
+ - Do not use customer data for harassment, fraud, spam, or unlawful resale.
278
+ - Password-free database URLs are restricted to loopback hosts or a local Redis Unix socket; do not attempt to bypass this validation.
279
+ - Never expose private settings, API keys, licensing data, or browser profiles.
@@ -0,0 +1,376 @@
1
+ #!/usr/bin/env node
2
+
3
+ 'use strict';
4
+
5
+ const childProcess = require('node:child_process');
6
+ const fs = require('node:fs');
7
+ const os = require('node:os');
8
+ const path = require('node:path');
9
+ const readline = require('node:readline');
10
+
11
+ const SKILL_NAME = 'crawlpro';
12
+ const PACKAGE_ROOT = path.resolve(__dirname, '..');
13
+ const PLATFORM = process.env.CRAWLPRO_INSTALL_TEST_PLATFORM || process.platform;
14
+ const ARCH = process.env.CRAWLPRO_INSTALL_TEST_ARCH || process.arch;
15
+ const INSTALL_HOME = process.env.CRAWLPRO_INSTALL_HOME || os.homedir();
16
+ const LOCAL_APP_DATA = process.env.CRAWLPRO_INSTALL_LOCALAPPDATA ||
17
+ process.env.LOCALAPPDATA || path.join(INSTALL_HOME, 'AppData', 'Local');
18
+
19
+ const SOURCE_FILES = [
20
+ ['SKILL.md', 'SKILL.md'],
21
+ ['README.md', 'README.md'],
22
+ ['README_CN.md', 'README_CN.md'],
23
+ ['main.exe', 'main.exe'],
24
+ ['start_chrome_debug.bat', 'start_chrome_debug.bat'],
25
+ ];
26
+
27
+ const CANONICAL_FILES = [
28
+ 'SKILL.md',
29
+ 'README.md',
30
+ 'README_CN.md',
31
+ 'main.exe',
32
+ 'start_chrome_debug.bat',
33
+ ];
34
+
35
+ function hermesHome() {
36
+ return process.env.HERMES_HOME || path.join(INSTALL_HOME, '.hermes');
37
+ }
38
+
39
+ const AGENTS = {
40
+ hermes: {
41
+ label: 'Hermes Agent',
42
+ command: 'hermes',
43
+ detectPaths: () => [
44
+ hermesHome(),
45
+ path.join(LOCAL_APP_DATA, 'Programs', 'Hermes'),
46
+ ],
47
+ destination: () => path.join(hermesHome(), 'skills', SKILL_NAME),
48
+ },
49
+ openclaw: {
50
+ label: 'OpenClaw',
51
+ command: 'openclaw',
52
+ detectPaths: () => [path.join(INSTALL_HOME, '.openclaw')],
53
+ destination: () => path.join(INSTALL_HOME, '.openclaw', 'skills', SKILL_NAME),
54
+ },
55
+ claude: {
56
+ label: 'Claude Code',
57
+ command: 'claude',
58
+ detectPaths: () => [path.join(INSTALL_HOME, '.claude')],
59
+ destination: () => path.join(INSTALL_HOME, '.claude', 'skills', SKILL_NAME),
60
+ },
61
+ codex: {
62
+ label: 'Codex',
63
+ command: 'codex',
64
+ detectPaths: () => [
65
+ path.join(INSTALL_HOME, '.codex'),
66
+ path.join(LOCAL_APP_DATA, 'Programs', 'Codex'),
67
+ ],
68
+ destination: () => path.join(INSTALL_HOME, '.agents', 'skills', SKILL_NAME),
69
+ },
70
+ };
71
+
72
+ const AGENT_ORDER = ['hermes', 'openclaw', 'claude', 'codex'];
73
+
74
+ function usage() {
75
+ console.log(`CrawlPro Windows Skill 安装器
76
+
77
+ 用法:
78
+ npx crawlpro
79
+ npx crawlpro --agent hermes,codex
80
+ npx crawlpro --all --yes
81
+
82
+ 平台包直接调用:
83
+ npx crawlpro-win
84
+
85
+ 选项:
86
+ --agent <名称> 安装到指定 Agent;可用逗号分隔多个名称
87
+ 支持 hermes、openclaw、claude、codex
88
+ --all 安装到检测到的全部 Agent
89
+ --yes, -y 更新已有安装时不再询问
90
+ --dry-run 只显示操作,不写入文件
91
+ --list 只显示检测结果
92
+ --help, -h 显示帮助`);
93
+ }
94
+
95
+ function parseArgs(argv) {
96
+ const options = {
97
+ requestedAgents: '',
98
+ all: false,
99
+ yes: false,
100
+ dryRun: false,
101
+ list: false,
102
+ help: false,
103
+ };
104
+
105
+ for (let index = 0; index < argv.length; index += 1) {
106
+ const argument = argv[index];
107
+ if (argument === '--agent') {
108
+ const value = argv[index + 1];
109
+ if (!value || value.startsWith('--')) {
110
+ throw new Error('--agent 后需要 Agent 名称。');
111
+ }
112
+ options.requestedAgents = value;
113
+ index += 1;
114
+ } else if (argument.startsWith('--agent=')) {
115
+ options.requestedAgents = argument.slice('--agent='.length);
116
+ } else if (argument === '--all') {
117
+ options.all = true;
118
+ } else if (argument === '--yes' || argument === '-y') {
119
+ options.yes = true;
120
+ } else if (argument === '--dry-run') {
121
+ options.dryRun = true;
122
+ } else if (argument === '--list') {
123
+ options.list = true;
124
+ } else if (argument === '--help' || argument === '-h') {
125
+ options.help = true;
126
+ } else {
127
+ throw new Error(`未知参数:${argument}`);
128
+ }
129
+ }
130
+
131
+ if (options.requestedAgents && options.all) {
132
+ throw new Error('--agent 和 --all 不能同时使用。');
133
+ }
134
+ return options;
135
+ }
136
+
137
+ function commandExists(command) {
138
+ try {
139
+ const result = childProcess.spawnSync('where.exe', [command], {
140
+ stdio: 'ignore',
141
+ windowsHide: true,
142
+ });
143
+ return result.status === 0;
144
+ } catch {
145
+ return false;
146
+ }
147
+ }
148
+
149
+ function agentDetected(key) {
150
+ const agent = AGENTS[key];
151
+ return commandExists(agent.command) || agent.detectPaths().some((entry) => fs.existsSync(entry));
152
+ }
153
+
154
+ function printDetection() {
155
+ console.log('CrawlPro 支持的 Agent:');
156
+ for (const key of AGENT_ORDER) {
157
+ const status = agentDetected(key) ? '已检测' : '未检测';
158
+ const suffix = agentDetected(key) ? ` ${AGENTS[key].destination()}` : '';
159
+ console.log(` [${status}] ${AGENTS[key].label}${suffix}`);
160
+ }
161
+ }
162
+
163
+ function normalizeAgent(value) {
164
+ const normalized = value.trim().toLowerCase();
165
+ const aliases = {
166
+ hermes: 'hermes',
167
+ 'hermes-agent': 'hermes',
168
+ openclaw: 'openclaw',
169
+ 'open-claw': 'openclaw',
170
+ claude: 'claude',
171
+ 'claude-code': 'claude',
172
+ claudecode: 'claude',
173
+ codex: 'codex',
174
+ 'openai-codex': 'codex',
175
+ };
176
+ return aliases[normalized] || null;
177
+ }
178
+
179
+ function parseRequestedAgents(raw) {
180
+ const selected = [];
181
+ for (const item of raw.replaceAll(',', ',').split(',')) {
182
+ if (!item.trim()) continue;
183
+ const key = normalizeAgent(item);
184
+ if (!key) throw new Error(`不支持的 Agent:${item.trim()}`);
185
+ if (!selected.includes(key)) selected.push(key);
186
+ }
187
+ return selected;
188
+ }
189
+
190
+ function ask(question) {
191
+ const terminal = readline.createInterface({ input: process.stdin, output: process.stdout });
192
+ return new Promise((resolve) => {
193
+ terminal.question(question, (answer) => {
194
+ terminal.close();
195
+ resolve(answer.trim());
196
+ });
197
+ });
198
+ }
199
+
200
+ async function selectInteractively(detected) {
201
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
202
+ throw new Error('当前不是交互式终端,请使用 --agent 或 --all。');
203
+ }
204
+ console.log('\n请选择安装目标,可输入一个或多个编号(例如 1,3):');
205
+ detected.forEach((key, index) => console.log(` ${index + 1}) ${AGENTS[key].label}`));
206
+ console.log(' a) 全部');
207
+ console.log(' q) 退出');
208
+ const answer = (await ask('选择:')).replaceAll(',', ',').replaceAll(/\s/g, '');
209
+ if (/^q$/i.test(answer)) return [];
210
+ if (/^a$/i.test(answer)) return [...detected];
211
+
212
+ const selected = [];
213
+ for (const value of answer.split(',')) {
214
+ if (!/^\d+$/.test(value)) throw new Error(`无效选项:${value}`);
215
+ const index = Number(value) - 1;
216
+ if (index < 0 || index >= detected.length) throw new Error(`无效选项:${value}`);
217
+ if (!selected.includes(detected[index])) selected.push(detected[index]);
218
+ }
219
+ return selected;
220
+ }
221
+
222
+ function verifyPackage() {
223
+ for (const [sourceRelative] of SOURCE_FILES) {
224
+ const source = path.join(PACKAGE_ROOT, sourceRelative);
225
+ if (!fs.statSync(source, { throwIfNoEntry: false })?.isFile()) {
226
+ throw new Error(`npm 包不完整,缺少 ${sourceRelative}。`);
227
+ }
228
+ }
229
+ }
230
+
231
+ function installationExists(destination) {
232
+ return CANONICAL_FILES.some((relative) => fs.existsSync(path.join(destination, relative)));
233
+ }
234
+
235
+ function timestamp() {
236
+ const now = new Date();
237
+ const pad = (value) => String(value).padStart(2, '0');
238
+ return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-` +
239
+ `${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}-${process.pid}`;
240
+ }
241
+
242
+ function backupExistingFiles(key, destination) {
243
+ const backupRoot = path.join(
244
+ LOCAL_APP_DATA,
245
+ 'CrawlProInstaller',
246
+ 'backups',
247
+ key,
248
+ timestamp(),
249
+ );
250
+ let copied = 0;
251
+ for (const relative of CANONICAL_FILES) {
252
+ const source = path.join(destination, relative);
253
+ if (!fs.existsSync(source)) continue;
254
+ const target = path.join(backupRoot, relative);
255
+ fs.mkdirSync(path.dirname(target), { recursive: true });
256
+ fs.copyFileSync(source, target);
257
+ copied += 1;
258
+ }
259
+ if (copied > 0) console.log(` 旧版本已备份:${backupRoot}`);
260
+ return backupRoot;
261
+ }
262
+
263
+ function atomicCopy(source, destination) {
264
+ fs.mkdirSync(path.dirname(destination), { recursive: true });
265
+ const temporary = `${destination}.crawlpro-new-${process.pid}`;
266
+ const previous = `${destination}.crawlpro-old-${process.pid}`;
267
+ fs.copyFileSync(source, temporary);
268
+
269
+ let movedPrevious = false;
270
+ try {
271
+ if (fs.existsSync(destination)) {
272
+ fs.renameSync(destination, previous);
273
+ movedPrevious = true;
274
+ }
275
+ fs.renameSync(temporary, destination);
276
+ if (movedPrevious) fs.rmSync(previous, { force: true });
277
+ } catch (error) {
278
+ fs.rmSync(temporary, { force: true });
279
+ if (movedPrevious && !fs.existsSync(destination) && fs.existsSync(previous)) {
280
+ fs.renameSync(previous, destination);
281
+ }
282
+ if (error && ['EBUSY', 'EPERM', 'EACCES'].includes(error.code)) {
283
+ throw new Error(`无法更新 ${destination};请关闭 CrawlPro 和对应 Agent 后重试。`);
284
+ }
285
+ throw error;
286
+ }
287
+ }
288
+
289
+ async function installAgent(key, options) {
290
+ const agent = AGENTS[key];
291
+ const destination = agent.destination();
292
+ console.log(`\n安装到 ${agent.label}:${destination}`);
293
+
294
+ if (installationExists(destination) && !options.yes && !options.dryRun) {
295
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
296
+ throw new Error('目标已存在;非交互模式更新请加 --yes。');
297
+ }
298
+ const answer = await ask(' 已存在 CrawlPro,备份并更新?[y/N] ');
299
+ if (!/^y$/i.test(answer)) {
300
+ console.log(' 已跳过。');
301
+ return;
302
+ }
303
+ }
304
+
305
+ if (options.dryRun) {
306
+ console.log(' [dry-run] 将保留现有授权、配置和输出数据,并更新发布文件。');
307
+ return;
308
+ }
309
+
310
+ if (installationExists(destination)) backupExistingFiles(key, destination);
311
+ for (const [sourceRelative, destinationRelative] of SOURCE_FILES) {
312
+ atomicCopy(
313
+ path.join(PACKAGE_ROOT, sourceRelative),
314
+ path.join(destination, destinationRelative),
315
+ );
316
+ }
317
+
318
+ for (const required of ['main.exe', 'start_chrome_debug.bat']) {
319
+ if (!fs.statSync(path.join(destination, required), { throwIfNoEntry: false })?.isFile()) {
320
+ throw new Error(`发布文件校验失败:${required}`);
321
+ }
322
+ }
323
+ console.log(' 安装完成。现有配置和生成数据未被删除。');
324
+ }
325
+
326
+ async function main() {
327
+ const options = parseArgs(process.argv.slice(2));
328
+ if (options.help) {
329
+ usage();
330
+ return;
331
+ }
332
+ verifyPackage();
333
+ if (PLATFORM !== 'win32' || ARCH !== 'x64') {
334
+ throw new Error(`crawlpro-win 仅支持 Windows x64;当前系统为 ${PLATFORM} ${ARCH}。`);
335
+ }
336
+
337
+ const detected = AGENT_ORDER.filter(agentDetected);
338
+ if (options.list) {
339
+ printDetection();
340
+ return;
341
+ }
342
+
343
+ let selected;
344
+ if (options.requestedAgents) {
345
+ selected = parseRequestedAgents(options.requestedAgents);
346
+ for (const key of selected) {
347
+ if (!agentDetected(key)) {
348
+ console.error(`警告:未检测到 ${AGENTS[key].label},仍按显式参数继续安装。`);
349
+ }
350
+ }
351
+ } else if (options.all) {
352
+ selected = [...detected];
353
+ } else {
354
+ printDetection();
355
+ if (detected.length === 0) {
356
+ throw new Error('没有检测到受支持的 Agent。安装 Agent 后重试,或使用 --agent 显式指定目标。');
357
+ }
358
+ selected = await selectInteractively(detected);
359
+ }
360
+
361
+ if (selected.length === 0) {
362
+ if (!options.requestedAgents && !options.all) console.log('已取消。');
363
+ else throw new Error('没有可安装的目标。');
364
+ return;
365
+ }
366
+
367
+ for (const key of selected) await installAgent(key, options);
368
+ console.log(options.dryRun
369
+ ? '\ndry-run 完成,没有写入文件。'
370
+ : '\nCrawlPro 安装完成。请在对应 Agent 中新建会话,让 Skill 清单重新加载。');
371
+ }
372
+
373
+ main().catch((error) => {
374
+ console.error(`错误:${error.message}`);
375
+ process.exitCode = 1;
376
+ });
package/main.exe ADDED
Binary file
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "crawlpro-win",
3
+ "version": "1.0.0",
4
+ "description": "Install the prebuilt CrawlPro Skill for Windows x64 into Hermes, OpenClaw, Claude Code, or Codex.",
5
+ "license": "SEE LICENSE IN LICENSE",
6
+ "author": "Xi'an Wenyao Network Information Technology Co., Ltd.",
7
+ "homepage": "https://www.wenyaotech.com/",
8
+ "bin": {
9
+ "crawlpro-win": "bin/crawlpro-win.cjs"
10
+ },
11
+ "files": [
12
+ "bin/crawlpro-win.cjs",
13
+ "main.exe",
14
+ "start_chrome_debug.bat",
15
+ "SKILL.md",
16
+ "README.md",
17
+ "README_CN.md",
18
+ "LICENSE"
19
+ ],
20
+ "os": [
21
+ "win32"
22
+ ],
23
+ "cpu": [
24
+ "x64"
25
+ ],
26
+ "engines": {
27
+ "node": ">=18",
28
+ "npm": ">=9"
29
+ },
30
+ "scripts": {
31
+ "prepack": "node ../../tooling/sync-shared.cjs windows",
32
+ "prepublishOnly": "npm test",
33
+ "test": "node ../../tooling/sync-shared.cjs windows && node --check bin/crawlpro-win.cjs && node test/npm-installer-smoke.cjs"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public",
37
+ "registry": "https://registry.npmjs.org/"
38
+ },
39
+ "keywords": [
40
+ "agent-skill",
41
+ "claude-code",
42
+ "codex",
43
+ "crawlpro",
44
+ "douyin",
45
+ "hermes-agent",
46
+ "openclaw",
47
+ "windows"
48
+ ]
49
+ }
@@ -0,0 +1,63 @@
1
+ @echo off
2
+ chcp 65001 >nul
3
+ echo ============================================
4
+ echo Start Chromium debug mode on port 19553
5
+ echo ============================================
6
+ echo.
7
+
8
+ rem Prefer Microsoft Edge, then fall back to Google Chrome.
9
+ set "BROWSER="
10
+ set "BROWSER_NAME="
11
+ set "PROFILE_NAME="
12
+ if exist "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" (
13
+ set "BROWSER=C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe"
14
+ set "BROWSER_NAME=Microsoft Edge"
15
+ set "PROFILE_NAME=.douyin_edge_profile"
16
+ )
17
+ if not defined BROWSER if exist "C:\Program Files\Microsoft\Edge\Application\msedge.exe" (
18
+ set "BROWSER=C:\Program Files\Microsoft\Edge\Application\msedge.exe"
19
+ set "BROWSER_NAME=Microsoft Edge"
20
+ set "PROFILE_NAME=.douyin_edge_profile"
21
+ )
22
+ if not defined BROWSER if exist "%LOCALAPPDATA%\Microsoft\Edge\Application\msedge.exe" (
23
+ set "BROWSER=%LOCALAPPDATA%\Microsoft\Edge\Application\msedge.exe"
24
+ set "BROWSER_NAME=Microsoft Edge"
25
+ set "PROFILE_NAME=.douyin_edge_profile"
26
+ )
27
+ if not defined BROWSER if exist "C:\Program Files\Google\Chrome\Application\chrome.exe" (
28
+ set "BROWSER=C:\Program Files\Google\Chrome\Application\chrome.exe"
29
+ set "BROWSER_NAME=Google Chrome"
30
+ set "PROFILE_NAME=.douyin_chrome_profile"
31
+ )
32
+ if not defined BROWSER if exist "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" (
33
+ set "BROWSER=C:\Program Files (x86)\Google\Chrome\Application\chrome.exe"
34
+ set "BROWSER_NAME=Google Chrome"
35
+ set "PROFILE_NAME=.douyin_chrome_profile"
36
+ )
37
+ if not defined BROWSER if exist "%LOCALAPPDATA%\Google\Chrome\Application\chrome.exe" (
38
+ set "BROWSER=%LOCALAPPDATA%\Google\Chrome\Application\chrome.exe"
39
+ set "BROWSER_NAME=Google Chrome"
40
+ set "PROFILE_NAME=.douyin_chrome_profile"
41
+ )
42
+
43
+ if not defined BROWSER (
44
+ echo Microsoft Edge or Google Chrome was not found.
45
+ echo Install a Chromium browser that supports --remote-debugging-port=19553.
46
+ pause
47
+ exit /b 1
48
+ )
49
+
50
+ echo Browser: %BROWSER_NAME%
51
+ echo Executable: %BROWSER%
52
+
53
+ rem Keep separate persistent profiles for Edge and Chrome.
54
+ set "DEBUG_PROFILE=%~dp0%PROFILE_NAME%"
55
+ if not exist "%DEBUG_PROFILE%" mkdir "%DEBUG_PROFILE%"
56
+
57
+ start "" "%BROWSER%" --remote-debugging-port=19553 --user-data-dir="%DEBUG_PROFILE%" --new-window "https://www.douyin.com/"
58
+
59
+ echo.
60
+ echo %BROWSER_NAME% started. Scan the Douyin QR code on the first run.
61
+ echo Run main.exe check again after login.
62
+ echo.
63
+ pause