@sovovs/bycli 1.8.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli-manifest.json +0 -61
- package/dist/src/adapter-shadow.js +2 -2
- package/dist/src/browser/analyze.d.ts +10 -99
- package/dist/src/browser/analyze.js +10 -226
- package/dist/src/browser/cdp.js +64 -7
- package/dist/src/browser/navigation-guard.d.ts +78 -0
- package/dist/src/browser/navigation-guard.js +142 -0
- package/dist/src/browser/navigation-guard.test.d.ts +1 -0
- package/dist/src/browser/url-policy.d.ts +89 -0
- package/dist/src/browser/url-policy.js +311 -0
- package/dist/src/browser/url-policy.test.d.ts +1 -0
- package/dist/src/browser/verify-fixture.js +2 -2
- package/dist/src/cli.js +10 -13
- package/dist/src/config-paths.d.ts +19 -0
- package/dist/src/config-paths.js +31 -0
- package/dist/src/daemon.js +240 -0
- package/dist/src/discovery.d.ts +5 -3
- package/dist/src/discovery.js +8 -6
- package/dist/src/execution.js +8 -3
- package/dist/src/main.js +18 -2
- package/dist/src/plugin.js +11 -8
- package/dist/src/recorder/highlevel/analyze.d.ts +29 -0
- package/dist/src/recorder/highlevel/analyze.js +91 -0
- package/dist/src/recorder/highlevel/init.d.ts +125 -0
- package/dist/src/recorder/highlevel/init.js +279 -0
- package/dist/src/recorder/highlevel/init.test.d.ts +1 -0
- package/dist/src/recorder/highlevel/verify.d.ts +73 -0
- package/dist/src/recorder/highlevel/verify.js +74 -0
- package/dist/src/recorder/http/analyze-runner.d.ts +28 -0
- package/dist/src/recorder/http/analyze-runner.js +78 -0
- package/dist/src/recorder/http/analyze-runner.test.d.ts +1 -0
- package/dist/src/recorder/http/handlers.d.ts +51 -0
- package/dist/src/recorder/http/handlers.js +219 -0
- package/dist/src/recorder/http/wrapper-config.d.ts +20 -0
- package/dist/src/recorder/http/wrapper-config.js +82 -0
- package/dist/src/recorder/http/wrapper-envelope.d.ts +31 -0
- package/dist/src/recorder/http/wrapper-envelope.js +51 -0
- package/dist/src/recorder/http/wrapper-gates.d.ts +16 -0
- package/dist/src/recorder/http/wrapper-gates.js +28 -0
- package/dist/src/recorder/http/wrapper-metrics.d.ts +1 -0
- package/dist/src/recorder/http/wrapper-metrics.js +5 -0
- package/dist/src/recorder/http/wrapper-registry.d.ts +50 -0
- package/dist/src/recorder/http/wrapper-registry.js +98 -0
- package/dist/src/recorder/http/wrapper-server.d.ts +59 -0
- package/dist/src/recorder/http/wrapper-server.js +240 -0
- package/dist/src/recorder/http/wrapper.test.d.ts +1 -0
- package/dist/src/recorder/observability/logger.d.ts +5 -0
- package/dist/src/recorder/observability/logger.js +13 -0
- package/dist/src/recorder/runner/config.d.ts +34 -0
- package/dist/src/recorder/runner/config.js +84 -0
- package/dist/src/recorder/runner/config.test.d.ts +1 -0
- package/dist/src/recorder/runner/reap.d.ts +79 -0
- package/dist/src/recorder/runner/reap.js +230 -0
- package/dist/src/recorder/runner/reap.test.d.ts +1 -0
- package/dist/src/recorder/runner/runner-port.d.ts +95 -0
- package/dist/src/recorder/runner/runner-port.js +518 -0
- package/dist/src/recorder/runner/runner-port.test.d.ts +1 -0
- package/dist/src/recorder/runner/session-keys.d.ts +28 -0
- package/dist/src/recorder/runner/session-keys.js +72 -0
- package/dist/src/recorder/runner/session-keys.test.d.ts +1 -0
- package/dist/src/recorder/runner/verify-runner-main.d.ts +95 -0
- package/dist/src/recorder/runner/verify-runner-main.js +275 -0
- package/dist/src/recorder/runner/verify-runner-main.test.d.ts +1 -0
- package/package.json +8 -5
- package/scripts/m1-daemon-navigate-drive.mjs +138 -0
- package/scripts/m1-extension-spike.mjs +237 -0
- package/scripts/m1-live-spike.mjs +156 -0
- package/scripts/m6b-connect-back-spike.sh +31 -0
- package/scripts/recorder.sh +186 -0
- package/clis/12306/utils.test.js +0 -331
- package/clis/1688/assets.test.js +0 -39
- package/clis/1688/download.test.js +0 -31
- package/clis/1688/item.test.js +0 -67
- package/clis/1688/search.test.js +0 -75
- package/clis/1688/shared.test.js +0 -57
- package/clis/1688/store.test.js +0 -62
- package/clis/36kr/article.test.js +0 -46
- package/clis/36kr/hot.test.js +0 -15
- package/clis/36kr/news.test.js +0 -85
- package/clis/51job/utils.test.js +0 -69
- package/clis/_atlassian/shared.test.js +0 -170
- package/clis/aibase/news.test.js +0 -59
- package/clis/amazon/bestsellers.test.js +0 -29
- package/clis/amazon/discussion.test.js +0 -124
- package/clis/amazon/offer.test.js +0 -29
- package/clis/amazon/product.test.js +0 -24
- package/clis/amazon/rankings.test.js +0 -41
- package/clis/amazon/search.test.js +0 -22
- package/clis/amazon/shared.test.js +0 -44
- package/clis/apple-podcasts/commands.test.js +0 -119
- package/clis/apple-podcasts/utils.test.js +0 -57
- package/clis/arxiv/arxiv.test.js +0 -112
- package/clis/baidu-scholar/search.test.js +0 -23
- package/clis/barchart/greeks.test.js +0 -138
- package/clis/bilibili/comment.test.js +0 -153
- package/clis/bilibili/comments.test.js +0 -142
- package/clis/bilibili/dynamic.test.js +0 -68
- package/clis/bilibili/hot.test.js +0 -17
- package/clis/bilibili/subtitle.test.js +0 -196
- package/clis/bilibili/summary.test.js +0 -210
- package/clis/bilibili/utils.test.js +0 -61
- package/clis/bilibili/video.test.js +0 -132
- package/clis/binance/commands.test.js +0 -70
- package/clis/bloomberg/utils.test.js +0 -129
- package/clis/booking/booking.test.js +0 -356
- package/clis/boss/chatlist.test.js +0 -211
- package/clis/boss/chatmsg.test.js +0 -230
- package/clis/boss/search.test.js +0 -78
- package/clis/boss/utils.test.js +0 -34
- package/clis/brave/search.test.js +0 -76
- package/clis/chaoxing/utils.test.js +0 -45
- package/clis/chatgpt/commands.test.js +0 -45
- package/clis/chatgpt/envelope.test.js +0 -108
- package/clis/chatgpt/image.test.js +0 -188
- package/clis/chatgpt/utils.test.js +0 -330
- package/clis/chatgpt-app/ax.test.js +0 -35
- package/clis/chatwise/composer.test.js +0 -186
- package/clis/chess/analyze.test.js +0 -79
- package/clis/chess/game.test.js +0 -178
- package/clis/chess/games.test.js +0 -164
- package/clis/chess/stats.test.js +0 -79
- package/clis/chess/utils.test.js +0 -230
- package/clis/claude/ask.test.js +0 -338
- package/clis/claude/commands.test.js +0 -118
- package/clis/claude/utils.test.js +0 -148
- package/clis/cnki/search.test.js +0 -18
- package/clis/codex/sidebar.test.js +0 -329
- package/clis/coingecko/coingecko.test.js +0 -109
- package/clis/confluence/commands.test.js +0 -195
- package/clis/coupang/coupang.test.js +0 -159
- package/clis/coupang/utils.test.js +0 -62
- package/clis/ctrip/ctrip.test.js +0 -719
- package/clis/dblp/dblp.test.js +0 -397
- package/clis/deepseek/ask.test.js +0 -312
- package/clis/deepseek/detail.test.js +0 -81
- package/clis/deepseek/send.test.js +0 -107
- package/clis/deepseek/utils.test.js +0 -370
- package/clis/defillama/defillama.test.js +0 -99
- package/clis/devto/devto.test.js +0 -236
- package/clis/dianping/dianping.test.js +0 -578
- package/clis/douban/download.test.js +0 -170
- package/clis/douban/movie-hot.test.js +0 -14
- package/clis/douban/search.test.js +0 -11
- package/clis/douban/subject.test.js +0 -11
- package/clis/douban/utils.test.js +0 -438
- package/clis/doubao/detail.test.js +0 -42
- package/clis/doubao/history.test.js +0 -37
- package/clis/doubao/utils.test.js +0 -344
- package/clis/douyin/_shared/browser-fetch.test.js +0 -64
- package/clis/douyin/_shared/creation-id.test.js +0 -22
- package/clis/douyin/_shared/imagex-upload.test.js +0 -87
- package/clis/douyin/_shared/sts2.test.js +0 -27
- package/clis/douyin/_shared/text-extra.test.js +0 -37
- package/clis/douyin/_shared/timing.test.js +0 -28
- package/clis/douyin/_shared/tos-upload-short-read.test.js +0 -83
- package/clis/douyin/_shared/tos-upload.test.js +0 -229
- package/clis/douyin/_shared/transcode.test.js +0 -91
- package/clis/douyin/_shared/vod-upload.test.js +0 -38
- package/clis/douyin/activities.test.js +0 -55
- package/clis/douyin/collections.test.js +0 -45
- package/clis/douyin/delete.test.js +0 -100
- package/clis/douyin/draft.test.js +0 -335
- package/clis/douyin/drafts.test.js +0 -11
- package/clis/douyin/hashtag.test.js +0 -171
- package/clis/douyin/location.test.js +0 -23
- package/clis/douyin/profile.test.js +0 -46
- package/clis/douyin/publish-upload-id.test.js +0 -170
- package/clis/douyin/publish.test.js +0 -38
- package/clis/douyin/stats.test.js +0 -22
- package/clis/douyin/update.test.js +0 -11
- package/clis/douyin/user-videos.test.js +0 -151
- package/clis/douyin/videos.test.js +0 -54
- package/clis/duckduckgo/search.test.js +0 -128
- package/clis/duckduckgo/suggest.test.js +0 -66
- package/clis/eastmoney/hot-rank.test.js +0 -59
- package/clis/endoflife/endoflife.test.js +0 -51
- package/clis/facebook/feed.test.js +0 -169
- package/clis/facebook/marketplace.test.js +0 -91
- package/clis/facebook/notifications.test.js +0 -458
- package/clis/facebook/search.test.js +0 -55
- package/clis/flathub/flathub.test.js +0 -90
- package/clis/flomo/memos.test.js +0 -144
- package/clis/gemini/ask.test.js +0 -100
- package/clis/gemini/deep-research-result.test.js +0 -155
- package/clis/gemini/deep-research.test.js +0 -181
- package/clis/gemini/reply-state.test.js +0 -671
- package/clis/gemini/utils.test.js +0 -198
- package/clis/geogebra/geogebra.test.js +0 -175
- package/clis/gitee/search.test.js +0 -65
- package/clis/gitee/user.test.js +0 -63
- package/clis/google/utils.test.js +0 -75
- package/clis/google-scholar/cite.test.js +0 -47
- package/clis/google-scholar/profile.test.js +0 -49
- package/clis/google-scholar/search.test.js +0 -71
- package/clis/goproxy/goproxy.test.js +0 -103
- package/clis/gov-law/commands.test.js +0 -39
- package/clis/gov-policy/gov-policy.test.js +0 -224
- package/clis/grok/ask.test.js +0 -29
- package/clis/grok/utils.test.js +0 -103
- package/clis/hackernews/hackernews.test.js +0 -132
- package/clis/hf/hf.test.js +0 -16
- package/clis/hupu/hot.test.js +0 -224
- package/clis/imdb/utils.test.js +0 -88
- package/clis/indeed/indeed.test.js +0 -375
- package/clis/instagram/_shared/private-publish.test.js +0 -705
- package/clis/instagram/_shared/protocol-capture.test.js +0 -114
- package/clis/instagram/download.test.js +0 -118
- package/clis/instagram/note.test.js +0 -53
- package/clis/instagram/post.test.js +0 -1647
- package/clis/instagram/reel.test.js +0 -167
- package/clis/instagram/story.test.js +0 -134
- package/clis/jd/commands.test.js +0 -41
- package/clis/jd/item.test.js +0 -354
- package/clis/jianyu/search.test.js +0 -153
- package/clis/jianyu/shared/procurement-contract.test.js +0 -72
- package/clis/jianyu/shared/procurement-detail.test.js +0 -84
- package/clis/jike/read.test.js +0 -86
- package/clis/jira/commands.test.js +0 -287
- package/clis/juejin/search.js +0 -123
- package/clis/juejin/search.test.js +0 -98
- package/clis/lesswrong/frontpage.test.js +0 -37
- package/clis/lichess/lichess.test.js +0 -85
- package/clis/linkedin/connect.test.js +0 -213
- package/clis/linkedin/inbox.test.js +0 -152
- package/clis/linkedin/job-detail.test.js +0 -38
- package/clis/linkedin/jobs-preferences.test.js +0 -43
- package/clis/linkedin/people-search.test.js +0 -216
- package/clis/linkedin/post-analytics.test.js +0 -40
- package/clis/linkedin/posts.test.js +0 -40
- package/clis/linkedin/profile-analytics.test.js +0 -67
- package/clis/linkedin/profile-experience.test.js +0 -152
- package/clis/linkedin/profile-projects.test.js +0 -111
- package/clis/linkedin/profile-read.test.js +0 -77
- package/clis/linkedin/safe-send.test.js +0 -204
- package/clis/linkedin/salesnav-inbox.test.js +0 -113
- package/clis/linkedin/salesnav-message.test.js +0 -172
- package/clis/linkedin/salesnav-search.test.js +0 -76
- package/clis/linkedin/salesnav-thread.test.js +0 -79
- package/clis/linkedin/search.test.js +0 -222
- package/clis/linkedin/sent-invitations.test.js +0 -62
- package/clis/linkedin/services-read.test.js +0 -105
- package/clis/linkedin/thread-snapshot.test.js +0 -89
- package/clis/linkedin/timeline.test.js +0 -81
- package/clis/linkedin-learning/course.test.js +0 -114
- package/clis/linkedin-learning/search.test.js +0 -144
- package/clis/linkedin-learning/trending.test.js +0 -123
- package/clis/linux-do/feed.test.js +0 -153
- package/clis/linux-do/topic-content.test.js +0 -59
- package/clis/lobsters/lobsters.test.js +0 -169
- package/clis/notebooklm/add-source.test.js +0 -97
- package/clis/notebooklm/compat.test.js +0 -16
- package/clis/notebooklm/create.test.js +0 -58
- package/clis/notebooklm/generate-audio.test.js +0 -63
- package/clis/notebooklm/generate-slides.test.js +0 -75
- package/clis/notebooklm/history.test.js +0 -58
- package/clis/notebooklm/note-list.test.js +0 -56
- package/clis/notebooklm/notes-get.test.js +0 -72
- package/clis/notebooklm/open.test.js +0 -63
- package/clis/notebooklm/rpc.test.js +0 -131
- package/clis/notebooklm/source-fulltext.test.js +0 -106
- package/clis/notebooklm/source-get.test.js +0 -84
- package/clis/notebooklm/source-guide.test.js +0 -104
- package/clis/notebooklm/summary.test.js +0 -78
- package/clis/notebooklm/utils.test.js +0 -449
- package/clis/notebooklm/write-note.test.js +0 -70
- package/clis/nuget/nuget.test.js +0 -111
- package/clis/oeis/oeis.test.js +0 -88
- package/clis/ones/task-helpers.test.js +0 -12
- package/clis/ones/worklog.test.js +0 -20
- package/clis/openfda/openfda.test.js +0 -114
- package/clis/openreview/openreview.test.js +0 -427
- package/clis/osv/osv.test.js +0 -97
- package/clis/paperreview/commands.test.js +0 -243
- package/clis/paperreview/utils.test.js +0 -49
- package/clis/pixiv/download.test.js +0 -87
- package/clis/pixiv/illusts.test.js +0 -99
- package/clis/pixiv/search.test.js +0 -83
- package/clis/powerchina/search.test.js +0 -93
- package/clis/producthunt/utils.test.js +0 -64
- package/clis/pubmed/pubmed.test.js +0 -276
- package/clis/quark/utils.test.js +0 -58
- package/clis/qwen/utils.test.js +0 -45
- package/clis/reddit/extract-media.test.js +0 -149
- package/clis/reddit/frontpage.test.js +0 -34
- package/clis/reddit/home.test.js +0 -170
- package/clis/reddit/hot.test.js +0 -32
- package/clis/reddit/popular.test.js +0 -26
- package/clis/reddit/read.test.js +0 -335
- package/clis/reddit/reply.test.js +0 -89
- package/clis/reddit/search.test.js +0 -26
- package/clis/reddit/subreddit-info.test.js +0 -163
- package/clis/reddit/subreddit.test.js +0 -31
- package/clis/reddit/subscribed.test.js +0 -168
- package/clis/reddit/whoami.test.js +0 -105
- package/clis/rednote/navigation.test.js +0 -26
- package/clis/rednote/rednote.test.js +0 -222
- package/clis/rest-countries/rest-countries.test.js +0 -83
- package/clis/reuters/reuters.test.js +0 -299
- package/clis/rfc/rfc.test.js +0 -74
- package/clis/sinafinance/stock.test.js +0 -59
- package/clis/spotify/utils.test.js +0 -67
- package/clis/stackoverflow/stackoverflow.test.js +0 -404
- package/clis/steam/steam.test.js +0 -46
- package/clis/substack/utils.test.js +0 -44
- package/clis/suno/commands.test.js +0 -188
- package/clis/suno/download.test.js +0 -151
- package/clis/suno/generate.test.js +0 -252
- package/clis/suno/utils.test.js +0 -329
- package/clis/taobao/commands.test.js +0 -41
- package/clis/tdx/hot-rank.test.js +0 -59
- package/clis/ths/hot-rank.test.js +0 -64
- package/clis/tieba/commands.test.js +0 -79
- package/clis/tieba/utils.test.js +0 -290
- package/clis/tiktok/creator-videos.test.js +0 -113
- package/clis/tiktok/refactor.test.js +0 -389
- package/clis/tiktok/write-refactor.test.js +0 -370
- package/clis/toutiao/toutiao.test.js +0 -378
- package/clis/tvmaze/tvmaze.test.js +0 -93
- package/clis/twitter/bookmark-folder.test.js +0 -395
- package/clis/twitter/bookmark-folders.test.js +0 -152
- package/clis/twitter/bookmark.test.js +0 -74
- package/clis/twitter/bookmarks.test.js +0 -205
- package/clis/twitter/delete.test.js +0 -85
- package/clis/twitter/device-follow.test.js +0 -287
- package/clis/twitter/download.test.js +0 -457
- package/clis/twitter/followers.test.js +0 -62
- package/clis/twitter/following.test.js +0 -338
- package/clis/twitter/hide-reply.test.js +0 -76
- package/clis/twitter/like.test.js +0 -73
- package/clis/twitter/likes.test.js +0 -220
- package/clis/twitter/list-add.test.js +0 -133
- package/clis/twitter/list-create.test.js +0 -169
- package/clis/twitter/list-remove.test.js +0 -110
- package/clis/twitter/list-tweets.test.js +0 -196
- package/clis/twitter/lists.test.js +0 -253
- package/clis/twitter/post.test.js +0 -258
- package/clis/twitter/profile.test.js +0 -39
- package/clis/twitter/quote.test.js +0 -194
- package/clis/twitter/reply.test.js +0 -237
- package/clis/twitter/retweet.test.js +0 -73
- package/clis/twitter/search.test.js +0 -349
- package/clis/twitter/shared.test.js +0 -794
- package/clis/twitter/thread.test.js +0 -58
- package/clis/twitter/timeline.test.js +0 -104
- package/clis/twitter/trending.test.js +0 -15
- package/clis/twitter/tweets.test.js +0 -362
- package/clis/twitter/unbookmark.test.js +0 -73
- package/clis/twitter/unlike.test.js +0 -75
- package/clis/twitter/unretweet.test.js +0 -73
- package/clis/twitter/utils.test.js +0 -169
- package/clis/uisdc/news.test.js +0 -66
- package/clis/uiverse/_shared.test.js +0 -62
- package/clis/uiverse/navigation.test.js +0 -12
- package/clis/upwork/upwork.test.js +0 -566
- package/clis/wanfang/search.test.js +0 -23
- package/clis/web/read.test.js +0 -392
- package/clis/weibo/delete.test.js +0 -94
- package/clis/weibo/envelope.test.js +0 -85
- package/clis/weibo/favorites.test.js +0 -114
- package/clis/weibo/publish.test.js +0 -192
- package/clis/weibo/user-posts.test.js +0 -92
- package/clis/weibo/utils.test.js +0 -36
- package/clis/weixin/drafts.test.js +0 -69
- package/clis/weixin/search.test.js +0 -227
- package/clis/weread/ai-outline.test.js +0 -83
- package/clis/weread/book-search.test.js +0 -242
- package/clis/weread/commands.test.js +0 -398
- package/clis/weread/private-api-regression.test.js +0 -369
- package/clis/weread/search-regression.test.js +0 -494
- package/clis/weread/utils.test.js +0 -128
- package/clis/weread-official/commands.test.js +0 -385
- package/clis/weread-official/utils.test.js +0 -242
- package/clis/wikidata/wikidata.test.js +0 -83
- package/clis/wikipedia/trending.test.js +0 -57
- package/clis/wttr/wttr.test.js +0 -84
- package/clis/xianyu/chat.test.js +0 -84
- package/clis/xianyu/im.test.js +0 -253
- package/clis/xianyu/item.test.js +0 -56
- package/clis/xianyu/publish.test.js +0 -220
- package/clis/xianyu/search.test.js +0 -17
- package/clis/xiaoe/xiaoe.test.js +0 -486
- package/clis/xiaohongshu/comments.test.js +0 -258
- package/clis/xiaohongshu/creator-note-detail.test.js +0 -451
- package/clis/xiaohongshu/creator-notes-summary.test.js +0 -94
- package/clis/xiaohongshu/creator-notes.test.js +0 -281
- package/clis/xiaohongshu/creator-stats.test.js +0 -24
- package/clis/xiaohongshu/delete-note.test.js +0 -172
- package/clis/xiaohongshu/download.test.js +0 -316
- package/clis/xiaohongshu/navigation.test.js +0 -34
- package/clis/xiaohongshu/note.test.js +0 -249
- package/clis/xiaohongshu/publish.test.js +0 -608
- package/clis/xiaohongshu/search.test.js +0 -325
- package/clis/xiaohongshu/user-helpers.test.js +0 -160
- package/clis/xiaoyuzhou/auth.test.js +0 -124
- package/clis/xiaoyuzhou/download.test.js +0 -135
- package/clis/xiaoyuzhou/podcast-episodes.test.js +0 -78
- package/clis/xiaoyuzhou/transcript.test.js +0 -195
- package/clis/xiaoyuzhou/utils.test.js +0 -39
- package/clis/xueqiu/comments.test.js +0 -696
- package/clis/xueqiu/danjuan-utils.test.js +0 -41
- package/clis/xueqiu/utils.test.js +0 -26
- package/clis/yahoo/search.test.js +0 -94
- package/clis/youdao/note.test.js +0 -99
- package/clis/youtube/channel.test.js +0 -59
- package/clis/youtube/feed.test.js +0 -131
- package/clis/youtube/transcript-group.test.js +0 -99
- package/clis/youtube/transcript.test.js +0 -296
- package/clis/youtube/utils.test.js +0 -84
- package/clis/yuanbao/ask.test.js +0 -124
- package/clis/yuanbao/new.test.js +0 -30
- package/clis/yuanbao/shared.test.js +0 -80
- package/clis/zhihu/answer-comments.test.js +0 -287
- package/clis/zhihu/answer-detail.test.js +0 -338
- package/clis/zhihu/answer.test.js +0 -54
- package/clis/zhihu/collection.test.js +0 -337
- package/clis/zhihu/collections.test.js +0 -182
- package/clis/zhihu/comment.test.js +0 -50
- package/clis/zhihu/download.test.js +0 -12
- package/clis/zhihu/favorite.test.js +0 -59
- package/clis/zhihu/follow.test.js +0 -42
- package/clis/zhihu/like.test.js +0 -41
- package/clis/zhihu/question.test.js +0 -251
- package/clis/zhihu/recommend.test.js +0 -143
- package/clis/zhihu/search.test.js +0 -198
- package/clis/zhihu/target.test.js +0 -77
- package/clis/zhihu/text.test.js +0 -24
- package/clis/zhihu/write-shared.test.js +0 -176
- package/clis/zlibrary/commands.test.js +0 -65
- package/clis/zsxq/search.test.js +0 -24
- package/clis/zsxq/topic.test.js +0 -30
- package/clis/zsxq/topics.test.js +0 -24
- package/clis/zsxq/utils.test.js +0 -31
- package/dist/src/adapter-shadow.test.js +0 -49
- package/dist/src/adapter-source.test.js +0 -29
- package/dist/src/browser/analyze.test.js +0 -166
- package/dist/src/browser/article-extract.e2e.test.js +0 -105
- package/dist/src/browser/article-extract.test.js +0 -94
- package/dist/src/browser/ax-snapshot.test.js +0 -91
- package/dist/src/browser/base-page.test.js +0 -758
- package/dist/src/browser/cdp-click-fixture.test.js +0 -87
- package/dist/src/browser/cdp.test.js +0 -79
- package/dist/src/browser/compound.test.js +0 -175
- package/dist/src/browser/daemon-client.test.js +0 -199
- package/dist/src/browser/dom-helpers.test.js +0 -106
- package/dist/src/browser/dom-snapshot.test.js +0 -313
- package/dist/src/browser/errors.test.js +0 -60
- package/dist/src/browser/extract.test.js +0 -129
- package/dist/src/browser/find.test.js +0 -180
- package/dist/src/browser/html-tree.test.js +0 -181
- package/dist/src/browser/network-cache.test.js +0 -58
- package/dist/src/browser/network-key.test.js +0 -49
- package/dist/src/browser/page.test.js +0 -378
- package/dist/src/browser/shape-filter.test.js +0 -101
- package/dist/src/browser/shape.test.js +0 -82
- package/dist/src/browser/stealth.test.js +0 -134
- package/dist/src/browser/target-errors.test.js +0 -94
- package/dist/src/browser/target-resolver.test.js +0 -118
- package/dist/src/browser/utils.test.js +0 -29
- package/dist/src/browser/verify-fixture.test.js +0 -219
- package/dist/src/browser.test.js +0 -232
- package/dist/src/build-manifest.test.js +0 -341
- package/dist/src/capabilityRouting.test.js +0 -63
- package/dist/src/cli-argv-preprocess.test.js +0 -209
- package/dist/src/cli.test.js +0 -2803
- package/dist/src/commanderAdapter.test.js +0 -352
- package/dist/src/commands/daemon.test.js +0 -219
- package/dist/src/completion.test.js +0 -24
- package/dist/src/convention-audit.test.js +0 -247
- package/dist/src/daemon.test.js +0 -60
- package/dist/src/doctor.test.js +0 -263
- package/dist/src/download/article-download.test.js +0 -235
- package/dist/src/download/index.test.js +0 -140
- package/dist/src/download/media-download.test.js +0 -112
- package/dist/src/download/progress.test.js +0 -36
- package/dist/src/electron-apps.test.js +0 -49
- package/dist/src/engine.test.js +0 -312
- package/dist/src/errors.test.js +0 -109
- package/dist/src/execution.test.js +0 -623
- package/dist/src/extension-manifest-regression.test.js +0 -12
- package/dist/src/external.test.js +0 -146
- package/dist/src/help.test.js +0 -58
- package/dist/src/hooks.test.js +0 -92
- package/dist/src/interceptor.test.js +0 -81
- package/dist/src/launcher.test.js +0 -138
- package/dist/src/node-network.test.js +0 -55
- package/dist/src/observation/artifact.test.js +0 -121
- package/dist/src/observation/manager.test.js +0 -13
- package/dist/src/observation/redaction.test.js +0 -32
- package/dist/src/observation/retention.test.js +0 -118
- package/dist/src/observation/ring-buffer.test.js +0 -22
- package/dist/src/output.test.js +0 -53
- package/dist/src/package-exports.test.js +0 -112
- package/dist/src/pipeline/executor.test.js +0 -182
- package/dist/src/pipeline/steps/download.test.js +0 -102
- package/dist/src/pipeline/steps/fetch.test.js +0 -117
- package/dist/src/pipeline/template.test.js +0 -175
- package/dist/src/pipeline/transform.test.js +0 -133
- package/dist/src/plugin-manifest.test.js +0 -179
- package/dist/src/plugin-scaffold.test.js +0 -83
- package/dist/src/plugin.test.js +0 -1324
- package/dist/src/registry.test.js +0 -209
- package/dist/src/runtime-detect.test.js +0 -39
- package/dist/src/serialization.test.js +0 -78
- package/dist/src/snapshotFormatter.test.js +0 -521
- package/dist/src/tui.test.js +0 -19
- package/dist/src/update-check.test.js +0 -116
- package/dist/src/utils.test.js +0 -155
- package/dist/src/validate.test.js +0 -90
- package/dist/src/weixin-download.test.js +0 -45
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { getRegistry } from '@sovovs/bycli/registry';
|
|
3
|
-
import { ArgumentError, AuthRequiredError, CommandExecutionError, EmptyResultError } from '@sovovs/bycli/errors';
|
|
4
|
-
import './people-search.js';
|
|
5
|
-
|
|
6
|
-
const {
|
|
7
|
-
parseLimit,
|
|
8
|
-
buildSearchUrl,
|
|
9
|
-
looksLinkedInAuthWall,
|
|
10
|
-
normalizeProfileUrl,
|
|
11
|
-
normalizePeopleRows,
|
|
12
|
-
parseNonNegativeCount,
|
|
13
|
-
extractionScript,
|
|
14
|
-
} = await import('./people-search.js').then((m) => m.__test__);
|
|
15
|
-
|
|
16
|
-
function extractionResult(rows, counts = {}) {
|
|
17
|
-
return {
|
|
18
|
-
rows,
|
|
19
|
-
candidate_count: counts.candidate_count ?? rows.length,
|
|
20
|
-
person_entries_count: counts.person_entries_count ?? counts.candidate_count ?? rows.length,
|
|
21
|
-
resolved_count: counts.resolved_count ?? rows.length,
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function makePage({
|
|
26
|
-
evaluateResult,
|
|
27
|
-
evaluateReject,
|
|
28
|
-
gotoReject,
|
|
29
|
-
cookies = [{ name: 'JSESSIONID', value: '"ajax:1234567890"' }],
|
|
30
|
-
} = {}) {
|
|
31
|
-
return {
|
|
32
|
-
goto: vi.fn().mockImplementation(() => gotoReject ? Promise.reject(gotoReject) : Promise.resolve(undefined)),
|
|
33
|
-
wait: vi.fn().mockResolvedValue(undefined),
|
|
34
|
-
getCookies: vi.fn().mockResolvedValue(cookies),
|
|
35
|
-
evaluate: vi.fn().mockImplementation(() => evaluateReject ? Promise.reject(evaluateReject) : Promise.resolve(evaluateResult)),
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
describe('linkedin people-search command', () => {
|
|
40
|
-
it('builds the canonical SSR search URL with encoded keywords', () => {
|
|
41
|
-
expect(buildSearchUrl('site reliability engineer'))
|
|
42
|
-
.toBe('https://www.linkedin.com/search/results/people/?keywords=site%20reliability%20engineer');
|
|
43
|
-
expect(buildSearchUrl('hello/world & stuff'))
|
|
44
|
-
.toBe('https://www.linkedin.com/search/results/people/?keywords=hello%2Fworld%20%26%20stuff');
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('validates --limit without silent clamping', () => {
|
|
48
|
-
expect(parseLimit(undefined)).toBe(5);
|
|
49
|
-
expect(parseLimit(1)).toBe(1);
|
|
50
|
-
expect(parseLimit(10)).toBe(10);
|
|
51
|
-
expect(() => parseLimit(0)).toThrow(ArgumentError);
|
|
52
|
-
expect(() => parseLimit(11)).toThrow(ArgumentError);
|
|
53
|
-
expect(() => parseLimit(-1)).toThrow(ArgumentError);
|
|
54
|
-
expect(() => parseLimit('abc')).toThrow(ArgumentError);
|
|
55
|
-
expect(() => parseLimit(1.5)).toThrow(ArgumentError);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('extraction script slices main.innerText by person-name boundaries', () => {
|
|
59
|
-
const s = extractionScript();
|
|
60
|
-
// Anchor enumeration finds /in/<handle>.
|
|
61
|
-
expect(s).toContain('a[href*="/in/"]');
|
|
62
|
-
expect(s).toContain('\\/in\\/([^/?#]+)');
|
|
63
|
-
// Text-slice approach: split main.innerText and locate names.
|
|
64
|
-
expect(s).toContain('main.innerText');
|
|
65
|
-
expect(s).toContain('lines.findIndex');
|
|
66
|
-
// Mutual-connection anchors are filtered out via the skip()
|
|
67
|
-
// predicate on the name-line match.
|
|
68
|
-
expect(s).toContain('mutual connection');
|
|
69
|
-
// Names dedup'd by handle.
|
|
70
|
-
expect(s).toContain('seenHandles');
|
|
71
|
-
// Aria-hidden span as canonical name source.
|
|
72
|
-
expect(s).toContain('span[aria-hidden="true"]');
|
|
73
|
-
// Only operates on the people-search page.
|
|
74
|
-
expect(s).toContain('search\\/results\\/people');
|
|
75
|
-
expect(s).toContain('candidate_count');
|
|
76
|
-
expect(s).toContain('resolved_count');
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('normalizes only stable LinkedIn profile identities', () => {
|
|
80
|
-
expect(normalizeProfileUrl('https://www.linkedin.com/in/alice-engineer/?mini=true'))
|
|
81
|
-
.toBe('https://www.linkedin.com/in/alice-engineer/');
|
|
82
|
-
expect(normalizeProfileUrl('https://linkedin.com/in/bob-builder')).toBe('https://www.linkedin.com/in/bob-builder/');
|
|
83
|
-
expect(normalizeProfileUrl('https://evil-linkedin.com/in/bob-builder')).toBe('');
|
|
84
|
-
expect(normalizeProfileUrl('http://www.linkedin.com/in/bob-builder')).toBe('');
|
|
85
|
-
expect(normalizeProfileUrl('https://www.linkedin.com/company/bycli')).toBe('');
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('detects LinkedIn auth-wall URLs separately from CUL redirects', () => {
|
|
89
|
-
expect(looksLinkedInAuthWall('https://www.linkedin.com/authwall Sign in to continue')).toBe(true);
|
|
90
|
-
expect(looksLinkedInAuthWall('https://www.linkedin.com/checkpoint/challenge security verification required')).toBe(true);
|
|
91
|
-
expect(looksLinkedInAuthWall('https://www.linkedin.com/feed/')).toBe(false);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('rejects malformed extraction rows instead of fabricating success rows', () => {
|
|
95
|
-
expect(() => normalizePeopleRows({})).toThrow(CommandExecutionError);
|
|
96
|
-
expect(() => normalizePeopleRows([null])).toThrow(CommandExecutionError);
|
|
97
|
-
expect(() => normalizePeopleRows([{ name: 'No URL', headline: 'h', location: 'l', profile_url: '' }]))
|
|
98
|
-
.toThrow(CommandExecutionError);
|
|
99
|
-
expect(() => normalizePeopleRows([{ name: '', headline: 'h', location: 'l', profile_url: 'https://www.linkedin.com/in/no-name/' }]))
|
|
100
|
-
.toThrow(CommandExecutionError);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('validates extraction evidence counters', () => {
|
|
104
|
-
expect(parseNonNegativeCount(0, 'candidate_count')).toBe(0);
|
|
105
|
-
expect(parseNonNegativeCount(2, 'candidate_count')).toBe(2);
|
|
106
|
-
expect(() => parseNonNegativeCount(undefined, 'candidate_count')).toThrow(CommandExecutionError);
|
|
107
|
-
expect(() => parseNonNegativeCount(-1, 'candidate_count')).toThrow(CommandExecutionError);
|
|
108
|
-
expect(() => parseNonNegativeCount(1.2, 'candidate_count')).toThrow(CommandExecutionError);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('returns ranked rows when the page yields people', async () => {
|
|
112
|
-
const cmd = getRegistry().get('linkedin/people-search');
|
|
113
|
-
expect(cmd?.func).toBeTypeOf('function');
|
|
114
|
-
const page = makePage({
|
|
115
|
-
evaluateResult: extractionResult([
|
|
116
|
-
{ name: 'Alice Engineer', headline: 'Staff SWE at Acme', location: 'Berlin', profile_url: 'https://www.linkedin.com/in/alice-engineer/' },
|
|
117
|
-
{ name: 'Bob Builder', headline: 'CTO at Globex', location: 'Remote', profile_url: 'https://www.linkedin.com/in/bob-builder/' },
|
|
118
|
-
]),
|
|
119
|
-
});
|
|
120
|
-
const result = await cmd.func(page, { keywords: 'reinforcement learning', limit: 5 });
|
|
121
|
-
expect(page.goto).toHaveBeenCalledWith('https://www.linkedin.com/search/results/people/?keywords=reinforcement%20learning');
|
|
122
|
-
expect(result).toEqual([
|
|
123
|
-
{ rank: 1, name: 'Alice Engineer', headline: 'Staff SWE at Acme', location: 'Berlin', profile_url: 'https://www.linkedin.com/in/alice-engineer/' },
|
|
124
|
-
{ rank: 2, name: 'Bob Builder', headline: 'CTO at Globex', location: 'Remote', profile_url: 'https://www.linkedin.com/in/bob-builder/' },
|
|
125
|
-
]);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('slices to --limit when more rows are extracted than requested', async () => {
|
|
129
|
-
const cmd = getRegistry().get('linkedin/people-search');
|
|
130
|
-
const page = makePage({
|
|
131
|
-
evaluateResult: extractionResult(Array.from({ length: 8 }, (_, i) => ({
|
|
132
|
-
name: `Person ${i}`, headline: 'h', location: 'l', profile_url: `https://www.linkedin.com/in/p${i}/`,
|
|
133
|
-
}))),
|
|
134
|
-
});
|
|
135
|
-
const result = await cmd.func(page, { keywords: 'x', limit: 3 });
|
|
136
|
-
expect(result).toHaveLength(3);
|
|
137
|
-
expect(result.map((r) => r.rank)).toEqual([1, 2, 3]);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it('throws AuthRequiredError when JSESSIONID cookie is missing', async () => {
|
|
141
|
-
const cmd = getRegistry().get('linkedin/people-search');
|
|
142
|
-
const page = makePage({ cookies: [], evaluateResult: extractionResult([]) });
|
|
143
|
-
await expect(cmd.func(page, { keywords: 'x', limit: 5 })).rejects.toBeInstanceOf(AuthRequiredError);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it('treats malformed cookie lookup results as CommandExecutionError', async () => {
|
|
147
|
-
const cmd = getRegistry().get('linkedin/people-search');
|
|
148
|
-
const page = makePage({ cookies: null, evaluateResult: extractionResult([]) });
|
|
149
|
-
await expect(cmd.func(page, { keywords: 'x', limit: 5 })).rejects.toBeInstanceOf(CommandExecutionError);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it('treats LinkedIn redirect away from search page as a CUL-flavoured CommandExecutionError', async () => {
|
|
153
|
-
const cmd = getRegistry().get('linkedin/people-search');
|
|
154
|
-
const page = makePage({ evaluateResult: { error: 'not on people search page', url: 'https://www.linkedin.com/' } });
|
|
155
|
-
await expect(cmd.func(page, { keywords: 'x', limit: 5 })).rejects.toBeInstanceOf(CommandExecutionError);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it('treats LinkedIn auth-wall redirects as AuthRequiredError', async () => {
|
|
159
|
-
const cmd = getRegistry().get('linkedin/people-search');
|
|
160
|
-
const page = makePage({ evaluateResult: { error: 'not on people search page', url: 'https://www.linkedin.com/authwall?trk=people_search' } });
|
|
161
|
-
await expect(cmd.func(page, { keywords: 'x', limit: 5 })).rejects.toBeInstanceOf(AuthRequiredError);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
it('wraps browser extraction exceptions as CommandExecutionError', async () => {
|
|
165
|
-
const cmd = getRegistry().get('linkedin/people-search');
|
|
166
|
-
const page = makePage({ evaluateReject: new SyntaxError('Unexpected token <') });
|
|
167
|
-
await expect(cmd.func(page, { keywords: 'x', limit: 5 })).rejects.toBeInstanceOf(CommandExecutionError);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it('wraps browser navigation exceptions as CommandExecutionError', async () => {
|
|
171
|
-
const cmd = getRegistry().get('linkedin/people-search');
|
|
172
|
-
const page = makePage({ gotoReject: new Error('navigation failed') });
|
|
173
|
-
await expect(cmd.func(page, { keywords: 'x', limit: 5 })).rejects.toBeInstanceOf(CommandExecutionError);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it('throws EmptyResultError when the page rendered zero rows', async () => {
|
|
177
|
-
const cmd = getRegistry().get('linkedin/people-search');
|
|
178
|
-
const page = makePage({ evaluateResult: extractionResult([]) });
|
|
179
|
-
await expect(cmd.func(page, { keywords: 'x', limit: 5 })).rejects.toBeInstanceOf(EmptyResultError);
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
it('treats profile candidates without stable parsed rows as parser drift', async () => {
|
|
183
|
-
const cmd = getRegistry().get('linkedin/people-search');
|
|
184
|
-
const page = makePage({
|
|
185
|
-
evaluateResult: extractionResult([], {
|
|
186
|
-
candidate_count: 1,
|
|
187
|
-
person_entries_count: 1,
|
|
188
|
-
resolved_count: 0,
|
|
189
|
-
}),
|
|
190
|
-
});
|
|
191
|
-
await expect(cmd.func(page, { keywords: 'x', limit: 5 })).rejects.toBeInstanceOf(CommandExecutionError);
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it('treats missing rows array as parser drift, not empty results', async () => {
|
|
195
|
-
const cmd = getRegistry().get('linkedin/people-search');
|
|
196
|
-
const page = makePage({ evaluateResult: {} });
|
|
197
|
-
await expect(cmd.func(page, { keywords: 'x', limit: 5 })).rejects.toBeInstanceOf(CommandExecutionError);
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
it('rejects empty keywords with ArgumentError before navigation', async () => {
|
|
201
|
-
const cmd = getRegistry().get('linkedin/people-search');
|
|
202
|
-
const page = makePage({ evaluateResult: extractionResult([]) });
|
|
203
|
-
await expect(cmd.func(page, { keywords: ' ', limit: 5 })).rejects.toBeInstanceOf(ArgumentError);
|
|
204
|
-
expect(page.goto).not.toHaveBeenCalled();
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it('registers with the expected columns and arg shape', () => {
|
|
208
|
-
const cmd = getRegistry().get('linkedin/people-search');
|
|
209
|
-
expect(cmd?.columns).toEqual(['rank', 'name', 'headline', 'location', 'profile_url']);
|
|
210
|
-
expect(cmd?.access).toBe('read');
|
|
211
|
-
expect(cmd?.browser).toBe(true);
|
|
212
|
-
const keywordsArg = cmd?.args?.find((a) => a.name === 'keywords');
|
|
213
|
-
expect(keywordsArg?.positional).toBe(true);
|
|
214
|
-
expect(keywordsArg?.required).toBe(true);
|
|
215
|
-
});
|
|
216
|
-
});
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { getRegistry } from '@sovovs/bycli/registry';
|
|
3
|
-
import { EmptyResultError } from '@sovovs/bycli/errors';
|
|
4
|
-
import './post-analytics.js';
|
|
5
|
-
|
|
6
|
-
const { summarize } = await import('./post-analytics.js').then((m) => m.__test__);
|
|
7
|
-
|
|
8
|
-
describe('linkedin post-analytics adapter', () => {
|
|
9
|
-
const command = getRegistry().get('linkedin/post-analytics');
|
|
10
|
-
|
|
11
|
-
it('registers command shape', () => {
|
|
12
|
-
expect(command).toBeDefined();
|
|
13
|
-
expect(command.strategy).toBe('cookie');
|
|
14
|
-
expect(command.browser).toBe(true);
|
|
15
|
-
expect(command.columns).toContain('total_reactions');
|
|
16
|
-
expect(command.columns).not.toContain('total_engagement_score');
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('summarizes raw counters without custom scoring', () => {
|
|
20
|
-
const out = summarize([
|
|
21
|
-
{ body: 'Latest post', reactions: 10, comments: 3, reposts: 1, impressions: 50, media: 'image', url: 'u1', posted_at: '1d' },
|
|
22
|
-
{ body: 'Older post', reactions: 2, comments: 0, reposts: 0, impressions: 20, media: '', url: '', posted_at: '2d' },
|
|
23
|
-
]);
|
|
24
|
-
expect(out).toMatchObject({
|
|
25
|
-
posts_analyzed: 2,
|
|
26
|
-
total_reactions: 12,
|
|
27
|
-
total_comments: 3,
|
|
28
|
-
total_reposts: 1,
|
|
29
|
-
total_impressions: 70,
|
|
30
|
-
posts_with_media: 1,
|
|
31
|
-
posts_with_urls: 1,
|
|
32
|
-
latest_posted_at: '1d',
|
|
33
|
-
latest_url: 'u1',
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('rejects empty analytics input', () => {
|
|
38
|
-
expect(() => summarize([])).toThrow(EmptyResultError);
|
|
39
|
-
});
|
|
40
|
-
});
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { getRegistry } from '@sovovs/bycli/registry';
|
|
3
|
-
import { CommandExecutionError } from '@sovovs/bycli/errors';
|
|
4
|
-
import './posts.js';
|
|
5
|
-
|
|
6
|
-
const { activityUrl, parseMetric, parseReactionText, normalizePost } = await import('./posts-core.js');
|
|
7
|
-
|
|
8
|
-
describe('linkedin posts adapter', () => {
|
|
9
|
-
const command = getRegistry().get('linkedin/posts');
|
|
10
|
-
|
|
11
|
-
it('registers command shape', () => {
|
|
12
|
-
expect(command).toBeDefined();
|
|
13
|
-
expect(command.strategy).toBe('cookie');
|
|
14
|
-
expect(command.browser).toBe(true);
|
|
15
|
-
expect(command.columns).toContain('reactions');
|
|
16
|
-
expect(command.columns).toContain('media_urls');
|
|
17
|
-
expect(command.columns).toContain('raw_text');
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('builds profile activity URL', () => {
|
|
21
|
-
expect(activityUrl('https://www.linkedin.com/in/gauravsaxena1997/')).toBe('https://www.linkedin.com/in/gauravsaxena1997/recent-activity/all/');
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('parses compact metrics', () => {
|
|
25
|
-
expect(parseMetric('1,200 reactions')).toBe(1200);
|
|
26
|
-
expect(parseMetric('2.5k comments')).toBe(2500);
|
|
27
|
-
expect(parseReactionText('19 Divyang Bhargava and 18 others 12 comments')).toBe(19);
|
|
28
|
-
expect(parseReactionText('32 5 comments')).toBe(32);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('normalizes post rows and rejects identity-free rows', () => {
|
|
32
|
-
expect(() => normalizePost({ text: '', url: '' })).toThrow(CommandExecutionError);
|
|
33
|
-
expect(normalizePost({ author: 'AliceAlice', body: 'Post body', reactions: '3', impressions: '12', media: 'image: demo', media_urls: 'https://example.com/a.png' }))
|
|
34
|
-
.toMatchObject({ author: 'Alice', body: 'Post body', reactions: 3, impressions: 12, media: 'image: demo', media_urls: 'https://example.com/a.png' });
|
|
35
|
-
expect(normalizePost({ body: 'Post body', media_urls: 'javascript:alert(1) | https://example.com/a.png' }))
|
|
36
|
-
.toMatchObject({ media_urls: 'https://example.com/a.png' });
|
|
37
|
-
expect(normalizePost({ body: 'Post body', url: 'javascript:alert(1)' }))
|
|
38
|
-
.toMatchObject({ url: '' });
|
|
39
|
-
});
|
|
40
|
-
});
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { getRegistry } from '@sovovs/bycli/registry';
|
|
3
|
-
import { CommandExecutionError, EmptyResultError } from '@sovovs/bycli/errors';
|
|
4
|
-
import './profile-analytics.js';
|
|
5
|
-
|
|
6
|
-
const {
|
|
7
|
-
normalizeProfileAnalyticsUrl,
|
|
8
|
-
parseMetric,
|
|
9
|
-
parseDashboardMetrics,
|
|
10
|
-
normalizeAnalytics,
|
|
11
|
-
} = await import('./profile-analytics.js').then((m) => m.__test__);
|
|
12
|
-
|
|
13
|
-
describe('linkedin profile-analytics adapter', () => {
|
|
14
|
-
const command = getRegistry().get('linkedin/profile-analytics');
|
|
15
|
-
|
|
16
|
-
it('registers command shape', () => {
|
|
17
|
-
expect(command).toBeDefined();
|
|
18
|
-
expect(command.strategy).toBe('cookie');
|
|
19
|
-
expect(command.browser).toBe(true);
|
|
20
|
-
expect(command.columns).toEqual([
|
|
21
|
-
'profile_url',
|
|
22
|
-
'profile_views',
|
|
23
|
-
'post_impressions',
|
|
24
|
-
'search_appearances',
|
|
25
|
-
'followers',
|
|
26
|
-
'connections',
|
|
27
|
-
'raw_analytics',
|
|
28
|
-
]);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('normalizes profile url default and explicit /in URL', () => {
|
|
32
|
-
expect(normalizeProfileAnalyticsUrl(undefined)).toBe('https://www.linkedin.com/in/me/');
|
|
33
|
-
expect(normalizeProfileAnalyticsUrl('https://www.linkedin.com/in/gauravsaxena1997/')).toBe('https://www.linkedin.com/in/gauravsaxena1997/');
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('rejects non-profile URLs', () => {
|
|
37
|
-
expect(() => normalizeProfileAnalyticsUrl('https://www.linkedin.com/jobs/')).toThrow(CommandExecutionError);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('parses compact dashboard metrics', () => {
|
|
41
|
-
expect(parseMetric('1.2K')).toBe('1200');
|
|
42
|
-
expect(parseDashboardMetrics('31 post impressions 23 search appearances 32 profile views 1,234 followers 500 connections'))
|
|
43
|
-
.toEqual({
|
|
44
|
-
profile_views: '32',
|
|
45
|
-
post_impressions: '31',
|
|
46
|
-
search_appearances: '23',
|
|
47
|
-
followers: '1234',
|
|
48
|
-
connections: '500',
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('normalizes browser payload into columns', () => {
|
|
53
|
-
expect(normalizeAnalytics({
|
|
54
|
-
profile_url: 'https://www.linkedin.com/in/me/',
|
|
55
|
-
raw_analytics: '31 post impressions | 23 search appearances | 32 profile views',
|
|
56
|
-
})).toMatchObject({
|
|
57
|
-
profile_views: '32',
|
|
58
|
-
post_impressions: '31',
|
|
59
|
-
search_appearances: '23',
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('does not emit an all-empty analytics row', () => {
|
|
64
|
-
expect(() => normalizeAnalytics({ profile_url: 'https://www.linkedin.com/in/me/', raw_analytics: '' }))
|
|
65
|
-
.toThrow(EmptyResultError);
|
|
66
|
-
});
|
|
67
|
-
});
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { getRegistry } from '@sovovs/bycli/registry';
|
|
3
|
-
import { CommandExecutionError } from '@sovovs/bycli/errors';
|
|
4
|
-
import './profile-experience.js';
|
|
5
|
-
|
|
6
|
-
const {
|
|
7
|
-
normalizeProfileUrl,
|
|
8
|
-
profileExperienceUrl,
|
|
9
|
-
decodeLinkedInSafetyUrl,
|
|
10
|
-
parseDateRangeParts,
|
|
11
|
-
parseCompanyLine,
|
|
12
|
-
parseLocationLine,
|
|
13
|
-
parseExperienceText,
|
|
14
|
-
parseExperienceSectionText,
|
|
15
|
-
buildExperienceExtractionScript,
|
|
16
|
-
buildDialogExtractionScript,
|
|
17
|
-
normalizeExperience,
|
|
18
|
-
} = await import('./profile-experience.js').then((m) => m.__test__);
|
|
19
|
-
|
|
20
|
-
describe('linkedin profile-experience adapter', () => {
|
|
21
|
-
const command = getRegistry().get('linkedin/profile-experience');
|
|
22
|
-
|
|
23
|
-
it('registers command shape', () => {
|
|
24
|
-
expect(command).toBeDefined();
|
|
25
|
-
expect(command.strategy).toBe('cookie');
|
|
26
|
-
expect(command.browser).toBe(true);
|
|
27
|
-
expect(command.columns).toEqual([
|
|
28
|
-
'rank',
|
|
29
|
-
'total_count',
|
|
30
|
-
'title',
|
|
31
|
-
'employment_type',
|
|
32
|
-
'company',
|
|
33
|
-
'date_range',
|
|
34
|
-
'start_date',
|
|
35
|
-
'end_date',
|
|
36
|
-
'location',
|
|
37
|
-
'location_type',
|
|
38
|
-
'description',
|
|
39
|
-
'skills',
|
|
40
|
-
'media',
|
|
41
|
-
'urls',
|
|
42
|
-
'skill_url',
|
|
43
|
-
'media_url',
|
|
44
|
-
'profile_url',
|
|
45
|
-
'raw_text',
|
|
46
|
-
]);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('normalizes default and explicit profile URLs', () => {
|
|
50
|
-
expect(normalizeProfileUrl(undefined)).toBe('https://www.linkedin.com/in/me/');
|
|
51
|
-
expect(normalizeProfileUrl('https://www.linkedin.com/in/gauravsaxena1997?x=1')).toBe('https://www.linkedin.com/in/gauravsaxena1997?x=1');
|
|
52
|
-
expect(profileExperienceUrl('https://www.linkedin.com/in/gauravsaxena1997?x=1')).toBe('https://www.linkedin.com/in/gauravsaxena1997/details/experience/');
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('rejects non-profile URLs', () => {
|
|
56
|
-
expect(() => normalizeProfileUrl('https://www.linkedin.com/jobs/')).toThrow(CommandExecutionError);
|
|
57
|
-
expect(() => profileExperienceUrl('https://www.linkedin.com/in/me/')).toThrow(CommandExecutionError);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('parses company, date, and location line variants', () => {
|
|
61
|
-
expect(parseCompanyLine('Self Employed · Freelance')).toEqual({
|
|
62
|
-
company: 'Self Employed',
|
|
63
|
-
employment_type: 'Freelance',
|
|
64
|
-
});
|
|
65
|
-
expect(parseDateRangeParts('Feb 2026 – Present · 4 mos')).toEqual({
|
|
66
|
-
dateRange: 'Feb 2026 – Present',
|
|
67
|
-
startDate: 'Feb 2026',
|
|
68
|
-
endDate: 'Present',
|
|
69
|
-
});
|
|
70
|
-
expect(parseLocationLine('Jaipur, Rajasthan, India · Remote')).toEqual({
|
|
71
|
-
location: 'Jaipur, Rajasthan, India',
|
|
72
|
-
location_type: 'Remote',
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('parses visible experience text into fields', () => {
|
|
77
|
-
expect(parseExperienceText(`Senior Full-Stack AI Engineer
|
|
78
|
-
Self Employed · Freelance
|
|
79
|
-
Feb 2026 – Present · 4 mos
|
|
80
|
-
Jaipur, Rajasthan, India · Remote
|
|
81
|
-
Building AI-enabled SaaS products and agentic workflows.
|
|
82
|
-
Skills: Generative AI, Next.js, TypeScript`, 'https://www.linkedin.com/in/me/', 0, 1)).toMatchObject({
|
|
83
|
-
rank: 1,
|
|
84
|
-
total_count: 1,
|
|
85
|
-
title: 'Senior Full-Stack AI Engineer',
|
|
86
|
-
employment_type: 'Freelance',
|
|
87
|
-
company: 'Self Employed',
|
|
88
|
-
date_range: 'Feb 2026 – Present',
|
|
89
|
-
start_date: 'Feb 2026',
|
|
90
|
-
end_date: 'Present',
|
|
91
|
-
location: 'Jaipur, Rajasthan, India',
|
|
92
|
-
location_type: 'Remote',
|
|
93
|
-
description: 'Building AI-enabled SaaS products and agentic workflows.',
|
|
94
|
-
skills: 'Generative AI, Next.js, TypeScript',
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('parses a LinkedIn experience section rendered as one section', () => {
|
|
99
|
-
const rows = parseExperienceSectionText(`Experience
|
|
100
|
-
|
|
101
|
-
Senior Software Engineer
|
|
102
|
-
Zetwerk Manufacturing
|
|
103
|
-
Apr 2022 – Present · 4 yrs 2 mos
|
|
104
|
-
Bengaluru, Karnataka, India · Hybrid
|
|
105
|
-
Led development and mentored team members.
|
|
106
|
-
Skills: Angular, Node.js
|
|
107
|
-
|
|
108
|
-
Software Engineer
|
|
109
|
-
OnGraph Technologies Pvt. Ltd.
|
|
110
|
-
Jun 2019 – Apr 2022 · 2 yrs 11 mos
|
|
111
|
-
Jaipur, Rajasthan, India · On-site
|
|
112
|
-
Full-stack development for client projects.
|
|
113
|
-
Skills: React, MongoDB
|
|
114
|
-
|
|
115
|
-
Who your viewers also viewed`, 'https://www.linkedin.com/in/gauravsaxena1997/');
|
|
116
|
-
|
|
117
|
-
expect(rows).toHaveLength(2);
|
|
118
|
-
expect(rows[0]).toMatchObject({
|
|
119
|
-
total_count: 2,
|
|
120
|
-
title: 'Senior Software Engineer',
|
|
121
|
-
company: 'Zetwerk Manufacturing',
|
|
122
|
-
date_range: 'Apr 2022 – Present',
|
|
123
|
-
location_type: 'Hybrid',
|
|
124
|
-
});
|
|
125
|
-
expect(rows[1]).toMatchObject({
|
|
126
|
-
total_count: 2,
|
|
127
|
-
title: 'Software Engineer',
|
|
128
|
-
company: 'OnGraph Technologies Pvt. Ltd.',
|
|
129
|
-
date_range: 'Jun 2019 – Apr 2022',
|
|
130
|
-
location_type: 'On-site',
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it('normalizes rows and requires a title', () => {
|
|
135
|
-
expect(() => normalizeExperience({ title: '' })).toThrow(CommandExecutionError);
|
|
136
|
-
expect(normalizeExperience({ rank: '2', total_count: '4', title: ' AI Engineer ', company: ' Self Employed ', skill_url: ' https://example.com/skills ' }))
|
|
137
|
-
.toMatchObject({ rank: 2, total_count: 4, title: 'AI Engineer', company: 'Self Employed', skill_url: 'https://example.com/skills' });
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it('decodes LinkedIn safety redirect URLs', () => {
|
|
141
|
-
expect(decodeLinkedInSafetyUrl('https://www.linkedin.com/safety/go/?url=https%3A%2F%2Fexample.com%2Fdemo&urlhash=x'))
|
|
142
|
-
.toBe('https://example.com/demo');
|
|
143
|
-
expect(decodeLinkedInSafetyUrl('https://www.linkedin.com/safety/go/?url=javascript%3Aalert(1)&urlhash=x'))
|
|
144
|
-
.toBe('');
|
|
145
|
-
expect(decodeLinkedInSafetyUrl('javascript:alert(1)')).toBe('');
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it('builds browser extraction scripts that parse as JavaScript', () => {
|
|
149
|
-
expect(() => new Function(buildExperienceExtractionScript())).not.toThrow();
|
|
150
|
-
expect(() => new Function(buildDialogExtractionScript())).not.toThrow();
|
|
151
|
-
});
|
|
152
|
-
});
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { getRegistry } from '@sovovs/bycli/registry';
|
|
3
|
-
import { CommandExecutionError } from '@sovovs/bycli/errors';
|
|
4
|
-
import './profile-projects.js';
|
|
5
|
-
|
|
6
|
-
const { normalizeProfileUrl, profileProjectsUrl, parseProjectText, parseProjectsSectionText, decodeLinkedInSafetyUrl, normalizeProject } = await import('./profile-projects.js').then((m) => m.__test__);
|
|
7
|
-
|
|
8
|
-
describe('linkedin profile-projects adapter', () => {
|
|
9
|
-
const command = getRegistry().get('linkedin/profile-projects');
|
|
10
|
-
|
|
11
|
-
it('registers command shape', () => {
|
|
12
|
-
expect(command).toBeDefined();
|
|
13
|
-
expect(command.strategy).toBe('cookie');
|
|
14
|
-
expect(command.browser).toBe(true);
|
|
15
|
-
expect(command.columns).toEqual([
|
|
16
|
-
'rank',
|
|
17
|
-
'title',
|
|
18
|
-
'date_range',
|
|
19
|
-
'associated_with',
|
|
20
|
-
'description',
|
|
21
|
-
'skills',
|
|
22
|
-
'media',
|
|
23
|
-
'urls',
|
|
24
|
-
'profile_url',
|
|
25
|
-
'raw_text',
|
|
26
|
-
]);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('normalizes default and explicit profile URLs', () => {
|
|
30
|
-
expect(normalizeProfileUrl(undefined)).toBe('https://www.linkedin.com/in/me/');
|
|
31
|
-
expect(normalizeProfileUrl('https://www.linkedin.com/in/gauravsaxena1997?x=1')).toBe('https://www.linkedin.com/in/gauravsaxena1997?x=1');
|
|
32
|
-
expect(profileProjectsUrl('https://www.linkedin.com/in/gauravsaxena1997?x=1')).toBe('https://www.linkedin.com/in/gauravsaxena1997/details/projects/');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('rejects non-profile URLs', () => {
|
|
36
|
-
expect(() => normalizeProfileUrl('https://www.linkedin.com/jobs/')).toThrow(CommandExecutionError);
|
|
37
|
-
expect(() => profileProjectsUrl('https://www.linkedin.com/in/me/')).toThrow(CommandExecutionError);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('parses visible project text into fields', () => {
|
|
41
|
-
expect(parseProjectText(`OpenCLI Contributions
|
|
42
|
-
Jan 2026 - Present
|
|
43
|
-
Associated with Open Source
|
|
44
|
-
Browser automation and CLI adapter work
|
|
45
|
-
Skills: JavaScript, Browser Automation`, 'https://www.linkedin.com/in/me/', 0)).toMatchObject({
|
|
46
|
-
rank: 1,
|
|
47
|
-
title: 'OpenCLI Contributions',
|
|
48
|
-
date_range: 'Jan 2026 - Present',
|
|
49
|
-
associated_with: 'Open Source',
|
|
50
|
-
description: 'Browser automation and CLI adapter work',
|
|
51
|
-
skills: 'JavaScript, Browser Automation',
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('parses a LinkedIn projects section rendered as one section', () => {
|
|
56
|
-
const rows = parseProjectsSectionText(`Projects
|
|
57
|
-
|
|
58
|
-
Data Lake
|
|
59
|
-
|
|
60
|
-
May 2018 – Present
|
|
61
|
-
|
|
62
|
-
Associated with AdHoc Networks, Jaipur
|
|
63
|
-
|
|
64
|
-
Show project
|
|
65
|
-
|
|
66
|
-
Data Lake is a Data consolidation project.
|
|
67
|
-
- It provide HDFS services with Docker and VM.
|
|
68
|
-
|
|
69
|
-
Karyavahi
|
|
70
|
-
|
|
71
|
-
May 2017 – Present
|
|
72
|
-
|
|
73
|
-
Associated with Jaipur Engineering College & Research Center,jaipur
|
|
74
|
-
|
|
75
|
-
Show project
|
|
76
|
-
|
|
77
|
-
A social media for social issues.
|
|
78
|
-
Framework Used: Django , Bootstrap
|
|
79
|
-
|
|
80
|
-
Who your viewers also viewed`, 'https://www.linkedin.com/in/gauravsaxena1997/');
|
|
81
|
-
|
|
82
|
-
expect(rows).toHaveLength(2);
|
|
83
|
-
expect(rows[0]).toMatchObject({
|
|
84
|
-
title: 'Data Lake',
|
|
85
|
-
date_range: 'May 2018 – Present',
|
|
86
|
-
associated_with: 'AdHoc Networks, Jaipur',
|
|
87
|
-
description: 'Data Lake is a Data consolidation project. - It provide HDFS services with Docker and VM.',
|
|
88
|
-
});
|
|
89
|
-
expect(rows[1]).toMatchObject({
|
|
90
|
-
title: 'Karyavahi',
|
|
91
|
-
date_range: 'May 2017 – Present',
|
|
92
|
-
associated_with: 'Jaipur Engineering College & Research Center,jaipur',
|
|
93
|
-
description: 'A social media for social issues. Framework Used: Django , Bootstrap',
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('normalizes rows and requires a title', () => {
|
|
98
|
-
expect(() => normalizeProject({ title: '' })).toThrow(CommandExecutionError);
|
|
99
|
-
expect(normalizeProject({ rank: '2', title: ' Moniqo ', urls: ' https://example.com ' }))
|
|
100
|
-
.toMatchObject({ rank: 2, title: 'Moniqo', urls: 'https://example.com/' });
|
|
101
|
-
expect(normalizeProject({ title: ' Moniqo ', urls: 'javascript:alert(1) | https://example.com/demo' }))
|
|
102
|
-
.toMatchObject({ urls: 'https://example.com/demo' });
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('decodes LinkedIn safety redirect URLs', () => {
|
|
106
|
-
expect(decodeLinkedInSafetyUrl('https://www.linkedin.com/safety/go/?url=https%3A%2F%2Fgithub.com%2Fjackwener%2FOpenCLI&urlhash=x'))
|
|
107
|
-
.toBe('https://github.com/jackwener/OpenCLI');
|
|
108
|
-
expect(decodeLinkedInSafetyUrl('https://www.linkedin.com/safety/go/?url=javascript%3Aalert(1)&urlhash=x'))
|
|
109
|
-
.toBe('');
|
|
110
|
-
});
|
|
111
|
-
});
|