@jackwener/opencli 1.6.0 → 1.6.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/CHANGELOG.md +8 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +27 -45
- package/README.zh-CN.md +32 -34
- package/autoresearch/browse-tasks.json +18 -20
- package/autoresearch/commands/debug.ts +163 -0
- package/autoresearch/commands/fix.ts +145 -0
- package/autoresearch/commands/plan.ts +88 -0
- package/autoresearch/commands/run.ts +138 -0
- package/autoresearch/config.ts +82 -0
- package/autoresearch/engine.ts +359 -0
- package/autoresearch/eval-all.ts +127 -0
- package/autoresearch/eval-browse.ts +1 -1
- package/autoresearch/eval-publish.ts +238 -0
- package/autoresearch/eval-save.ts +249 -0
- package/autoresearch/eval-skill.ts +14 -8
- package/autoresearch/eval-v2ex.ts +220 -0
- package/autoresearch/eval-zhihu.ts +230 -0
- package/autoresearch/logger.ts +69 -0
- package/autoresearch/presets/combined-reliability.ts +27 -0
- package/autoresearch/presets/index.ts +23 -0
- package/autoresearch/presets/operate-reliability.ts +24 -0
- package/autoresearch/presets/save-reliability.ts +26 -0
- package/autoresearch/presets/skill-quality.ts +20 -0
- package/autoresearch/presets/v2ex-reliability.ts +24 -0
- package/autoresearch/presets/zhihu-reliability.ts +25 -0
- package/autoresearch/publish-tasks.json +345 -0
- package/autoresearch/run-save.sh +11 -0
- package/autoresearch/save-adapters/xhs-explore-deep.ts +64 -0
- package/autoresearch/save-adapters/xhs-note-comments.ts +61 -0
- package/autoresearch/save-adapters/xhs-search-full.ts +62 -0
- package/autoresearch/save-adapters/zhihu-hot-detail.ts +52 -0
- package/autoresearch/save-adapters/zhihu-question-full.ts +57 -0
- package/autoresearch/save-adapters/zhihu-search-detail.ts +53 -0
- package/autoresearch/save-tasks.json +281 -0
- package/autoresearch/v2ex-tasks.json +899 -0
- package/autoresearch/zhihu-tasks.json +848 -0
- package/bun.lock +615 -0
- package/dist/browser/base-page.d.ts +4 -2
- package/dist/browser/base-page.js +37 -4
- package/dist/browser/bridge.js +10 -8
- package/dist/browser/cdp.js +2 -6
- package/dist/browser/daemon-client.d.ts +11 -1
- package/dist/browser/daemon-client.js +3 -0
- package/dist/browser/dom-helpers.d.ts +4 -2
- package/dist/browser/dom-helpers.js +42 -31
- package/dist/browser/dom-snapshot.js +23 -1
- package/dist/browser/page.d.ts +7 -2
- package/dist/browser/page.js +112 -30
- package/dist/browser.test.js +1 -1
- package/dist/build-manifest.d.ts +1 -0
- package/dist/build-manifest.js +1 -0
- package/dist/cli-manifest.json +1133 -182
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +48 -7
- package/dist/cli.test.d.ts +1 -0
- package/dist/cli.test.js +88 -0
- package/dist/clis/1688/item.d.ts +70 -0
- package/dist/clis/1688/item.js +187 -0
- package/dist/clis/1688/item.test.d.ts +1 -0
- package/dist/clis/1688/item.test.js +67 -0
- package/dist/clis/1688/search.d.ts +56 -0
- package/dist/clis/1688/search.js +309 -0
- package/dist/clis/1688/search.test.d.ts +1 -0
- package/dist/clis/1688/search.test.js +75 -0
- package/dist/clis/1688/shared.d.ts +112 -0
- package/dist/clis/1688/shared.js +514 -0
- package/dist/clis/1688/shared.test.d.ts +1 -0
- package/dist/clis/1688/shared.test.js +57 -0
- package/dist/clis/1688/store.d.ts +45 -0
- package/dist/clis/1688/store.js +226 -0
- package/dist/clis/1688/store.test.d.ts +1 -0
- package/dist/clis/1688/store.test.js +62 -0
- package/dist/clis/amazon/bestsellers.d.ts +0 -20
- package/dist/clis/amazon/bestsellers.js +6 -129
- package/dist/clis/amazon/bestsellers.test.js +12 -3
- package/dist/clis/amazon/movers-shakers.d.ts +1 -0
- package/dist/clis/amazon/movers-shakers.js +7 -0
- package/dist/clis/amazon/new-releases.d.ts +1 -0
- package/dist/clis/amazon/new-releases.js +7 -0
- package/dist/clis/amazon/rankings.d.ts +59 -0
- package/dist/clis/amazon/rankings.js +226 -0
- package/dist/clis/amazon/rankings.test.d.ts +1 -0
- package/dist/clis/amazon/rankings.test.js +41 -0
- package/dist/clis/amazon/shared.d.ts +11 -0
- package/dist/clis/amazon/shared.js +121 -11
- package/dist/clis/amazon/shared.test.js +11 -0
- package/dist/clis/bilibili/comments.js +2 -2
- package/dist/clis/bilibili/comments.test.js +3 -2
- package/dist/clis/bilibili/download.js +2 -1
- package/dist/clis/bilibili/subtitle.js +4 -3
- package/dist/clis/bilibili/subtitle.test.js +2 -1
- package/dist/clis/bilibili/utils.d.ts +5 -0
- package/dist/clis/bilibili/utils.js +30 -0
- package/dist/clis/bilibili/utils.test.d.ts +1 -0
- package/dist/clis/bilibili/utils.test.js +17 -0
- package/dist/clis/douban/marks.js +1 -1
- package/dist/clis/douban/subject.yaml +50 -19
- package/dist/clis/doubao/utils.js +32 -12
- package/dist/clis/douyin/_shared/browser-fetch.test.js +0 -1
- package/dist/clis/douyin/_shared/transcode.test.js +0 -2
- package/dist/clis/douyin/draft.test.js +0 -2
- package/dist/clis/facebook/search.test.js +0 -2
- package/dist/clis/gemini/ask.js +9 -3
- package/dist/clis/gemini/ask.test.d.ts +1 -0
- package/dist/clis/gemini/ask.test.js +100 -0
- package/dist/clis/gemini/reply-state.test.d.ts +1 -0
- package/dist/clis/gemini/reply-state.test.js +641 -0
- package/dist/clis/gemini/utils.d.ts +44 -1
- package/dist/clis/gemini/utils.js +528 -61
- package/dist/clis/gemini/utils.test.js +149 -2
- package/dist/clis/hupu/detail.d.ts +1 -0
- package/dist/clis/hupu/detail.js +72 -0
- package/dist/clis/hupu/hot.yaml +43 -0
- package/dist/clis/hupu/like.d.ts +1 -0
- package/dist/clis/hupu/like.js +75 -0
- package/dist/clis/hupu/reply.d.ts +1 -0
- package/dist/clis/hupu/reply.js +71 -0
- package/dist/clis/hupu/search.d.ts +1 -0
- package/dist/clis/hupu/search.js +59 -0
- package/dist/clis/hupu/unlike.d.ts +1 -0
- package/dist/clis/hupu/unlike.js +75 -0
- package/dist/clis/hupu/utils.d.ts +20 -0
- package/dist/clis/hupu/utils.js +319 -0
- package/dist/clis/instagram/_shared/private-publish.d.ts +138 -0
- package/dist/clis/instagram/_shared/private-publish.js +1030 -0
- package/dist/clis/instagram/_shared/private-publish.test.d.ts +1 -0
- package/dist/clis/instagram/_shared/private-publish.test.js +705 -0
- package/dist/clis/instagram/_shared/protocol-capture.d.ts +26 -0
- package/dist/clis/instagram/_shared/protocol-capture.js +282 -0
- package/dist/clis/instagram/_shared/protocol-capture.test.d.ts +1 -0
- package/dist/clis/instagram/_shared/protocol-capture.test.js +114 -0
- package/dist/clis/instagram/_shared/runtime-info.d.ts +9 -0
- package/dist/clis/instagram/_shared/runtime-info.js +81 -0
- package/dist/clis/instagram/note.d.ts +1 -0
- package/dist/clis/instagram/note.js +222 -0
- package/dist/clis/instagram/note.test.d.ts +1 -0
- package/dist/clis/instagram/note.test.js +81 -0
- package/dist/clis/instagram/post.d.ts +4 -0
- package/dist/clis/instagram/post.js +1496 -0
- package/dist/clis/instagram/post.test.d.ts +1 -0
- package/dist/clis/instagram/post.test.js +1647 -0
- package/dist/clis/instagram/reel.d.ts +1 -0
- package/dist/clis/instagram/reel.js +826 -0
- package/dist/clis/instagram/reel.test.d.ts +1 -0
- package/dist/clis/instagram/reel.test.js +167 -0
- package/dist/clis/instagram/story.d.ts +1 -0
- package/dist/clis/instagram/story.js +115 -0
- package/dist/clis/instagram/story.test.d.ts +1 -0
- package/dist/clis/instagram/story.test.js +167 -0
- package/dist/clis/sinafinance/stock-rank.d.ts +4 -0
- package/dist/clis/sinafinance/stock-rank.js +65 -0
- package/dist/clis/substack/utils.test.js +0 -2
- package/dist/clis/twitter/post.js +72 -45
- package/dist/clis/twitter/post.test.d.ts +1 -0
- package/dist/clis/twitter/post.test.js +116 -0
- package/dist/clis/twitter/reply.d.ts +12 -0
- package/dist/clis/twitter/reply.js +257 -35
- package/dist/clis/twitter/reply.test.d.ts +1 -0
- package/dist/clis/twitter/reply.test.js +151 -0
- package/dist/clis/twitter/search.js +67 -5
- package/dist/clis/twitter/search.test.js +83 -5
- package/dist/clis/xianyu/chat.d.ts +7 -0
- package/dist/clis/xianyu/chat.js +146 -0
- package/dist/clis/xianyu/chat.test.d.ts +1 -0
- package/dist/clis/xianyu/chat.test.js +15 -0
- package/dist/clis/xianyu/item.d.ts +7 -0
- package/dist/clis/xianyu/item.js +152 -0
- package/dist/clis/xianyu/item.test.d.ts +1 -0
- package/dist/clis/xianyu/item.test.js +56 -0
- package/dist/clis/xianyu/search.d.ts +10 -0
- package/dist/clis/xianyu/search.js +134 -0
- package/dist/clis/xianyu/search.test.d.ts +1 -0
- package/dist/clis/xianyu/search.test.js +17 -0
- package/dist/clis/xianyu/utils.d.ts +1 -0
- package/dist/clis/xianyu/utils.js +8 -0
- package/dist/clis/xiaoe/catalog.yaml +129 -0
- package/dist/clis/xiaoe/content.yaml +43 -0
- package/dist/clis/xiaoe/courses.yaml +73 -0
- package/dist/clis/xiaoe/detail.yaml +39 -0
- package/dist/clis/xiaoe/play-url.yaml +124 -0
- package/dist/clis/xiaohongshu/comments.test.js +0 -2
- package/dist/clis/xiaohongshu/creator-note-detail.test.js +0 -2
- package/dist/clis/xiaohongshu/creator-notes.test.js +0 -2
- package/dist/clis/xiaohongshu/download.test.js +0 -2
- package/dist/clis/xiaohongshu/note.test.js +0 -2
- package/dist/clis/xiaohongshu/publish.test.js +0 -2
- package/dist/clis/xiaohongshu/search.js +29 -20
- package/dist/clis/xiaohongshu/search.test.js +56 -48
- package/dist/clis/yuanbao/ask.d.ts +21 -0
- package/dist/clis/yuanbao/ask.js +427 -0
- package/dist/clis/yuanbao/ask.test.d.ts +1 -0
- package/dist/clis/yuanbao/ask.test.js +124 -0
- package/dist/clis/yuanbao/new.d.ts +1 -0
- package/dist/clis/yuanbao/new.js +70 -0
- package/dist/clis/yuanbao/new.test.d.ts +1 -0
- package/dist/clis/yuanbao/new.test.js +30 -0
- package/dist/clis/yuanbao/shared.d.ts +13 -0
- package/dist/clis/yuanbao/shared.js +49 -0
- package/dist/clis/zhihu/question.js +30 -19
- package/dist/clis/zhihu/question.test.js +34 -16
- package/dist/commanderAdapter.js +8 -4
- package/dist/commanderAdapter.test.js +42 -0
- package/dist/completion.js +3 -1
- package/dist/completion.test.d.ts +1 -0
- package/dist/completion.test.js +23 -0
- package/dist/doctor.js +1 -1
- package/dist/electron-apps.d.ts +2 -0
- package/dist/electron-apps.js +7 -1
- package/dist/errors.js +1 -1
- package/dist/execution.js +25 -35
- package/dist/explore.js +1 -1
- package/dist/launcher.d.ts +4 -0
- package/dist/launcher.js +64 -8
- package/dist/launcher.test.js +88 -7
- package/dist/output.d.ts +2 -0
- package/dist/output.js +10 -1
- package/dist/output.test.d.ts +0 -3
- package/dist/output.test.js +59 -92
- package/dist/pipeline/executor.test.js +0 -2
- package/dist/pipeline/steps/download.test.js +0 -2
- package/dist/registry.d.ts +2 -0
- package/dist/serialization.d.ts +1 -0
- package/dist/serialization.js +1 -0
- package/dist/types.d.ts +9 -2
- package/docs/.vitepress/config.mts +4 -0
- package/docs/adapters/browser/1688.md +52 -0
- package/docs/adapters/browser/36kr.md +2 -1
- package/docs/adapters/browser/doubao.md +5 -1
- package/docs/adapters/browser/hupu.md +53 -0
- package/docs/adapters/browser/sinafinance.md +32 -2
- package/docs/adapters/browser/weibo.md +6 -1
- package/docs/adapters/browser/wikipedia.md +2 -0
- package/docs/adapters/browser/xianyu.md +42 -0
- package/docs/adapters/browser/xiaoe.md +44 -0
- package/docs/adapters/browser/yuanbao.md +64 -0
- package/docs/adapters/index.md +14 -5
- package/docs/comparison.md +1 -1
- package/docs/developer/ai-workflow.md +2 -2
- package/docs/developer/contributing.md +1 -1
- package/docs/developer/testing.md +2 -0
- package/docs/guide/plugins.md +1 -0
- package/docs/guide/troubleshooting.md +11 -0
- package/docs/superpowers/specs/2026-04-03-v2ex-autoresearch-design.md +41 -0
- package/docs/zh/guide/plugins.md +1 -0
- package/extension/dist/background.js +1127 -0
- package/extension/src/background.test.ts +39 -0
- package/extension/src/background.ts +223 -34
- package/extension/src/cdp.ts +194 -4
- package/extension/src/protocol.ts +22 -1
- package/package.json +3 -2
- package/scripts/postinstall.js +1 -1
- package/skills/opencli-explorer/SKILL.md +1 -1
- package/skills/opencli-oneshot/SKILL.md +2 -2
- package/skills/opencli-operate/SKILL.md +120 -27
- package/skills/opencli-usage/SKILL.md +31 -20
- package/skills/opencli-usage/browser.md +114 -16
- package/skills/opencli-usage/public-api.md +32 -3
- package/skills/smart-search/SKILL.md +156 -0
- package/skills/smart-search/references/sources-ai.md +74 -0
- package/skills/smart-search/references/sources-info.md +43 -0
- package/skills/smart-search/references/sources-media.md +50 -0
- package/skills/smart-search/references/sources-other.md +42 -0
- package/skills/smart-search/references/sources-shopping.md +31 -0
- package/skills/smart-search/references/sources-social.md +51 -0
- package/skills/smart-search/references/sources-tech.md +42 -0
- package/skills/smart-search/references/sources-travel.md +20 -0
- package/src/browser/base-page.ts +41 -6
- package/src/browser/bridge.ts +11 -8
- package/src/browser/cdp.ts +1 -8
- package/src/browser/daemon-client.ts +11 -1
- package/src/browser/dom-helpers.ts +43 -31
- package/src/browser/dom-snapshot.ts +23 -1
- package/src/browser/page.ts +115 -31
- package/src/browser.test.ts +1 -1
- package/src/build-manifest.ts +2 -0
- package/src/cli.test.ts +133 -0
- package/src/cli.ts +73 -11
- package/src/clis/1688/item.test.ts +69 -0
- package/src/clis/1688/item.ts +282 -0
- package/src/clis/1688/search.test.ts +81 -0
- package/src/clis/1688/search.ts +402 -0
- package/src/clis/1688/shared.test.ts +75 -0
- package/src/clis/1688/shared.ts +623 -0
- package/src/clis/1688/store.test.ts +69 -0
- package/src/clis/1688/store.ts +300 -0
- package/src/clis/amazon/bestsellers.test.ts +12 -3
- package/src/clis/amazon/bestsellers.ts +6 -178
- package/src/clis/amazon/movers-shakers.ts +8 -0
- package/src/clis/amazon/new-releases.ts +8 -0
- package/src/clis/amazon/rankings.test.ts +47 -0
- package/src/clis/amazon/rankings.ts +312 -0
- package/src/clis/amazon/shared.test.ts +16 -0
- package/src/clis/amazon/shared.ts +134 -12
- package/src/clis/bilibili/comments.test.ts +4 -3
- package/src/clis/bilibili/comments.ts +2 -2
- package/src/clis/bilibili/download.ts +2 -1
- package/src/clis/bilibili/subtitle.test.ts +2 -1
- package/src/clis/bilibili/subtitle.ts +4 -3
- package/src/clis/bilibili/utils.test.ts +21 -0
- package/src/clis/bilibili/utils.ts +27 -0
- package/src/clis/douban/marks.ts +1 -1
- package/src/clis/douban/subject.yaml +50 -19
- package/src/clis/doubao/utils.ts +32 -12
- package/src/clis/douyin/_shared/browser-fetch.test.ts +0 -1
- package/src/clis/douyin/_shared/transcode.test.ts +0 -2
- package/src/clis/douyin/draft.test.ts +0 -2
- package/src/clis/facebook/search.test.ts +0 -2
- package/src/clis/gemini/ask.test.ts +116 -0
- package/src/clis/gemini/ask.ts +10 -3
- package/src/clis/gemini/reply-state.test.ts +708 -0
- package/src/clis/gemini/utils.test.ts +184 -2
- package/src/clis/gemini/utils.ts +588 -60
- package/src/clis/hupu/detail.ts +126 -0
- package/src/clis/hupu/hot.yaml +43 -0
- package/src/clis/hupu/like.ts +76 -0
- package/src/clis/hupu/reply.ts +76 -0
- package/src/clis/hupu/search.ts +95 -0
- package/src/clis/hupu/unlike.ts +76 -0
- package/src/clis/hupu/utils.ts +381 -0
- package/src/clis/instagram/_shared/private-publish.test.ts +827 -0
- package/src/clis/instagram/_shared/private-publish.ts +1303 -0
- package/src/clis/instagram/_shared/protocol-capture.test.ts +148 -0
- package/src/clis/instagram/_shared/protocol-capture.ts +321 -0
- package/src/clis/instagram/_shared/runtime-info.ts +91 -0
- package/src/clis/instagram/note.test.ts +96 -0
- package/src/clis/instagram/note.ts +254 -0
- package/src/clis/instagram/post.test.ts +1716 -0
- package/src/clis/instagram/post.ts +1620 -0
- package/src/clis/instagram/reel.test.ts +191 -0
- package/src/clis/instagram/reel.ts +886 -0
- package/src/clis/instagram/story.test.ts +191 -0
- package/src/clis/instagram/story.ts +151 -0
- package/src/clis/sinafinance/stock-rank.ts +68 -0
- package/src/clis/substack/utils.test.ts +0 -2
- package/src/clis/twitter/post.test.ts +157 -0
- package/src/clis/twitter/post.ts +82 -48
- package/src/clis/twitter/reply.test.ts +177 -0
- package/src/clis/twitter/reply.ts +285 -39
- package/src/clis/twitter/search.test.ts +88 -5
- package/src/clis/twitter/search.ts +68 -5
- package/src/clis/xianyu/chat.test.ts +20 -0
- package/src/clis/xianyu/chat.ts +175 -0
- package/src/clis/xianyu/item.test.ts +67 -0
- package/src/clis/xianyu/item.ts +172 -0
- package/src/clis/xianyu/search.test.ts +22 -0
- package/src/clis/xianyu/search.ts +151 -0
- package/src/clis/xianyu/utils.ts +9 -0
- package/src/clis/xiaoe/catalog.yaml +129 -0
- package/src/clis/xiaoe/content.yaml +43 -0
- package/src/clis/xiaoe/courses.yaml +73 -0
- package/src/clis/xiaoe/detail.yaml +39 -0
- package/src/clis/xiaoe/play-url.yaml +124 -0
- package/src/clis/xiaohongshu/comments.test.ts +0 -2
- package/src/clis/xiaohongshu/creator-note-detail.test.ts +0 -2
- package/src/clis/xiaohongshu/creator-notes.test.ts +0 -2
- package/src/clis/xiaohongshu/download.test.ts +0 -2
- package/src/clis/xiaohongshu/note.test.ts +0 -2
- package/src/clis/xiaohongshu/publish.test.ts +0 -2
- package/src/clis/xiaohongshu/search.test.ts +59 -48
- package/src/clis/xiaohongshu/search.ts +31 -21
- package/src/clis/yuanbao/ask.test.ts +156 -0
- package/src/clis/yuanbao/ask.ts +522 -0
- package/src/clis/yuanbao/new.test.ts +36 -0
- package/src/clis/yuanbao/new.ts +81 -0
- package/src/clis/yuanbao/shared.ts +57 -0
- package/src/clis/zhihu/question.test.ts +42 -17
- package/src/clis/zhihu/question.ts +31 -26
- package/src/commanderAdapter.test.ts +51 -0
- package/src/commanderAdapter.ts +8 -4
- package/src/completion.test.ts +30 -0
- package/src/completion.ts +3 -1
- package/src/doctor.ts +1 -1
- package/src/electron-apps.ts +9 -1
- package/src/errors.ts +1 -1
- package/src/execution.ts +26 -30
- package/src/explore.ts +1 -1
- package/src/launcher.test.ts +121 -7
- package/src/launcher.ts +87 -9
- package/src/output.test.ts +50 -90
- package/src/output.ts +10 -1
- package/src/pipeline/executor.test.ts +0 -2
- package/src/pipeline/steps/download.test.ts +0 -2
- package/src/registry.ts +2 -0
- package/src/serialization.ts +2 -0
- package/src/types.ts +9 -2
- package/tests/e2e/browser-auth.test.ts +9 -0
- package/CLI-EXPLORER.md +0 -724
- package/CLI-ONESHOT.md +0 -216
- package/SKILL.md +0 -59
|
@@ -20,8 +20,6 @@ function createPageMock(evaluateResults: any[]): IPage {
|
|
|
20
20
|
getFormState: vi.fn().mockResolvedValue({ forms: [], orphanFields: [] }),
|
|
21
21
|
wait: vi.fn().mockResolvedValue(undefined),
|
|
22
22
|
tabs: vi.fn().mockResolvedValue([]),
|
|
23
|
-
closeTab: vi.fn().mockResolvedValue(undefined),
|
|
24
|
-
newTab: vi.fn().mockResolvedValue(undefined),
|
|
25
23
|
selectTab: vi.fn().mockResolvedValue(undefined),
|
|
26
24
|
networkRequests: vi.fn().mockResolvedValue([]),
|
|
27
25
|
consoleMessages: vi.fn().mockResolvedValue([]),
|
|
@@ -41,8 +39,8 @@ describe('xiaohongshu search', () => {
|
|
|
41
39
|
expect(cmd?.func).toBeTypeOf('function');
|
|
42
40
|
|
|
43
41
|
const page = createPageMock([
|
|
44
|
-
// First evaluate:
|
|
45
|
-
|
|
42
|
+
// First evaluate: MutationObserver wait (login wall detected)
|
|
43
|
+
'login_wall',
|
|
46
44
|
]);
|
|
47
45
|
|
|
48
46
|
await expect(cmd!.func!(page, { query: '特斯拉', limit: 5 })).rejects.toThrow(
|
|
@@ -63,21 +61,18 @@ describe('xiaohongshu search', () => {
|
|
|
63
61
|
'https://www.xiaohongshu.com/user/profile/635a9c720000000018028b40?xsec_token=user-token&xsec_source=pc_search';
|
|
64
62
|
|
|
65
63
|
const page = createPageMock([
|
|
66
|
-
// First evaluate:
|
|
67
|
-
|
|
68
|
-
// Second evaluate: main DOM extraction
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
},
|
|
79
|
-
],
|
|
80
|
-
},
|
|
64
|
+
// First evaluate: MutationObserver wait (content appeared)
|
|
65
|
+
'content',
|
|
66
|
+
// Second evaluate: main DOM extraction (returns array directly)
|
|
67
|
+
[
|
|
68
|
+
{
|
|
69
|
+
title: '某鱼买FSD被坑了4万',
|
|
70
|
+
author: '随风',
|
|
71
|
+
likes: '261',
|
|
72
|
+
url: detailUrl,
|
|
73
|
+
author_url: authorUrl,
|
|
74
|
+
},
|
|
75
|
+
],
|
|
81
76
|
]);
|
|
82
77
|
|
|
83
78
|
const result = await cmd!.func!(page, { query: '特斯拉', limit: 1 });
|
|
@@ -103,35 +98,32 @@ describe('xiaohongshu search', () => {
|
|
|
103
98
|
expect(cmd?.func).toBeTypeOf('function');
|
|
104
99
|
|
|
105
100
|
const page = createPageMock([
|
|
106
|
-
// First evaluate:
|
|
107
|
-
|
|
108
|
-
// Second evaluate: main DOM extraction
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
},
|
|
133
|
-
],
|
|
134
|
-
},
|
|
101
|
+
// First evaluate: MutationObserver wait (content appeared)
|
|
102
|
+
'content',
|
|
103
|
+
// Second evaluate: main DOM extraction (returns array directly)
|
|
104
|
+
[
|
|
105
|
+
{
|
|
106
|
+
title: 'Result A',
|
|
107
|
+
author: 'UserA',
|
|
108
|
+
likes: '10',
|
|
109
|
+
url: 'https://www.xiaohongshu.com/search_result/aaa',
|
|
110
|
+
author_url: '',
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
title: '',
|
|
114
|
+
author: 'UserB',
|
|
115
|
+
likes: '5',
|
|
116
|
+
url: 'https://www.xiaohongshu.com/search_result/bbb',
|
|
117
|
+
author_url: '',
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
title: 'Result C',
|
|
121
|
+
author: 'UserC',
|
|
122
|
+
likes: '3',
|
|
123
|
+
url: 'https://www.xiaohongshu.com/search_result/ccc',
|
|
124
|
+
author_url: '',
|
|
125
|
+
},
|
|
126
|
+
],
|
|
135
127
|
]);
|
|
136
128
|
|
|
137
129
|
const result = (await cmd!.func!(page, { query: '测试', limit: 1 })) as any[];
|
|
@@ -140,6 +132,25 @@ describe('xiaohongshu search', () => {
|
|
|
140
132
|
expect(result).toHaveLength(1);
|
|
141
133
|
expect(result[0]).toMatchObject({ rank: 1, title: 'Result A' });
|
|
142
134
|
});
|
|
135
|
+
|
|
136
|
+
it('waits for content via MutationObserver before extracting', async () => {
|
|
137
|
+
const cmd = getRegistry().get('xiaohongshu/search');
|
|
138
|
+
expect(cmd?.func).toBeTypeOf('function');
|
|
139
|
+
|
|
140
|
+
const page = createPageMock([
|
|
141
|
+
// First evaluate: MutationObserver wait (content appeared)
|
|
142
|
+
'content',
|
|
143
|
+
// Second evaluate: extraction (returns empty array)
|
|
144
|
+
[],
|
|
145
|
+
]);
|
|
146
|
+
|
|
147
|
+
const result = (await cmd!.func!(page, { query: '测试等待', limit: 5 })) as any[];
|
|
148
|
+
expect(result).toHaveLength(0);
|
|
149
|
+
// Only one navigation, no retry
|
|
150
|
+
expect(page.goto).toHaveBeenCalledTimes(1);
|
|
151
|
+
// Two evaluate calls: wait + extraction
|
|
152
|
+
expect(page.evaluate).toHaveBeenCalledTimes(2);
|
|
153
|
+
});
|
|
143
154
|
});
|
|
144
155
|
|
|
145
156
|
describe('noteIdToDate (ObjectID timestamp parsing)', () => {
|
|
@@ -9,6 +9,29 @@
|
|
|
9
9
|
import { cli, Strategy } from '../../registry.js';
|
|
10
10
|
import { AuthRequiredError } from '../../errors.js';
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Wait for search results or login wall using MutationObserver (max 5s).
|
|
14
|
+
* Returns 'content' if note items appeared, 'login_wall' if login gate
|
|
15
|
+
* detected, or 'timeout' if neither appeared within the deadline.
|
|
16
|
+
*/
|
|
17
|
+
const WAIT_FOR_CONTENT_JS = `
|
|
18
|
+
new Promise((resolve) => {
|
|
19
|
+
const detect = () => {
|
|
20
|
+
if (document.querySelector('section.note-item')) return 'content';
|
|
21
|
+
if (/登录后查看搜索结果/.test(document.body?.innerText || '')) return 'login_wall';
|
|
22
|
+
return null;
|
|
23
|
+
};
|
|
24
|
+
const found = detect();
|
|
25
|
+
if (found) return resolve(found);
|
|
26
|
+
const observer = new MutationObserver(() => {
|
|
27
|
+
const result = detect();
|
|
28
|
+
if (result) { observer.disconnect(); resolve(result); }
|
|
29
|
+
});
|
|
30
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
31
|
+
setTimeout(() => { observer.disconnect(); resolve('timeout'); }, 5000);
|
|
32
|
+
})
|
|
33
|
+
`;
|
|
34
|
+
|
|
12
35
|
/**
|
|
13
36
|
* Extract approximate publish date from a Xiaohongshu note URL.
|
|
14
37
|
* XHS note IDs follow MongoDB ObjectID format where the first 8 hex
|
|
@@ -42,15 +65,13 @@ cli({
|
|
|
42
65
|
await page.goto(
|
|
43
66
|
`https://www.xiaohongshu.com/search_result?keyword=${keyword}&source=web_search_result_notes`
|
|
44
67
|
);
|
|
45
|
-
await page.wait(3);
|
|
46
68
|
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (loginCheck) {
|
|
69
|
+
// Wait for search results to render (or login wall to appear).
|
|
70
|
+
// Uses MutationObserver to resolve as soon as content appears,
|
|
71
|
+
// instead of a fixed delay + blind retry.
|
|
72
|
+
const waitResult = await page.evaluate(WAIT_FOR_CONTENT_JS);
|
|
73
|
+
|
|
74
|
+
if (waitResult === 'login_wall') {
|
|
54
75
|
throw new AuthRequiredError(
|
|
55
76
|
'www.xiaohongshu.com',
|
|
56
77
|
'Xiaohongshu search results are blocked behind a login wall',
|
|
@@ -62,8 +83,6 @@ cli({
|
|
|
62
83
|
|
|
63
84
|
const payload = await page.evaluate(`
|
|
64
85
|
(() => {
|
|
65
|
-
const loginWall = /登录后查看搜索结果/.test(document.body.innerText || '');
|
|
66
|
-
|
|
67
86
|
const normalizeUrl = (href) => {
|
|
68
87
|
if (!href) return '';
|
|
69
88
|
if (href.startsWith('http://') || href.startsWith('https://')) return href;
|
|
@@ -107,20 +126,11 @@ cli({
|
|
|
107
126
|
});
|
|
108
127
|
});
|
|
109
128
|
|
|
110
|
-
return
|
|
111
|
-
loginWall,
|
|
112
|
-
results,
|
|
113
|
-
};
|
|
129
|
+
return results;
|
|
114
130
|
})()
|
|
115
131
|
`);
|
|
116
132
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if ((payload as any).loginWall) {
|
|
120
|
-
throw new AuthRequiredError('www.xiaohongshu.com', 'Xiaohongshu search results are blocked behind a login wall');
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const data: any[] = Array.isArray((payload as any).results) ? (payload as any).results : [];
|
|
133
|
+
const data: any[] = Array.isArray(payload) ? (payload as any[]) : [];
|
|
124
134
|
return data
|
|
125
135
|
.filter((item: any) => item.title)
|
|
126
136
|
.slice(0, kwargs.limit)
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import type { IPage } from '../../types.js';
|
|
3
|
+
import { AuthRequiredError, CommandExecutionError, TimeoutError } from '../../errors.js';
|
|
4
|
+
import { __test__ } from './ask.js';
|
|
5
|
+
import { askCommand } from './ask.js';
|
|
6
|
+
|
|
7
|
+
describe('yuanbao ask helpers', () => {
|
|
8
|
+
describe('isOnYuanbao', () => {
|
|
9
|
+
const fakePage = (url: string | Error): IPage =>
|
|
10
|
+
({ evaluate: () => url instanceof Error ? Promise.reject(url) : Promise.resolve(url) }) as unknown as IPage;
|
|
11
|
+
|
|
12
|
+
it('returns true for yuanbao.tencent.com URLs', async () => {
|
|
13
|
+
expect(await __test__.isOnYuanbao(fakePage('https://yuanbao.tencent.com/'))).toBe(true);
|
|
14
|
+
expect(await __test__.isOnYuanbao(fakePage('https://yuanbao.tencent.com/chat/abc'))).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('returns false for non-yuanbao domains', async () => {
|
|
18
|
+
expect(await __test__.isOnYuanbao(fakePage('https://example.com/?next=yuanbao.tencent.com'))).toBe(false);
|
|
19
|
+
expect(await __test__.isOnYuanbao(fakePage('about:blank'))).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('returns false when evaluate throws', async () => {
|
|
23
|
+
expect(await __test__.isOnYuanbao(fakePage(new Error('detached')))).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('removes echoed prompt prefixes from transcript additions', () => {
|
|
28
|
+
expect(__test__.sanitizeYuanbaoResponseText('你好\n你好,我是元宝。', '你好')).toBe('你好,我是元宝。');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('filters transient in-progress assistant placeholders', () => {
|
|
32
|
+
expect(__test__.sanitizeYuanbaoResponseText('正在搜索资料', '张雪机车相关的股票有哪些?')).toBe('');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('normalizes boolean flags with explicit defaults', () => {
|
|
36
|
+
expect(__test__.normalizeBooleanFlag(undefined, true)).toBe(true);
|
|
37
|
+
expect(__test__.normalizeBooleanFlag(undefined, false)).toBe(false);
|
|
38
|
+
expect(__test__.normalizeBooleanFlag('true', false)).toBe(true);
|
|
39
|
+
expect(__test__.normalizeBooleanFlag('1', false)).toBe(true);
|
|
40
|
+
expect(__test__.normalizeBooleanFlag('yes', false)).toBe(true);
|
|
41
|
+
expect(__test__.normalizeBooleanFlag('false', true)).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('ignores baseline lines and echoed prompts when collecting additions', () => {
|
|
45
|
+
const response = __test__.collectYuanbaoTranscriptAdditions(
|
|
46
|
+
['旧消息'],
|
|
47
|
+
['旧消息', '你好', '你好\n你好,我是元宝。'],
|
|
48
|
+
'你好',
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
expect(response).toBe('你好,我是元宝。');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('prefers fresh assistant messages over echoed prompts and older messages', () => {
|
|
55
|
+
const response = __test__.pickLatestYuanbaoAssistantCandidate(
|
|
56
|
+
['旧回复', '你好', '你好!我是元宝,由腾讯推出的AI助手。'],
|
|
57
|
+
1,
|
|
58
|
+
'你好',
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
expect(response).toBe('你好!我是元宝,由腾讯推出的AI助手。');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('converts assistant html tables to markdown tables via turndown', () => {
|
|
65
|
+
const markdown = __test__.convertYuanbaoHtmlToMarkdown(`
|
|
66
|
+
<h3>核心产业链概念股一览</h3>
|
|
67
|
+
<table>
|
|
68
|
+
<thead>
|
|
69
|
+
<tr><th>细分赛道</th><th>核心标的</th></tr>
|
|
70
|
+
</thead>
|
|
71
|
+
<tbody>
|
|
72
|
+
<tr><td>光模块</td><td>中际旭创</td></tr>
|
|
73
|
+
</tbody>
|
|
74
|
+
</table>
|
|
75
|
+
`);
|
|
76
|
+
|
|
77
|
+
expect(markdown).toContain('### 核心产业链概念股一览');
|
|
78
|
+
expect(markdown).toContain('| 细分赛道 | 核心标的 |');
|
|
79
|
+
expect(markdown).toContain('| --- | --- |');
|
|
80
|
+
expect(markdown).toContain('| 光模块 | 中际旭创 |');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('tracks stabilization by incrementing repeats and resetting on changes', () => {
|
|
84
|
+
expect(__test__.updateStableState('', 0, '第一段')).toEqual({
|
|
85
|
+
previousText: '第一段',
|
|
86
|
+
stableCount: 0,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
expect(__test__.updateStableState('第一段', 0, '第一段')).toEqual({
|
|
90
|
+
previousText: '第一段',
|
|
91
|
+
stableCount: 1,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
expect(__test__.updateStableState('第一段', 1, '第二段')).toEqual({
|
|
95
|
+
previousText: '第二段',
|
|
96
|
+
stableCount: 0,
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
function createAskPageMock(overrides: {
|
|
102
|
+
currentUrl?: string;
|
|
103
|
+
hasLoginGate?: boolean;
|
|
104
|
+
sendResult?: { ok?: boolean; reason?: string; detail?: string; action?: string };
|
|
105
|
+
} = {}): IPage {
|
|
106
|
+
const currentUrl = overrides.currentUrl ?? 'https://yuanbao.tencent.com/';
|
|
107
|
+
const hasLoginGate = overrides.hasLoginGate ?? false;
|
|
108
|
+
const sendResult = overrides.sendResult;
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
goto: vi.fn().mockResolvedValue(undefined),
|
|
112
|
+
wait: vi.fn().mockResolvedValue(undefined),
|
|
113
|
+
evaluate: vi.fn().mockImplementation(async (script: string) => {
|
|
114
|
+
if (script === 'window.location.href') return currentUrl;
|
|
115
|
+
if (script.includes('微信扫码登录')) return hasLoginGate;
|
|
116
|
+
if (script.includes('[dt-button-id="internet_search"]')) return { found: false, enabled: false };
|
|
117
|
+
if (script.includes('[dt-button-id="deep_think"]')) return { found: false, enabled: false };
|
|
118
|
+
if (script.includes('.agent-chat__list__item--ai')) return [];
|
|
119
|
+
if (script.includes('const stopLines = new Set([')) return [];
|
|
120
|
+
if (script.includes('Failed to insert the prompt into the Yuanbao composer.')) {
|
|
121
|
+
return sendResult ?? { ok: true, action: 'click' };
|
|
122
|
+
}
|
|
123
|
+
throw new Error(`Unexpected evaluate script in test: ${script.slice(0, 80)}`);
|
|
124
|
+
}),
|
|
125
|
+
} as unknown as IPage;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
describe('yuanbao ask command', () => {
|
|
129
|
+
it('throws AuthRequiredError when Yuanbao shows a login gate before sending', async () => {
|
|
130
|
+
const page = createAskPageMock({ hasLoginGate: true });
|
|
131
|
+
|
|
132
|
+
await expect(askCommand.func!(page, { prompt: '你好', timeout: '60', search: true, think: false }))
|
|
133
|
+
.rejects.toBeInstanceOf(AuthRequiredError);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('throws CommandExecutionError when the prompt cannot be sent', async () => {
|
|
137
|
+
const page = createAskPageMock({
|
|
138
|
+
sendResult: {
|
|
139
|
+
ok: false,
|
|
140
|
+
reason: 'Yuanbao composer was not found.',
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
await expect(askCommand.func!(page, { prompt: '你好', timeout: '60', search: true, think: false }))
|
|
145
|
+
.rejects.toBeInstanceOf(CommandExecutionError);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('throws TimeoutError when no response arrives before timeout', async () => {
|
|
149
|
+
const page = createAskPageMock({
|
|
150
|
+
sendResult: { ok: true, action: 'click' },
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
await expect(askCommand.func!(page, { prompt: '你好', timeout: '-1', search: true, think: false }))
|
|
154
|
+
.rejects.toBeInstanceOf(TimeoutError);
|
|
155
|
+
});
|
|
156
|
+
});
|