@jackwener/opencli 1.7.12 → 1.7.14
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 +8 -7
- package/README.zh-CN.md +9 -8
- package/cli-manifest.json +12128 -6665
- package/clis/1point3acres/digest.js +35 -0
- package/clis/1point3acres/forum.js +51 -0
- package/clis/1point3acres/forums.js +44 -0
- package/clis/1point3acres/hot.js +35 -0
- package/clis/1point3acres/latest.js +35 -0
- package/clis/1point3acres/notifications.js +64 -0
- package/clis/1point3acres/search.js +71 -0
- package/clis/1point3acres/thread.js +117 -0
- package/clis/1point3acres/user.js +77 -0
- package/clis/1point3acres/utils.js +247 -0
- package/clis/_shared/desktop-commands.js +4 -0
- package/clis/aibase/news.js +110 -0
- package/clis/aibase/news.test.js +59 -0
- package/clis/amazon/discussion.test.js +1 -28
- package/clis/antigravity/watch.js +3 -2
- package/clis/arxiv/author.js +44 -0
- package/clis/baidu-scholar/search.js +0 -1
- package/clis/bbc/topic.js +57 -0
- package/clis/bbc/utils.js +79 -0
- package/clis/chaoxing/assignments.js +1 -1
- package/clis/chaoxing/exams.js +1 -1
- package/clis/chatgpt/ask.js +57 -0
- package/clis/chatgpt/commands.test.js +45 -0
- package/clis/chatgpt/detail.js +46 -0
- package/clis/chatgpt/history.js +39 -0
- package/clis/chatgpt/image.js +12 -11
- package/clis/chatgpt/image.test.js +23 -0
- package/clis/chatgpt/new.js +25 -0
- package/clis/chatgpt/read.js +43 -0
- package/clis/chatgpt/send.js +46 -0
- package/clis/chatgpt/status.js +29 -0
- package/clis/chatgpt/utils.js +294 -4
- package/clis/chatgpt/utils.test.js +13 -0
- package/clis/chatgpt-app/ask.js +6 -3
- package/clis/chatwise/ask.js +16 -43
- package/clis/chatwise/composer.test.js +186 -0
- package/clis/chatwise/send.js +2 -24
- package/clis/chatwise/utils.js +143 -0
- package/clis/claude/ask.js +1 -1
- package/clis/claude/detail.js +1 -0
- package/clis/claude/history.js +1 -0
- package/clis/claude/new.js +1 -0
- package/clis/claude/read.js +1 -0
- package/clis/claude/send.js +1 -0
- package/clis/claude/status.js +1 -0
- package/clis/codex/ask.js +15 -9
- package/clis/codex/history.js +16 -33
- package/clis/codex/projects.js +28 -0
- package/clis/codex/read.js +10 -4
- package/clis/codex/send.js +10 -3
- package/clis/codex/sidebar.js +356 -0
- package/clis/codex/sidebar.test.js +329 -0
- package/clis/coingecko/categories.js +75 -0
- package/clis/coingecko/coin.js +107 -0
- package/clis/coingecko/coingecko.test.js +109 -0
- package/clis/coingecko/derivatives.js +84 -0
- package/clis/coingecko/exchanges.js +74 -0
- package/clis/coingecko/global.js +71 -0
- package/clis/coingecko/top.js +64 -0
- package/clis/coingecko/trending.js +55 -0
- package/clis/coupang/add-to-cart.js +21 -13
- package/clis/coupang/coupang.test.js +159 -0
- package/clis/coupang/product.js +257 -0
- package/clis/coupang/search.js +38 -16
- package/clis/coupang/utils.js +55 -1
- package/clis/crates/crate.js +62 -0
- package/clis/crates/search.js +44 -0
- package/clis/crates/utils.js +72 -0
- package/clis/ctrip/ctrip.test.js +234 -0
- package/clis/ctrip/hotel-suggest.js +45 -0
- package/clis/ctrip/search.js +22 -68
- package/clis/ctrip/utils.js +175 -0
- package/clis/cursor/ask.js +6 -3
- package/clis/dblp/author.js +133 -0
- package/clis/dblp/venue.js +64 -0
- package/clis/deepseek/ask.js +12 -7
- package/clis/deepseek/ask.test.js +13 -13
- package/clis/deepseek/detail.js +38 -0
- package/clis/deepseek/detail.test.js +81 -0
- package/clis/deepseek/history.js +1 -0
- package/clis/deepseek/new.js +1 -0
- package/clis/deepseek/read.js +1 -0
- package/clis/deepseek/send.js +140 -0
- package/clis/deepseek/send.test.js +107 -0
- package/clis/deepseek/status.js +1 -0
- package/clis/deepseek/utils.js +66 -0
- package/clis/deepseek/utils.test.js +107 -1
- package/clis/defillama/defillama.test.js +99 -0
- package/clis/defillama/protocol.js +84 -0
- package/clis/defillama/protocols.js +55 -0
- package/clis/defillama/utils.js +99 -0
- package/clis/devto/latest.js +74 -0
- package/clis/dockerhub/image.js +52 -0
- package/clis/dockerhub/search.js +47 -0
- package/clis/dockerhub/utils.js +100 -0
- package/clis/doubao/ask.js +7 -3
- package/clis/doubao/detail.js +1 -0
- package/clis/doubao/history.js +1 -0
- package/clis/doubao/meeting-summary.js +1 -0
- package/clis/doubao/meeting-transcript.js +1 -0
- package/clis/doubao/new.js +1 -0
- package/clis/doubao/read.js +1 -0
- package/clis/doubao/send.js +1 -0
- package/clis/doubao/status.js +1 -0
- package/clis/douyin/draft.test.js +1 -30
- package/clis/endoflife/endoflife.test.js +51 -0
- package/clis/endoflife/product.js +55 -0
- package/clis/endoflife/utils.js +89 -0
- package/clis/facebook/__fixtures__/notifications-page.html +13 -0
- package/clis/facebook/notifications.js +326 -30
- package/clis/facebook/notifications.test.js +458 -0
- package/clis/flathub/app.js +71 -0
- package/clis/flathub/flathub.test.js +90 -0
- package/clis/flathub/search.js +80 -0
- package/clis/flathub/utils.js +114 -0
- package/clis/gemini/ask.js +7 -3
- package/clis/gemini/ask.test.js +2 -2
- package/clis/gemini/deep-research-result.js +6 -2
- package/clis/gemini/deep-research-result.test.js +15 -14
- package/clis/gemini/deep-research.js +8 -4
- package/clis/gemini/deep-research.test.js +15 -18
- package/clis/gemini/image.js +7 -2
- package/clis/gemini/new.js +1 -0
- package/clis/gemini/utils.js +0 -4
- package/clis/google-scholar/cite.js +0 -1
- package/clis/google-scholar/profile.js +0 -1
- package/clis/google-scholar/search.js +0 -1
- package/clis/goproxy/goproxy.test.js +103 -0
- package/clis/goproxy/module.js +47 -0
- package/clis/goproxy/utils.js +165 -0
- package/clis/goproxy/versions.js +59 -0
- package/clis/gov-law/recent.js +0 -1
- package/clis/gov-law/search.js +0 -1
- package/clis/gov-policy/__fixtures__/recent.html +16 -0
- package/clis/gov-policy/__fixtures__/search.html +41 -0
- package/clis/gov-policy/gov-policy.test.js +224 -0
- package/clis/gov-policy/recent.js +66 -24
- package/clis/gov-policy/search.js +65 -23
- package/clis/gov-policy/utils.js +54 -0
- package/clis/grok/ask.js +49 -265
- package/clis/grok/ask.test.js +21 -46
- package/clis/grok/detail.js +60 -0
- package/clis/grok/history.js +48 -0
- package/clis/grok/{image.ts → image.js} +56 -70
- package/clis/grok/image.test.ts +20 -0
- package/clis/grok/new.js +20 -0
- package/clis/grok/read.js +39 -0
- package/clis/grok/send.js +50 -0
- package/clis/grok/status.js +41 -0
- package/clis/grok/utils.js +326 -0
- package/clis/grok/utils.test.js +103 -0
- package/clis/hf/datasets.js +88 -0
- package/clis/hf/hf.test.js +16 -0
- package/clis/hf/models.js +91 -0
- package/clis/hf/paper.js +79 -0
- package/clis/hf/spaces.js +101 -0
- package/clis/hf/top.js +1 -0
- package/clis/homebrew/cask.js +39 -0
- package/clis/homebrew/formula.js +41 -0
- package/clis/homebrew/popular.js +54 -0
- package/clis/homebrew/utils.js +100 -0
- package/clis/hupu/__fixtures__/hot-home.html +64 -0
- package/clis/hupu/detail.js +0 -1
- package/clis/hupu/hot.js +156 -35
- package/clis/hupu/hot.test.js +224 -0
- package/clis/hupu/search.js +0 -1
- package/clis/instagram/note.js +1 -1
- package/clis/instagram/note.test.js +1 -29
- package/clis/instagram/post.js +1 -1
- package/clis/instagram/post.test.js +1 -1
- package/clis/instagram/reel.js +1 -1
- package/clis/instagram/story.js +1 -1
- package/clis/instagram/story.test.js +1 -34
- package/clis/jd/commands.test.js +1 -24
- package/clis/lichess/lichess.test.js +85 -0
- package/clis/lichess/top.js +46 -0
- package/clis/lichess/user.js +91 -0
- package/clis/lichess/utils.js +97 -0
- package/clis/linkedin/search.js +107 -10
- package/clis/linkedin/search.test.js +222 -0
- package/clis/linux-do/feed.js +2 -5
- package/clis/linux-do/feed.test.js +35 -0
- package/clis/lobsters/domain.js +92 -0
- package/clis/maven/artifact.js +49 -0
- package/clis/maven/search.js +51 -0
- package/clis/maven/utils.js +110 -0
- package/clis/mdn/search.js +97 -0
- package/clis/medium/tag.js +135 -0
- package/clis/npm/downloads.js +59 -0
- package/clis/npm/package.js +70 -0
- package/clis/npm/search.js +49 -0
- package/clis/npm/utils.js +76 -0
- package/clis/nuget/nuget.test.js +111 -0
- package/clis/nuget/package.js +101 -0
- package/clis/nuget/search.js +69 -0
- package/clis/nuget/utils.js +87 -0
- package/clis/nvd/cve.js +121 -0
- package/clis/oeis/oeis.test.js +88 -0
- package/clis/oeis/search.js +63 -0
- package/clis/oeis/sequence.js +71 -0
- package/clis/oeis/utils.js +88 -0
- package/clis/openalex/search.js +69 -0
- package/clis/openalex/utils.js +160 -0
- package/clis/openalex/work.js +65 -0
- package/clis/openfda/drug-label.js +74 -0
- package/clis/openfda/food-recall.js +65 -0
- package/clis/openfda/openfda.test.js +114 -0
- package/clis/openfda/utils.js +67 -0
- package/clis/osv/osv.test.js +97 -0
- package/clis/osv/query.js +72 -0
- package/clis/osv/utils.js +169 -0
- package/clis/osv/vulnerability.js +54 -0
- package/clis/packagist/package.js +49 -0
- package/clis/packagist/search.js +43 -0
- package/clis/packagist/utils.js +113 -0
- package/clis/paperreview/feedback.js +1 -1
- package/clis/paperreview/review.js +1 -1
- package/clis/paperreview/submit.js +1 -1
- package/clis/pixiv/download.test.js +1 -1
- package/clis/pixiv/illusts.test.js +1 -1
- package/clis/pixiv/search.test.js +1 -1
- package/clis/pubmed/article.js +50 -0
- package/clis/pubmed/author.js +64 -0
- package/clis/pubmed/citations.js +36 -0
- package/clis/pubmed/pubmed.test.js +276 -0
- package/clis/pubmed/related.js +45 -0
- package/clis/pubmed/search.js +75 -0
- package/clis/pubmed/utils.js +309 -0
- package/clis/pypi/downloads.js +66 -0
- package/clis/pypi/package.js +79 -0
- package/clis/pypi/utils.js +55 -0
- package/clis/quark/mv.js +1 -1
- package/clis/quark/save.js +1 -1
- package/clis/qwen/ask.js +85 -0
- package/clis/qwen/detail.js +62 -0
- package/clis/qwen/history.js +61 -0
- package/clis/qwen/image.js +179 -0
- package/clis/qwen/new.js +23 -0
- package/clis/qwen/read.js +41 -0
- package/clis/qwen/send.js +55 -0
- package/clis/qwen/status.js +37 -0
- package/clis/qwen/utils.js +409 -0
- package/clis/qwen/utils.test.js +45 -0
- package/clis/rest-countries/country.js +65 -0
- package/clis/rest-countries/region.js +64 -0
- package/clis/rest-countries/rest-countries.test.js +83 -0
- package/clis/rest-countries/utils.js +126 -0
- package/clis/reuters/article-detail.js +53 -0
- package/clis/reuters/reuters.test.js +299 -0
- package/clis/reuters/search.js +45 -34
- package/clis/reuters/utils.js +159 -0
- package/clis/rfc/rfc.js +52 -0
- package/clis/rfc/rfc.test.js +74 -0
- package/clis/rfc/utils.js +72 -0
- package/clis/rubygems/gem.js +42 -0
- package/clis/rubygems/search.js +47 -0
- package/clis/rubygems/utils.js +86 -0
- package/clis/stackoverflow/related.js +66 -0
- package/clis/stackoverflow/stackoverflow.test.js +58 -0
- package/clis/stackoverflow/tag.js +60 -0
- package/clis/stackoverflow/user.js +50 -0
- package/clis/stackoverflow/utils.js +118 -0
- package/clis/steam/app.js +67 -0
- package/clis/steam/search.js +58 -0
- package/clis/steam/steam.test.js +46 -0
- package/clis/steam/utils.js +107 -0
- package/clis/taobao/commands.test.js +1 -24
- package/clis/test-utils.js +61 -0
- package/clis/tieba/hot.js +0 -1
- package/clis/tiktok/comment.js +128 -41
- package/clis/tiktok/creator-videos.js +270 -0
- package/clis/tiktok/creator-videos.test.js +113 -0
- package/clis/tiktok/explore.js +137 -29
- package/clis/tiktok/follow.js +115 -33
- package/clis/tiktok/following.js +157 -36
- package/clis/tiktok/friends.js +139 -37
- package/clis/tiktok/live.js +137 -41
- package/clis/tiktok/notifications.js +141 -38
- package/clis/tiktok/refactor.test.js +389 -0
- package/clis/tiktok/unfollow.js +124 -38
- package/clis/tiktok/user.js +203 -29
- package/clis/tiktok/utils.js +505 -0
- package/clis/tiktok/write-refactor.test.js +370 -0
- package/clis/toutiao/articles.js +36 -62
- package/clis/toutiao/hot.js +63 -0
- package/clis/toutiao/toutiao.test.js +378 -0
- package/clis/toutiao/utils.js +161 -0
- package/clis/tvmaze/search.js +61 -0
- package/clis/tvmaze/show.js +60 -0
- package/clis/tvmaze/tvmaze.test.js +93 -0
- package/clis/tvmaze/utils.js +110 -0
- package/clis/twitter/accept.js +1 -1
- package/clis/twitter/followers.js +134 -69
- package/clis/twitter/quote.js +139 -0
- package/clis/twitter/quote.test.js +106 -0
- package/clis/twitter/reply-dm.js +1 -1
- package/clis/twitter/reply.test.js +1 -29
- package/clis/twitter/retweet.js +99 -0
- package/clis/twitter/retweet.test.js +69 -0
- package/clis/twitter/shared.js +38 -0
- package/clis/twitter/shared.test.js +28 -1
- package/clis/twitter/unlike.js +87 -0
- package/clis/twitter/unlike.test.js +72 -0
- package/clis/twitter/unretweet.js +99 -0
- package/clis/twitter/unretweet.test.js +69 -0
- package/clis/uisdc/news.js +105 -0
- package/clis/uisdc/news.test.js +66 -0
- package/clis/wanfang/search.js +0 -1
- package/clis/web/read.js +47 -17
- package/clis/web/read.test.js +101 -1
- package/clis/weixin/create-draft.js +1 -1
- package/clis/weixin/drafts.js +1 -1
- package/clis/weixin/drafts.test.js +5 -1
- package/clis/weixin/search.js +157 -0
- package/clis/weixin/search.test.js +227 -0
- package/clis/wikidata/entity.js +60 -0
- package/clis/wikidata/search.js +50 -0
- package/clis/wikidata/utils.js +117 -0
- package/clis/wikidata/wikidata.test.js +83 -0
- package/clis/wikipedia/page.js +95 -0
- package/clis/wttr/current.js +63 -0
- package/clis/wttr/forecast.js +71 -0
- package/clis/wttr/utils.js +50 -0
- package/clis/wttr/wttr.test.js +84 -0
- package/clis/xianyu/chat.js +16 -4
- package/clis/xianyu/chat.test.js +64 -0
- package/clis/xianyu/publish.js +485 -0
- package/clis/xianyu/publish.test.js +220 -0
- package/clis/xiaoe/catalog.js +105 -40
- package/clis/xiaoe/content.js +164 -29
- package/clis/xiaoe/courses.js +86 -29
- package/clis/xiaoe/xiaoe.test.js +486 -0
- package/clis/xiaohongshu/creator-notes-summary.js +1 -1
- package/clis/xiaohongshu/publish.js +16 -3
- package/clis/xiaohongshu/publish.test.js +46 -1
- package/clis/youtube/transcript.js +13 -19
- package/clis/youtube/transcript.test.js +17 -0
- package/clis/yuanbao/ask.js +17 -66
- package/clis/yuanbao/ask.test.js +5 -5
- package/clis/yuanbao/detail.js +65 -0
- package/clis/yuanbao/history.js +51 -0
- package/clis/yuanbao/new.js +1 -0
- package/clis/yuanbao/read.js +38 -0
- package/clis/yuanbao/send.js +57 -0
- package/clis/yuanbao/shared.js +297 -5
- package/clis/yuanbao/shared.test.js +80 -0
- package/clis/yuanbao/status.js +44 -0
- package/clis/zlibrary/commands.test.js +1 -11
- package/dist/src/browser/base-page.d.ts +9 -0
- package/dist/src/browser/base-page.js +44 -1
- package/dist/src/browser/base-page.test.js +66 -0
- package/dist/src/browser/bridge.js +47 -45
- package/dist/src/browser/cdp.d.ts +1 -0
- package/dist/src/browser/cdp.js +51 -9
- package/dist/src/browser/daemon-client.d.ts +4 -0
- package/dist/src/browser/errors.js +1 -1
- package/dist/src/browser/page.d.ts +1 -1
- package/dist/src/browser/page.js +3 -1
- package/dist/src/browser/page.test.js +29 -0
- package/dist/src/browser/target-errors.d.ts +2 -1
- package/dist/src/browser/target-errors.js +1 -0
- package/dist/src/browser/target-resolver.d.ts +25 -0
- package/dist/src/browser/target-resolver.js +43 -0
- package/dist/src/browser.test.js +18 -0
- package/dist/src/build-manifest.js +9 -4
- package/dist/src/build-manifest.test.js +2 -8
- package/dist/src/capabilityRouting.d.ts +16 -1
- package/dist/src/capabilityRouting.js +24 -1
- package/dist/src/capabilityRouting.test.js +19 -1
- package/dist/src/cli.js +76 -11
- package/dist/src/cli.test.js +241 -1
- package/dist/src/commanderAdapter.js +23 -9
- package/dist/src/commanderAdapter.test.js +0 -1
- package/dist/src/discovery.js +2 -5
- package/dist/src/errors.js +1 -1
- package/dist/src/execution.d.ts +1 -1
- package/dist/src/execution.js +111 -27
- package/dist/src/execution.test.js +326 -17
- package/dist/src/help.d.ts +27 -2
- package/dist/src/help.js +196 -23
- package/dist/src/help.test.d.ts +1 -0
- package/dist/src/help.test.js +54 -0
- package/dist/src/main.js +14 -1
- package/dist/src/manifest-types.d.ts +5 -3
- package/dist/src/pipeline/executor.js +1 -1
- package/dist/src/pipeline/executor.test.js +8 -0
- package/dist/src/pipeline/registry.d.ts +9 -0
- package/dist/src/pipeline/registry.js +13 -1
- package/dist/src/pipeline/steps/browser.d.ts +1 -0
- package/dist/src/pipeline/steps/browser.js +10 -0
- package/dist/src/pipeline/steps/download.test.js +1 -0
- package/dist/src/registry-api.d.ts +1 -1
- package/dist/src/registry.d.ts +12 -11
- package/dist/src/registry.js +16 -6
- package/dist/src/registry.test.js +2 -2
- package/dist/src/runtime.d.ts +2 -1
- package/dist/src/runtime.js +1 -1
- package/dist/src/serialization.d.ts +2 -2
- package/dist/src/serialization.js +4 -6
- package/dist/src/serialization.test.js +17 -0
- package/dist/src/types.d.ts +17 -0
- package/dist/src/validate.js +15 -11
- package/dist/src/validate.test.d.ts +9 -0
- package/dist/src/validate.test.js +90 -0
- package/package.json +1 -1
- package/scripts/fetch-adapters.js +1 -1
- package/scripts/typed-error-lint-baseline.json +5 -77
- package/clis/ctrip/search.test.js +0 -64
- package/clis/gov-policy/commands.test.js +0 -27
- package/clis/linux-do/category.js +0 -37
- package/clis/linux-do/hot.js +0 -26
- package/clis/linux-do/latest.js +0 -19
- package/clis/pixiv/test-utils.js +0 -23
- package/clis/toutiao/articles.test.js +0 -30
- package/dist/src/analysis.d.ts +0 -40
- package/dist/src/analysis.js +0 -172
|
@@ -282,6 +282,48 @@ describe('xiaohongshu publish', () => {
|
|
|
282
282
|
},
|
|
283
283
|
]);
|
|
284
284
|
});
|
|
285
|
+
it('falls back to DataTransfer upload when CDP file injection is blocked by Chrome', async () => {
|
|
286
|
+
const cmd = getRegistry().get('xiaohongshu/publish');
|
|
287
|
+
expect(cmd?.func).toBeTypeOf('function');
|
|
288
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-xhs-publish-'));
|
|
289
|
+
const imagePath = path.join(tempDir, 'demo.jpg');
|
|
290
|
+
fs.writeFileSync(imagePath, Buffer.from([0xff, 0xd8, 0xff, 0xd9]));
|
|
291
|
+
const setFileInput = vi.fn().mockRejectedValue(new Error('Chrome Not allowed'));
|
|
292
|
+
const page = createPageMock([
|
|
293
|
+
'https://creator.xiaohongshu.com/publish/publish?from=menu_left&target=image',
|
|
294
|
+
{ ok: true, target: '上传图文', text: '上传图文' },
|
|
295
|
+
{ state: 'editor_ready', hasTitleInput: true, hasImageInput: true, hasVideoSurface: false },
|
|
296
|
+
'input[type="file"][accept*="image"],input[type="file"][accept*=".jpg"],input[type="file"][accept*=".jpeg"],input[type="file"][accept*=".png"],input[type="file"][accept*=".gif"],input[type="file"][accept*=".webp"]',
|
|
297
|
+
{ ok: true, count: 1 },
|
|
298
|
+
false,
|
|
299
|
+
true,
|
|
300
|
+
{ ok: true, sel: 'input[maxlength="20"]', kind: 'input' },
|
|
301
|
+
{ ok: true, actual: 'CDP被拒后回退' },
|
|
302
|
+
{ ok: true, sel: '[contenteditable="true"][class*="content"]', kind: 'contenteditable' },
|
|
303
|
+
{ ok: true, actual: 'DataTransfer fallback path' },
|
|
304
|
+
true,
|
|
305
|
+
'https://creator.xiaohongshu.com/publish/success',
|
|
306
|
+
'发布成功',
|
|
307
|
+
], {
|
|
308
|
+
setFileInput,
|
|
309
|
+
});
|
|
310
|
+
const result = await cmd.func(page, {
|
|
311
|
+
title: 'CDP被拒后回退',
|
|
312
|
+
content: 'DataTransfer fallback path',
|
|
313
|
+
images: imagePath,
|
|
314
|
+
topics: '',
|
|
315
|
+
draft: false,
|
|
316
|
+
});
|
|
317
|
+
const evaluateCalls = page.evaluate.mock.calls.map((args) => String(args[0]));
|
|
318
|
+
expect(setFileInput).toHaveBeenCalledWith([imagePath], expect.stringContaining('input[type="file"][accept*="image"]'));
|
|
319
|
+
expect(evaluateCalls.some((code) => code.includes('dt.items.add(new File'))).toBe(true);
|
|
320
|
+
expect(result).toEqual([
|
|
321
|
+
{
|
|
322
|
+
status: '✅ 发布成功',
|
|
323
|
+
detail: '"CDP被拒后回退" · 1张图片 · 发布成功',
|
|
324
|
+
},
|
|
325
|
+
]);
|
|
326
|
+
});
|
|
285
327
|
it('fails fast when only a generic file input exists on the page', async () => {
|
|
286
328
|
const cmd = getRegistry().get('xiaohongshu/publish');
|
|
287
329
|
expect(cmd?.func).toBeTypeOf('function');
|
|
@@ -336,8 +378,11 @@ describe('xiaohongshu publish', () => {
|
|
|
336
378
|
draft: false,
|
|
337
379
|
});
|
|
338
380
|
const evaluateCalls = page.evaluate.mock.calls.map((args) => String(args[0]));
|
|
339
|
-
|
|
381
|
+
const tabSelectCode = evaluateCalls.find((code) => code.includes("const targets = ['上传图文', '图文', '图片']"));
|
|
382
|
+
expect(tabSelectCode).toBeTruthy();
|
|
383
|
+
expect(tabSelectCode.indexOf('if (text === target)')).toBeLessThan(tabSelectCode.indexOf('text.startsWith(target)'));
|
|
340
384
|
expect(evaluateCalls.some((code) => code.includes("No image file input found on page"))).toBe(true);
|
|
385
|
+
expect(page.goto).toHaveBeenCalledWith(expect.stringContaining('target=image'));
|
|
341
386
|
expect(result).toEqual([
|
|
342
387
|
{
|
|
343
388
|
status: '✅ 发布成功',
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* YouTube transcript —
|
|
2
|
+
* YouTube transcript — extracts caption tracks from watch page bootstrap data.
|
|
3
3
|
*
|
|
4
|
-
* The
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* The old Android InnerTube client path stopped reliably returning captions.
|
|
5
|
+
* We now match youtube/video.js: fetch watch HTML with the browser session and
|
|
6
|
+
* parse ytInitialPlayerResponse.captions.playerCaptionsTracklistRenderer.
|
|
7
7
|
*
|
|
8
8
|
* Modes:
|
|
9
9
|
* --mode grouped (default): sentences merged, speaker detection, chapters
|
|
10
10
|
* --mode raw: every caption segment as-is with precise timestamps
|
|
11
11
|
*/
|
|
12
12
|
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
13
|
-
import { parseVideoId, prepareYoutubeApiPage } from './utils.js';
|
|
13
|
+
import { extractJsonAssignmentFromHtml, parseVideoId, prepareYoutubeApiPage } from './utils.js';
|
|
14
14
|
import { groupTranscriptSegments, formatGroupedTranscript, } from './transcript-group.js';
|
|
15
15
|
import { CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';
|
|
16
16
|
cli({
|
|
@@ -32,27 +32,21 @@ cli({
|
|
|
32
32
|
await prepareYoutubeApiPage(page);
|
|
33
33
|
const lang = kwargs.lang || '';
|
|
34
34
|
const mode = kwargs.mode || 'grouped';
|
|
35
|
-
// Step 1: Get caption track URL
|
|
35
|
+
// Step 1: Get caption track URL from watch page HTML
|
|
36
36
|
const captionData = await page.evaluate(`
|
|
37
37
|
(async () => {
|
|
38
|
-
const
|
|
39
|
-
const apiKey = cfg.INNERTUBE_API_KEY;
|
|
40
|
-
if (!apiKey) return { error: 'INNERTUBE_API_KEY not found on page' };
|
|
38
|
+
const extractJsonAssignmentFromHtml = ${extractJsonAssignmentFromHtml.toString()};
|
|
41
39
|
|
|
42
|
-
const
|
|
43
|
-
method: 'POST',
|
|
40
|
+
const watchResp = await fetch('/watch?v=' + encodeURIComponent(${JSON.stringify(videoId)}), {
|
|
44
41
|
credentials: 'include',
|
|
45
|
-
headers: { 'Content-Type': 'application/json' },
|
|
46
|
-
body: JSON.stringify({
|
|
47
|
-
context: { client: { clientName: 'ANDROID', clientVersion: '20.10.38' } },
|
|
48
|
-
videoId: ${JSON.stringify(videoId)}
|
|
49
|
-
})
|
|
50
42
|
});
|
|
43
|
+
if (!watchResp.ok) return { error: 'Watch HTML returned HTTP ' + watchResp.status };
|
|
51
44
|
|
|
52
|
-
|
|
53
|
-
const
|
|
45
|
+
const html = await watchResp.text();
|
|
46
|
+
const player = extractJsonAssignmentFromHtml(html, 'ytInitialPlayerResponse');
|
|
47
|
+
if (!player) return { error: 'ytInitialPlayerResponse not found in watch HTML' };
|
|
54
48
|
|
|
55
|
-
const renderer =
|
|
49
|
+
const renderer = player.captions?.playerCaptionsTracklistRenderer;
|
|
56
50
|
if (!renderer?.captionTracks?.length) {
|
|
57
51
|
return { error: 'No captions available for this video' };
|
|
58
52
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const transcriptSource = readFileSync(resolve(__dirname, 'transcript.js'), 'utf8');
|
|
8
|
+
|
|
9
|
+
describe('youtube transcript source contract', () => {
|
|
10
|
+
it('gets caption tracks from watch page bootstrap data, not Android InnerTube', () => {
|
|
11
|
+
expect(transcriptSource).toContain("fetch('/watch?v='");
|
|
12
|
+
expect(transcriptSource).toContain("extractJsonAssignmentFromHtml(html, 'ytInitialPlayerResponse')");
|
|
13
|
+
expect(transcriptSource).toContain('playerCaptionsTracklistRenderer');
|
|
14
|
+
expect(transcriptSource).not.toContain('/youtubei/v1/player');
|
|
15
|
+
expect(transcriptSource).not.toContain("clientName: 'ANDROID'");
|
|
16
|
+
});
|
|
17
|
+
});
|
package/clis/yuanbao/ask.js
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
2
2
|
import { htmlToMarkdown } from '@jackwener/opencli/utils';
|
|
3
|
-
import { CommandExecutionError, TimeoutError } from '@jackwener/opencli/errors';
|
|
4
|
-
import {
|
|
3
|
+
import { ArgumentError, CommandExecutionError, TimeoutError } from '@jackwener/opencli/errors';
|
|
4
|
+
import {
|
|
5
|
+
YUANBAO_DOMAIN,
|
|
6
|
+
IS_VISIBLE_JS,
|
|
7
|
+
authRequired,
|
|
8
|
+
isOnYuanbao,
|
|
9
|
+
ensureYuanbaoPage,
|
|
10
|
+
hasLoginGate,
|
|
11
|
+
normalizeBooleanFlag,
|
|
12
|
+
sendYuanbaoMessage,
|
|
13
|
+
} from './shared.js';
|
|
5
14
|
const YUANBAO_RESPONSE_POLL_INTERVAL_SECONDS = 2;
|
|
6
15
|
const YUANBAO_MIN_WAIT_MS = 8_000;
|
|
7
16
|
const YUANBAO_STABLE_POLLS_REQUIRED = 3;
|
|
@@ -12,14 +21,6 @@ function sendFailure(reason, detail) {
|
|
|
12
21
|
function normalizeText(value) {
|
|
13
22
|
return typeof value === 'string' ? value.trim() : '';
|
|
14
23
|
}
|
|
15
|
-
function normalizeBooleanFlag(value, fallback) {
|
|
16
|
-
if (typeof value === 'boolean')
|
|
17
|
-
return value;
|
|
18
|
-
if (value == null || value === '')
|
|
19
|
-
return fallback;
|
|
20
|
-
const normalized = String(value).trim().toLowerCase();
|
|
21
|
-
return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on';
|
|
22
|
-
}
|
|
23
24
|
export function convertYuanbaoHtmlToMarkdown(value) {
|
|
24
25
|
return htmlToMarkdown(value, (td) => {
|
|
25
26
|
td.addRule('table', {
|
|
@@ -272,59 +273,6 @@ async function setYuanbaoDeepThink(page, enabled) {
|
|
|
272
273
|
})()`);
|
|
273
274
|
await page.wait(0.5);
|
|
274
275
|
}
|
|
275
|
-
async function sendYuanbaoMessage(page, prompt) {
|
|
276
|
-
return await page.evaluate(`(async () => {
|
|
277
|
-
const waitFor = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
278
|
-
${IS_VISIBLE_JS}
|
|
279
|
-
|
|
280
|
-
const composer = Array.from(document.querySelectorAll('.ql-editor[contenteditable="true"], .ql-editor, [contenteditable="true"]'))
|
|
281
|
-
.find(isVisible);
|
|
282
|
-
|
|
283
|
-
if (!(composer instanceof HTMLElement)) {
|
|
284
|
-
return {
|
|
285
|
-
ok: false,
|
|
286
|
-
reason: 'Yuanbao composer was not found.',
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
try {
|
|
291
|
-
composer.focus();
|
|
292
|
-
const selection = window.getSelection();
|
|
293
|
-
const range = document.createRange();
|
|
294
|
-
range.selectNodeContents(composer);
|
|
295
|
-
range.collapse(false);
|
|
296
|
-
selection?.removeAllRanges();
|
|
297
|
-
selection?.addRange(range);
|
|
298
|
-
composer.textContent = '';
|
|
299
|
-
document.execCommand('insertText', false, ${JSON.stringify(prompt)});
|
|
300
|
-
composer.dispatchEvent(new InputEvent('input', { bubbles: true, data: ${JSON.stringify(prompt)}, inputType: 'insertText' }));
|
|
301
|
-
await waitFor(200);
|
|
302
|
-
} catch (error) {
|
|
303
|
-
return {
|
|
304
|
-
ok: false,
|
|
305
|
-
reason: 'Failed to insert the prompt into the Yuanbao composer.',
|
|
306
|
-
detail: error instanceof Error ? error.message : String(error),
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
const submit = Array.from(document.querySelectorAll('a[class*="send-btn"], button[class*="send-btn"]'))
|
|
311
|
-
.find((node) => {
|
|
312
|
-
if (!(node instanceof HTMLElement) || !isVisible(node)) return false;
|
|
313
|
-
const className = node.className || '';
|
|
314
|
-
if (typeof className === 'string' && className.includes('disabled')) return false;
|
|
315
|
-
return true;
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
if (submit instanceof HTMLElement) {
|
|
319
|
-
submit.click();
|
|
320
|
-
return { ok: true, action: 'click' };
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
composer.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true }));
|
|
324
|
-
composer.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true }));
|
|
325
|
-
return { ok: true, action: 'enter' };
|
|
326
|
-
})()`);
|
|
327
|
-
}
|
|
328
276
|
async function waitForYuanbaoResponse(page, baselineAssistantCount, beforeLines, prompt, timeoutSeconds) {
|
|
329
277
|
const startTime = Date.now();
|
|
330
278
|
let previousText = '';
|
|
@@ -357,19 +305,22 @@ export const askCommand = cli({
|
|
|
357
305
|
domain: YUANBAO_DOMAIN,
|
|
358
306
|
strategy: Strategy.COOKIE,
|
|
359
307
|
browser: true,
|
|
308
|
+
browserSession: { reuse: 'site' },
|
|
360
309
|
navigateBefore: false,
|
|
361
310
|
defaultFormat: 'plain',
|
|
362
|
-
timeoutSeconds: 180,
|
|
363
311
|
args: [
|
|
364
312
|
{ name: 'prompt', required: true, positional: true, help: 'Prompt to send' },
|
|
365
|
-
{ name: 'timeout', required: false, help: 'Max seconds to wait (default: 60)', default:
|
|
313
|
+
{ name: 'timeout', type: 'int', required: false, help: 'Max seconds to wait (default: 60)', default: 60 },
|
|
366
314
|
{ name: 'search', type: 'boolean', required: false, help: 'Enable Yuanbao internet search (default: true)', default: true },
|
|
367
315
|
{ name: 'think', type: 'boolean', required: false, help: 'Enable Yuanbao deep thinking (default: false)', default: false },
|
|
368
316
|
],
|
|
369
317
|
columns: ['Role', 'Text'],
|
|
370
318
|
func: async (page, kwargs) => {
|
|
371
319
|
const prompt = kwargs.prompt;
|
|
372
|
-
const timeout =
|
|
320
|
+
const timeout = kwargs.timeout;
|
|
321
|
+
if (!Number.isInteger(timeout) || timeout < 1) {
|
|
322
|
+
throw new ArgumentError('--timeout must be a positive integer (seconds)');
|
|
323
|
+
}
|
|
373
324
|
const useSearch = normalizeBooleanFlag(kwargs.search, true);
|
|
374
325
|
const useThink = normalizeBooleanFlag(kwargs.think, false);
|
|
375
326
|
await ensureYuanbaoPage(page);
|
package/clis/yuanbao/ask.test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { AuthRequiredError, CommandExecutionError
|
|
2
|
+
import { ArgumentError, AuthRequiredError, CommandExecutionError } from '@jackwener/opencli/errors';
|
|
3
3
|
import { __test__ } from './ask.js';
|
|
4
4
|
import { askCommand } from './ask.js';
|
|
5
5
|
describe('yuanbao ask helpers', () => {
|
|
@@ -101,7 +101,7 @@ function createAskPageMock(overrides = {}) {
|
|
|
101
101
|
describe('yuanbao ask command', () => {
|
|
102
102
|
it('throws AuthRequiredError when Yuanbao shows a login gate before sending', async () => {
|
|
103
103
|
const page = createAskPageMock({ hasLoginGate: true });
|
|
104
|
-
await expect(askCommand.func(page, { prompt: '你好', timeout:
|
|
104
|
+
await expect(askCommand.func(page, { prompt: '你好', timeout: 60, search: true, think: false }))
|
|
105
105
|
.rejects.toBeInstanceOf(AuthRequiredError);
|
|
106
106
|
});
|
|
107
107
|
it('throws CommandExecutionError when the prompt cannot be sent', async () => {
|
|
@@ -111,14 +111,14 @@ describe('yuanbao ask command', () => {
|
|
|
111
111
|
reason: 'Yuanbao composer was not found.',
|
|
112
112
|
},
|
|
113
113
|
});
|
|
114
|
-
await expect(askCommand.func(page, { prompt: '你好', timeout:
|
|
114
|
+
await expect(askCommand.func(page, { prompt: '你好', timeout: 60, search: true, think: false }))
|
|
115
115
|
.rejects.toBeInstanceOf(CommandExecutionError);
|
|
116
116
|
});
|
|
117
117
|
it('throws TimeoutError when no response arrives before timeout', async () => {
|
|
118
118
|
const page = createAskPageMock({
|
|
119
119
|
sendResult: { ok: true, action: 'click' },
|
|
120
120
|
});
|
|
121
|
-
await expect(askCommand.func(page, { prompt: '你好', timeout:
|
|
122
|
-
.rejects.toBeInstanceOf(
|
|
121
|
+
await expect(askCommand.func(page, { prompt: '你好', timeout: -1, search: true, think: false }))
|
|
122
|
+
.rejects.toBeInstanceOf(ArgumentError);
|
|
123
123
|
});
|
|
124
124
|
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
2
|
+
import { EmptyResultError } from '@jackwener/opencli/errors';
|
|
3
|
+
import {
|
|
4
|
+
YUANBAO_DOMAIN,
|
|
5
|
+
YUANBAO_URL,
|
|
6
|
+
getYuanbaoMessageBubbles,
|
|
7
|
+
parseYuanbaoSessionId,
|
|
8
|
+
hasLoginGate,
|
|
9
|
+
authRequired,
|
|
10
|
+
} from './shared.js';
|
|
11
|
+
import { convertYuanbaoHtmlToMarkdown } from './ask.js';
|
|
12
|
+
|
|
13
|
+
cli({
|
|
14
|
+
site: 'yuanbao',
|
|
15
|
+
name: 'detail',
|
|
16
|
+
access: 'read',
|
|
17
|
+
description: 'Open a Yuanbao conversation by ID and read its messages',
|
|
18
|
+
domain: YUANBAO_DOMAIN,
|
|
19
|
+
strategy: Strategy.COOKIE,
|
|
20
|
+
browser: true,
|
|
21
|
+
browserSession: { reuse: 'site' },
|
|
22
|
+
navigateBefore: false,
|
|
23
|
+
args: [
|
|
24
|
+
{
|
|
25
|
+
name: 'id',
|
|
26
|
+
positional: true,
|
|
27
|
+
required: true,
|
|
28
|
+
help: 'Full https://yuanbao.tencent.com/chat/<agentId>/<convId> URL or "<agentId>/<convId>" pair (a UUID alone is not enough — Yuanbao requires the agent slug)',
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
columns: ['Role', 'Text'],
|
|
32
|
+
func: async (page, kwargs) => {
|
|
33
|
+
const { agentId, convId } = parseYuanbaoSessionId(kwargs.id);
|
|
34
|
+
await page.goto(`${YUANBAO_URL}chat/${agentId}/${convId}`, { waitUntil: 'load', settleMs: 2500 });
|
|
35
|
+
await page.wait(2);
|
|
36
|
+
if (await hasLoginGate(page)) {
|
|
37
|
+
throw authRequired('Yuanbao opened a login gate when navigating to the conversation.');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Poll up to ~20s for the transcript to render. The page shell loads
|
|
41
|
+
// before history is fetched, so a fixed wait races the empty render.
|
|
42
|
+
let bubbles = [];
|
|
43
|
+
const POLL_DEADLINE_MS = 20_000;
|
|
44
|
+
const POLL_INTERVAL_S = 1;
|
|
45
|
+
const startedAt = Date.now();
|
|
46
|
+
while (Date.now() - startedAt < POLL_DEADLINE_MS) {
|
|
47
|
+
bubbles = await getYuanbaoMessageBubbles(page);
|
|
48
|
+
if (bubbles.length > 0) break;
|
|
49
|
+
await page.wait(POLL_INTERVAL_S);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!bubbles.length) {
|
|
53
|
+
throw new EmptyResultError(
|
|
54
|
+
'yuanbao detail',
|
|
55
|
+
`No visible messages found for conversation ${agentId}/${convId}. Verify the IDs are correct and that the session belongs to the current login.`,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return bubbles.map((b) => ({
|
|
59
|
+
Role: b.role,
|
|
60
|
+
Text: b.role === 'Assistant' && b.html
|
|
61
|
+
? (convertYuanbaoHtmlToMarkdown(b.html).trim() || b.text)
|
|
62
|
+
: b.text,
|
|
63
|
+
}));
|
|
64
|
+
},
|
|
65
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
2
|
+
import { ArgumentError, EmptyResultError } from '@jackwener/opencli/errors';
|
|
3
|
+
import {
|
|
4
|
+
YUANBAO_DOMAIN,
|
|
5
|
+
YUANBAO_URL,
|
|
6
|
+
ensureYuanbaoPage,
|
|
7
|
+
getYuanbaoSessionList,
|
|
8
|
+
hasLoginGate,
|
|
9
|
+
authRequired,
|
|
10
|
+
} from './shared.js';
|
|
11
|
+
|
|
12
|
+
cli({
|
|
13
|
+
site: 'yuanbao',
|
|
14
|
+
name: 'history',
|
|
15
|
+
access: 'read',
|
|
16
|
+
description: 'List recent Yuanbao conversations from the sidebar (requires login)',
|
|
17
|
+
domain: YUANBAO_DOMAIN,
|
|
18
|
+
strategy: Strategy.COOKIE,
|
|
19
|
+
browser: true,
|
|
20
|
+
browserSession: { reuse: 'site' },
|
|
21
|
+
navigateBefore: false,
|
|
22
|
+
args: [
|
|
23
|
+
{ name: 'limit', type: 'int', default: 20, help: 'Max conversations to list (sidebar virtual scroll caps actual count)' },
|
|
24
|
+
],
|
|
25
|
+
columns: ['Index', 'Title', 'AgentId', 'SessionId', 'Url'],
|
|
26
|
+
func: async (page, kwargs) => {
|
|
27
|
+
const limit = Number(kwargs.limit ?? 20);
|
|
28
|
+
if (!Number.isInteger(limit) || limit <= 0) {
|
|
29
|
+
throw new ArgumentError('limit', 'must be a positive integer');
|
|
30
|
+
}
|
|
31
|
+
await ensureYuanbaoPage(page);
|
|
32
|
+
if (await hasLoginGate(page)) {
|
|
33
|
+
throw authRequired('Yuanbao opened a login gate when reading the sidebar.');
|
|
34
|
+
}
|
|
35
|
+
await page.wait(1.5);
|
|
36
|
+
const sessions = await getYuanbaoSessionList(page, limit);
|
|
37
|
+
if (!sessions.length) {
|
|
38
|
+
throw new EmptyResultError(
|
|
39
|
+
'yuanbao history',
|
|
40
|
+
'No Yuanbao conversations found in the sidebar. Either the account is logged out, the sidebar is collapsed, or the user truly has no chat history yet.',
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return sessions.map((s, i) => ({
|
|
44
|
+
Index: i + 1,
|
|
45
|
+
Title: s.title || '(untitled)',
|
|
46
|
+
AgentId: s.agentId,
|
|
47
|
+
SessionId: s.cid,
|
|
48
|
+
Url: `${YUANBAO_URL}chat/${s.agentId}/${s.cid}`,
|
|
49
|
+
}));
|
|
50
|
+
},
|
|
51
|
+
});
|
package/clis/yuanbao/new.js
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
2
|
+
import {
|
|
3
|
+
YUANBAO_DOMAIN,
|
|
4
|
+
ensureYuanbaoPage,
|
|
5
|
+
getYuanbaoMessageBubbles,
|
|
6
|
+
} from './shared.js';
|
|
7
|
+
import { convertYuanbaoHtmlToMarkdown } from './ask.js';
|
|
8
|
+
|
|
9
|
+
cli({
|
|
10
|
+
site: 'yuanbao',
|
|
11
|
+
name: 'read',
|
|
12
|
+
access: 'read',
|
|
13
|
+
description: 'Read messages in the current Yuanbao conversation',
|
|
14
|
+
domain: YUANBAO_DOMAIN,
|
|
15
|
+
strategy: Strategy.COOKIE,
|
|
16
|
+
browser: true,
|
|
17
|
+
browserSession: { reuse: 'site' },
|
|
18
|
+
navigateBefore: false,
|
|
19
|
+
args: [],
|
|
20
|
+
columns: ['Role', 'Text'],
|
|
21
|
+
func: async (page) => {
|
|
22
|
+
await ensureYuanbaoPage(page);
|
|
23
|
+
await page.wait(1.5);
|
|
24
|
+
const bubbles = await getYuanbaoMessageBubbles(page);
|
|
25
|
+
if (!bubbles.length) {
|
|
26
|
+
return [{ Role: 'system', Text: 'No visible messages in the current Yuanbao conversation.' }];
|
|
27
|
+
}
|
|
28
|
+
return bubbles.map((b) => ({
|
|
29
|
+
Role: b.role,
|
|
30
|
+
// Assistant turns render markdown HTML; convert to markdown so the
|
|
31
|
+
// text column carries usable structure (lists, code, tables) rather
|
|
32
|
+
// than collapsed innerText.
|
|
33
|
+
Text: b.role === 'Assistant' && b.html
|
|
34
|
+
? (convertYuanbaoHtmlToMarkdown(b.html).trim() || b.text)
|
|
35
|
+
: b.text,
|
|
36
|
+
}));
|
|
37
|
+
},
|
|
38
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
2
|
+
import { ArgumentError, CommandExecutionError } from '@jackwener/opencli/errors';
|
|
3
|
+
import {
|
|
4
|
+
YUANBAO_DOMAIN,
|
|
5
|
+
authRequired,
|
|
6
|
+
ensureYuanbaoPage,
|
|
7
|
+
hasLoginGate,
|
|
8
|
+
normalizeBooleanFlag,
|
|
9
|
+
sendYuanbaoMessage,
|
|
10
|
+
startNewYuanbaoChat,
|
|
11
|
+
} from './shared.js';
|
|
12
|
+
|
|
13
|
+
cli({
|
|
14
|
+
site: 'yuanbao',
|
|
15
|
+
name: 'send',
|
|
16
|
+
access: 'write',
|
|
17
|
+
description: 'Fire-and-forget: send a prompt to Yuanbao without waiting for the reply',
|
|
18
|
+
domain: YUANBAO_DOMAIN,
|
|
19
|
+
strategy: Strategy.COOKIE,
|
|
20
|
+
browser: true,
|
|
21
|
+
browserSession: { reuse: 'site' },
|
|
22
|
+
navigateBefore: false,
|
|
23
|
+
args: [
|
|
24
|
+
{ name: 'prompt', positional: true, required: true, help: 'Prompt to send to Yuanbao' },
|
|
25
|
+
{ name: 'new', type: 'boolean', default: false, help: 'Start a new chat before sending' },
|
|
26
|
+
],
|
|
27
|
+
columns: ['Status', 'Prompt'],
|
|
28
|
+
func: async (page, kwargs) => {
|
|
29
|
+
const prompt = String(kwargs.prompt || '').trim();
|
|
30
|
+
if (!prompt) throw new ArgumentError('prompt', 'is required');
|
|
31
|
+
const startFresh = normalizeBooleanFlag(kwargs.new, false);
|
|
32
|
+
|
|
33
|
+
await ensureYuanbaoPage(page);
|
|
34
|
+
if (await hasLoginGate(page)) {
|
|
35
|
+
throw authRequired('Yuanbao opened a login gate before sending the prompt.');
|
|
36
|
+
}
|
|
37
|
+
if (startFresh) {
|
|
38
|
+
const action = await startNewYuanbaoChat(page);
|
|
39
|
+
if (action === 'blocked') {
|
|
40
|
+
throw authRequired('Yuanbao opened a login gate while starting a new chat.');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const send = await sendYuanbaoMessage(page, prompt);
|
|
44
|
+
if (!send?.ok) {
|
|
45
|
+
if (await hasLoginGate(page)) {
|
|
46
|
+
throw authRequired('Yuanbao opened a login gate instead of accepting the prompt.');
|
|
47
|
+
}
|
|
48
|
+
throw new CommandExecutionError(
|
|
49
|
+
send?.reason || 'Failed to send Yuanbao prompt',
|
|
50
|
+
send?.detail
|
|
51
|
+
? `Detail: ${send.detail}`
|
|
52
|
+
: 'Make sure the Yuanbao chat composer is visible and not in a disabled state.',
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return [{ Status: 'sent', Prompt: prompt }];
|
|
56
|
+
},
|
|
57
|
+
});
|