@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
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as os from 'node:os';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Resolvers for the byCLI user-data root and its well-known subdirectories.
|
|
5
|
+
*
|
|
6
|
+
* The root is `BYCLI_CONFIG_DIR` when set, else `~/.bycli`. These are FUNCTIONS,
|
|
7
|
+
* not module-level constants, on purpose: `BYCLI_CONFIG_DIR` is set dynamically
|
|
8
|
+
* (tests, the verify child sandbox in runner-port.ts) — a const captured at import
|
|
9
|
+
* time would freeze the wrong root and leak across tests. Always resolve on call.
|
|
10
|
+
*
|
|
11
|
+
* Existing duplicate idioms this consolidates: observation/artifact.ts, browser/profile.ts.
|
|
12
|
+
*/
|
|
13
|
+
export function getConfigDir() {
|
|
14
|
+
return process.env.BYCLI_CONFIG_DIR || path.join(os.homedir(), '.bycli');
|
|
15
|
+
}
|
|
16
|
+
/** User adapter root: `$CONFIG_DIR/clis`. Adapters live at `<clis>/<site>/<command>.js`. */
|
|
17
|
+
export function getUserClisDir() {
|
|
18
|
+
return path.join(getConfigDir(), 'clis');
|
|
19
|
+
}
|
|
20
|
+
/** Per-site memory/recorder root: `$CONFIG_DIR/sites`. */
|
|
21
|
+
export function getSitesDir() {
|
|
22
|
+
return path.join(getConfigDir(), 'sites');
|
|
23
|
+
}
|
|
24
|
+
/** A single site's directory: `$CONFIG_DIR/sites/<site>`. */
|
|
25
|
+
export function getSiteDir(site) {
|
|
26
|
+
return path.join(getSitesDir(), site);
|
|
27
|
+
}
|
|
28
|
+
/** A site's recorder artifact dir: `$CONFIG_DIR/sites/<site>/recorder`. */
|
|
29
|
+
export function getSiteRecorderDir(site) {
|
|
30
|
+
return path.join(getSiteDir(site), 'recorder');
|
|
31
|
+
}
|
package/dist/src/daemon.js
CHANGED
|
@@ -26,9 +26,34 @@ import { EXIT_CODES } from './errors.js';
|
|
|
26
26
|
import { log } from './logger.js';
|
|
27
27
|
import { PKG_VERSION } from './version.js';
|
|
28
28
|
import { DEFAULT_CONTEXT_ID } from './browser/profile.js';
|
|
29
|
+
import { createAdapterDraft, saveAdapterSource, recoverInitTransactions } from './recorder/highlevel/init.js';
|
|
30
|
+
import { verifyAdapter } from './recorder/highlevel/verify.js';
|
|
31
|
+
import { defaultRunnerPort, setDefaultRunnerDaemonPort, setDefaultRunnerObservability, setDefaultRunnerTempGuard } from './recorder/runner/runner-port.js';
|
|
32
|
+
import { createMetrics } from '@sovovs/bycli-recorder-core';
|
|
33
|
+
import { createRecorderLogger } from './recorder/observability/logger.js';
|
|
34
|
+
import { reapOrphanedVerifyRuns } from './recorder/runner/reap.js';
|
|
35
|
+
import { resolveTempPolicy, resolveRunnerConfig, resolveTempCapacity } from './recorder/runner/config.js';
|
|
36
|
+
import { defaultSessionKeyRegistry } from './recorder/runner/session-keys.js';
|
|
29
37
|
import { recordExtensionVersion } from './update-check.js';
|
|
30
38
|
import { buildCommandDispatchFailure, buildExtensionDisconnectFailure, getResponseCorsHeaders, } from './daemon-utils.js';
|
|
31
39
|
const PORT = parseInt(process.env.BYCLI_DAEMON_PORT ?? String(DEFAULT_DAEMON_PORT), 10);
|
|
40
|
+
// The verify runner (M6b) spawns child processes that connect back to THIS daemon for a
|
|
41
|
+
// browser Page. Hand them our port (→ BYCLI_DAEMON_PORT in the child env) so the child's
|
|
42
|
+
// Page reaches us, not a freshly-spawned daemon. Must run before the first /v1/verify
|
|
43
|
+
// builds the default runner singleton.
|
|
44
|
+
setDefaultRunnerDaemonPort(PORT);
|
|
45
|
+
// ─── Observability (#1b · 09) ────────────────────────────────────────
|
|
46
|
+
// daemon/runner-side structured logs + metrics (M8 only did dashboard-be). The metrics instance is
|
|
47
|
+
// a process singleton, shared by reference into the runner port (#1c) so GET /metrics shows runner
|
|
48
|
+
// counters too. LOG_LEVEL is read once at startup — the daemon has no ConfigPort, so no hot reload
|
|
49
|
+
// here (that's a larger, separate milestone); a malformed value falls back to 'info'.
|
|
50
|
+
const LOG_LEVELS = ['error', 'warn', 'info', 'debug'];
|
|
51
|
+
const envLogLevel = process.env.LOG_LEVEL ?? '';
|
|
52
|
+
const metrics = createMetrics();
|
|
53
|
+
const logger = createRecorderLogger(LOG_LEVELS.includes(envLogLevel) ? envLogLevel : 'info');
|
|
54
|
+
// Share these singletons with the verify runner (#1c) BEFORE the first defaultRunnerPort() use, so
|
|
55
|
+
// runner counters surface on GET /metrics and runner logs share the daemon's level.
|
|
56
|
+
setDefaultRunnerObservability(metrics, logger);
|
|
32
57
|
const extensionProfiles = new Map();
|
|
33
58
|
const pending = new Map();
|
|
34
59
|
let commandResultUnknownCount = 0;
|
|
@@ -119,6 +144,7 @@ function unregisterExtensionConnection(ws) {
|
|
|
119
144
|
});
|
|
120
145
|
if (failure.countAsCommandResultUnknown) {
|
|
121
146
|
commandResultUnknownCount++;
|
|
147
|
+
metrics.inc('daemon_command_result_unknown_total'); // #1b: structured metric alongside the /status field
|
|
122
148
|
log.warn(`[daemon] Command result unknown after extension disconnect (id=${id}, action=${p.action}, context=${contextId})`);
|
|
123
149
|
}
|
|
124
150
|
p.reject(new DaemonCommandFailure(failure.message, failure.errorCode, failure.errorHint, failure.status));
|
|
@@ -150,6 +176,11 @@ function readBody(req) {
|
|
|
150
176
|
});
|
|
151
177
|
}
|
|
152
178
|
function jsonResponse(res, status, data, extraHeaders) {
|
|
179
|
+
// Stash any errorCode for the request-completion metrics choke point (#1b), so handlers don't
|
|
180
|
+
// each have to wire it through. Only the bounded ErrorCode enum reaches a label/log field.
|
|
181
|
+
const ec = data?.errorCode;
|
|
182
|
+
if (typeof ec === 'string')
|
|
183
|
+
res.__errorCode = ec;
|
|
153
184
|
res.writeHead(status, { 'Content-Type': 'application/json', ...extraHeaders });
|
|
154
185
|
res.end(JSON.stringify(data));
|
|
155
186
|
}
|
|
@@ -174,6 +205,32 @@ async function handleRequest(req, res) {
|
|
|
174
205
|
}
|
|
175
206
|
const url = req.url ?? '/';
|
|
176
207
|
const pathname = url.split('?')[0];
|
|
208
|
+
// Request-completion metrics choke point (#1b · 09): one place records type/status/errorCode +
|
|
209
|
+
// latency, no per-handler scatter. Only the high-level surface (/v1/*) and the /command bridge are
|
|
210
|
+
// counted; the dynamic /v1/requests/{id} segment collapses to a bounded route template so the
|
|
211
|
+
// `operation` label can't grow unboundedly. /v1/* operations also get a structured log line
|
|
212
|
+
// (low volume, the recording ops); /command is counted but not logged (per-command WS bridge is
|
|
213
|
+
// high volume). Never logs headers/token/body — only operation/status/errorCode/durationMs.
|
|
214
|
+
const reqStarted = Date.now();
|
|
215
|
+
res.on('finish', () => {
|
|
216
|
+
let operation = null;
|
|
217
|
+
if (pathname.startsWith('/v1/requests/'))
|
|
218
|
+
operation = 'v1.requests';
|
|
219
|
+
else if (pathname.startsWith('/v1/'))
|
|
220
|
+
operation = `v1${pathname.slice(3).replace(/\//g, '.')}`; // /v1/init → v1.init
|
|
221
|
+
else if (pathname === '/command')
|
|
222
|
+
operation = 'command';
|
|
223
|
+
if (!operation)
|
|
224
|
+
return;
|
|
225
|
+
const status = res.statusCode < 400 ? 'ok' : 'failed';
|
|
226
|
+
const errorCode = res.__errorCode;
|
|
227
|
+
const durationMs = Date.now() - reqStarted;
|
|
228
|
+
metrics.inc('daemon_requests_total', { operation, status, errorCode });
|
|
229
|
+
if (operation === 'v1.verify')
|
|
230
|
+
metrics.observe('daemon_verify_duration_ms', durationMs);
|
|
231
|
+
if (operation.startsWith('v1'))
|
|
232
|
+
logger.info(`daemon.${operation}`, { status, errorCode, durationMs });
|
|
233
|
+
});
|
|
177
234
|
// Health-check endpoint — no X-byCLI header required.
|
|
178
235
|
// Used by the extension to silently probe daemon reachability before
|
|
179
236
|
// attempting a WebSocket connection (avoids uncatchable ERR_CONNECTION_REFUSED).
|
|
@@ -193,6 +250,102 @@ async function handleRequest(req, res) {
|
|
|
193
250
|
jsonResponse(res, 403, { ok: false, error: 'Forbidden: missing X-byCLI header' });
|
|
194
251
|
return;
|
|
195
252
|
}
|
|
253
|
+
// High-level init (M5b · A'): daemon hosts the FS-writing init capability; the
|
|
254
|
+
// logic lives in the main-repo init module (createAdapterDraft). dashboard-be
|
|
255
|
+
// forwards here rather than writing main-repo adapter paths itself. Synchronous
|
|
256
|
+
// (init is a short FS op, not a long-running runner); requestId/async is deferred.
|
|
257
|
+
if (req.method === 'POST' && pathname === '/v1/init') {
|
|
258
|
+
try {
|
|
259
|
+
const body = JSON.parse(await readBody(req));
|
|
260
|
+
if (typeof body.name !== 'string' || !body.name) {
|
|
261
|
+
jsonResponse(res, 400, { ok: false, errorCode: 'validation_failed', error: 'name required' });
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const result = createAdapterDraft(body);
|
|
265
|
+
if (!result.ok) {
|
|
266
|
+
// init failures are all client-fixable 400s (validation_failed / responsible_use_required).
|
|
267
|
+
jsonResponse(res, 400, { ok: false, errorCode: result.errorCode, error: result.reason });
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
jsonResponse(res, 200, { ok: true, data: { report: result.report, dryRun: result.dryRun, generatedSource: result.generatedSource } });
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
jsonResponse(res, 400, { ok: false, errorCode: 'validation_failed', error: err instanceof Error ? err.message : String(err) });
|
|
274
|
+
}
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
// N4: save a reviewed, full LLM-generated adapter source to clis/ (verify-then-save flow).
|
|
278
|
+
if (req.method === 'POST' && pathname === '/v1/save-adapter') {
|
|
279
|
+
try {
|
|
280
|
+
const body = JSON.parse(await readBody(req));
|
|
281
|
+
if (typeof body.name !== 'string' || !body.name || typeof body.source !== 'string' || !body.source) {
|
|
282
|
+
jsonResponse(res, 400, { ok: false, errorCode: 'validation_failed', error: 'name and source required' });
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const result = saveAdapterSource(body);
|
|
286
|
+
if (!result.ok) {
|
|
287
|
+
jsonResponse(res, 400, { ok: false, errorCode: result.errorCode, error: result.reason });
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
jsonResponse(res, 200, { ok: true, data: { adapterPath: result.adapterPath, reportPath: result.reportPath } });
|
|
291
|
+
}
|
|
292
|
+
catch (err) {
|
|
293
|
+
jsonResponse(res, 400, { ok: false, errorCode: 'validation_failed', error: err instanceof Error ? err.message : String(err) });
|
|
294
|
+
}
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
// High-level verify (A'): interface + delegation seam. The adapter JS runs in the
|
|
298
|
+
// M6 child-process runner (08) — verifyAdapter delegates to the real spawn-based
|
|
299
|
+
// RunnerPort (defaultRunnerPort), so runner_protocol_error now signals an actual
|
|
300
|
+
// runner fault, not "not implemented". The evidence HMAC is keyed by a per-session salt
|
|
301
|
+
// held only in daemon memory (M7a · 04:111): be forwards the non-secret sessionId, the
|
|
302
|
+
// daemon mints + holds the salt, and it never crosses the wire.
|
|
303
|
+
if (req.method === 'POST' && pathname === '/v1/verify') {
|
|
304
|
+
try {
|
|
305
|
+
const body = JSON.parse(await readBody(req));
|
|
306
|
+
if (typeof body.name !== 'string' || !body.name) {
|
|
307
|
+
jsonResponse(res, 400, { ok: false, errorCode: 'validation_failed', error: 'name required' });
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const sessionHmacKey = defaultSessionKeyRegistry().keyFor(body.sessionId);
|
|
311
|
+
const result = await verifyAdapter(body, sessionHmacKey);
|
|
312
|
+
if (!result.ok) {
|
|
313
|
+
const status = result.errorCode === 'validation_failed' ? 400
|
|
314
|
+
: result.errorCode === 'queue_full' ? 429 // 03: queue/concurrency exceeded
|
|
315
|
+
: 500;
|
|
316
|
+
// requestId/sessionId correlation the choke point (res-only) can't see (09:89).
|
|
317
|
+
logger.warn('daemon.verify', { sessionId: body.sessionId, status: 'failed', errorCode: result.errorCode });
|
|
318
|
+
jsonResponse(res, status, { ok: false, errorCode: result.errorCode, error: result.reason });
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
logger.info('daemon.verify', { requestId: result.requestId, sessionId: body.sessionId, stage: 'accepted' });
|
|
322
|
+
jsonResponse(res, 200, { ok: true, data: { requestId: result.requestId } });
|
|
323
|
+
}
|
|
324
|
+
catch (err) {
|
|
325
|
+
jsonResponse(res, 400, { ok: false, errorCode: 'validation_failed', error: err instanceof Error ? err.message : String(err) });
|
|
326
|
+
}
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
// High-level request status (M6 · ADR-0007): be polls verify status here. The runner
|
|
330
|
+
// registry is keyed by the canonical requestId (be ↔ daemon ↔ runner share one id);
|
|
331
|
+
// returns summary-only VerifySummary (raw seed/stdout/stderr/trace path never included).
|
|
332
|
+
if (req.method === 'GET' && pathname.startsWith('/v1/requests/')) {
|
|
333
|
+
const requestId = decodeURIComponent(pathname.slice('/v1/requests/'.length));
|
|
334
|
+
if (!requestId) {
|
|
335
|
+
jsonResponse(res, 400, { ok: false, errorCode: 'validation_failed', error: 'requestId required' });
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const runStatus = defaultRunnerPort().getRunStatus(requestId);
|
|
339
|
+
if (!runStatus) {
|
|
340
|
+
jsonResponse(res, 404, { ok: false, errorCode: 'request_not_found', error: 'unknown requestId' });
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
// RunStatus (queued|running|succeeded|failed|timeout|cancelled) maps 1:1 to request status;
|
|
344
|
+
// summary is null while queued/running, a VerifySummary once terminal (M6c adds queued).
|
|
345
|
+
logger.debug('daemon.requests', { requestId, status: runStatus.status });
|
|
346
|
+
jsonResponse(res, 200, { ok: true, data: { requestId, status: runStatus.status, result: runStatus.summary } });
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
196
349
|
if (req.method === 'GET' && pathname === '/status') {
|
|
197
350
|
const uptime = process.uptime();
|
|
198
351
|
const mem = process.memoryUsage();
|
|
@@ -226,6 +379,13 @@ async function handleRequest(req, res) {
|
|
|
226
379
|
});
|
|
227
380
|
return;
|
|
228
381
|
}
|
|
382
|
+
// Metrics scrape (#1b · 09): loopback diagnostic, same X-byCLI posture as /status (no token).
|
|
383
|
+
// The counter/histogram registry is pure data in recorder-core; this endpoint (http-bound) stays
|
|
384
|
+
// in the daemon transport layer. Values are non-sensitive enums/counts only (09).
|
|
385
|
+
if (req.method === 'GET' && pathname === '/metrics') {
|
|
386
|
+
jsonResponse(res, 200, { ok: true, ...metrics.snapshot() });
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
229
389
|
if (req.method === 'GET' && pathname === '/logs') {
|
|
230
390
|
const params = new URL(url, `http://localhost:${PORT}`).searchParams;
|
|
231
391
|
const level = params.get('level');
|
|
@@ -411,6 +571,86 @@ wss.on('connection', (ws) => {
|
|
|
411
571
|
// ─── Start ───────────────────────────────────────────────────────────
|
|
412
572
|
httpServer.listen(PORT, '127.0.0.1', () => {
|
|
413
573
|
log.info(`[daemon] Listening on http://127.0.0.1:${PORT}`);
|
|
574
|
+
// Temp-store reap policy (M7b · 09:27-29). Resolved once at startup; out-of-range env → throws,
|
|
575
|
+
// but we keep the daemon alive by falling back to the (validated-elsewhere) defaults on error.
|
|
576
|
+
let tempPolicy;
|
|
577
|
+
try {
|
|
578
|
+
tempPolicy = resolveTempPolicy();
|
|
579
|
+
}
|
|
580
|
+
catch (err) {
|
|
581
|
+
log.warn(`[daemon] temp policy config invalid, using defaults: ${err instanceof Error ? err.message : String(err)}`);
|
|
582
|
+
tempPolicy = { tempTtlMs: 3_600_000, startupReapMaxAgeMs: 86_400_000, orphanKillGraceMs: 1_500 };
|
|
583
|
+
}
|
|
584
|
+
// Age-reap safety floor (08 / M7b): a verify cannot legitimately outlive timeoutMs + killGraceMs,
|
|
585
|
+
// so the age threshold must never drop below it — otherwise a short RECORDER_TEMP_TTL_MS (min 60s)
|
|
586
|
+
// would let the sweep kill a still-running verify (timeoutMs reaches 600s). Fall back to the
|
|
587
|
+
// absolute config ceiling (max timeoutMs + max killGraceMs) if runner config is unreadable.
|
|
588
|
+
let minLeakAgeMs = 600_000 + 30_000;
|
|
589
|
+
try {
|
|
590
|
+
const rc = resolveRunnerConfig();
|
|
591
|
+
minLeakAgeMs = rc.timeoutMs + rc.killGraceMs;
|
|
592
|
+
}
|
|
593
|
+
catch { /* keep the absolute ceiling (safest, highest floor) */ }
|
|
594
|
+
// #1d temp-store capacity guard: the runner refuses a new verify (temp_store_full) past the high
|
|
595
|
+
// watermark rather than risk filling the disk. Resolved at startup (temp root is restart-only,
|
|
596
|
+
// 09:182); bad env falls back to defaults (consistent with temp policy above). Registered BEFORE the
|
|
597
|
+
// first defaultRunnerPort() use (no request is served until after this listen callback returns). The
|
|
598
|
+
// on-pressure sweep reuses the same age-bounded reap as the periodic sweep (never kills a live verify).
|
|
599
|
+
let tempCapacity;
|
|
600
|
+
try {
|
|
601
|
+
tempCapacity = resolveTempCapacity();
|
|
602
|
+
}
|
|
603
|
+
catch (err) {
|
|
604
|
+
log.warn(`[daemon] temp capacity config invalid, using defaults: ${err instanceof Error ? err.message : String(err)}`);
|
|
605
|
+
tempCapacity = resolveTempCapacity({}); // empty env → validated defaults
|
|
606
|
+
}
|
|
607
|
+
setDefaultRunnerTempGuard(tempCapacity, () => {
|
|
608
|
+
reapOrphanedVerifyRuns({ log: (m) => log.info(m), maxAgeMs: tempPolicy.tempTtlMs, minLeakAgeMs, graceMs: tempPolicy.orphanKillGraceMs });
|
|
609
|
+
});
|
|
610
|
+
// Startup reap (08:37): clean up verify temp dirs orphaned by a previously-crashed daemon
|
|
611
|
+
// (ownerPid-dead), plus any dir older than the startup age backstop (M7b leak sweep). A live
|
|
612
|
+
// sibling's in-flight verify (young + owner alive, or within the hard-deadline floor) is never touched.
|
|
613
|
+
try {
|
|
614
|
+
const r = reapOrphanedVerifyRuns({ log: (m) => log.info(m), maxAgeMs: tempPolicy.startupReapMaxAgeMs, minLeakAgeMs, graceMs: tempPolicy.orphanKillGraceMs });
|
|
615
|
+
// #1c · 09 startup reap count (orphan runners killed, temp dirs swept)
|
|
616
|
+
if (r.orphans)
|
|
617
|
+
metrics.inc('startup_reap_orphans_total', undefined, r.orphans);
|
|
618
|
+
if (r.killed)
|
|
619
|
+
metrics.inc('startup_reap_killed_total', undefined, r.killed);
|
|
620
|
+
if (r.deleted)
|
|
621
|
+
metrics.inc('startup_reap_deleted_total', undefined, r.deleted);
|
|
622
|
+
}
|
|
623
|
+
catch (err) {
|
|
624
|
+
log.warn(`[daemon] verify startup reap failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
625
|
+
}
|
|
626
|
+
// Periodic TTL sweep (M7b): while the daemon runs, reap dirs older than the temp TTL — but never
|
|
627
|
+
// below the run hard-deadline floor (minLeakAgeMs), so an aggressive TTL can't kill a live verify.
|
|
628
|
+
// Unref'd so it never keeps the process alive on its own.
|
|
629
|
+
const sweep = setInterval(() => {
|
|
630
|
+
try {
|
|
631
|
+
const r = reapOrphanedVerifyRuns({ log: (m) => log.info(m), maxAgeMs: tempPolicy.tempTtlMs, minLeakAgeMs, graceMs: tempPolicy.orphanKillGraceMs });
|
|
632
|
+
if (r.killed)
|
|
633
|
+
metrics.inc('temp_sweep_killed_total', undefined, r.killed);
|
|
634
|
+
if (r.deleted)
|
|
635
|
+
metrics.inc('temp_sweep_deleted_total', undefined, r.deleted);
|
|
636
|
+
// Drop salts for abandoned sessions (M7a TTL backstop; daemon restart already rotates all).
|
|
637
|
+
const dropped = defaultSessionKeyRegistry().sweepExpired();
|
|
638
|
+
if (dropped)
|
|
639
|
+
metrics.inc('session_keys_dropped_total', undefined, dropped);
|
|
640
|
+
}
|
|
641
|
+
catch (err) {
|
|
642
|
+
log.warn(`[daemon] verify temp sweep failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
643
|
+
}
|
|
644
|
+
}, tempPolicy.tempTtlMs);
|
|
645
|
+
sweep.unref?.();
|
|
646
|
+
// Init write-transaction crash recovery (07:106-117): resolve any adapter draft interrupted
|
|
647
|
+
// mid-write by a previous crash (roll-forward marker / quarantine unprovenanced / roll back).
|
|
648
|
+
try {
|
|
649
|
+
recoverInitTransactions({ log: (m) => log.info(m) });
|
|
650
|
+
}
|
|
651
|
+
catch (err) {
|
|
652
|
+
log.warn(`[daemon] init startup recovery failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
653
|
+
}
|
|
414
654
|
});
|
|
415
655
|
httpServer.on('error', (err) => {
|
|
416
656
|
if (err.code === 'EADDRINUSE') {
|
package/dist/src/discovery.d.ts
CHANGED
|
@@ -7,11 +7,13 @@
|
|
|
7
7
|
* when their command is executed.
|
|
8
8
|
* 2. FALLBACK (filesystem scan): Traditional runtime discovery for development.
|
|
9
9
|
*/
|
|
10
|
-
/** User runtime directory: ~/.bycli
|
|
10
|
+
/** User runtime directory: BYCLI_CONFIG_DIR or ~/.bycli.
|
|
11
|
+
* Resolved once at module load; the entry process fixes BYCLI_CONFIG_DIR before import. */
|
|
11
12
|
export declare const USER_BYCLI_DIR: string;
|
|
12
|
-
/** User CLIs directory:
|
|
13
|
+
/** User CLIs directory: <config>/clis */
|
|
13
14
|
export declare const USER_CLIS_DIR: string;
|
|
14
|
-
/** Plugins directory:
|
|
15
|
+
/** Plugins directory: <config>/plugins/ (plugins are out of scope for the clis/sites
|
|
16
|
+
* relocation; kept as a const that many plugin.ts/tests consumers rely on). */
|
|
15
17
|
export declare const PLUGINS_DIR: string;
|
|
16
18
|
/**
|
|
17
19
|
* Ensure ~/.bycli/node_modules/@sovovs/bycli symlink exists so that
|
package/dist/src/discovery.js
CHANGED
|
@@ -8,18 +8,20 @@
|
|
|
8
8
|
* 2. FALLBACK (filesystem scan): Traditional runtime discovery for development.
|
|
9
9
|
*/
|
|
10
10
|
import * as fs from 'node:fs';
|
|
11
|
-
import * as os from 'node:os';
|
|
12
11
|
import * as path from 'node:path';
|
|
13
12
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
14
13
|
import { Strategy, registerCommand } from './registry.js';
|
|
15
14
|
import { getErrorMessage } from './errors.js';
|
|
16
15
|
import { log } from './logger.js';
|
|
17
16
|
import { findPackageRoot, getCliManifestPath } from './package-paths.js';
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
export const
|
|
22
|
-
/**
|
|
17
|
+
import { getConfigDir, getUserClisDir } from './config-paths.js';
|
|
18
|
+
/** User runtime directory: BYCLI_CONFIG_DIR or ~/.bycli.
|
|
19
|
+
* Resolved once at module load; the entry process fixes BYCLI_CONFIG_DIR before import. */
|
|
20
|
+
export const USER_BYCLI_DIR = getConfigDir();
|
|
21
|
+
/** User CLIs directory: <config>/clis */
|
|
22
|
+
export const USER_CLIS_DIR = getUserClisDir();
|
|
23
|
+
/** Plugins directory: <config>/plugins/ (plugins are out of scope for the clis/sites
|
|
24
|
+
* relocation; kept as a const that many plugin.ts/tests consumers rely on). */
|
|
23
25
|
export const PLUGINS_DIR = path.join(USER_BYCLI_DIR, 'plugins');
|
|
24
26
|
/** Matches files that register commands via cli() or lifecycle hooks */
|
|
25
27
|
const PLUGIN_MODULE_PATTERN = /\b(?:cli|onStartup|onBeforeExecute|onAfterExecute)\s*\(/;
|
package/dist/src/execution.js
CHANGED
|
@@ -13,7 +13,8 @@ import { getRegistry, fullName, } from './registry.js';
|
|
|
13
13
|
import { pathToFileURL } from 'node:url';
|
|
14
14
|
import * as crypto from 'node:crypto';
|
|
15
15
|
import * as fs from 'node:fs';
|
|
16
|
-
import * as
|
|
16
|
+
import * as path from 'node:path';
|
|
17
|
+
import { getUserClisDir } from './config-paths.js';
|
|
17
18
|
import { executePipeline } from './pipeline/index.js';
|
|
18
19
|
import { adapterLoadError, ArgumentError, CommandExecutionError, attachTraceReceipt, getErrorMessage } from './errors.js';
|
|
19
20
|
import { shouldUseBrowserSession } from './capabilityRouting.js';
|
|
@@ -28,7 +29,11 @@ import { resolveAdapterSourcePath } from './adapter-source.js';
|
|
|
28
29
|
const _loadedModules = new Map();
|
|
29
30
|
/** Track mtime of loaded user adapter files for hot-reload in daemon mode. */
|
|
30
31
|
const _moduleMtimes = new Map();
|
|
31
|
-
|
|
32
|
+
/** User adapter dir with a trailing separator, so `startsWith` can't match a sibling
|
|
33
|
+
* like `clis-foo`. Resolved per-call (config-paths.ts rationale) — no import-time freeze. */
|
|
34
|
+
function userClisPrefix() {
|
|
35
|
+
return getUserClisDir() + path.sep;
|
|
36
|
+
}
|
|
32
37
|
function normalizeTraceMode(raw) {
|
|
33
38
|
if (raw === undefined || raw === null || raw === '' || raw === 'off')
|
|
34
39
|
return 'off';
|
|
@@ -83,7 +88,7 @@ async function runCommand(cmd, page, kwargs, debug) {
|
|
|
83
88
|
if (internal._lazy && internal._modulePath) {
|
|
84
89
|
const modulePath = internal._modulePath;
|
|
85
90
|
// Hot-reload: if a user adapter's file has changed on disk, invalidate cache
|
|
86
|
-
const isUserAdapter = modulePath.startsWith(
|
|
91
|
+
const isUserAdapter = modulePath.startsWith(userClisPrefix());
|
|
87
92
|
if (isUserAdapter && _loadedModules.has(modulePath)) {
|
|
88
93
|
try {
|
|
89
94
|
const stat = fs.statSync(modulePath);
|
package/dist/src/main.js
CHANGED
|
@@ -13,8 +13,8 @@ if (process.platform !== 'win32') {
|
|
|
13
13
|
process.env.PATH = [...cur].join(':');
|
|
14
14
|
}
|
|
15
15
|
import * as fs from 'node:fs';
|
|
16
|
-
import * as os from 'node:os';
|
|
17
16
|
import * as path from 'node:path';
|
|
17
|
+
import { getUserClisDir } from './config-paths.js';
|
|
18
18
|
import { fileURLToPath } from 'node:url';
|
|
19
19
|
import { getCompletionsFromManifest, hasAllManifests, printCompletionScriptFast } from './completion-fast.js';
|
|
20
20
|
import { findPackageRoot, getCliManifestPath } from './package-paths.js';
|
|
@@ -26,7 +26,7 @@ const __dirname = path.dirname(__filename);
|
|
|
26
26
|
// Adapters are JS-first and live at <package-root>/clis/.
|
|
27
27
|
// Use findPackageRoot so the path works both in dev (src/main.ts) and prod (dist/src/main.js).
|
|
28
28
|
const BUILTIN_CLIS = path.join(findPackageRoot(__filename), 'clis');
|
|
29
|
-
const USER_CLIS =
|
|
29
|
+
const USER_CLIS = getUserClisDir();
|
|
30
30
|
// ── Ultra-fast path: lightweight commands bypass full discovery ──────────
|
|
31
31
|
// These are high-frequency or trivial paths that must not pay the startup tax.
|
|
32
32
|
const argv = process.argv.slice(2);
|
|
@@ -52,6 +52,22 @@ if (argv[0] === 'completion' && argv.length >= 2) {
|
|
|
52
52
|
}
|
|
53
53
|
// Unknown shell — fall through to full path for proper error handling
|
|
54
54
|
}
|
|
55
|
+
// Fast path: internal verify-runner — the child process spawned by the verify RunnerPort
|
|
56
|
+
// (08). It loads ONLY the target adapter (by path, from input.json) and emits JSONL; it must
|
|
57
|
+
// never run full adapter discovery, and its stdout must stay pure JSONL.
|
|
58
|
+
if (argv[0] === 'internal' && argv[1] === 'verify-runner') {
|
|
59
|
+
const { runVerifyRunnerCli } = await import('./recorder/runner/verify-runner-main.js');
|
|
60
|
+
await runVerifyRunnerCli(argv.slice(2));
|
|
61
|
+
process.exit(process.exitCode ?? EXIT_CODES.SUCCESS);
|
|
62
|
+
}
|
|
63
|
+
// Fast path: internal highlevel-http — the OPTIONAL standalone High-Level HTTP wrapper (M9,
|
|
64
|
+
// 07 · Optional HTTP Wrapper). Default off; opt-in only via this command. Binds 127.0.0.1 and
|
|
65
|
+
// keeps running, so it must skip full adapter discovery. Distinct from the daemon /v1/* family.
|
|
66
|
+
if (argv[0] === 'internal' && argv[1] === 'highlevel-http') {
|
|
67
|
+
const { runWrapperCli } = await import('./recorder/http/wrapper-server.js');
|
|
68
|
+
await runWrapperCli(); // blocks while the server is alive; resolves on close (SIGTERM/SIGINT)
|
|
69
|
+
process.exit(process.exitCode ?? EXIT_CODES.SUCCESS); // never fall through to CLI discovery
|
|
70
|
+
}
|
|
55
71
|
// Fast path: --get-completions — read from manifest, skip discovery
|
|
56
72
|
const getCompIdx = process.argv.indexOf('--get-completions');
|
|
57
73
|
if (getCompIdx !== -1) {
|
package/dist/src/plugin.js
CHANGED
|
@@ -1013,13 +1013,16 @@ function getPluginSource(dir) {
|
|
|
1013
1013
|
}
|
|
1014
1014
|
/** Parse a plugin source string into clone URL, repo name, and optional sub-plugin. */
|
|
1015
1015
|
function parseSource(source) {
|
|
1016
|
+
// Strip either the legacy `bycli-plugin-` or the current `opencli-plugin-`
|
|
1017
|
+
// repo-name prefix when deriving the local plugin name.
|
|
1018
|
+
const PLUGIN_NAME_PREFIX = /^(?:bycli|opencli)-plugin-/;
|
|
1016
1019
|
if (source.startsWith('file://')) {
|
|
1017
1020
|
try {
|
|
1018
1021
|
const localPath = path.resolve(fileURLToPath(source));
|
|
1019
1022
|
return {
|
|
1020
1023
|
type: 'local',
|
|
1021
1024
|
localPath,
|
|
1022
|
-
name: path.basename(localPath).replace(
|
|
1025
|
+
name: path.basename(localPath).replace(PLUGIN_NAME_PREFIX, ''),
|
|
1023
1026
|
};
|
|
1024
1027
|
}
|
|
1025
1028
|
catch {
|
|
@@ -1031,14 +1034,14 @@ function parseSource(source) {
|
|
|
1031
1034
|
return {
|
|
1032
1035
|
type: 'local',
|
|
1033
1036
|
localPath,
|
|
1034
|
-
name: path.basename(localPath).replace(
|
|
1037
|
+
name: path.basename(localPath).replace(PLUGIN_NAME_PREFIX, ''),
|
|
1035
1038
|
};
|
|
1036
1039
|
}
|
|
1037
1040
|
// github:user/repo/subplugin (monorepo specific sub-plugin)
|
|
1038
1041
|
const githubSubMatch = source.match(/^github:([\w.-]+)\/([\w.-]+)\/([\w.-]+)$/);
|
|
1039
1042
|
if (githubSubMatch) {
|
|
1040
1043
|
const [, user, repo, sub] = githubSubMatch;
|
|
1041
|
-
const name = repo.replace(
|
|
1044
|
+
const name = repo.replace(PLUGIN_NAME_PREFIX, '');
|
|
1042
1045
|
return {
|
|
1043
1046
|
type: 'git',
|
|
1044
1047
|
cloneUrl: `https://github.com/${user}/${repo}.git`,
|
|
@@ -1050,7 +1053,7 @@ function parseSource(source) {
|
|
|
1050
1053
|
const githubMatch = source.match(/^github:([\w.-]+)\/([\w.-]+)$/);
|
|
1051
1054
|
if (githubMatch) {
|
|
1052
1055
|
const [, user, repo] = githubMatch;
|
|
1053
|
-
const name = repo.replace(
|
|
1056
|
+
const name = repo.replace(PLUGIN_NAME_PREFIX, '');
|
|
1054
1057
|
return {
|
|
1055
1058
|
type: 'git',
|
|
1056
1059
|
cloneUrl: `https://github.com/${user}/${repo}.git`,
|
|
@@ -1061,7 +1064,7 @@ function parseSource(source) {
|
|
|
1061
1064
|
const urlMatch = source.match(/^https?:\/\/github\.com\/([\w.-]+)\/([\w.-]+?)(?:\.git)?$/);
|
|
1062
1065
|
if (urlMatch) {
|
|
1063
1066
|
const [, user, repo] = urlMatch;
|
|
1064
|
-
const name = repo.replace(
|
|
1067
|
+
const name = repo.replace(PLUGIN_NAME_PREFIX, '');
|
|
1065
1068
|
return {
|
|
1066
1069
|
type: 'git',
|
|
1067
1070
|
cloneUrl: `https://github.com/${user}/${repo}.git`,
|
|
@@ -1075,7 +1078,7 @@ function parseSource(source) {
|
|
|
1075
1078
|
const pathPart = sshUrlMatch[1];
|
|
1076
1079
|
const segments = pathPart.split('/');
|
|
1077
1080
|
const repoSegment = segments.pop();
|
|
1078
|
-
const name = repoSegment.replace(
|
|
1081
|
+
const name = repoSegment.replace(PLUGIN_NAME_PREFIX, '');
|
|
1079
1082
|
return { type: 'git', cloneUrl: source, name };
|
|
1080
1083
|
}
|
|
1081
1084
|
// git@host:user/repo.git (SCP-style)
|
|
@@ -1084,7 +1087,7 @@ function parseSource(source) {
|
|
|
1084
1087
|
const pathPart = scpMatch[1];
|
|
1085
1088
|
const segments = pathPart.split('/');
|
|
1086
1089
|
const repoSegment = segments.pop();
|
|
1087
|
-
const name = repoSegment.replace(
|
|
1090
|
+
const name = repoSegment.replace(PLUGIN_NAME_PREFIX, '');
|
|
1088
1091
|
return { type: 'git', cloneUrl: source, name };
|
|
1089
1092
|
}
|
|
1090
1093
|
// Generic https/http git URL (non-GitHub hosts)
|
|
@@ -1093,7 +1096,7 @@ function parseSource(source) {
|
|
|
1093
1096
|
const pathPart = genericHttpMatch[1];
|
|
1094
1097
|
const segments = pathPart.split('/');
|
|
1095
1098
|
const repoSegment = segments.pop();
|
|
1096
|
-
const name = repoSegment.replace(
|
|
1099
|
+
const name = repoSegment.replace(PLUGIN_NAME_PREFIX, '');
|
|
1097
1100
|
// Ensure clone URL ends with .git
|
|
1098
1101
|
const cloneUrl = source.endsWith('.git') ? source : `${source}.git`;
|
|
1099
1102
|
return { type: 'git', cloneUrl, name };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* High-Level analyze module (M5a, 07-high-level-services.md · analyzeBrowser).
|
|
3
|
+
*
|
|
4
|
+
* Module-first (ADR-0004): drives the Page abstraction through the multi-step
|
|
5
|
+
* site probe (goto / wait / network capture / window-global probe / cookies) to
|
|
6
|
+
* assemble PageSignals, then calls the PURE analyzeSite() classifier. In-process
|
|
7
|
+
* async, no child process (07:50). Hosted by the main-repo daemon high-level
|
|
8
|
+
* surface (Codex A' verdict); dashboard-be only forwards.
|
|
9
|
+
*
|
|
10
|
+
* Timeout surfaces as `analyze_timeout` (07:52), never a runner error.
|
|
11
|
+
*/
|
|
12
|
+
import type { IPage } from '../../types.js';
|
|
13
|
+
import { type AdapterRef, type AnalyzeReport } from '../../browser/analyze.js';
|
|
14
|
+
export interface AnalyzeInput {
|
|
15
|
+
url: string;
|
|
16
|
+
/** site-level settle window after load (ms); default 2000 (07 input). */
|
|
17
|
+
settleMs?: number;
|
|
18
|
+
}
|
|
19
|
+
export declare class AnalyzeTimeoutError extends Error {
|
|
20
|
+
readonly code = "analyze_timeout";
|
|
21
|
+
constructor(ms: number);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Collect PageSignals by driving the page, then classify with the pure analyzeSite.
|
|
25
|
+
* `registry` is the adapter registry used for nearest-adapter matching.
|
|
26
|
+
*/
|
|
27
|
+
export declare function analyzeBrowser(page: IPage, registry: Map<string, AdapterRef>, input: AnalyzeInput): Promise<AnalyzeReport>;
|
|
28
|
+
/** Wrap analyzeBrowser with a deadline → AnalyzeTimeoutError (07:52 analyze_timeout). */
|
|
29
|
+
export declare function analyzeBrowserWithTimeout(page: IPage, registry: Map<string, AdapterRef>, input: AnalyzeInput, timeoutMs: number): Promise<AnalyzeReport>;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* High-Level analyze module (M5a, 07-high-level-services.md · analyzeBrowser).
|
|
3
|
+
*
|
|
4
|
+
* Module-first (ADR-0004): drives the Page abstraction through the multi-step
|
|
5
|
+
* site probe (goto / wait / network capture / window-global probe / cookies) to
|
|
6
|
+
* assemble PageSignals, then calls the PURE analyzeSite() classifier. In-process
|
|
7
|
+
* async, no child process (07:50). Hosted by the main-repo daemon high-level
|
|
8
|
+
* surface (Codex A' verdict); dashboard-be only forwards.
|
|
9
|
+
*
|
|
10
|
+
* Timeout surfaces as `analyze_timeout` (07:52), never a runner error.
|
|
11
|
+
*/
|
|
12
|
+
import { analyzeSite } from '../../browser/analyze.js';
|
|
13
|
+
import { generateInterceptorJs } from '../../interceptor.js';
|
|
14
|
+
export class AnalyzeTimeoutError extends Error {
|
|
15
|
+
code = 'analyze_timeout';
|
|
16
|
+
constructor(ms) {
|
|
17
|
+
super(`analyze timed out after ${ms}ms`);
|
|
18
|
+
this.name = 'AnalyzeTimeoutError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const DEFAULT_SETTLE_MS = 2000;
|
|
22
|
+
/** Window-global + cookie + finalUrl probe (mirrors the CLI analyze probe). */
|
|
23
|
+
const PROBE_JS = `(function(){
|
|
24
|
+
return {
|
|
25
|
+
cookieNames: (document.cookie || '').split(';').map(function(c){ return c.trim().split('=')[0]; }).filter(Boolean),
|
|
26
|
+
initialState: {
|
|
27
|
+
__INITIAL_STATE__: typeof window.__INITIAL_STATE__ !== 'undefined',
|
|
28
|
+
__NUXT__: typeof window.__NUXT__ !== 'undefined',
|
|
29
|
+
__NEXT_DATA__: typeof window.__NEXT_DATA__ !== 'undefined',
|
|
30
|
+
__APOLLO_STATE__: typeof window.__APOLLO_STATE__ !== 'undefined',
|
|
31
|
+
},
|
|
32
|
+
title: document.title || '',
|
|
33
|
+
finalUrl: location.href,
|
|
34
|
+
};
|
|
35
|
+
})()`;
|
|
36
|
+
/**
|
|
37
|
+
* Collect PageSignals by driving the page, then classify with the pure analyzeSite.
|
|
38
|
+
* `registry` is the adapter registry used for nearest-adapter matching.
|
|
39
|
+
*/
|
|
40
|
+
export async function analyzeBrowser(page, registry, input) {
|
|
41
|
+
const settleMs = input.settleMs ?? DEFAULT_SETTLE_MS;
|
|
42
|
+
// Prefer the session-level CDP network capture; fall back to an injected interceptor.
|
|
43
|
+
const hasSessionCapture = (await page.startNetworkCapture?.()) ?? false;
|
|
44
|
+
await page.goto(input.url);
|
|
45
|
+
await page.wait(Math.max(1, Math.round(settleMs / 1000)));
|
|
46
|
+
if (!hasSessionCapture) {
|
|
47
|
+
// match-all: analyze wants every json/text response, not a filtered subset.
|
|
48
|
+
try {
|
|
49
|
+
await page.evaluate(generateInterceptorJs('true'));
|
|
50
|
+
}
|
|
51
|
+
catch { /* non-fatal */ }
|
|
52
|
+
}
|
|
53
|
+
await page.wait(1);
|
|
54
|
+
const rawItems = ((await page.readNetworkCapture?.()) ?? []);
|
|
55
|
+
const networkEntries = rawItems.map((e) => ({
|
|
56
|
+
url: e.url ?? '',
|
|
57
|
+
status: e.status ?? 0,
|
|
58
|
+
contentType: e.ct ?? '',
|
|
59
|
+
bodyPreview: typeof e.body === 'string'
|
|
60
|
+
? e.body.slice(0, 2000)
|
|
61
|
+
: (e.body ? JSON.stringify(e.body).slice(0, 2000) : null),
|
|
62
|
+
}));
|
|
63
|
+
const probe = await page.evaluate(PROBE_JS);
|
|
64
|
+
const browserCookieNames = (await page.getCookies({ url: probe.finalUrl || input.url }).catch(() => []))
|
|
65
|
+
.map((c) => c.name)
|
|
66
|
+
.filter(Boolean);
|
|
67
|
+
const cookieNames = [...new Set([...probe.cookieNames, ...browserCookieNames])];
|
|
68
|
+
const signals = {
|
|
69
|
+
requestedUrl: input.url,
|
|
70
|
+
finalUrl: probe.finalUrl,
|
|
71
|
+
cookieNames,
|
|
72
|
+
networkEntries,
|
|
73
|
+
initialState: probe.initialState,
|
|
74
|
+
title: probe.title,
|
|
75
|
+
};
|
|
76
|
+
return analyzeSite(signals, registry);
|
|
77
|
+
}
|
|
78
|
+
/** Wrap analyzeBrowser with a deadline → AnalyzeTimeoutError (07:52 analyze_timeout). */
|
|
79
|
+
export async function analyzeBrowserWithTimeout(page, registry, input, timeoutMs) {
|
|
80
|
+
let timer;
|
|
81
|
+
const timeout = new Promise((_, reject) => {
|
|
82
|
+
timer = setTimeout(() => reject(new AnalyzeTimeoutError(timeoutMs)), timeoutMs);
|
|
83
|
+
});
|
|
84
|
+
try {
|
|
85
|
+
return await Promise.race([analyzeBrowser(page, registry, input), timeout]);
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
if (timer)
|
|
89
|
+
clearTimeout(timer);
|
|
90
|
+
}
|
|
91
|
+
}
|