@sovovs/bycli 1.8.0 → 2.1.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/clis/twitter/search.js +3 -3
- 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 +67 -7
- package/dist/src/browser/daemon-client.d.ts +2 -0
- package/dist/src/browser/daemon-client.js +1 -1
- package/dist/src/browser/extension-capabilities.d.ts +13 -0
- package/dist/src/browser/extension-capabilities.js +22 -0
- package/dist/src/browser/extension-capabilities.test.d.ts +1 -0
- package/dist/src/browser/extension-version-metadata.test.d.ts +1 -0
- 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/page.d.ts +1 -0
- package/dist/src/browser/page.js +20 -1
- 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/build-manifest.js +4 -2
- package/dist/src/capabilityRouting.d.ts +3 -2
- package/dist/src/capabilityRouting.js +10 -2
- package/dist/src/cli.js +11 -14
- package/dist/src/commanderAdapter.js +5 -5
- package/dist/src/config-paths.d.ts +19 -0
- package/dist/src/config-paths.js +31 -0
- package/dist/src/daemon.js +256 -0
- package/dist/src/discovery.d.ts +10 -3
- package/dist/src/discovery.js +20 -10
- package/dist/src/discovery.test.d.ts +1 -0
- package/dist/src/execution.d.ts +5 -0
- package/dist/src/execution.js +272 -48
- package/dist/src/help.js +8 -8
- package/dist/src/main.js +18 -2
- package/dist/src/manifest-schema.d.ts +9 -0
- package/dist/src/manifest-schema.js +162 -0
- package/dist/src/manifest-schema.test.d.ts +1 -0
- package/dist/src/manifest-types.d.ts +1 -1
- package/dist/src/observation/redaction.js +10 -4
- 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 +96 -0
- package/dist/src/recorder/runner/verify-runner-main.js +292 -0
- package/dist/src/recorder/runner/verify-runner-main.test.d.ts +1 -0
- package/dist/src/registry-api.d.ts +1 -1
- package/dist/src/registry-api.types.test.d.ts +1 -0
- package/dist/src/registry-transaction.d.ts +42 -0
- package/dist/src/registry-transaction.js +194 -0
- package/dist/src/registry-transaction.test.d.ts +1 -0
- package/dist/src/registry.d.ts +58 -16
- package/dist/src/registry.js +131 -15
- package/dist/src/serialization.d.ts +1 -1
- package/dist/src/serialization.js +3 -3
- package/dist/src/types.d.ts +2 -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/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
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
* }
|
|
29
29
|
*/
|
|
30
30
|
import * as fs from 'node:fs';
|
|
31
|
-
import * as os from 'node:os';
|
|
32
31
|
import * as path from 'node:path';
|
|
32
|
+
import { getSitesDir } from '../config-paths.js';
|
|
33
33
|
const DEFAULT_MAX_TOP_LEVEL_KEYS = 12;
|
|
34
34
|
const DEFAULT_MAX_NESTED_DEPTH = 1;
|
|
35
35
|
const ID_SHAPED_KEY_PATTERNS = [
|
|
@@ -51,7 +51,7 @@ const ID_SHAPED_KEY_PATTERNS = [
|
|
|
51
51
|
/^uri$/i,
|
|
52
52
|
];
|
|
53
53
|
export function fixturePath(site, command) {
|
|
54
|
-
return path.join(
|
|
54
|
+
return path.join(getSitesDir(), site, 'verify', `${command}.json`);
|
|
55
55
|
}
|
|
56
56
|
export function loadFixture(site, command) {
|
|
57
57
|
const p = fixturePath(site, command);
|
|
@@ -28,6 +28,7 @@ import { getErrorMessage } from './errors.js';
|
|
|
28
28
|
import { fullName, getRegistry } from './registry.js';
|
|
29
29
|
import { findPackageRoot, getCliManifestPath } from './package-paths.js';
|
|
30
30
|
import { isRecord } from './utils.js';
|
|
31
|
+
import { canonicalizeManifestArgSchema } from './manifest-schema.js';
|
|
31
32
|
const PACKAGE_ROOT = findPackageRoot(fileURLToPath(import.meta.url));
|
|
32
33
|
const CLIS_DIR = path.join(PACKAGE_ROOT, 'clis');
|
|
33
34
|
// Write manifest next to clis/ so both dev and installed runtime can find it.
|
|
@@ -58,7 +59,7 @@ function toManifestArgs(args) {
|
|
|
58
59
|
return args.map(arg => ({
|
|
59
60
|
name: arg.name,
|
|
60
61
|
type: arg.type ?? 'str',
|
|
61
|
-
default: arg.default,
|
|
62
|
+
...(Object.prototype.hasOwnProperty.call(arg, 'default') ? { default: arg.default } : {}),
|
|
62
63
|
required: !!arg.required,
|
|
63
64
|
valueRequired: !!arg.valueRequired || undefined,
|
|
64
65
|
positional: arg.positional || undefined,
|
|
@@ -85,6 +86,7 @@ function isCliCommandValue(value, site) {
|
|
|
85
86
|
&& Array.isArray(value.args);
|
|
86
87
|
}
|
|
87
88
|
function toManifestEntry(cmd, modulePath, sourceFile) {
|
|
89
|
+
canonicalizeManifestArgSchema(cmd.args, `Command ${fullName(cmd)}`);
|
|
88
90
|
return {
|
|
89
91
|
site: cmd.site,
|
|
90
92
|
name: cmd.name,
|
|
@@ -94,7 +96,7 @@ function toManifestEntry(cmd, modulePath, sourceFile) {
|
|
|
94
96
|
example: cmd.example,
|
|
95
97
|
domain: cmd.domain,
|
|
96
98
|
strategy: (cmd.strategy ?? 'public').toString().toLowerCase(),
|
|
97
|
-
browser: cmd.browser
|
|
99
|
+
browser: cmd.browser,
|
|
98
100
|
args: toManifestArgs(cmd.args),
|
|
99
101
|
columns: cmd.columns,
|
|
100
102
|
defaultFormat: cmd.defaultFormat,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CliCommand } from './registry.js';
|
|
1
|
+
import type { BrowserCliCommand, CliCommand, NonBrowserCliCommand } from './registry.js';
|
|
2
2
|
/**
|
|
3
3
|
* Pipeline steps that require a live browser session.
|
|
4
4
|
*
|
|
@@ -16,4 +16,5 @@ export declare const BROWSER_ONLY_STEPS: Set<string>;
|
|
|
16
16
|
export declare function _validateBrowserOnlyStepsAgainstRegistry(): {
|
|
17
17
|
extras: string[];
|
|
18
18
|
};
|
|
19
|
-
export declare function shouldUseBrowserSession(cmd:
|
|
19
|
+
export declare function shouldUseBrowserSession(cmd: BrowserCliCommand | NonBrowserCliCommand): boolean;
|
|
20
|
+
export declare function shouldUseBrowserSession(cmd: CliCommand, resolvedBrowser: boolean): boolean;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CommandExecutionError } from './errors.js';
|
|
1
2
|
import { getRegisteredStepNames } from './pipeline/registry.js';
|
|
2
3
|
/**
|
|
3
4
|
* Pipeline steps that require a live browser session.
|
|
@@ -40,8 +41,15 @@ function pipelineNeedsBrowserSession(pipeline) {
|
|
|
40
41
|
return Object.keys(step).some((op) => BROWSER_ONLY_STEPS.has(op));
|
|
41
42
|
});
|
|
42
43
|
}
|
|
43
|
-
export function shouldUseBrowserSession(cmd) {
|
|
44
|
-
|
|
44
|
+
export function shouldUseBrowserSession(cmd, resolvedBrowser) {
|
|
45
|
+
let browserRequired = resolvedBrowser;
|
|
46
|
+
if (browserRequired === undefined) {
|
|
47
|
+
if (cmd.browser === 'conditional') {
|
|
48
|
+
throw new CommandExecutionError(`Conditional browser requirement for ${cmd.site}/${cmd.name} must be resolved before capability routing`);
|
|
49
|
+
}
|
|
50
|
+
browserRequired = cmd.browser;
|
|
51
|
+
}
|
|
52
|
+
if (!browserRequired)
|
|
45
53
|
return false;
|
|
46
54
|
if (cmd.func)
|
|
47
55
|
return true;
|
package/dist/src/cli.js
CHANGED
|
@@ -8,6 +8,7 @@ import * as fs from 'node:fs';
|
|
|
8
8
|
import * as os from 'node:os';
|
|
9
9
|
import * as path from 'node:path';
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import { getUserClisDir, getSitesDir } from './config-paths.js';
|
|
11
12
|
import { Command, InvalidArgumentError, Option } from 'commander';
|
|
12
13
|
import { findPackageRoot, getBuiltEntryCandidates } from './package-paths.js';
|
|
13
14
|
import { fullName, getRegistry, strategyLabel } from './registry.js';
|
|
@@ -148,7 +149,7 @@ function emitNetworkError(code, message, extra = {}) {
|
|
|
148
149
|
process.exitCode = NETWORK_ERROR_EXIT[code] ?? EXIT_CODES.GENERIC_ERROR;
|
|
149
150
|
}
|
|
150
151
|
export function checkSiteMemory(site) {
|
|
151
|
-
const siteDir = path.join(
|
|
152
|
+
const siteDir = path.join(getSitesDir(), site);
|
|
152
153
|
const endpointsPath = path.join(siteDir, 'endpoints.json');
|
|
153
154
|
const notesPath = path.join(siteDir, 'notes.md');
|
|
154
155
|
let endpointsCount = 0;
|
|
@@ -505,7 +506,7 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
|
|
|
505
506
|
description: c.description,
|
|
506
507
|
access: c.access,
|
|
507
508
|
strategy: strategyLabel(c),
|
|
508
|
-
browser:
|
|
509
|
+
browser: c.browser,
|
|
509
510
|
args: formatArgSummary(c.args),
|
|
510
511
|
}));
|
|
511
512
|
renderOutput(rows, {
|
|
@@ -2357,10 +2358,9 @@ Examples:
|
|
|
2357
2358
|
process.exitCode = EXIT_CODES.USAGE_ERROR;
|
|
2358
2359
|
return;
|
|
2359
2360
|
}
|
|
2360
|
-
const os = await import('node:os');
|
|
2361
2361
|
const fs = await import('node:fs');
|
|
2362
2362
|
const path = await import('node:path');
|
|
2363
|
-
const dir = path.join(
|
|
2363
|
+
const dir = path.join(getUserClisDir(), site);
|
|
2364
2364
|
const filePath = path.join(dir, `${command}.js`);
|
|
2365
2365
|
if (fs.existsSync(filePath)) {
|
|
2366
2366
|
console.log(`Adapter already exists: ${filePath}`);
|
|
@@ -2427,7 +2427,7 @@ cli({
|
|
|
2427
2427
|
}
|
|
2428
2428
|
const { execFileSync } = await import('node:child_process');
|
|
2429
2429
|
const { loadFixture, writeFixture, deriveFixture, validateRows, validateRowShape, fixturePath, expandFixtureArgs, parseSeedArgs } = await import('./browser/verify-fixture.js');
|
|
2430
|
-
const filePath = path.join(
|
|
2430
|
+
const filePath = path.join(getUserClisDir(), site, `${command}.js`);
|
|
2431
2431
|
if (!fs.existsSync(filePath)) {
|
|
2432
2432
|
console.error(`Adapter not found: ${filePath}`);
|
|
2433
2433
|
console.error(`Run "bycli browser init ${name}" to create it.`);
|
|
@@ -2773,8 +2773,7 @@ cli({
|
|
|
2773
2773
|
.command('status')
|
|
2774
2774
|
.description('Show which sites have local overrides vs using official baseline')
|
|
2775
2775
|
.action(async () => {
|
|
2776
|
-
const
|
|
2777
|
-
const userClisDir = path.join(os.homedir(), '.bycli', 'clis');
|
|
2776
|
+
const userClisDir = getUserClisDir();
|
|
2778
2777
|
const builtinClisDir = BUILTIN_CLIS;
|
|
2779
2778
|
try {
|
|
2780
2779
|
const userEntries = await fs.promises.readdir(userClisDir, { withFileTypes: true });
|
|
@@ -2789,7 +2788,7 @@ cli({
|
|
|
2789
2788
|
console.log('No local adapter overrides. All sites use the official baseline.');
|
|
2790
2789
|
return;
|
|
2791
2790
|
}
|
|
2792
|
-
console.log(`Local overrides in
|
|
2791
|
+
console.log(`Local overrides in ${userClisDir} (${userSites.length} sites):\n`);
|
|
2793
2792
|
for (const site of userSites) {
|
|
2794
2793
|
const isOfficial = builtinSites.includes(site);
|
|
2795
2794
|
const label = isOfficial ? 'override' : 'custom';
|
|
@@ -2806,8 +2805,7 @@ cli({
|
|
|
2806
2805
|
.description('Copy an official adapter to ~/.bycli/clis/ for local editing')
|
|
2807
2806
|
.argument('<site>', 'Site name (e.g. twitter, bilibili)')
|
|
2808
2807
|
.action(async (site) => {
|
|
2809
|
-
const
|
|
2810
|
-
const userClisDir = path.join(os.homedir(), '.bycli', 'clis');
|
|
2808
|
+
const userClisDir = getUserClisDir();
|
|
2811
2809
|
const builtinSiteDir = path.join(BUILTIN_CLIS, site);
|
|
2812
2810
|
const userSiteDir = path.join(userClisDir, site);
|
|
2813
2811
|
try {
|
|
@@ -2820,13 +2818,13 @@ cli({
|
|
|
2820
2818
|
}
|
|
2821
2819
|
try {
|
|
2822
2820
|
await fs.promises.access(userSiteDir);
|
|
2823
|
-
console.error(`Site "${site}" already exists in
|
|
2821
|
+
console.error(`Site "${site}" already exists in ${userClisDir}. Use "bycli adapter reset ${site}" first to restore official version.`);
|
|
2824
2822
|
process.exitCode = EXIT_CODES.USAGE_ERROR;
|
|
2825
2823
|
return;
|
|
2826
2824
|
}
|
|
2827
2825
|
catch { /* good, doesn't exist yet */ }
|
|
2828
2826
|
fs.cpSync(builtinSiteDir, userSiteDir, { recursive: true });
|
|
2829
|
-
console.log(`✅ Ejected "${site}" to
|
|
2827
|
+
console.log(`✅ Ejected "${site}" to ${userSiteDir}/`);
|
|
2830
2828
|
console.log('You can now edit the adapter files. Changes take effect immediately.');
|
|
2831
2829
|
console.log('Note: Official updates to this adapter will overwrite your changes.');
|
|
2832
2830
|
});
|
|
@@ -2836,8 +2834,7 @@ cli({
|
|
|
2836
2834
|
.argument('[site]', 'Site name (e.g. twitter, bilibili)')
|
|
2837
2835
|
.option('--all', 'Reset all local overrides')
|
|
2838
2836
|
.action(async (site, opts) => {
|
|
2839
|
-
const
|
|
2840
|
-
const userClisDir = path.join(os.homedir(), '.bycli', 'clis');
|
|
2837
|
+
const userClisDir = getUserClisDir();
|
|
2841
2838
|
if (opts.all) {
|
|
2842
2839
|
try {
|
|
2843
2840
|
const userEntries = await fs.promises.readdir(userClisDir, { withFileTypes: true });
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { log } from './logger.js';
|
|
13
13
|
import yaml from 'js-yaml';
|
|
14
|
-
import { fullName, getRegistry } from './registry.js';
|
|
14
|
+
import { fullName, getRegistry, hasBrowserCapability } from './registry.js';
|
|
15
15
|
import { render as renderOutput } from './output.js';
|
|
16
16
|
import { executeCommand, prepareCommandArgs } from './execution.js';
|
|
17
17
|
import { commandHelpData, formatCommandHelpText, formatCommandListTerm, formatSiteCommandDescription, formatSiteHelpText, getRequestedHelpFormat, renderStructuredHelp, siteHelpData, } from './help.js';
|
|
@@ -48,7 +48,7 @@ export function registerCommandToProgram(siteCmd, cmd) {
|
|
|
48
48
|
.option('-f, --format <fmt>', 'Output format: table, plain, json, yaml, md, csv', 'table')
|
|
49
49
|
.option('--trace <mode>', 'Trace capture: off, on, retain-on-failure', 'off')
|
|
50
50
|
.option('-v, --verbose', 'Debug output', false);
|
|
51
|
-
if (cmd
|
|
51
|
+
if (hasBrowserCapability(cmd)) {
|
|
52
52
|
subCmd
|
|
53
53
|
.option('--window <mode>', 'Browser window mode: foreground or background')
|
|
54
54
|
.option('--site-session <mode>', 'Adapter site session lifecycle: ephemeral or persistent')
|
|
@@ -108,9 +108,9 @@ export function registerCommandToProgram(siteCmd, cmd) {
|
|
|
108
108
|
prepared: true,
|
|
109
109
|
...(typeof globals.profile === 'string' && globals.profile.trim() ? { profile: globals.profile.trim() } : {}),
|
|
110
110
|
...(typeof optionsRecord.trace === 'string' && optionsRecord.trace !== 'off' ? { trace: optionsRecord.trace } : {}),
|
|
111
|
-
...(cmd
|
|
112
|
-
...(cmd
|
|
113
|
-
...(cmd
|
|
111
|
+
...(hasBrowserCapability(cmd) && typeof optionsRecord.window === 'string' ? { windowMode: optionsRecord.window } : {}),
|
|
112
|
+
...(hasBrowserCapability(cmd) && typeof optionsRecord.siteSession === 'string' ? { siteSession: optionsRecord.siteSession } : {}),
|
|
113
|
+
...(hasBrowserCapability(cmd) && typeof optionsRecord.keepTab === 'string' ? { keepTab: optionsRecord.keepTab } : {}),
|
|
114
114
|
});
|
|
115
115
|
if (result === null || result === undefined) {
|
|
116
116
|
return;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolvers for the byCLI user-data root and its well-known subdirectories.
|
|
3
|
+
*
|
|
4
|
+
* The root is `BYCLI_CONFIG_DIR` when set, else `~/.bycli`. These are FUNCTIONS,
|
|
5
|
+
* not module-level constants, on purpose: `BYCLI_CONFIG_DIR` is set dynamically
|
|
6
|
+
* (tests, the verify child sandbox in runner-port.ts) — a const captured at import
|
|
7
|
+
* time would freeze the wrong root and leak across tests. Always resolve on call.
|
|
8
|
+
*
|
|
9
|
+
* Existing duplicate idioms this consolidates: observation/artifact.ts, browser/profile.ts.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getConfigDir(): string;
|
|
12
|
+
/** User adapter root: `$CONFIG_DIR/clis`. Adapters live at `<clis>/<site>/<command>.js`. */
|
|
13
|
+
export declare function getUserClisDir(): string;
|
|
14
|
+
/** Per-site memory/recorder root: `$CONFIG_DIR/sites`. */
|
|
15
|
+
export declare function getSitesDir(): string;
|
|
16
|
+
/** A single site's directory: `$CONFIG_DIR/sites/<site>`. */
|
|
17
|
+
export declare function getSiteDir(site: string): string;
|
|
18
|
+
/** A site's recorder artifact dir: `$CONFIG_DIR/sites/<site>/recorder`. */
|
|
19
|
+
export declare function getSiteRecorderDir(site: string): string;
|
|
@@ -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,35 @@ 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';
|
|
38
|
+
import { EXTENSION_CAPABILITY_MISSING_ERROR_CODE, EXTENSION_CAPABILITY_MISSING_HTTP_STATUS, extensionCapabilityHint, missingRequiredExtensionCapability, normalizeExtensionCapabilities, } from './browser/extension-capabilities.js';
|
|
30
39
|
import { buildCommandDispatchFailure, buildExtensionDisconnectFailure, getResponseCorsHeaders, } from './daemon-utils.js';
|
|
31
40
|
const PORT = parseInt(process.env.BYCLI_DAEMON_PORT ?? String(DEFAULT_DAEMON_PORT), 10);
|
|
41
|
+
// The verify runner (M6b) spawns child processes that connect back to THIS daemon for a
|
|
42
|
+
// browser Page. Hand them our port (→ BYCLI_DAEMON_PORT in the child env) so the child's
|
|
43
|
+
// Page reaches us, not a freshly-spawned daemon. Must run before the first /v1/verify
|
|
44
|
+
// builds the default runner singleton.
|
|
45
|
+
setDefaultRunnerDaemonPort(PORT);
|
|
46
|
+
// ─── Observability (#1b · 09) ────────────────────────────────────────
|
|
47
|
+
// daemon/runner-side structured logs + metrics (M8 only did dashboard-be). The metrics instance is
|
|
48
|
+
// a process singleton, shared by reference into the runner port (#1c) so GET /metrics shows runner
|
|
49
|
+
// counters too. LOG_LEVEL is read once at startup — the daemon has no ConfigPort, so no hot reload
|
|
50
|
+
// here (that's a larger, separate milestone); a malformed value falls back to 'info'.
|
|
51
|
+
const LOG_LEVELS = ['error', 'warn', 'info', 'debug'];
|
|
52
|
+
const envLogLevel = process.env.LOG_LEVEL ?? '';
|
|
53
|
+
const metrics = createMetrics();
|
|
54
|
+
const logger = createRecorderLogger(LOG_LEVELS.includes(envLogLevel) ? envLogLevel : 'info');
|
|
55
|
+
// Share these singletons with the verify runner (#1c) BEFORE the first defaultRunnerPort() use, so
|
|
56
|
+
// runner counters surface on GET /metrics and runner logs share the daemon's level.
|
|
57
|
+
setDefaultRunnerObservability(metrics, logger);
|
|
32
58
|
const extensionProfiles = new Map();
|
|
33
59
|
const pending = new Map();
|
|
34
60
|
let commandResultUnknownCount = 0;
|
|
@@ -98,6 +124,7 @@ function registerExtensionConnection(ws, rawContextId) {
|
|
|
98
124
|
ws,
|
|
99
125
|
extensionVersion: current?.ws === ws ? current.extensionVersion : null,
|
|
100
126
|
extensionCompatRange: current?.ws === ws ? current.extensionCompatRange : null,
|
|
127
|
+
extensionCapabilities: current?.ws === ws ? current.extensionCapabilities : [],
|
|
101
128
|
lastSeenAt: Date.now(),
|
|
102
129
|
};
|
|
103
130
|
extensionProfiles.set(contextId, connection);
|
|
@@ -119,6 +146,7 @@ function unregisterExtensionConnection(ws) {
|
|
|
119
146
|
});
|
|
120
147
|
if (failure.countAsCommandResultUnknown) {
|
|
121
148
|
commandResultUnknownCount++;
|
|
149
|
+
metrics.inc('daemon_command_result_unknown_total'); // #1b: structured metric alongside the /status field
|
|
122
150
|
log.warn(`[daemon] Command result unknown after extension disconnect (id=${id}, action=${p.action}, context=${contextId})`);
|
|
123
151
|
}
|
|
124
152
|
p.reject(new DaemonCommandFailure(failure.message, failure.errorCode, failure.errorHint, failure.status));
|
|
@@ -150,6 +178,11 @@ function readBody(req) {
|
|
|
150
178
|
});
|
|
151
179
|
}
|
|
152
180
|
function jsonResponse(res, status, data, extraHeaders) {
|
|
181
|
+
// Stash any errorCode for the request-completion metrics choke point (#1b), so handlers don't
|
|
182
|
+
// each have to wire it through. Only the bounded ErrorCode enum reaches a label/log field.
|
|
183
|
+
const ec = data?.errorCode;
|
|
184
|
+
if (typeof ec === 'string')
|
|
185
|
+
res.__errorCode = ec;
|
|
153
186
|
res.writeHead(status, { 'Content-Type': 'application/json', ...extraHeaders });
|
|
154
187
|
res.end(JSON.stringify(data));
|
|
155
188
|
}
|
|
@@ -174,6 +207,32 @@ async function handleRequest(req, res) {
|
|
|
174
207
|
}
|
|
175
208
|
const url = req.url ?? '/';
|
|
176
209
|
const pathname = url.split('?')[0];
|
|
210
|
+
// Request-completion metrics choke point (#1b · 09): one place records type/status/errorCode +
|
|
211
|
+
// latency, no per-handler scatter. Only the high-level surface (/v1/*) and the /command bridge are
|
|
212
|
+
// counted; the dynamic /v1/requests/{id} segment collapses to a bounded route template so the
|
|
213
|
+
// `operation` label can't grow unboundedly. /v1/* operations also get a structured log line
|
|
214
|
+
// (low volume, the recording ops); /command is counted but not logged (per-command WS bridge is
|
|
215
|
+
// high volume). Never logs headers/token/body — only operation/status/errorCode/durationMs.
|
|
216
|
+
const reqStarted = Date.now();
|
|
217
|
+
res.on('finish', () => {
|
|
218
|
+
let operation = null;
|
|
219
|
+
if (pathname.startsWith('/v1/requests/'))
|
|
220
|
+
operation = 'v1.requests';
|
|
221
|
+
else if (pathname.startsWith('/v1/'))
|
|
222
|
+
operation = `v1${pathname.slice(3).replace(/\//g, '.')}`; // /v1/init → v1.init
|
|
223
|
+
else if (pathname === '/command')
|
|
224
|
+
operation = 'command';
|
|
225
|
+
if (!operation)
|
|
226
|
+
return;
|
|
227
|
+
const status = res.statusCode < 400 ? 'ok' : 'failed';
|
|
228
|
+
const errorCode = res.__errorCode;
|
|
229
|
+
const durationMs = Date.now() - reqStarted;
|
|
230
|
+
metrics.inc('daemon_requests_total', { operation, status, errorCode });
|
|
231
|
+
if (operation === 'v1.verify')
|
|
232
|
+
metrics.observe('daemon_verify_duration_ms', durationMs);
|
|
233
|
+
if (operation.startsWith('v1'))
|
|
234
|
+
logger.info(`daemon.${operation}`, { status, errorCode, durationMs });
|
|
235
|
+
});
|
|
177
236
|
// Health-check endpoint — no X-byCLI header required.
|
|
178
237
|
// Used by the extension to silently probe daemon reachability before
|
|
179
238
|
// attempting a WebSocket connection (avoids uncatchable ERR_CONNECTION_REFUSED).
|
|
@@ -193,6 +252,102 @@ async function handleRequest(req, res) {
|
|
|
193
252
|
jsonResponse(res, 403, { ok: false, error: 'Forbidden: missing X-byCLI header' });
|
|
194
253
|
return;
|
|
195
254
|
}
|
|
255
|
+
// High-level init (M5b · A'): daemon hosts the FS-writing init capability; the
|
|
256
|
+
// logic lives in the main-repo init module (createAdapterDraft). dashboard-be
|
|
257
|
+
// forwards here rather than writing main-repo adapter paths itself. Synchronous
|
|
258
|
+
// (init is a short FS op, not a long-running runner); requestId/async is deferred.
|
|
259
|
+
if (req.method === 'POST' && pathname === '/v1/init') {
|
|
260
|
+
try {
|
|
261
|
+
const body = JSON.parse(await readBody(req));
|
|
262
|
+
if (typeof body.name !== 'string' || !body.name) {
|
|
263
|
+
jsonResponse(res, 400, { ok: false, errorCode: 'validation_failed', error: 'name required' });
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const result = createAdapterDraft(body);
|
|
267
|
+
if (!result.ok) {
|
|
268
|
+
// init failures are all client-fixable 400s (validation_failed / responsible_use_required).
|
|
269
|
+
jsonResponse(res, 400, { ok: false, errorCode: result.errorCode, error: result.reason });
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
jsonResponse(res, 200, { ok: true, data: { report: result.report, dryRun: result.dryRun, generatedSource: result.generatedSource } });
|
|
273
|
+
}
|
|
274
|
+
catch (err) {
|
|
275
|
+
jsonResponse(res, 400, { ok: false, errorCode: 'validation_failed', error: err instanceof Error ? err.message : String(err) });
|
|
276
|
+
}
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
// N4: save a reviewed, full LLM-generated adapter source to clis/ (verify-then-save flow).
|
|
280
|
+
if (req.method === 'POST' && pathname === '/v1/save-adapter') {
|
|
281
|
+
try {
|
|
282
|
+
const body = JSON.parse(await readBody(req));
|
|
283
|
+
if (typeof body.name !== 'string' || !body.name || typeof body.source !== 'string' || !body.source) {
|
|
284
|
+
jsonResponse(res, 400, { ok: false, errorCode: 'validation_failed', error: 'name and source required' });
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const result = saveAdapterSource(body);
|
|
288
|
+
if (!result.ok) {
|
|
289
|
+
jsonResponse(res, 400, { ok: false, errorCode: result.errorCode, error: result.reason });
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
jsonResponse(res, 200, { ok: true, data: { adapterPath: result.adapterPath, reportPath: result.reportPath } });
|
|
293
|
+
}
|
|
294
|
+
catch (err) {
|
|
295
|
+
jsonResponse(res, 400, { ok: false, errorCode: 'validation_failed', error: err instanceof Error ? err.message : String(err) });
|
|
296
|
+
}
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
// High-level verify (A'): interface + delegation seam. The adapter JS runs in the
|
|
300
|
+
// M6 child-process runner (08) — verifyAdapter delegates to the real spawn-based
|
|
301
|
+
// RunnerPort (defaultRunnerPort), so runner_protocol_error now signals an actual
|
|
302
|
+
// runner fault, not "not implemented". The evidence HMAC is keyed by a per-session salt
|
|
303
|
+
// held only in daemon memory (M7a · 04:111): be forwards the non-secret sessionId, the
|
|
304
|
+
// daemon mints + holds the salt, and it never crosses the wire.
|
|
305
|
+
if (req.method === 'POST' && pathname === '/v1/verify') {
|
|
306
|
+
try {
|
|
307
|
+
const body = JSON.parse(await readBody(req));
|
|
308
|
+
if (typeof body.name !== 'string' || !body.name) {
|
|
309
|
+
jsonResponse(res, 400, { ok: false, errorCode: 'validation_failed', error: 'name required' });
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const sessionHmacKey = defaultSessionKeyRegistry().keyFor(body.sessionId);
|
|
313
|
+
const result = await verifyAdapter(body, sessionHmacKey);
|
|
314
|
+
if (!result.ok) {
|
|
315
|
+
const status = result.errorCode === 'validation_failed' ? 400
|
|
316
|
+
: result.errorCode === 'queue_full' ? 429 // 03: queue/concurrency exceeded
|
|
317
|
+
: 500;
|
|
318
|
+
// requestId/sessionId correlation the choke point (res-only) can't see (09:89).
|
|
319
|
+
logger.warn('daemon.verify', { sessionId: body.sessionId, status: 'failed', errorCode: result.errorCode });
|
|
320
|
+
jsonResponse(res, status, { ok: false, errorCode: result.errorCode, error: result.reason });
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
logger.info('daemon.verify', { requestId: result.requestId, sessionId: body.sessionId, stage: 'accepted' });
|
|
324
|
+
jsonResponse(res, 200, { ok: true, data: { requestId: result.requestId } });
|
|
325
|
+
}
|
|
326
|
+
catch (err) {
|
|
327
|
+
jsonResponse(res, 400, { ok: false, errorCode: 'validation_failed', error: err instanceof Error ? err.message : String(err) });
|
|
328
|
+
}
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
// High-level request status (M6 · ADR-0007): be polls verify status here. The runner
|
|
332
|
+
// registry is keyed by the canonical requestId (be ↔ daemon ↔ runner share one id);
|
|
333
|
+
// returns summary-only VerifySummary (raw seed/stdout/stderr/trace path never included).
|
|
334
|
+
if (req.method === 'GET' && pathname.startsWith('/v1/requests/')) {
|
|
335
|
+
const requestId = decodeURIComponent(pathname.slice('/v1/requests/'.length));
|
|
336
|
+
if (!requestId) {
|
|
337
|
+
jsonResponse(res, 400, { ok: false, errorCode: 'validation_failed', error: 'requestId required' });
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const runStatus = defaultRunnerPort().getRunStatus(requestId);
|
|
341
|
+
if (!runStatus) {
|
|
342
|
+
jsonResponse(res, 404, { ok: false, errorCode: 'request_not_found', error: 'unknown requestId' });
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
// RunStatus (queued|running|succeeded|failed|timeout|cancelled) maps 1:1 to request status;
|
|
346
|
+
// summary is null while queued/running, a VerifySummary once terminal (M6c adds queued).
|
|
347
|
+
logger.debug('daemon.requests', { requestId, status: runStatus.status });
|
|
348
|
+
jsonResponse(res, 200, { ok: true, data: { requestId, status: runStatus.status, result: runStatus.summary } });
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
196
351
|
if (req.method === 'GET' && pathname === '/status') {
|
|
197
352
|
const uptime = process.uptime();
|
|
198
353
|
const mem = process.memoryUsage();
|
|
@@ -204,6 +359,7 @@ async function handleRequest(req, res) {
|
|
|
204
359
|
extensionConnected: true,
|
|
205
360
|
extensionVersion: profile.extensionVersion ?? undefined,
|
|
206
361
|
extensionCompatRange: profile.extensionCompatRange ?? undefined,
|
|
362
|
+
extensionCapabilities: profile.extensionCapabilities,
|
|
207
363
|
pending: [...pending.values()].filter((entry) => entry.contextId === profile.contextId).length,
|
|
208
364
|
lastSeenAt: profile.lastSeenAt,
|
|
209
365
|
}));
|
|
@@ -215,6 +371,7 @@ async function handleRequest(req, res) {
|
|
|
215
371
|
extensionConnected: !!route.connection,
|
|
216
372
|
extensionVersion: route.connection?.extensionVersion ?? undefined,
|
|
217
373
|
extensionCompatRange: route.connection?.extensionCompatRange ?? undefined,
|
|
374
|
+
extensionCapabilities: route.connection?.extensionCapabilities,
|
|
218
375
|
contextId: route.connection?.contextId ?? requestedContextId,
|
|
219
376
|
profileRequired: route.errorCode === 'profile_required',
|
|
220
377
|
profileDisconnected: route.errorCode === 'profile_disconnected',
|
|
@@ -226,6 +383,13 @@ async function handleRequest(req, res) {
|
|
|
226
383
|
});
|
|
227
384
|
return;
|
|
228
385
|
}
|
|
386
|
+
// Metrics scrape (#1b · 09): loopback diagnostic, same X-byCLI posture as /status (no token).
|
|
387
|
+
// The counter/histogram registry is pure data in recorder-core; this endpoint (http-bound) stays
|
|
388
|
+
// in the daemon transport layer. Values are non-sensitive enums/counts only (09).
|
|
389
|
+
if (req.method === 'GET' && pathname === '/metrics') {
|
|
390
|
+
jsonResponse(res, 200, { ok: true, ...metrics.snapshot() });
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
229
393
|
if (req.method === 'GET' && pathname === '/logs') {
|
|
230
394
|
const params = new URL(url, `http://localhost:${PORT}`).searchParams;
|
|
231
395
|
const level = params.get('level');
|
|
@@ -263,6 +427,17 @@ async function handleRequest(req, res) {
|
|
|
263
427
|
});
|
|
264
428
|
return;
|
|
265
429
|
}
|
|
430
|
+
const requiredCapability = missingRequiredExtensionCapability(body, route.connection.extensionCapabilities);
|
|
431
|
+
if (requiredCapability) {
|
|
432
|
+
jsonResponse(res, EXTENSION_CAPABILITY_MISSING_HTTP_STATUS, {
|
|
433
|
+
id: body.id,
|
|
434
|
+
ok: false,
|
|
435
|
+
errorCode: EXTENSION_CAPABILITY_MISSING_ERROR_CODE,
|
|
436
|
+
error: `Connected Browser Bridge does not advertise ${requiredCapability}.`,
|
|
437
|
+
errorHint: extensionCapabilityHint(requiredCapability),
|
|
438
|
+
});
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
266
441
|
const timeoutMs = typeof body.timeout === 'number' && body.timeout > 0
|
|
267
442
|
? body.timeout * 1000
|
|
268
443
|
: 120000;
|
|
@@ -369,6 +544,7 @@ wss.on('connection', (ws) => {
|
|
|
369
544
|
const connection = registerExtensionConnection(ws, msg.contextId);
|
|
370
545
|
connection.extensionVersion = typeof msg.version === 'string' ? msg.version : null;
|
|
371
546
|
connection.extensionCompatRange = typeof msg.compatRange === 'string' ? msg.compatRange : null;
|
|
547
|
+
connection.extensionCapabilities = normalizeExtensionCapabilities(msg.capabilities);
|
|
372
548
|
connection.lastSeenAt = Date.now();
|
|
373
549
|
if (connection.extensionVersion)
|
|
374
550
|
recordExtensionVersion(connection.extensionVersion);
|
|
@@ -411,6 +587,86 @@ wss.on('connection', (ws) => {
|
|
|
411
587
|
// ─── Start ───────────────────────────────────────────────────────────
|
|
412
588
|
httpServer.listen(PORT, '127.0.0.1', () => {
|
|
413
589
|
log.info(`[daemon] Listening on http://127.0.0.1:${PORT}`);
|
|
590
|
+
// Temp-store reap policy (M7b · 09:27-29). Resolved once at startup; out-of-range env → throws,
|
|
591
|
+
// but we keep the daemon alive by falling back to the (validated-elsewhere) defaults on error.
|
|
592
|
+
let tempPolicy;
|
|
593
|
+
try {
|
|
594
|
+
tempPolicy = resolveTempPolicy();
|
|
595
|
+
}
|
|
596
|
+
catch (err) {
|
|
597
|
+
log.warn(`[daemon] temp policy config invalid, using defaults: ${err instanceof Error ? err.message : String(err)}`);
|
|
598
|
+
tempPolicy = { tempTtlMs: 3_600_000, startupReapMaxAgeMs: 86_400_000, orphanKillGraceMs: 1_500 };
|
|
599
|
+
}
|
|
600
|
+
// Age-reap safety floor (08 / M7b): a verify cannot legitimately outlive timeoutMs + killGraceMs,
|
|
601
|
+
// so the age threshold must never drop below it — otherwise a short RECORDER_TEMP_TTL_MS (min 60s)
|
|
602
|
+
// would let the sweep kill a still-running verify (timeoutMs reaches 600s). Fall back to the
|
|
603
|
+
// absolute config ceiling (max timeoutMs + max killGraceMs) if runner config is unreadable.
|
|
604
|
+
let minLeakAgeMs = 600_000 + 30_000;
|
|
605
|
+
try {
|
|
606
|
+
const rc = resolveRunnerConfig();
|
|
607
|
+
minLeakAgeMs = rc.timeoutMs + rc.killGraceMs;
|
|
608
|
+
}
|
|
609
|
+
catch { /* keep the absolute ceiling (safest, highest floor) */ }
|
|
610
|
+
// #1d temp-store capacity guard: the runner refuses a new verify (temp_store_full) past the high
|
|
611
|
+
// watermark rather than risk filling the disk. Resolved at startup (temp root is restart-only,
|
|
612
|
+
// 09:182); bad env falls back to defaults (consistent with temp policy above). Registered BEFORE the
|
|
613
|
+
// first defaultRunnerPort() use (no request is served until after this listen callback returns). The
|
|
614
|
+
// on-pressure sweep reuses the same age-bounded reap as the periodic sweep (never kills a live verify).
|
|
615
|
+
let tempCapacity;
|
|
616
|
+
try {
|
|
617
|
+
tempCapacity = resolveTempCapacity();
|
|
618
|
+
}
|
|
619
|
+
catch (err) {
|
|
620
|
+
log.warn(`[daemon] temp capacity config invalid, using defaults: ${err instanceof Error ? err.message : String(err)}`);
|
|
621
|
+
tempCapacity = resolveTempCapacity({}); // empty env → validated defaults
|
|
622
|
+
}
|
|
623
|
+
setDefaultRunnerTempGuard(tempCapacity, () => {
|
|
624
|
+
reapOrphanedVerifyRuns({ log: (m) => log.info(m), maxAgeMs: tempPolicy.tempTtlMs, minLeakAgeMs, graceMs: tempPolicy.orphanKillGraceMs });
|
|
625
|
+
});
|
|
626
|
+
// Startup reap (08:37): clean up verify temp dirs orphaned by a previously-crashed daemon
|
|
627
|
+
// (ownerPid-dead), plus any dir older than the startup age backstop (M7b leak sweep). A live
|
|
628
|
+
// sibling's in-flight verify (young + owner alive, or within the hard-deadline floor) is never touched.
|
|
629
|
+
try {
|
|
630
|
+
const r = reapOrphanedVerifyRuns({ log: (m) => log.info(m), maxAgeMs: tempPolicy.startupReapMaxAgeMs, minLeakAgeMs, graceMs: tempPolicy.orphanKillGraceMs });
|
|
631
|
+
// #1c · 09 startup reap count (orphan runners killed, temp dirs swept)
|
|
632
|
+
if (r.orphans)
|
|
633
|
+
metrics.inc('startup_reap_orphans_total', undefined, r.orphans);
|
|
634
|
+
if (r.killed)
|
|
635
|
+
metrics.inc('startup_reap_killed_total', undefined, r.killed);
|
|
636
|
+
if (r.deleted)
|
|
637
|
+
metrics.inc('startup_reap_deleted_total', undefined, r.deleted);
|
|
638
|
+
}
|
|
639
|
+
catch (err) {
|
|
640
|
+
log.warn(`[daemon] verify startup reap failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
641
|
+
}
|
|
642
|
+
// Periodic TTL sweep (M7b): while the daemon runs, reap dirs older than the temp TTL — but never
|
|
643
|
+
// below the run hard-deadline floor (minLeakAgeMs), so an aggressive TTL can't kill a live verify.
|
|
644
|
+
// Unref'd so it never keeps the process alive on its own.
|
|
645
|
+
const sweep = setInterval(() => {
|
|
646
|
+
try {
|
|
647
|
+
const r = reapOrphanedVerifyRuns({ log: (m) => log.info(m), maxAgeMs: tempPolicy.tempTtlMs, minLeakAgeMs, graceMs: tempPolicy.orphanKillGraceMs });
|
|
648
|
+
if (r.killed)
|
|
649
|
+
metrics.inc('temp_sweep_killed_total', undefined, r.killed);
|
|
650
|
+
if (r.deleted)
|
|
651
|
+
metrics.inc('temp_sweep_deleted_total', undefined, r.deleted);
|
|
652
|
+
// Drop salts for abandoned sessions (M7a TTL backstop; daemon restart already rotates all).
|
|
653
|
+
const dropped = defaultSessionKeyRegistry().sweepExpired();
|
|
654
|
+
if (dropped)
|
|
655
|
+
metrics.inc('session_keys_dropped_total', undefined, dropped);
|
|
656
|
+
}
|
|
657
|
+
catch (err) {
|
|
658
|
+
log.warn(`[daemon] verify temp sweep failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
659
|
+
}
|
|
660
|
+
}, tempPolicy.tempTtlMs);
|
|
661
|
+
sweep.unref?.();
|
|
662
|
+
// Init write-transaction crash recovery (07:106-117): resolve any adapter draft interrupted
|
|
663
|
+
// mid-write by a previous crash (roll-forward marker / quarantine unprovenanced / roll back).
|
|
664
|
+
try {
|
|
665
|
+
recoverInitTransactions({ log: (m) => log.info(m) });
|
|
666
|
+
}
|
|
667
|
+
catch (err) {
|
|
668
|
+
log.warn(`[daemon] init startup recovery failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
669
|
+
}
|
|
414
670
|
});
|
|
415
671
|
httpServer.on('error', (err) => {
|
|
416
672
|
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
|
|
@@ -35,6 +37,11 @@ export declare function ensureUserAdapters(): Promise<void>;
|
|
|
35
37
|
* Uses pre-compiled manifest when available for instant startup.
|
|
36
38
|
*/
|
|
37
39
|
export declare function discoverClis(...dirs: string[]): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Fast-path: register commands from pre-compiled manifest.
|
|
42
|
+
* TS modules are deferred — loaded lazily on first execution.
|
|
43
|
+
*/
|
|
44
|
+
export declare function loadFromManifest(manifestPath: string, clisDir: string): Promise<boolean>;
|
|
38
45
|
/**
|
|
39
46
|
* Discover and register plugins from ~/.bycli/plugins/.
|
|
40
47
|
* Each subdirectory is treated as a plugin (site = directory name).
|