publishport-opencli 1.0.0 → 1.0.2
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/README.md +61 -61
- package/README.zh-CN.md +68 -68
- package/cli-manifest.json +954 -61
- package/clis/1688/shared.js +2 -2
- package/clis/36kr/news.js +1 -1
- package/clis/_atlassian/shared.js +1 -1
- package/clis/_shared/article/auth.js +1 -1
- package/clis/_shared/article/publish.js +1 -1
- package/clis/_shared/content-guard.js +1 -1
- package/clis/_shared/self-hosted-sites.js +2 -0
- package/clis/_shared/site-auth.js +1 -1
- package/clis/_shared/token-auth.js +2 -2
- package/clis/_shared/video-publish.js +1 -1
- package/clis/aibase/news.js +1 -1
- package/clis/amazon/shared.js +2 -2
- package/clis/antigravity/SKILL.md +12 -12
- package/clis/antigravity/storage.js +1 -1
- package/clis/apple-podcasts/episodes.js +1 -1
- package/clis/archive/item.js +1 -1
- package/clis/archive/search.js +1 -1
- package/clis/archive/snapshots.js +1 -1
- package/clis/archive/wayback.js +1 -1
- package/clis/arxiv/author.js +1 -1
- package/clis/baijiahao/publish.js +1 -1
- package/clis/bbc/utils.js +1 -1
- package/clis/bloomberg/businessweek.js +1 -1
- package/clis/bloomberg/utils.js +1 -1
- package/clis/booking/search.js +1 -1
- package/clis/boss/utils.js +1 -1
- package/clis/chatgpt/ask.js +1 -1
- package/clis/chatgpt/detail.js +1 -1
- package/clis/chatgpt/history.js +1 -1
- package/clis/chatgpt/project-file-add.js +1 -1
- package/clis/chatgpt/project-list.js +1 -1
- package/clis/chatgpt/utils.js +2 -2
- package/clis/chess/utils.js +1 -1
- package/clis/claude/ask.js +1 -1
- package/clis/claude/history.js +1 -1
- package/clis/claude/utils.js +1 -1
- package/clis/codex/extract-diff.js +1 -1
- package/clis/coingecko/coin.js +1 -1
- package/clis/crates/utils.js +1 -1
- package/clis/csdn/stats.js +1 -1
- package/clis/dblp/utils.js +1 -1
- package/clis/defillama/utils.js +1 -1
- package/clis/devto/publish.js +1 -1
- package/clis/discord-app/utils.js +1 -1
- package/clis/dockerhub/utils.js +1 -1
- package/clis/douban/utils.js +1 -1
- package/clis/douyin/_shared/tos-upload.js +1 -1
- package/clis/douyin/hashtag.js +1 -1
- package/clis/douyin/publish-image.js +1 -1
- package/clis/douyin/publish.js +1 -1
- package/clis/eastmoney/announcement.js +1 -1
- package/clis/eastmoney/rank.js +1 -1
- package/clis/eastmoney/sectors.js +1 -1
- package/clis/endoflife/utils.js +1 -1
- package/clis/flathub/utils.js +1 -1
- package/clis/gemini/detail.js +1 -1
- package/clis/gemini/read.js +1 -1
- package/clis/geogebra/add-circle.js +1 -1
- package/clis/geogebra/add-line.js +1 -1
- package/clis/geogebra/add-point.js +1 -1
- package/clis/geogebra/add-polygon.js +1 -1
- package/clis/geogebra/eval.js +1 -1
- package/clis/geogebra/hexagon.js +1 -1
- package/clis/geogebra/info.js +1 -1
- package/clis/geogebra/triangle.js +1 -1
- package/clis/ghost/login.js +1 -0
- package/clis/ghost/publish.js +1 -1
- package/clis/ghost/shared.js +24 -1
- package/clis/ghost/sites.js +1 -0
- package/clis/ghost/whoami.js +1 -1
- package/clis/gitee/user.js +2 -2
- package/clis/github-trending/repos.js +1 -1
- package/clis/goproxy/utils.js +1 -1
- package/clis/grok/export-all.js +1 -1
- package/clis/grok/export.js +1 -1
- package/clis/hashnode/publish.js +1 -1
- package/clis/hf/datasets.js +1 -1
- package/clis/hf/models.js +1 -1
- package/clis/hf/paper.js +1 -1
- package/clis/hf/spaces.js +1 -1
- package/clis/homebrew/utils.js +1 -1
- package/clis/instagram/note.js +1 -1
- package/clis/jimeng/generate.js +1 -1
- package/clis/juejin/utils.js +1 -1
- package/clis/kuaishou/publish.js +1 -1
- package/clis/lesswrong/tag.js +1 -1
- package/clis/lichess/utils.js +1 -1
- package/clis/linkedin/search.js +2 -2
- package/clis/linux-do/feed.js +2 -2
- package/clis/lobsters/domain.js +1 -1
- package/clis/mastodon/login.js +1 -0
- package/clis/mastodon/post.js +1 -1
- package/clis/mastodon/shared.js +10 -10
- package/clis/mastodon/sites.js +1 -0
- package/clis/mastodon/whoami.js +1 -1
- package/clis/maven/utils.js +1 -1
- package/clis/mdn/search.js +1 -1
- package/clis/medium/tag.js +1 -1
- package/clis/notebooklm/current.js +1 -1
- package/clis/notebooklm/get.js +1 -1
- package/clis/notebooklm/history.js +1 -1
- package/clis/notebooklm/note-list.js +1 -1
- package/clis/notebooklm/notes-get.js +1 -1
- package/clis/notebooklm/open.js +1 -1
- package/clis/notebooklm/source-fulltext.js +1 -1
- package/clis/notebooklm/source-get.js +1 -1
- package/clis/notebooklm/source-guide.js +1 -1
- package/clis/notebooklm/source-list.js +1 -1
- package/clis/notebooklm/summary.js +1 -1
- package/clis/notebooklm/utils.js +1 -1
- package/clis/npm/utils.js +1 -1
- package/clis/nuget/utils.js +1 -1
- package/clis/nvd/cve.js +1 -1
- package/clis/oeis/utils.js +1 -1
- package/clis/ones/common.js +2 -2
- package/clis/ones/me.js +1 -1
- package/clis/ones/tasks.js +1 -1
- package/clis/ones/token-info.js +1 -1
- package/clis/ones/worklog.js +1 -1
- package/clis/openalex/utils.js +1 -1
- package/clis/oschina/stats.js +1 -1
- package/clis/osv/utils.js +1 -1
- package/clis/packagist/utils.js +1 -1
- package/clis/pixiv/detail.js +1 -1
- package/clis/pixiv/user.js +1 -1
- package/clis/producthunt/utils.js +1 -1
- package/clis/pypi/utils.js +1 -1
- package/clis/qiita/gql.js +2 -2
- package/clis/quora/__fixtures__/dom.html +39 -0
- package/clis/quora/answer-comments.js +9 -0
- package/clis/quora/answer-detail.js +1 -0
- package/clis/quora/answer-queue.js +1 -0
- package/clis/quora/comment.js +23 -0
- package/clis/quora/create-answer.js +4 -4
- package/clis/quora/create-question.js +3 -3
- package/clis/quora/follow.js +15 -0
- package/clis/quora/followers.js +1 -0
- package/clis/quora/question.js +1 -0
- package/clis/quora/search.js +1 -0
- package/clis/quora/shared.js +26 -11
- package/clis/quora/stats.js +1 -0
- package/clis/quora/upvote.js +14 -0
- package/clis/quora/user-answers.js +1 -0
- package/clis/quora/user.js +1 -0
- package/clis/rest-countries/utils.js +1 -1
- package/clis/rfc/utils.js +1 -1
- package/clis/rubygems/utils.js +1 -1
- package/clis/semanticscholar/utils.js +1 -1
- package/clis/slock/errors.js +1 -1
- package/clis/spotify/spotify.js +1 -1
- package/clis/spotify/utils.js +1 -1
- package/clis/stackoverflow/utils.js +1 -1
- package/clis/steam/utils.js +1 -1
- package/clis/substack/auth.js +2 -2
- package/clis/suno/generate.js +2 -2
- package/clis/suno/list.js +3 -3
- package/clis/telegram/send.js +1 -1
- package/clis/tiktok/creator-videos.js +1 -1
- package/clis/tiktok/publish.js +1 -1
- package/clis/tiktok/utils.js +1 -1
- package/clis/trae-cn/activity.js +1 -1
- package/clis/trae-cn/approve.js +1 -1
- package/clis/trae-cn/ask.js +1 -1
- package/clis/trae-cn/dump.js +1 -1
- package/clis/trae-cn/export.js +1 -1
- package/clis/trae-cn/model.js +1 -1
- package/clis/trae-cn/new.js +1 -1
- package/clis/trae-cn/read.js +1 -1
- package/clis/trae-cn/screenshot.js +1 -1
- package/clis/trae-cn/select-model.js +1 -1
- package/clis/trae-cn/send.js +1 -1
- package/clis/trae-cn/setup.js +1 -1
- package/clis/trae-cn/status.js +1 -1
- package/clis/trae-cn/targets.js +1 -1
- package/clis/trae-cn/watch.js +1 -1
- package/clis/trae-solo/state-fs.js +1 -1
- package/clis/tumblr/publish.js +1 -1
- package/clis/tvmaze/utils.js +1 -1
- package/clis/twitter/bookmark-folder.js +1 -1
- package/clis/twitter/download.js +1 -1
- package/clis/twitter/followers.js +2 -2
- package/clis/twitter/following.js +2 -2
- package/clis/twitter/likes.js +1 -1
- package/clis/twitter/list-add-batch.js +1 -1
- package/clis/twitter/list-add-core.js +1 -1
- package/clis/twitter/list-add.js +1 -1
- package/clis/twitter/list-create.js +1 -1
- package/clis/twitter/list-delete.js +3 -3
- package/clis/twitter/list-remove-batch.js +1 -1
- package/clis/twitter/list-remove.js +1 -1
- package/clis/twitter/list-tweets.js +1 -1
- package/clis/twitter/profile.js +1 -1
- package/clis/twitter/search.js +1 -1
- package/clis/twitter/shared.js +1 -1
- package/clis/twitter/tweets.js +1 -1
- package/clis/uisdc/news.js +1 -1
- package/clis/v2ex/daily.js +2 -2
- package/clis/v2ex/me.js +2 -2
- package/clis/v2ex/notifications.js +1 -1
- package/clis/wikidata/utils.js +1 -1
- package/clis/wikipedia/page.js +2 -2
- package/clis/wikipedia/summary.js +1 -1
- package/clis/wikipedia/utils.js +1 -1
- package/clis/wordpress/login.js +1 -1
- package/clis/wordpress/publish.js +1 -1
- package/clis/wordpress/shared.js +3 -3
- package/clis/wordpress/sites.js +1 -0
- package/clis/wordpress/whoami.js +1 -1
- package/clis/woshipm/stats.js +1 -1
- package/clis/xiaoe/content.js +1 -1
- package/clis/xiaohongshu/draft-delete.js +1 -1
- package/clis/xiaohongshu/draft-open.js +1 -1
- package/clis/xiaohongshu/draft-utils.js +2 -2
- package/clis/xiaohongshu/publish-video.js +1 -1
- package/clis/xiaohongshu/publish.js +1 -1
- package/clis/xiaoyuzhou/auth.js +1 -1
- package/clis/xiaoyuzhou/transcript.js +1 -1
- package/clis/yollomi/background.js +1 -1
- package/clis/yollomi/edit.js +1 -1
- package/clis/yollomi/generate.js +1 -1
- package/clis/yollomi/try-on.js +1 -1
- package/clis/youtube/publish.js +1 -1
- package/clis/zhihu/answer-comments.js +1 -1
- package/clis/zhihu/answer-detail.js +1 -1
- package/clis/zhihu/collection.js +2 -2
- package/clis/zhihu/collections.js +1 -1
- package/clis/zhihu/question.js +1 -1
- package/clis/zhihu/search.js +1 -1
- package/clis/zhihu/target.js +1 -1
- package/clis/zhihu/user-arg.js +1 -1
- package/dist/src/adapter-shadow.js +1 -1
- package/dist/src/browser/analyze.js +1 -1
- package/dist/src/browser/article-extract.d.ts +1 -1
- package/dist/src/browser/base-page.js +3 -3
- package/dist/src/browser/cdp.js +1 -1
- package/dist/src/browser/daemon-client.d.ts +1 -1
- package/dist/src/browser/daemon-lifecycle.js +6 -6
- package/dist/src/browser/daemon-version.js +1 -1
- package/dist/src/browser/dom-snapshot.d.ts +1 -1
- package/dist/src/browser/dom-snapshot.js +2 -2
- package/dist/src/browser/errors.js +2 -2
- package/dist/src/browser/network-cache.js +1 -1
- package/dist/src/browser/profile.js +1 -1
- package/dist/src/browser/target-resolver.js +4 -4
- package/dist/src/browser/verify-fixture.js +1 -1
- package/dist/src/browser-session-lock.js +1 -1
- package/dist/src/build-manifest.d.ts +1 -1
- package/dist/src/build-manifest.js +1 -1
- package/dist/src/cli-argv-preprocess.d.ts +4 -4
- package/dist/src/cli-argv-preprocess.js +1 -1
- package/dist/src/cli.d.ts +1 -1
- package/dist/src/cli.js +19 -19
- package/dist/src/commanderAdapter.js +1 -1
- package/dist/src/commands/auth.js +2 -2
- package/dist/src/commands/daemon.d.ts +4 -4
- package/dist/src/commands/daemon.js +1 -1
- package/dist/src/completion-shared.js +12 -12
- package/dist/src/completion.d.ts +2 -2
- package/dist/src/constants.d.ts +1 -1
- package/dist/src/constants.js +1 -1
- package/dist/src/daemon-utils.d.ts +1 -1
- package/dist/src/daemon-utils.js +1 -1
- package/dist/src/daemon.d.ts +2 -2
- package/dist/src/daemon.js +1 -1
- package/dist/src/discovery.d.ts +7 -7
- package/dist/src/discovery.js +2 -2
- package/dist/src/doctor.d.ts +1 -1
- package/dist/src/doctor.js +6 -6
- package/dist/src/electron-apps.d.ts +1 -1
- package/dist/src/electron-apps.js +1 -1
- package/dist/src/errors.d.ts +2 -2
- package/dist/src/errors.js +1 -1
- package/dist/src/execution.js +1 -1
- package/dist/src/external.d.ts +1 -1
- package/dist/src/external.js +1 -1
- package/dist/src/help.d.ts +1 -1
- package/dist/src/help.js +3 -3
- package/dist/src/hooks.d.ts +1 -1
- package/dist/src/launcher.js +2 -2
- package/dist/src/logger.d.ts +1 -1
- package/dist/src/main.d.ts +1 -1
- package/dist/src/main.js +1 -1
- package/dist/src/observation/artifact.js +2 -2
- package/dist/src/package-paths.js +1 -1
- package/dist/src/package-paths.test.d.ts +1 -0
- package/dist/src/plugin-manifest.d.ts +12 -3
- package/dist/src/plugin-manifest.js +1 -1
- package/dist/src/plugin-scaffold.d.ts +2 -2
- package/dist/src/plugin-scaffold.js +9 -9
- package/dist/src/plugin.d.ts +4 -4
- package/dist/src/plugin.js +10 -10
- package/dist/src/rate-limit.js +1 -1
- package/dist/src/registry-api.d.ts +1 -1
- package/dist/src/runtime-detect.d.ts +1 -1
- package/dist/src/serialization.js +1 -1
- package/dist/src/skills.js +6 -6
- package/dist/src/user-runtime.d.ts +8 -0
- package/dist/src/user-runtime.js +1 -0
- package/dist/src/user-runtime.test.d.ts +1 -0
- package/dist/src/validate.js +1 -1
- package/dist/src/validate.test.d.ts +1 -1
- package/docs/guide/browser-bridge.md +23 -23
- package/docs/guide/electron-app-cli.md +3 -3
- package/docs/guide/exit-codes.md +3 -3
- package/docs/guide/extending-opencli.md +37 -37
- package/docs/guide/getting-started.md +19 -19
- package/docs/guide/installation.md +6 -6
- package/docs/guide/plugins.md +32 -32
- package/docs/guide/remote-orchestration.md +5 -5
- package/docs/guide/troubleshooting.md +9 -9
- package/package.json +3 -2
- package/scripts/fetch-adapters.js +6 -6
- package/scripts/postinstall.js +14 -14
- package/skills/opencli-adapter-author/SKILL.md +24 -24
- package/skills/opencli-adapter-author/references/adapter-template.md +13 -13
- package/skills/opencli-adapter-author/references/api-discovery.md +25 -25
- package/skills/opencli-adapter-author/references/coverage-matrix.md +3 -3
- package/skills/opencli-adapter-author/references/field-decode-playbook.md +10 -10
- package/skills/opencli-adapter-author/references/jsdom-fixture-pattern.md +2 -2
- package/skills/opencli-adapter-author/references/site-memory.md +11 -11
- package/skills/opencli-adapter-author/references/site-recon.md +7 -7
- package/skills/opencli-adapter-author/references/strategy-selection.md +3 -3
- package/skills/opencli-adapter-author/references/success-rate-pitfalls.md +4 -4
- package/skills/opencli-adapter-author/references/typed-errors.md +2 -2
- package/skills/opencli-autofix/SKILL.md +29 -29
- package/skills/opencli-browser/SKILL.md +55 -55
- package/skills/opencli-browser-sitemap/SKILL.md +7 -7
- package/skills/opencli-sitemap-author/SKILL.md +11 -11
- package/skills/opencli-sitemap-author/references/sitemap-schema.md +19 -19
- package/skills/opencli-usage/SKILL.md +43 -43
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
### "Extension not connected"
|
|
6
6
|
|
|
7
|
-
- Ensure the
|
|
8
|
-
- Run `
|
|
7
|
+
- Ensure the ppcli Browser Bridge extension is installed and **enabled** in `chrome://extensions`.
|
|
8
|
+
- Run `ppcli doctor` to diagnose connectivity.
|
|
9
9
|
|
|
10
10
|
### Empty data or 'Unauthorized' error
|
|
11
11
|
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
|
|
17
17
|
- A healthy Browser Bridge connection does not guarantee that the current page target exposes the data your adapter expects.
|
|
18
18
|
- Some browser adapters are sensitive to the active host or page context.
|
|
19
|
-
- Example: `
|
|
19
|
+
- Example: `ppcli 1688 item` may fail with `did not expose product context` if the target is too broad.
|
|
20
20
|
- Retry on a real item page, refresh the page in Chrome, and if needed narrow the target, for example:
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
|
-
OPENCLI_CDP_TARGET=detail.1688.com
|
|
23
|
+
OPENCLI_CDP_TARGET=detail.1688.com ppcli 1688 item 841141931191 -f json
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
### Node API errors
|
|
@@ -34,15 +34,15 @@ OPENCLI_CDP_TARGET=detail.1688.com opencli 1688 item 841141931191 -f json
|
|
|
34
34
|
curl localhost:19825/logs
|
|
35
35
|
|
|
36
36
|
# Stop the daemon
|
|
37
|
-
|
|
37
|
+
ppcli daemon stop
|
|
38
38
|
|
|
39
39
|
# Full diagnostics
|
|
40
|
-
|
|
40
|
+
ppcli doctor
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
> The daemon is persistent and stays alive until explicitly stopped (`
|
|
43
|
+
> The daemon is persistent and stays alive until explicitly stopped (`ppcli daemon stop`) or the package is uninstalled.
|
|
44
44
|
|
|
45
|
-
> When the CLI detects a stale daemon (version mismatch after `npm install -g @jackwener/opencli@latest`), it first asks the daemon to shut down via `/shutdown`, then falls back to `SIGKILL` if the daemon does not release the port within 3 seconds. Manual `
|
|
45
|
+
> When the CLI detects a stale daemon (version mismatch after `npm install -g @jackwener/opencli@latest`), it first asks the daemon to shut down via `/shutdown`, then falls back to `SIGKILL` if the daemon does not release the port within 3 seconds. Manual `ppcli daemon stop` is only needed if SIGKILL itself is rejected (cross-user owner / cross-machine PID file).
|
|
46
46
|
|
|
47
47
|
### Desktop adapter connection issues
|
|
48
48
|
|
|
@@ -66,4 +66,4 @@ npx tsc --noEmit
|
|
|
66
66
|
## Getting Help
|
|
67
67
|
|
|
68
68
|
- [GitHub Issues](https://github.com/jackwener/opencli/issues) — Bug reports and feature requests
|
|
69
|
-
- Run `
|
|
69
|
+
- Run `ppcli doctor` for comprehensive diagnostics
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "publishport-opencli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"provenance": false
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"type": "module",
|
|
13
13
|
"main": "dist/src/main.js",
|
|
14
14
|
"bin": {
|
|
15
|
+
"ppcli": "dist/src/main.js",
|
|
15
16
|
"opencli": "dist/src/main.js"
|
|
16
17
|
},
|
|
17
18
|
"exports": {
|
|
@@ -96,8 +97,8 @@
|
|
|
96
97
|
"ws": "^8.18.0"
|
|
97
98
|
},
|
|
98
99
|
"devDependencies": {
|
|
99
|
-
"@types/jsdom": "^27.0.0",
|
|
100
100
|
"@types/js-yaml": "^4.0.9",
|
|
101
|
+
"@types/jsdom": "^27.0.0",
|
|
101
102
|
"@types/node": "^25.5.2",
|
|
102
103
|
"@types/turndown": "^5.0.6",
|
|
103
104
|
"@types/ws": "^8.5.13",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Sparse adapter sync: keeps ~/.
|
|
4
|
+
* Sparse adapter sync: keeps ~/.ppcli/clis/ clean by removing stale overrides.
|
|
5
5
|
*
|
|
6
6
|
* Strategy (hash-based, site-level granularity):
|
|
7
7
|
* - When an official site has upstream changes: DELETE the local override
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - User-created custom sites (not in package): always preserved
|
|
11
11
|
* - Skips entirely if already synced at the same version
|
|
12
12
|
*
|
|
13
|
-
* ~/.
|
|
13
|
+
* ~/.ppcli/clis/ is a sparse override layer, not a full copy.
|
|
14
14
|
* Only eject-ed or user-modified sites appear here.
|
|
15
15
|
*
|
|
16
16
|
* Only runs on global install (npm install -g) or explicit OPENCLI_FETCH=1.
|
|
@@ -24,7 +24,7 @@ import { createHash } from 'node:crypto';
|
|
|
24
24
|
import { join, resolve, dirname, relative } from 'node:path';
|
|
25
25
|
import { homedir } from 'node:os';
|
|
26
26
|
|
|
27
|
-
const OPENCLI_DIR = join(homedir(), '.
|
|
27
|
+
const OPENCLI_DIR = join(homedir(), '.ppcli');
|
|
28
28
|
const USER_CLIS_DIR = join(OPENCLI_DIR, 'clis');
|
|
29
29
|
const MANIFEST_PATH = join(OPENCLI_DIR, 'adapter-manifest.json');
|
|
30
30
|
const PACKAGE_ROOT = resolve(import.meta.dirname, '..');
|
|
@@ -159,7 +159,7 @@ export function fetchAdapters() {
|
|
|
159
159
|
|
|
160
160
|
// 2. Sparse cleanup: for changed/removed official sites, delete local overrides.
|
|
161
161
|
// Do NOT copy new versions — runtime falls back to package baseline.
|
|
162
|
-
// Only eject-ed sites live in ~/.
|
|
162
|
+
// Only eject-ed sites live in ~/.ppcli/clis/.
|
|
163
163
|
let cleared = 0;
|
|
164
164
|
for (const site of changedSites) {
|
|
165
165
|
const siteDir = join(USER_CLIS_DIR, site);
|
|
@@ -204,9 +204,9 @@ export function fetchAdapters() {
|
|
|
204
204
|
}
|
|
205
205
|
if (yamlCleaned > 0) log(`Cleaned up ${yamlCleaned} stale .yaml adapter files`);
|
|
206
206
|
|
|
207
|
-
// 4. Clean up legacy compat shim files from ~/.
|
|
207
|
+
// 4. Clean up legacy compat shim files from ~/.ppcli/
|
|
208
208
|
// These were created by an older approach that placed re-export shims directly
|
|
209
|
-
// in ~/.
|
|
209
|
+
// in ~/.ppcli/ (e.g., registry.js, errors.js, browser/). The current approach
|
|
210
210
|
// uses a node_modules/@jackwener/opencli symlink instead.
|
|
211
211
|
const LEGACY_SHIM_FILES = [
|
|
212
212
|
'registry.js', 'errors.js', 'utils.js', 'launcher.js', 'logger.js', 'types.js',
|
package/scripts/postinstall.js
CHANGED
|
@@ -22,36 +22,36 @@ import { homedir } from 'node:os';
|
|
|
22
22
|
|
|
23
23
|
// ── Completion script content ──────────────────────────────────────────────
|
|
24
24
|
|
|
25
|
-
const BASH_COMPLETION = `# Bash completion for
|
|
25
|
+
const BASH_COMPLETION = `# Bash completion for ppcli (auto-installed)
|
|
26
26
|
_opencli_completions() {
|
|
27
27
|
local cur words cword
|
|
28
28
|
_get_comp_words_by_ref -n : cur words cword
|
|
29
29
|
|
|
30
30
|
local completions
|
|
31
|
-
completions=$(
|
|
31
|
+
completions=$(ppcli --get-completions --cursor "$cword" "\${words[@]:1}" 2>/dev/null)
|
|
32
32
|
|
|
33
33
|
COMPREPLY=( $(compgen -W "$completions" -- "$cur") )
|
|
34
34
|
__ltrim_colon_completions "$cur"
|
|
35
35
|
}
|
|
36
|
-
complete -F _opencli_completions
|
|
36
|
+
complete -F _opencli_completions ppcli
|
|
37
37
|
`;
|
|
38
38
|
|
|
39
|
-
const ZSH_COMPLETION = `#compdef
|
|
40
|
-
# Zsh completion for
|
|
39
|
+
const ZSH_COMPLETION = `#compdef ppcli
|
|
40
|
+
# Zsh completion for ppcli (auto-installed)
|
|
41
41
|
_opencli() {
|
|
42
42
|
local -a completions
|
|
43
43
|
local cword=$((CURRENT - 1))
|
|
44
|
-
completions=(\${(f)"$(
|
|
44
|
+
completions=(\${(f)"$(ppcli --get-completions --cursor "$cword" "\${words[@]:1}" 2>/dev/null)"})
|
|
45
45
|
compadd -a completions
|
|
46
46
|
}
|
|
47
47
|
_opencli
|
|
48
48
|
`;
|
|
49
49
|
|
|
50
|
-
const FISH_COMPLETION = `# Fish completion for
|
|
51
|
-
complete -c
|
|
50
|
+
const FISH_COMPLETION = `# Fish completion for ppcli (auto-installed)
|
|
51
|
+
complete -c ppcli -f -a '(
|
|
52
52
|
set -l tokens (commandline -cop)
|
|
53
53
|
set -l cursor (count (commandline -cop))
|
|
54
|
-
|
|
54
|
+
ppcli --get-completions --cursor $cursor $tokens[2..] 2>/dev/null
|
|
55
55
|
)'
|
|
56
56
|
`;
|
|
57
57
|
|
|
@@ -113,7 +113,7 @@ function main() {
|
|
|
113
113
|
}
|
|
114
114
|
case 'bash': {
|
|
115
115
|
const userCompDir = join(home, '.bash_completion.d');
|
|
116
|
-
const completionFile = join(userCompDir, '
|
|
116
|
+
const completionFile = join(userCompDir, 'ppcli');
|
|
117
117
|
ensureDir(userCompDir);
|
|
118
118
|
writeFileSync(completionFile, BASH_COMPLETION, 'utf8');
|
|
119
119
|
|
|
@@ -127,7 +127,7 @@ function main() {
|
|
|
127
127
|
}
|
|
128
128
|
case 'fish': {
|
|
129
129
|
const completionsDir = join(home, '.config', 'fish', 'completions');
|
|
130
|
-
const completionFile = join(completionsDir, '
|
|
130
|
+
const completionFile = join(completionsDir, 'ppcli.fish');
|
|
131
131
|
ensureDir(completionsDir);
|
|
132
132
|
writeFileSync(completionFile, FISH_COMPLETION, 'utf8');
|
|
133
133
|
|
|
@@ -144,7 +144,7 @@ function main() {
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
// ── Spotify credentials template ────────────────────────────────────
|
|
147
|
-
const opencliDir = join(home, '.
|
|
147
|
+
const opencliDir = join(home, '.ppcli');
|
|
148
148
|
const spotifyEnvFile = join(opencliDir, 'spotify.env');
|
|
149
149
|
ensureDir(opencliDir);
|
|
150
150
|
if (!existsSync(spotifyEnvFile)) {
|
|
@@ -156,7 +156,7 @@ function main() {
|
|
|
156
156
|
'utf8'
|
|
157
157
|
);
|
|
158
158
|
console.log(`✓ Spotify credentials template created at ${spotifyEnvFile}`);
|
|
159
|
-
console.log(` Edit the file and add your Client ID and Secret, then run:
|
|
159
|
+
console.log(` Edit the file and add your Client ID and Secret, then run: ppcli spotify auth`);
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
// ── Browser Bridge setup hint ───────────────────────────────────────
|
|
@@ -166,7 +166,7 @@ function main() {
|
|
|
166
166
|
console.log(' 1. Download: https://github.com/jackwener/opencli/releases');
|
|
167
167
|
console.log(' 2. In Chrome or Chromium, open chrome://extensions → enable Developer Mode → Load unpacked');
|
|
168
168
|
console.log('');
|
|
169
|
-
console.log(' Then run \x1b[
|
|
169
|
+
console.log(' Then run \x1b[36mppcli doctor\x1b[0m to verify.');
|
|
170
170
|
console.log('');
|
|
171
171
|
|
|
172
172
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: opencli-adapter-author
|
|
3
|
-
description: Use when writing an
|
|
4
|
-
allowed-tools: Bash(
|
|
3
|
+
description: Use when writing an ppcli adapter for a new site or adding a new command to an existing site. Guides end-to-end from first recon through field decoding, adapter coding, and verify. Replaces opencli-oneshot / opencli-explorer. For ad-hoc browser driving (no adapter), see opencli-browser instead; for a top-level orientation to ppcli, see opencli-usage.
|
|
4
|
+
allowed-tools: Bash(ppcli:*), Read, Edit, Write, Grep
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# opencli-adapter-author
|
|
8
8
|
|
|
9
|
-
你是要给一个站点写 adapter 的 agent。这份 skill 目标:**从零到通过 `
|
|
9
|
+
你是要给一个站点写 adapter 的 agent。这份 skill 目标:**从零到通过 `ppcli browser verify` 的 30 分钟内闭环**。
|
|
10
10
|
|
|
11
|
-
全程用现有工具:`
|
|
11
|
+
全程用现有工具:`ppcli browser *` / `ppcli doctor` / `ppcli browser init` / `ppcli browser verify`。没有新命令。
|
|
12
12
|
|
|
13
13
|
调试浏览器型 adapter 时,优先直接带上 `--trace on --keep-tab true --window foreground`。`--trace on` 每轮都落 trace artifact,`summary.md` 是失败/成功复盘入口;`--keep-tab true --window foreground` 让 tab lease 保留且浏览器窗口在前台,方便核对最终页面状态。
|
|
14
14
|
|
|
@@ -54,7 +54,7 @@ Strategy classes:
|
|
|
54
54
|
| `COOKIE_API` | stable | Node-side `fetch` + `page.getCookies()` / header helper 能拿数据 | cookie/CSRF 来源清楚,replay 非空 |
|
|
55
55
|
| `UI_SELECTOR` | visible-ui | publish/upload/click/表单,或页面语义比内部接口更稳 | selector 有语义锚点;错误路径是 typed error |
|
|
56
56
|
| `DOM_STATE` | visible-ui | 数据在 hydration state / bootstrap JSON / SSR HTML 里 | state key / script JSON / HTML 结构明确 |
|
|
57
|
-
| `PAGE_FETCH` | internal-unstable | 只能在页面上下文 `fetch` 才能复用 same-origin/session/runtime | `
|
|
57
|
+
| `PAGE_FETCH` | internal-unstable | 只能在页面上下文 `fetch` 才能复用 same-origin/session/runtime | `ppcli browser eval fetch(...)` 非空;必须解释为什么避不开内部接口 |
|
|
58
58
|
| `INTERCEPT` | internal-unstable | 请求签名复杂,但页面自己能自然发出请求 | 触发 UI 后能截到目标 response;必须解释为什么 UI/DOM 不够 |
|
|
59
59
|
|
|
60
60
|
选择规则:优先 `PUBLIC_API` / `COOKIE_API`。如果 UI/DOM 语义稳定,不要强行升级到 `PAGE_FETCH` / `INTERCEPT`。只有公开/官方接口不可用、UI/DOM 无法表达目标数据或操作时,才承担无契约内部接口的维护成本。
|
|
@@ -68,14 +68,14 @@ START
|
|
|
68
68
|
│
|
|
69
69
|
▼
|
|
70
70
|
┌──────────────────────────┐
|
|
71
|
-
│
|
|
71
|
+
│ ppcli doctor 通? │── no ──→ 修桥接(doctor 输出里的提示)
|
|
72
72
|
└──────────────────────────┘
|
|
73
73
|
│ yes
|
|
74
74
|
▼
|
|
75
75
|
┌────────────────────────────────────────────────────┐
|
|
76
76
|
│ 读站点记忆: │
|
|
77
|
-
│ 1. ~/.
|
|
78
|
-
│ 2. ~/.
|
|
77
|
+
│ 1. ~/.ppcli/sites/<site>/endpoints.json │
|
|
78
|
+
│ 2. ~/.ppcli/sites/<site>/notes.md │
|
|
79
79
|
│ 3. references/site-memory/<site>.md │
|
|
80
80
|
└────────────────────────────────────────────────────┘
|
|
81
81
|
│ 命中 endpoint + 字段 → 直接跳到【endpoint 验证】(不跳写 adapter!memory 可能过期)
|
|
@@ -110,14 +110,14 @@ START
|
|
|
110
110
|
│
|
|
111
111
|
▼
|
|
112
112
|
┌──────────────────────────┐
|
|
113
|
-
│
|
|
113
|
+
│ ppcli browser init │ 生成 ~/.ppcli/clis/<site>/<name>.js 骨架
|
|
114
114
|
│ 复制最像的邻居 adapter │
|
|
115
115
|
│ 改 name / URL / 映射三处 │
|
|
116
116
|
└──────────────────────────┘
|
|
117
117
|
│
|
|
118
118
|
▼
|
|
119
119
|
┌──────────────────────────┐
|
|
120
|
-
│
|
|
120
|
+
│ ppcli browser verify │── 失败 ──→ autofix skill,用 --trace retain-on-failure 回对应步骤
|
|
121
121
|
└──────────────────────────┘
|
|
122
122
|
│ 成功
|
|
123
123
|
▼
|
|
@@ -127,7 +127,7 @@ START
|
|
|
127
127
|
│ 对得上
|
|
128
128
|
▼
|
|
129
129
|
┌──────────────────────────┐
|
|
130
|
-
│ 回写 ~/.
|
|
130
|
+
│ 回写 ~/.ppcli/sites/ │ endpoints / field-map / notes / fixtures
|
|
131
131
|
└──────────────────────────┘
|
|
132
132
|
│
|
|
133
133
|
▼
|
|
@@ -139,14 +139,14 @@ DONE
|
|
|
139
139
|
## Runbook(一步一步勾选)
|
|
140
140
|
|
|
141
141
|
```
|
|
142
|
-
[ ] 1.
|
|
142
|
+
[ ] 1. ppcli doctor 返回 "Everything looks good"
|
|
143
143
|
[ ] 2. 读站点记忆:
|
|
144
|
-
[ ] ~/.
|
|
144
|
+
[ ] ~/.ppcli/sites/<site>/endpoints.json 存在?里面有想要的 endpoint?
|
|
145
145
|
[ ] references/site-memory/<site>.md 存在?看"已知 endpoint"节
|
|
146
146
|
[ ] 命中后:**跳到第 5(endpoint 验证) + 第 7(字段核对)**,不能直接跳第 9 写 adapter
|
|
147
147
|
[ ] memory 写入超过 30 天(看 `verified_at`)→ 当作过期,按冷启动走 Step 3 → 4
|
|
148
148
|
[ ] 3. 侦察(site-recon.md):
|
|
149
|
-
[ ] **首选**:`
|
|
149
|
+
[ ] **首选**:`ppcli browser analyze <url>` 一步拿 pattern + 反爬 + 最近 adapter + next step
|
|
150
150
|
[ ] `analyze` 结论模糊时再手跑:`open` → `wait time 2` (或 `wait xhr <regex>`) → `network`
|
|
151
151
|
[ ] 定 Pattern(A / B / C / D / E)
|
|
152
152
|
[ ] 4. API 发现(api-discovery.md)按 Pattern 选 §:
|
|
@@ -173,21 +173,21 @@ DONE
|
|
|
173
173
|
[ ] 类型 / 单位 / 百分比格式清楚
|
|
174
174
|
[ ] 顺序:识别列 → 业务数字 → metadata
|
|
175
175
|
[ ] 9. 写 adapter(adapter-template.md):
|
|
176
|
-
[ ]
|
|
176
|
+
[ ] ppcli browser init <site>/<name>
|
|
177
177
|
[ ] 找同站点或同类型最像的 adapter,cp 过来
|
|
178
178
|
[ ] 改 name / URL / 字段映射
|
|
179
|
-
[ ] 10.
|
|
180
|
-
[ ] 首轮通过后立刻 `--write-fixture` 生成 `~/.
|
|
179
|
+
[ ] 10. ppcli browser verify <site>/<name>
|
|
180
|
+
[ ] 首轮通过后立刻 `--write-fixture` 生成 `~/.ppcli/sites/<site>/verify/<cmd>.json` 种子
|
|
181
181
|
[ ] 手改种子:加 `patterns`(URL / 日期 / ID 格式)+ `notEmpty`(核心字段)+ 收紧 `rowCount`
|
|
182
|
-
[ ] 再跑一次 `
|
|
182
|
+
[ ] 再跑一次 `ppcli browser verify <site>/<name>`,确认 ✓ matches fixture
|
|
183
183
|
[ ] 11. 字段值 vs 网页肉眼比对(别只看 "Adapter works!")
|
|
184
184
|
[ ] 12. 回写站点记忆(**verify 通过 + 肉眼比对对得上之后**,schema 见 `references/site-memory.md`):
|
|
185
185
|
[ ] `endpoints.json`:以 endpoint 的短名为 key,value = `{url, method, params.{required,optional}, response, verified_at: YYYY-MM-DD, notes}`
|
|
186
186
|
[ ] `field-map.json`:只追加新代号。key = 字段代号,value = `{meaning, verified_at: YYYY-MM-DD, source}`;**已存在的 key 不要覆盖**,有冲突先和网页肉眼值对齐再写
|
|
187
187
|
[ ] `notes.md`:顶部追加一段 `## YYYY-MM-DD by <agent/user>`,写本次写 adapter 时遇到的新坑 / 新结论
|
|
188
|
-
[ ] `verify/<cmd>.json`:**必填。** `
|
|
188
|
+
[ ] `verify/<cmd>.json`:**必填。** `ppcli browser verify` 的期望值(args / rowCount / columns / types / patterns / notEmpty),Step 10 已经让你生成了,这里只是 checklist
|
|
189
189
|
[ ] `fixtures/<cmd>-<YYYYMMDDHHMM>.json`:存一份该 endpoint 的完整响应样本(去掉 cookie / token / 用户私有字段再存),给后续字段对比 / 离线 replay 用
|
|
190
|
-
[ ] 调试过程中如果在 repo / adapter 目录 dump 过临时文件(`.dbg-*.html` / `raw-*.json` / 等),**在 commit 前清干净**——这些本来就该落在 `~/.
|
|
190
|
+
[ ] 调试过程中如果在 repo / adapter 目录 dump 过临时文件(`.dbg-*.html` / `raw-*.json` / 等),**在 commit 前清干净**——这些本来就该落在 `~/.ppcli/sites/<site>/fixtures/` 或 `/tmp/`
|
|
191
191
|
```
|
|
192
192
|
|
|
193
193
|
---
|
|
@@ -225,7 +225,7 @@ DONE
|
|
|
225
225
|
| `references/field-decode-playbook.md` | Step 7 字段不在词典时 |
|
|
226
226
|
| `references/output-design.md` | Step 8 命名 / 类型 / 顺序 |
|
|
227
227
|
| `references/adapter-template.md` | Step 9 文件结构 + 活例子 `convertible.js` |
|
|
228
|
-
| `references/site-memory.md` | 总览:in-repo 种子 + 本地 `~/.
|
|
228
|
+
| `references/site-memory.md` | 总览:in-repo 种子 + 本地 `~/.ppcli/sites/` 的两层结构 |
|
|
229
229
|
| `references/site-memory/<site>.md` | Step 2 读站点公共知识(eastmoney / xueqiu / bilibili / tonghuashun 已铺) |
|
|
230
230
|
| `references/success-rate-pitfalls.md` | Step 7 / 11 踩坑前翻:11 种"verify 能过但数据是错的"静默失败(含 aria-label locale-dependence) |
|
|
231
231
|
| `references/jsdom-fixture-pattern.md` | 当 adapter 走 `page.evaluate` 内 DOM 抽取、且 mocked-evaluate 单测漏 silent bug 时——把 HTML 冻进 `clis/<site>/__fixtures__/` 用 JSDOM 跑(含 fixture 创建 mandatory `awk 'NF>0'` 收紧 + reverse-validate 纪律) |
|
|
@@ -240,16 +240,16 @@ DONE
|
|
|
240
240
|
- **中间解析对象 key 不能跟 `columns` 任一项重叠**(否则 silent-column-drop audit 误判,PR #1329 R1 真踩过;改成专属命名 + push row 时 destructure aliasing)
|
|
241
241
|
- **`browser:` field 决定 func 签名**:`browser:false → (args)`,`browser:true → (page, args)`。搞反时 `args` 实际是 debug flag,所有外部参数 silent fallback 到 default(PR #1329 upstream 之前 8 个 non-browser adapter 全踩过这个)
|
|
242
242
|
- 已知失败按 [`references/typed-errors.md`](./references/typed-errors.md) 5-classification 抛对应 typed error;**不要** silent `return []`,**不要** silent `return [{sentinel}]`,**不要** `Math.max/min` silent clamp 外部参数
|
|
243
|
-
- 写私人 adapter 用 `~/.
|
|
243
|
+
- 写私人 adapter 用 `~/.ppcli/clis/<site>/<name>.js`(免 build);要提 PR 才 copy 到 `clis/<site>/<name>.js`
|
|
244
244
|
- 站点记忆每轮回写:没记忆 → 用 skill → 产生记忆 → 下次变 5 分钟
|
|
245
|
-
- **调试过程中的原始 dump / 抓包 / HTML 样本只能落在 `~/.
|
|
245
|
+
- **调试过程中的原始 dump / 抓包 / HTML 样本只能落在 `~/.ppcli/sites/<site>/fixtures/` 或 `/tmp/`。严禁在 repo 根目录、`clis/<site>/` 或当前工作目录留 `.dbg-*.html / raw-*.json / sample.*` 这类临时文件**(PR diff 会带上去,别人 review 时很烦)。
|
|
246
246
|
- **JSDOM unit-test fixture(`clis/<site>/__fixtures__/<command>.html`)是上面那条的例外**——它是有意 commit 进 repo 的 review artifact,不是临时 dump。但因此 quality bar 要更高:必须按 `references/jsdom-fixture-pattern.md` 的 5 步做完(含 mandatory `awk 'NF>0'` 空白行收紧),并 reverse-validate 一道证明 regression guard 真能挂。
|
|
247
247
|
|
|
248
248
|
---
|
|
249
249
|
|
|
250
250
|
## 卡住了
|
|
251
251
|
|
|
252
|
-
- 诊断类:`
|
|
252
|
+
- 诊断类:`ppcli doctor` → 看 `notes.md` → 搜 autofix skill
|
|
253
253
|
- 字段解码类:`field-decode-playbook.md` 全三节走完 → 先输出 raw 迭代
|
|
254
254
|
- endpoint 找不到:api-discovery §5 intercept 兜底
|
|
255
255
|
|
|
@@ -94,7 +94,7 @@ cli({
|
|
|
94
94
|
cli({
|
|
95
95
|
site: 'eastmoney', // 第一级命名空间,目录名一致
|
|
96
96
|
name: 'convertible', // 第二级,CLI 上的子命令
|
|
97
|
-
description: '...', // 一句话,出现在 `
|
|
97
|
+
description: '...', // 一句话,出现在 `ppcli list` 和 `ppcli <site> -h`
|
|
98
98
|
domain: 'push2.eastmoney.com', // 主要请求域名(诊断面板用)
|
|
99
99
|
strategy: Strategy.PUBLIC, // PUBLIC / COOKIE / INTERCEPT / UI
|
|
100
100
|
browser: false, // PUBLIC 几乎总是 false;COOKIE/INTERCEPT/UI 一律 true
|
|
@@ -327,7 +327,7 @@ const SEND_BUTTON_SELECTORS = [
|
|
|
327
327
|
5. **失败要 typed fail-fast**:找不到 control 应该 `CommandExecutionError` / send-failed,不要返回空 rows 或假成功
|
|
328
328
|
6. **不要给 framework 加 `--i18n "zh:提交,ja:送信"` 这种 flag** —— 等于把 fallback list 从 adapter 挪到 CLI,多一层 indirection 还要维护翻译字典。这是 over-engineering,已经在评审时被否
|
|
329
329
|
|
|
330
|
-
为什么不在 daemon 端固定 Chrome locale?因为
|
|
330
|
+
为什么不在 daemon 端固定 Chrome locale?因为 ppcli **不启动 Chrome**——daemon 是连用户已经在跑的 Chrome(CDP via extension),用户可能就是中文 UI / 中文资料检索需求。强制 en-US 会破坏用户的正当工作流。
|
|
331
331
|
|
|
332
332
|
### Cookie 域的双查
|
|
333
333
|
|
|
@@ -383,9 +383,9 @@ if (/暂时没有提醒内容/.test(html)) {
|
|
|
383
383
|
|
|
384
384
|
---
|
|
385
385
|
|
|
386
|
-
## Verify fixture(每个 adapter 配一份 `~/.
|
|
386
|
+
## Verify fixture(每个 adapter 配一份 `~/.ppcli/sites/<site>/verify/<name>.json`)
|
|
387
387
|
|
|
388
|
-
verify fixture 是"adapter 产出长什么样"的结构锚点。没有它,`
|
|
388
|
+
verify fixture 是"adapter 产出长什么样"的结构锚点。没有它,`ppcli browser verify` 只能证"adapter 能跑完不抛",证不出数据没错位。**必写**。
|
|
389
389
|
|
|
390
390
|
详细 schema 见 `site-memory.md` 的 `verify/<cmd>.json` 节。这里只讲两个容易踩的地方:
|
|
391
391
|
|
|
@@ -428,28 +428,28 @@ named-flag adapter(`hot` / `latest` 类)可以直接让工具生成种子:
|
|
|
428
428
|
|
|
429
429
|
```bash
|
|
430
430
|
# 1. 让 verify 先跑一遍,--write-fixture 生成种子(默认追加 --limit 3)
|
|
431
|
-
|
|
431
|
+
ppcli browser verify 1point3acres/hot --write-fixture
|
|
432
432
|
|
|
433
|
-
# 2. 手改 ~/.
|
|
433
|
+
# 2. 手改 ~/.ppcli/sites/1point3acres/verify/hot.json
|
|
434
434
|
# - patterns: 加 URL / 日期 / ID 正则
|
|
435
435
|
# - notEmpty: 加核心字段(title / author / url)
|
|
436
436
|
# - rowCount: 收紧到业务合理区间
|
|
437
437
|
|
|
438
438
|
# 3. 再跑 verify,fixture 吃得动就 OK
|
|
439
|
-
|
|
439
|
+
ppcli browser verify 1point3acres/hot
|
|
440
440
|
```
|
|
441
441
|
|
|
442
442
|
positional adapter 目前 `--write-fixture` 没法表达主语,**首份 fixture 要手写**:
|
|
443
443
|
|
|
444
444
|
```bash
|
|
445
445
|
# 1. 先直跑 adapter 看输出长啥样
|
|
446
|
-
|
|
446
|
+
ppcli 1point3acres thread 1173710 --limit 2 --format json | head
|
|
447
447
|
|
|
448
|
-
# 2. 照着响应手写 ~/.
|
|
448
|
+
# 2. 照着响应手写 ~/.ppcli/sites/1point3acres/verify/thread.json
|
|
449
449
|
# (args 一定用数组: ["1173710", "--limit", "2"])
|
|
450
450
|
|
|
451
451
|
# 3. 跑 verify 核对
|
|
452
|
-
|
|
452
|
+
ppcli browser verify 1point3acres/thread
|
|
453
453
|
```
|
|
454
454
|
|
|
455
455
|
机器生成的种子只有 rowCount.min=1 / columns / types,挡不住字段值错位。**patterns + notEmpty 无论哪种情形都是肉写的**。
|
|
@@ -459,13 +459,13 @@ opencli browser verify 1point3acres/thread
|
|
|
459
459
|
## 私人 adapter vs repo 贡献
|
|
460
460
|
|
|
461
461
|
```
|
|
462
|
-
~/.
|
|
462
|
+
~/.ppcli/clis/<site>/<name>.js # 私人
|
|
463
463
|
clis/<site>/<name>.js # repo 贡献
|
|
464
464
|
```
|
|
465
465
|
|
|
466
466
|
**两者在 `cli({...})` 层面完全一样**。差别只在运行入口:
|
|
467
467
|
|
|
468
|
-
- 私人:写完立即可跑(`
|
|
468
|
+
- 私人:写完立即可跑(`ppcli <site> <name>`)
|
|
469
469
|
- repo:要 `npm run build` 才被注册
|
|
470
470
|
|
|
471
|
-
先在 `~/.
|
|
471
|
+
先在 `~/.ppcli/clis/` 调通再拷贝到 `clis/`。
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
### 0.1 反爬厂商 → 决定 fetch 能不能从 Node 走
|
|
14
14
|
|
|
15
|
-
`
|
|
15
|
+
`ppcli browser analyze <url>` 的 `anti_bot` 字段给答案;手查看 cookies 也行:
|
|
16
16
|
|
|
17
17
|
| cookie / body 信号 | 厂商 | 裸 Node fetch / curl 结果 | 策略 |
|
|
18
18
|
|------------------|------|-----|-----|
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
判断:
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
|
|
33
|
+
ppcli browser eval "fetch('https://<target-subdomain>/api/...', {credentials:'include'}).then(r=>r.status).catch(e=>'cors:'+e.message)"
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
- 返回 status 数字 → CORS 通,继续
|
|
@@ -39,7 +39,7 @@ opencli browser eval "fetch('https://<target-subdomain>/api/...', {credentials:'
|
|
|
39
39
|
**挡住时**:不要把 `credentials:'include'` 当万能药——这只解决"带 cookie",不解决"跨 origin"。降级路径:
|
|
40
40
|
|
|
41
41
|
1. 换同 origin 的 endpoint(同一个 subdomain 下的 API 往往更宽松)
|
|
42
|
-
2. 用 `
|
|
42
|
+
2. 用 `ppcli browser open https://<target-subdomain>/`,让页面在目标 subdomain 本身打开,再 fetch 相对路径
|
|
43
43
|
3. 真跨域且无替代 → 走 `§5 intercept`,从页面自身发的请求里抓响应
|
|
44
44
|
|
|
45
45
|
---
|
|
@@ -49,7 +49,7 @@ opencli browser eval "fetch('https://<target-subdomain>/api/...', {credentials:'
|
|
|
49
49
|
### 拿候选
|
|
50
50
|
|
|
51
51
|
```bash
|
|
52
|
-
|
|
52
|
+
ppcli browser network
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
默认输出是 JSON,每个候选都带:
|
|
@@ -59,7 +59,7 @@ opencli browser network
|
|
|
59
59
|
|
|
60
60
|
静态资源 / 埋点 / 追踪默认已过滤。默认会保留 JSON / XML / plain text / `text/javascript` 这类 API 响应;如果你确定浏览器 DevTools 里有目标请求但这里缺失,用 `--all` 查一遍是否被 content-type 或 URL 噪音过滤挡掉。
|
|
61
61
|
|
|
62
|
-
如果是冷启动,先看 `
|
|
62
|
+
如果是冷启动,先看 `ppcli browser analyze <url>` 里的 `api_candidates`:
|
|
63
63
|
|
|
64
64
|
- `verdict: "likely_data"`:优先 replay 这条,拿 status / content-type / sample shape 填 strategy note
|
|
65
65
|
- `verdict: "maybe_data"`:可以试,但必须人工核对字段是否是目标业务数据
|
|
@@ -81,7 +81,7 @@ opencli browser network
|
|
|
81
81
|
已经知道目标 body 该含哪些字段就直接让 CLI 把列表筛到只剩候选,不用自己 scroll 翻 shape:
|
|
82
82
|
|
|
83
83
|
```bash
|
|
84
|
-
|
|
84
|
+
ppcli browser network --filter author,text,likes
|
|
85
85
|
```
|
|
86
86
|
|
|
87
87
|
- 字段以英文逗号分隔;AND 语义,必须每个字段都作为 shape 路径的**任意一段**出现才保留(`$.data.items[0].author` 命中 `author`、`items`、`data` 都算)
|
|
@@ -97,10 +97,10 @@ opencli browser network --filter author,text,likes
|
|
|
97
97
|
候选定了再拉完整 body(by key,不是 index — 数组顺序会随每次 capture 变):
|
|
98
98
|
|
|
99
99
|
```bash
|
|
100
|
-
|
|
100
|
+
ppcli browser network --detail <key>
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
capture 会持久化到 `~/.
|
|
103
|
+
capture 会持久化到 `~/.ppcli/cache/browser-network/<session>.json`(默认 TTL 24h),所以 `--detail` 即使跨多条其他命令也还在。
|
|
104
104
|
|
|
105
105
|
### 关键 request headers
|
|
106
106
|
|
|
@@ -120,14 +120,14 @@ capture 会持久化到 `~/.opencli/cache/browser-network/<session>.json`(默
|
|
|
120
120
|
|
|
121
121
|
```bash
|
|
122
122
|
# 滚到底(虚拟列表)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
ppcli browser eval "window.scrollTo(0, document.body.scrollHeight)"
|
|
124
|
+
ppcli browser wait time 2
|
|
125
|
+
ppcli browser network
|
|
126
126
|
|
|
127
127
|
# 点某个按钮
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
128
|
+
ppcli browser click <N>
|
|
129
|
+
ppcli browser wait time 2
|
|
130
|
+
ppcli browser network
|
|
131
131
|
```
|
|
132
132
|
|
|
133
133
|
---
|
|
@@ -137,7 +137,7 @@ opencli browser network
|
|
|
137
137
|
首屏数据常挂在这几个全局变量上:
|
|
138
138
|
|
|
139
139
|
```bash
|
|
140
|
-
|
|
140
|
+
ppcli browser eval "Object.keys(window).filter(k=>k.startsWith('__'))"
|
|
141
141
|
```
|
|
142
142
|
|
|
143
143
|
命中的常见名:
|
|
@@ -153,7 +153,7 @@ opencli browser eval "Object.keys(window).filter(k=>k.startsWith('__'))"
|
|
|
153
153
|
取数据:
|
|
154
154
|
|
|
155
155
|
```bash
|
|
156
|
-
|
|
156
|
+
ppcli browser eval "JSON.stringify(window.__NEXT_DATA__).slice(0, 3000)"
|
|
157
157
|
```
|
|
158
158
|
|
|
159
159
|
**关键**:inline state 只覆盖首屏的一部分(通常是 SEO 相关字段)。分页 / 评论 / 懒加载还是得回 §1 抓 API。
|
|
@@ -167,7 +167,7 @@ opencli browser eval "JSON.stringify(window.__NEXT_DATA__).slice(0, 3000)"
|
|
|
167
167
|
### 扫 script src
|
|
168
168
|
|
|
169
169
|
```bash
|
|
170
|
-
|
|
170
|
+
ppcli browser eval "[...document.querySelectorAll('script[src]')].map(s=>s.src).filter(s=>!/\\.(css|png|jpg|svg|woff|mp4)$/.test(s)&&!/googletagmanager|crazyegg|sentry|doubleclick|amazon-adsystem|cloudflare/.test(s))"
|
|
171
171
|
```
|
|
172
172
|
|
|
173
173
|
看结果里的 hostname:
|
|
@@ -178,7 +178,7 @@ opencli browser eval "[...document.querySelectorAll('script[src]')].map(s=>s.src
|
|
|
178
178
|
### 搜 bundle 里的 baseURL
|
|
179
179
|
|
|
180
180
|
```bash
|
|
181
|
-
|
|
181
|
+
ppcli browser eval "(async()=>{const s=[...document.querySelectorAll('script[src]')].map(e=>e.src).find(s=>/main|app|index|bundle|chunk/.test(s));if(!s)return'no bundle';const t=await fetch(s).then(r=>r.text());const patterns=['baseURL','baseUrl','BASE_URL','apiHost','apiBase','API_HOST','API_BASE'];const hits=[];for(const p of patterns){let i=-1;while((i=t.indexOf(p,i+1))>-1&&hits.length<5)hits.push(t.slice(Math.max(0,i-5),i+80));}return hits})()"
|
|
182
182
|
```
|
|
183
183
|
|
|
184
184
|
命中 `baseURL:"https://api.foo.com"` 直接拿 host 拼 endpoint。
|
|
@@ -188,7 +188,7 @@ opencli browser eval "(async()=>{const s=[...document.querySelectorAll('script[s
|
|
|
188
188
|
像 eastmoney 这种经验 endpoint 可以直接喂:
|
|
189
189
|
|
|
190
190
|
```bash
|
|
191
|
-
|
|
191
|
+
ppcli browser eval "fetch('https://push2.eastmoney.com/api/qt/clist/get?fs=m:1+t:2&pn=1&pz=5&fltt=2&fid=f3&po=1&fields=f2,f3,f12,f14').then(r=>r.json())"
|
|
192
192
|
```
|
|
193
193
|
|
|
194
194
|
200 且数据对得上就认。
|
|
@@ -202,7 +202,7 @@ opencli browser eval "fetch('https://push2.eastmoney.com/api/qt/clist/get?fs=m:1
|
|
|
202
202
|
|
|
203
203
|
```bash
|
|
204
204
|
# 当前页加 .json 试
|
|
205
|
-
|
|
205
|
+
ppcli browser eval "fetch(location.pathname.replace(/\\/$/,'')+'.json').then(r=>r.ok?r.json():'no')"
|
|
206
206
|
```
|
|
207
207
|
|
|
208
208
|
---
|
|
@@ -214,7 +214,7 @@ opencli browser eval "fetch(location.pathname.replace(/\\/$/,'')+'.json').then(r
|
|
|
214
214
|
### Cookie 里
|
|
215
215
|
|
|
216
216
|
```bash
|
|
217
|
-
|
|
217
|
+
ppcli browser eval "document.cookie.split('; ').reduce((o,x)=>{const[k,v]=x.split('=');o[k]=v;return o},{})"
|
|
218
218
|
```
|
|
219
219
|
|
|
220
220
|
常见 token cookie 名:`ct0`(Twitter CSRF)、`xq_a_token`(雪球)、`SESSDATA`(B 站)、`_csrf / csrfToken`(通用)。
|
|
@@ -226,7 +226,7 @@ opencli browser eval "document.cookie.split('; ').reduce((o,x)=>{const[k,v]=x.sp
|
|
|
226
226
|
### localStorage / sessionStorage 里
|
|
227
227
|
|
|
228
228
|
```bash
|
|
229
|
-
|
|
229
|
+
ppcli browser eval "Object.keys(localStorage).map(k=>k+' => '+localStorage.getItem(k).slice(0,50))"
|
|
230
230
|
```
|
|
231
231
|
|
|
232
232
|
找 `token / auth / jwt / bearer` 关键字。
|
|
@@ -236,7 +236,7 @@ opencli browser eval "Object.keys(localStorage).map(k=>k+' => '+localStorage.get
|
|
|
236
236
|
有些站的 Bearer 是全站一个常量(Twitter 的匿名 Bearer)。在 bundle 里搜:
|
|
237
237
|
|
|
238
238
|
```bash
|
|
239
|
-
|
|
239
|
+
ppcli browser eval "(async()=>{const s=[...document.querySelectorAll('script[src]')].map(e=>e.src).find(s=>/main|app|bundle/.test(s));const t=await fetch(s).then(r=>r.text());const m=t.match(/Bearer\\s+[\\w-]{20,}/g);return m?.slice(0,3)||'not found'})()"
|
|
240
240
|
```
|
|
241
241
|
|
|
242
242
|
### Store action 绕签名
|
|
@@ -245,10 +245,10 @@ Vue + Pinia / Redux / React Context 有时直接调 store method 能绕过签名
|
|
|
245
245
|
|
|
246
246
|
```bash
|
|
247
247
|
# Pinia
|
|
248
|
-
|
|
248
|
+
ppcli browser eval "typeof __pinia !== 'undefined' ? Object.keys(__pinia.state.value) : 'no pinia'"
|
|
249
249
|
|
|
250
250
|
# 直接调 store action(每个站点具体 action 名要查)
|
|
251
|
-
|
|
251
|
+
ppcli browser eval "window.__pinia.state.value.someStore.someMethod({...})"
|
|
252
252
|
```
|
|
253
253
|
|
|
254
254
|
---
|