@jackwener/opencli 1.7.12 → 1.7.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -7
- package/README.zh-CN.md +9 -8
- package/cli-manifest.json +12194 -6843
- package/clis/1point3acres/digest.js +35 -0
- package/clis/1point3acres/forum.js +51 -0
- package/clis/1point3acres/forums.js +44 -0
- package/clis/1point3acres/hot.js +35 -0
- package/clis/1point3acres/latest.js +35 -0
- package/clis/1point3acres/notifications.js +64 -0
- package/clis/1point3acres/search.js +71 -0
- package/clis/1point3acres/thread.js +117 -0
- package/clis/1point3acres/user.js +77 -0
- package/clis/1point3acres/utils.js +247 -0
- package/clis/_shared/desktop-commands.js +4 -0
- package/clis/aibase/news.js +110 -0
- package/clis/aibase/news.test.js +59 -0
- package/clis/amazon/discussion.test.js +1 -28
- package/clis/antigravity/watch.js +3 -2
- package/clis/arxiv/author.js +44 -0
- package/clis/baidu-scholar/search.js +0 -1
- package/clis/bbc/topic.js +57 -0
- package/clis/bbc/utils.js +79 -0
- package/clis/chaoxing/assignments.js +1 -1
- package/clis/chaoxing/exams.js +1 -1
- package/clis/chatgpt/ask.js +57 -0
- package/clis/chatgpt/commands.test.js +45 -0
- package/clis/chatgpt/detail.js +46 -0
- package/clis/chatgpt/history.js +39 -0
- package/clis/chatgpt/image.js +12 -11
- package/clis/chatgpt/image.test.js +23 -0
- package/clis/chatgpt/new.js +25 -0
- package/clis/chatgpt/read.js +43 -0
- package/clis/chatgpt/send.js +46 -0
- package/clis/chatgpt/status.js +29 -0
- package/clis/chatgpt/utils.js +294 -4
- package/clis/chatgpt/utils.test.js +13 -0
- package/clis/chatgpt-app/ask.js +6 -3
- package/clis/chatwise/ask.js +16 -43
- package/clis/chatwise/composer.test.js +186 -0
- package/clis/chatwise/send.js +2 -24
- package/clis/chatwise/utils.js +143 -0
- package/clis/claude/ask.js +1 -1
- package/clis/claude/detail.js +1 -0
- package/clis/claude/history.js +1 -0
- package/clis/claude/new.js +1 -0
- package/clis/claude/read.js +1 -0
- package/clis/claude/send.js +1 -0
- package/clis/claude/status.js +1 -0
- package/clis/codex/ask.js +15 -9
- package/clis/codex/history.js +16 -33
- package/clis/codex/projects.js +28 -0
- package/clis/codex/read.js +10 -4
- package/clis/codex/send.js +10 -3
- package/clis/codex/sidebar.js +356 -0
- package/clis/codex/sidebar.test.js +329 -0
- package/clis/coingecko/categories.js +75 -0
- package/clis/coingecko/coin.js +107 -0
- package/clis/coingecko/coingecko.test.js +109 -0
- package/clis/coingecko/derivatives.js +84 -0
- package/clis/coingecko/exchanges.js +74 -0
- package/clis/coingecko/global.js +71 -0
- package/clis/coingecko/top.js +64 -0
- package/clis/coingecko/trending.js +55 -0
- package/clis/coupang/add-to-cart.js +21 -13
- package/clis/coupang/coupang.test.js +159 -0
- package/clis/coupang/product.js +257 -0
- package/clis/coupang/search.js +38 -16
- package/clis/coupang/utils.js +55 -1
- package/clis/crates/crate.js +62 -0
- package/clis/crates/search.js +44 -0
- package/clis/crates/utils.js +72 -0
- package/clis/ctrip/ctrip.test.js +234 -0
- package/clis/ctrip/hotel-suggest.js +45 -0
- package/clis/ctrip/search.js +22 -68
- package/clis/ctrip/utils.js +175 -0
- package/clis/cursor/ask.js +6 -3
- package/clis/dblp/author.js +133 -0
- package/clis/dblp/venue.js +64 -0
- package/clis/deepseek/ask.js +12 -7
- package/clis/deepseek/ask.test.js +13 -13
- package/clis/deepseek/detail.js +38 -0
- package/clis/deepseek/detail.test.js +81 -0
- package/clis/deepseek/history.js +1 -0
- package/clis/deepseek/new.js +1 -0
- package/clis/deepseek/read.js +1 -0
- package/clis/deepseek/send.js +140 -0
- package/clis/deepseek/send.test.js +107 -0
- package/clis/deepseek/status.js +1 -0
- package/clis/deepseek/utils.js +66 -0
- package/clis/deepseek/utils.test.js +107 -1
- package/clis/defillama/defillama.test.js +99 -0
- package/clis/defillama/protocol.js +84 -0
- package/clis/defillama/protocols.js +55 -0
- package/clis/defillama/utils.js +99 -0
- package/clis/devto/latest.js +74 -0
- package/clis/dockerhub/image.js +52 -0
- package/clis/dockerhub/search.js +47 -0
- package/clis/dockerhub/utils.js +100 -0
- package/clis/doubao/ask.js +7 -3
- package/clis/doubao/detail.js +1 -0
- package/clis/doubao/history.js +1 -0
- package/clis/doubao/meeting-summary.js +1 -0
- package/clis/doubao/meeting-transcript.js +1 -0
- package/clis/doubao/new.js +1 -0
- package/clis/doubao/read.js +1 -0
- package/clis/doubao/send.js +1 -0
- package/clis/doubao/status.js +1 -0
- package/clis/douyin/draft.test.js +1 -30
- package/clis/endoflife/endoflife.test.js +51 -0
- package/clis/endoflife/product.js +55 -0
- package/clis/endoflife/utils.js +89 -0
- package/clis/facebook/__fixtures__/notifications-page.html +13 -0
- package/clis/facebook/notifications.js +326 -30
- package/clis/facebook/notifications.test.js +458 -0
- package/clis/flathub/app.js +71 -0
- package/clis/flathub/flathub.test.js +90 -0
- package/clis/flathub/search.js +80 -0
- package/clis/flathub/utils.js +114 -0
- package/clis/gemini/ask.js +7 -3
- package/clis/gemini/ask.test.js +2 -2
- package/clis/gemini/deep-research-result.js +6 -2
- package/clis/gemini/deep-research-result.test.js +15 -14
- package/clis/gemini/deep-research.js +8 -4
- package/clis/gemini/deep-research.test.js +15 -18
- package/clis/gemini/image.js +7 -2
- package/clis/gemini/new.js +1 -0
- package/clis/gemini/utils.js +0 -4
- package/clis/google-scholar/cite.js +0 -1
- package/clis/google-scholar/profile.js +0 -1
- package/clis/google-scholar/search.js +0 -1
- package/clis/goproxy/goproxy.test.js +103 -0
- package/clis/goproxy/module.js +47 -0
- package/clis/goproxy/utils.js +165 -0
- package/clis/goproxy/versions.js +59 -0
- package/clis/gov-law/recent.js +0 -1
- package/clis/gov-law/search.js +0 -1
- package/clis/gov-policy/__fixtures__/recent.html +16 -0
- package/clis/gov-policy/__fixtures__/search.html +41 -0
- package/clis/gov-policy/gov-policy.test.js +224 -0
- package/clis/gov-policy/recent.js +66 -24
- package/clis/gov-policy/search.js +65 -23
- package/clis/gov-policy/utils.js +54 -0
- package/clis/grok/ask.js +49 -265
- package/clis/grok/ask.test.js +21 -46
- package/clis/grok/detail.js +60 -0
- package/clis/grok/history.js +48 -0
- package/clis/grok/{image.ts → image.js} +56 -70
- package/clis/grok/image.test.ts +20 -0
- package/clis/grok/new.js +20 -0
- package/clis/grok/read.js +39 -0
- package/clis/grok/send.js +50 -0
- package/clis/grok/status.js +41 -0
- package/clis/grok/utils.js +326 -0
- package/clis/grok/utils.test.js +103 -0
- package/clis/hf/datasets.js +88 -0
- package/clis/hf/hf.test.js +16 -0
- package/clis/hf/models.js +91 -0
- package/clis/hf/paper.js +79 -0
- package/clis/hf/spaces.js +101 -0
- package/clis/hf/top.js +1 -0
- package/clis/homebrew/cask.js +39 -0
- package/clis/homebrew/formula.js +41 -0
- package/clis/homebrew/popular.js +54 -0
- package/clis/homebrew/utils.js +100 -0
- package/clis/hupu/__fixtures__/hot-home.html +64 -0
- package/clis/hupu/detail.js +0 -1
- package/clis/hupu/hot.js +156 -35
- package/clis/hupu/hot.test.js +224 -0
- package/clis/hupu/search.js +0 -1
- package/clis/instagram/note.js +1 -1
- package/clis/instagram/note.test.js +1 -29
- package/clis/instagram/post.js +1 -1
- package/clis/instagram/post.test.js +1 -1
- package/clis/instagram/reel.js +1 -1
- package/clis/instagram/story.js +1 -1
- package/clis/instagram/story.test.js +1 -34
- package/clis/jd/commands.test.js +1 -24
- package/clis/lichess/lichess.test.js +85 -0
- package/clis/lichess/top.js +46 -0
- package/clis/lichess/user.js +91 -0
- package/clis/lichess/utils.js +97 -0
- package/clis/linkedin/search.js +107 -10
- package/clis/linkedin/search.test.js +222 -0
- package/clis/linux-do/feed.js +2 -5
- package/clis/linux-do/feed.test.js +35 -0
- package/clis/lobsters/domain.js +92 -0
- package/clis/maven/artifact.js +49 -0
- package/clis/maven/search.js +51 -0
- package/clis/maven/utils.js +110 -0
- package/clis/mdn/search.js +97 -0
- package/clis/medium/tag.js +135 -0
- package/clis/npm/downloads.js +59 -0
- package/clis/npm/package.js +70 -0
- package/clis/npm/search.js +49 -0
- package/clis/npm/utils.js +76 -0
- package/clis/nuget/nuget.test.js +111 -0
- package/clis/nuget/package.js +101 -0
- package/clis/nuget/search.js +69 -0
- package/clis/nuget/utils.js +87 -0
- package/clis/nvd/cve.js +121 -0
- package/clis/oeis/oeis.test.js +88 -0
- package/clis/oeis/search.js +63 -0
- package/clis/oeis/sequence.js +71 -0
- package/clis/oeis/utils.js +88 -0
- package/clis/openalex/search.js +69 -0
- package/clis/openalex/utils.js +160 -0
- package/clis/openalex/work.js +65 -0
- package/clis/openfda/drug-label.js +74 -0
- package/clis/openfda/food-recall.js +65 -0
- package/clis/openfda/openfda.test.js +114 -0
- package/clis/openfda/utils.js +67 -0
- package/clis/osv/osv.test.js +97 -0
- package/clis/osv/query.js +72 -0
- package/clis/osv/utils.js +169 -0
- package/clis/osv/vulnerability.js +54 -0
- package/clis/packagist/package.js +49 -0
- package/clis/packagist/search.js +43 -0
- package/clis/packagist/utils.js +113 -0
- package/clis/paperreview/feedback.js +1 -1
- package/clis/paperreview/review.js +1 -1
- package/clis/paperreview/submit.js +1 -1
- package/clis/pixiv/download.test.js +1 -1
- package/clis/pixiv/illusts.test.js +1 -1
- package/clis/pixiv/search.test.js +1 -1
- package/clis/pubmed/article.js +50 -0
- package/clis/pubmed/author.js +64 -0
- package/clis/pubmed/citations.js +36 -0
- package/clis/pubmed/pubmed.test.js +276 -0
- package/clis/pubmed/related.js +45 -0
- package/clis/pubmed/search.js +75 -0
- package/clis/pubmed/utils.js +309 -0
- package/clis/pypi/downloads.js +66 -0
- package/clis/pypi/package.js +79 -0
- package/clis/pypi/utils.js +55 -0
- package/clis/quark/mv.js +1 -1
- package/clis/quark/save.js +1 -1
- package/clis/qwen/ask.js +85 -0
- package/clis/qwen/detail.js +62 -0
- package/clis/qwen/history.js +61 -0
- package/clis/qwen/image.js +179 -0
- package/clis/qwen/new.js +23 -0
- package/clis/qwen/read.js +41 -0
- package/clis/qwen/send.js +55 -0
- package/clis/qwen/status.js +37 -0
- package/clis/qwen/utils.js +409 -0
- package/clis/qwen/utils.test.js +45 -0
- package/clis/rest-countries/country.js +65 -0
- package/clis/rest-countries/region.js +64 -0
- package/clis/rest-countries/rest-countries.test.js +83 -0
- package/clis/rest-countries/utils.js +126 -0
- package/clis/reuters/article-detail.js +53 -0
- package/clis/reuters/reuters.test.js +299 -0
- package/clis/reuters/search.js +45 -34
- package/clis/reuters/utils.js +159 -0
- package/clis/rfc/rfc.js +52 -0
- package/clis/rfc/rfc.test.js +74 -0
- package/clis/rfc/utils.js +72 -0
- package/clis/rubygems/gem.js +42 -0
- package/clis/rubygems/search.js +47 -0
- package/clis/rubygems/utils.js +86 -0
- package/clis/stackoverflow/related.js +66 -0
- package/clis/stackoverflow/stackoverflow.test.js +58 -0
- package/clis/stackoverflow/tag.js +60 -0
- package/clis/stackoverflow/user.js +50 -0
- package/clis/stackoverflow/utils.js +118 -0
- package/clis/steam/app.js +67 -0
- package/clis/steam/search.js +58 -0
- package/clis/steam/steam.test.js +46 -0
- package/clis/steam/utils.js +107 -0
- package/clis/taobao/commands.test.js +1 -24
- package/clis/test-utils.js +61 -0
- package/clis/tieba/hot.js +0 -1
- package/clis/tiktok/comment.js +128 -41
- package/clis/tiktok/creator-videos.js +270 -0
- package/clis/tiktok/creator-videos.test.js +113 -0
- package/clis/tiktok/explore.js +137 -29
- package/clis/tiktok/follow.js +115 -33
- package/clis/tiktok/following.js +157 -36
- package/clis/tiktok/friends.js +139 -37
- package/clis/tiktok/live.js +137 -41
- package/clis/tiktok/notifications.js +141 -38
- package/clis/tiktok/refactor.test.js +389 -0
- package/clis/tiktok/unfollow.js +124 -38
- package/clis/tiktok/user.js +203 -29
- package/clis/tiktok/utils.js +505 -0
- package/clis/tiktok/write-refactor.test.js +370 -0
- package/clis/toutiao/articles.js +36 -62
- package/clis/toutiao/hot.js +63 -0
- package/clis/toutiao/toutiao.test.js +378 -0
- package/clis/toutiao/utils.js +161 -0
- package/clis/tvmaze/search.js +61 -0
- package/clis/tvmaze/show.js +60 -0
- package/clis/tvmaze/tvmaze.test.js +93 -0
- package/clis/tvmaze/utils.js +110 -0
- package/clis/twitter/accept.js +1 -1
- package/clis/twitter/followers.js +134 -69
- package/clis/twitter/reply-dm.js +1 -1
- package/clis/twitter/reply.test.js +1 -29
- package/clis/uisdc/news.js +105 -0
- package/clis/uisdc/news.test.js +66 -0
- package/clis/wanfang/search.js +0 -1
- package/clis/web/read.js +47 -17
- package/clis/web/read.test.js +101 -1
- package/clis/weixin/create-draft.js +1 -1
- package/clis/weixin/drafts.js +1 -1
- package/clis/weixin/drafts.test.js +5 -1
- package/clis/weixin/search.js +157 -0
- package/clis/weixin/search.test.js +227 -0
- package/clis/wikidata/entity.js +60 -0
- package/clis/wikidata/search.js +50 -0
- package/clis/wikidata/utils.js +117 -0
- package/clis/wikidata/wikidata.test.js +83 -0
- package/clis/wikipedia/page.js +95 -0
- package/clis/wttr/current.js +63 -0
- package/clis/wttr/forecast.js +71 -0
- package/clis/wttr/utils.js +50 -0
- package/clis/wttr/wttr.test.js +84 -0
- package/clis/xianyu/chat.js +16 -4
- package/clis/xianyu/chat.test.js +64 -0
- package/clis/xianyu/publish.js +485 -0
- package/clis/xianyu/publish.test.js +220 -0
- package/clis/xiaoe/catalog.js +105 -40
- package/clis/xiaoe/content.js +164 -29
- package/clis/xiaoe/courses.js +86 -29
- package/clis/xiaoe/xiaoe.test.js +486 -0
- package/clis/xiaohongshu/creator-notes-summary.js +1 -1
- package/clis/xiaohongshu/publish.js +16 -3
- package/clis/xiaohongshu/publish.test.js +46 -1
- package/clis/youtube/transcript.js +13 -19
- package/clis/youtube/transcript.test.js +17 -0
- package/clis/yuanbao/ask.js +17 -66
- package/clis/yuanbao/ask.test.js +5 -5
- package/clis/yuanbao/detail.js +65 -0
- package/clis/yuanbao/history.js +51 -0
- package/clis/yuanbao/new.js +1 -0
- package/clis/yuanbao/read.js +38 -0
- package/clis/yuanbao/send.js +57 -0
- package/clis/yuanbao/shared.js +297 -5
- package/clis/yuanbao/shared.test.js +80 -0
- package/clis/yuanbao/status.js +44 -0
- package/clis/zlibrary/commands.test.js +1 -11
- package/dist/src/browser/base-page.d.ts +9 -0
- package/dist/src/browser/base-page.js +44 -1
- package/dist/src/browser/base-page.test.js +66 -0
- package/dist/src/browser/cdp.d.ts +1 -0
- package/dist/src/browser/cdp.js +51 -9
- package/dist/src/browser/daemon-client.d.ts +4 -0
- package/dist/src/browser/errors.js +1 -1
- package/dist/src/browser/page.d.ts +1 -1
- package/dist/src/browser/page.js +3 -1
- package/dist/src/browser/page.test.js +29 -0
- package/dist/src/browser/target-errors.d.ts +2 -1
- package/dist/src/browser/target-errors.js +1 -0
- package/dist/src/browser/target-resolver.d.ts +25 -0
- package/dist/src/browser/target-resolver.js +43 -0
- package/dist/src/build-manifest.js +9 -4
- package/dist/src/build-manifest.test.js +2 -8
- package/dist/src/capabilityRouting.d.ts +16 -1
- package/dist/src/capabilityRouting.js +24 -1
- package/dist/src/capabilityRouting.test.js +19 -1
- package/dist/src/cli.js +76 -11
- package/dist/src/cli.test.js +150 -0
- package/dist/src/commanderAdapter.js +0 -5
- package/dist/src/commanderAdapter.test.js +0 -1
- package/dist/src/discovery.js +2 -5
- package/dist/src/errors.js +1 -1
- package/dist/src/execution.d.ts +1 -1
- package/dist/src/execution.js +111 -27
- package/dist/src/execution.test.js +326 -17
- package/dist/src/help.d.ts +23 -2
- package/dist/src/help.js +41 -19
- package/dist/src/help.test.d.ts +1 -0
- package/dist/src/help.test.js +54 -0
- package/dist/src/main.js +14 -1
- package/dist/src/manifest-types.d.ts +5 -3
- package/dist/src/pipeline/executor.js +1 -1
- package/dist/src/pipeline/executor.test.js +8 -0
- package/dist/src/pipeline/registry.d.ts +9 -0
- package/dist/src/pipeline/registry.js +13 -1
- package/dist/src/pipeline/steps/browser.d.ts +1 -0
- package/dist/src/pipeline/steps/browser.js +10 -0
- package/dist/src/pipeline/steps/download.test.js +1 -0
- package/dist/src/registry-api.d.ts +1 -1
- package/dist/src/registry.d.ts +12 -11
- package/dist/src/registry.js +16 -6
- package/dist/src/registry.test.js +2 -2
- package/dist/src/runtime.d.ts +2 -1
- package/dist/src/runtime.js +1 -1
- package/dist/src/serialization.d.ts +2 -2
- package/dist/src/serialization.js +4 -6
- package/dist/src/serialization.test.js +17 -0
- package/dist/src/types.d.ts +17 -0
- package/dist/src/validate.js +15 -11
- package/dist/src/validate.test.d.ts +9 -0
- package/dist/src/validate.test.js +90 -0
- package/package.json +1 -1
- package/scripts/fetch-adapters.js +1 -1
- package/scripts/typed-error-lint-baseline.json +5 -77
- package/clis/ctrip/search.test.js +0 -64
- package/clis/gov-policy/commands.test.js +0 -27
- package/clis/linux-do/category.js +0 -37
- package/clis/linux-do/hot.js +0 -26
- package/clis/linux-do/latest.js +0 -19
- package/clis/pixiv/test-utils.js +0 -23
- package/clis/toutiao/articles.test.js +0 -30
- package/dist/src/analysis.d.ts +0 -40
- package/dist/src/analysis.js +0 -172
package/dist/src/cli.test.js
CHANGED
|
@@ -96,6 +96,90 @@ describe('createProgram root help descriptions', () => {
|
|
|
96
96
|
registry.set(key, value);
|
|
97
97
|
}
|
|
98
98
|
});
|
|
99
|
+
it('groups adapters into App / Site buckets by domain field', () => {
|
|
100
|
+
const registry = getRegistry();
|
|
101
|
+
const snapshot = new Map(registry);
|
|
102
|
+
registry.clear();
|
|
103
|
+
try {
|
|
104
|
+
cli({
|
|
105
|
+
site: 'bilibili',
|
|
106
|
+
name: 'hot',
|
|
107
|
+
access: 'read',
|
|
108
|
+
description: 'Bilibili hot videos',
|
|
109
|
+
domain: 'www.bilibili.com',
|
|
110
|
+
strategy: Strategy.PUBLIC,
|
|
111
|
+
browser: false,
|
|
112
|
+
});
|
|
113
|
+
cli({
|
|
114
|
+
site: 'chatwise',
|
|
115
|
+
name: 'ask',
|
|
116
|
+
access: 'write',
|
|
117
|
+
description: 'Ask Chatwise desktop app',
|
|
118
|
+
domain: 'localhost',
|
|
119
|
+
strategy: Strategy.UI,
|
|
120
|
+
browser: true,
|
|
121
|
+
});
|
|
122
|
+
const program = createProgram('', '');
|
|
123
|
+
const help = program.helpInformation();
|
|
124
|
+
// Two separate sections, each with own count
|
|
125
|
+
expect(help).toContain('App adapters (1):');
|
|
126
|
+
expect(help).toMatch(/App adapters \(1\):\n {2}chatwise/);
|
|
127
|
+
expect(help).toContain('Site adapters (1):');
|
|
128
|
+
expect(help).toMatch(/Site adapters \(1\):\n {2}bilibili/);
|
|
129
|
+
// App adapters appear before Site adapters (External CLIs are absent here)
|
|
130
|
+
expect(help.indexOf('App adapters')).toBeLessThan(help.indexOf('Site adapters'));
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
registry.clear();
|
|
134
|
+
for (const [key, value] of snapshot)
|
|
135
|
+
registry.set(key, value);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
it('exposes external_clis / app_adapters / site_adapters in structured help', () => {
|
|
139
|
+
const registry = getRegistry();
|
|
140
|
+
const snapshot = new Map(registry);
|
|
141
|
+
const argv = process.argv;
|
|
142
|
+
registry.clear();
|
|
143
|
+
try {
|
|
144
|
+
cli({
|
|
145
|
+
site: 'bilibili',
|
|
146
|
+
name: 'hot',
|
|
147
|
+
access: 'read',
|
|
148
|
+
description: 'Bilibili hot videos',
|
|
149
|
+
domain: 'www.bilibili.com',
|
|
150
|
+
strategy: Strategy.PUBLIC,
|
|
151
|
+
browser: false,
|
|
152
|
+
});
|
|
153
|
+
cli({
|
|
154
|
+
site: 'chatwise',
|
|
155
|
+
name: 'ask',
|
|
156
|
+
access: 'write',
|
|
157
|
+
description: 'Ask Chatwise desktop app',
|
|
158
|
+
domain: 'localhost',
|
|
159
|
+
strategy: Strategy.UI,
|
|
160
|
+
browser: true,
|
|
161
|
+
});
|
|
162
|
+
const program = createProgram('', '');
|
|
163
|
+
process.argv = ['node', 'opencli', '--help', '-f', 'yaml'];
|
|
164
|
+
const data = yaml.load(program.helpInformation());
|
|
165
|
+
expect(data.app_adapters.count).toBe(1);
|
|
166
|
+
expect(data.app_adapters.apps).toEqual(['chatwise']);
|
|
167
|
+
expect(data.site_adapters.count).toBe(1);
|
|
168
|
+
expect(data.site_adapters.sites).toEqual(['bilibili']);
|
|
169
|
+
expect(data.external_clis.count).toBeGreaterThanOrEqual(0);
|
|
170
|
+
expect(Array.isArray(data.external_clis.clis)).toBe(true);
|
|
171
|
+
// Adapters must NOT leak into the core commands list
|
|
172
|
+
const commandNames = data.commands.map((cmd) => cmd.name);
|
|
173
|
+
expect(commandNames).not.toContain('bilibili');
|
|
174
|
+
expect(commandNames).not.toContain('chatwise');
|
|
175
|
+
}
|
|
176
|
+
finally {
|
|
177
|
+
process.argv = argv;
|
|
178
|
+
registry.clear();
|
|
179
|
+
for (const [key, value] of snapshot)
|
|
180
|
+
registry.set(key, value);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
99
183
|
it('renders root structured help with built-ins and site adapter names', () => {
|
|
100
184
|
const registry = getRegistry();
|
|
101
185
|
const snapshot = new Map(registry);
|
|
@@ -1624,6 +1708,16 @@ describe('browser click/type commands', () => {
|
|
|
1624
1708
|
evaluate: vi.fn().mockResolvedValue(false),
|
|
1625
1709
|
click: vi.fn().mockResolvedValue({ matches_n: 1, match_level: 'exact' }),
|
|
1626
1710
|
typeText: vi.fn().mockResolvedValue({ matches_n: 1, match_level: 'exact' }),
|
|
1711
|
+
fillText: vi.fn().mockResolvedValue({
|
|
1712
|
+
filled: true,
|
|
1713
|
+
verified: true,
|
|
1714
|
+
expected: '',
|
|
1715
|
+
actual: '',
|
|
1716
|
+
length: 0,
|
|
1717
|
+
matches_n: 1,
|
|
1718
|
+
match_level: 'exact',
|
|
1719
|
+
mode: 'input',
|
|
1720
|
+
}),
|
|
1627
1721
|
wait: vi.fn().mockResolvedValue(undefined),
|
|
1628
1722
|
}));
|
|
1629
1723
|
it('emits {clicked, target, matches_n, match_level} on success', async () => {
|
|
@@ -1721,6 +1815,62 @@ describe('browser click/type commands', () => {
|
|
|
1721
1815
|
expect(browserState.page.click).toHaveBeenCalledWith('.field', { nth: 3 });
|
|
1722
1816
|
expect(browserState.page.typeText).toHaveBeenCalledWith('.field', 'x', { nth: 3 });
|
|
1723
1817
|
});
|
|
1818
|
+
it('fill: delegates exact raw text to page.fillText and emits verification details', async () => {
|
|
1819
|
+
browserState.page.fillText.mockResolvedValueOnce({
|
|
1820
|
+
filled: true,
|
|
1821
|
+
verified: true,
|
|
1822
|
+
expected: 'line1\\n/ / raw',
|
|
1823
|
+
actual: 'line1\\n/ / raw',
|
|
1824
|
+
length: 14,
|
|
1825
|
+
matches_n: 1,
|
|
1826
|
+
match_level: 'exact',
|
|
1827
|
+
mode: 'textarea',
|
|
1828
|
+
});
|
|
1829
|
+
const program = createProgram('', '');
|
|
1830
|
+
await program.parseAsync(['node', 'opencli', 'browser', 'fill', '#msg', 'line1\\n/ / raw']);
|
|
1831
|
+
expect(browserState.page.fillText).toHaveBeenCalledWith('#msg', 'line1\\n/ / raw', {});
|
|
1832
|
+
expect(lastJsonLog()).toEqual({
|
|
1833
|
+
filled: true,
|
|
1834
|
+
verified: true,
|
|
1835
|
+
target: '#msg',
|
|
1836
|
+
text: 'line1\\n/ / raw',
|
|
1837
|
+
actual: 'line1\\n/ / raw',
|
|
1838
|
+
length: 14,
|
|
1839
|
+
matches_n: 1,
|
|
1840
|
+
match_level: 'exact',
|
|
1841
|
+
mode: 'textarea',
|
|
1842
|
+
});
|
|
1843
|
+
expect(process.exitCode).toBeUndefined();
|
|
1844
|
+
});
|
|
1845
|
+
it('fill: sets a non-zero exit code when verification fails', async () => {
|
|
1846
|
+
browserState.page.fillText.mockResolvedValueOnce({
|
|
1847
|
+
filled: true,
|
|
1848
|
+
verified: false,
|
|
1849
|
+
expected: 'expected',
|
|
1850
|
+
actual: 'actual',
|
|
1851
|
+
length: 6,
|
|
1852
|
+
matches_n: 1,
|
|
1853
|
+
match_level: 'exact',
|
|
1854
|
+
});
|
|
1855
|
+
const program = createProgram('', '');
|
|
1856
|
+
await program.parseAsync(['node', 'opencli', 'browser', 'fill', '#msg', 'expected']);
|
|
1857
|
+
expect(lastJsonLog()).toEqual({
|
|
1858
|
+
filled: true,
|
|
1859
|
+
verified: false,
|
|
1860
|
+
target: '#msg',
|
|
1861
|
+
text: 'expected',
|
|
1862
|
+
actual: 'actual',
|
|
1863
|
+
length: 6,
|
|
1864
|
+
matches_n: 1,
|
|
1865
|
+
match_level: 'exact',
|
|
1866
|
+
});
|
|
1867
|
+
expect(process.exitCode).toBeDefined();
|
|
1868
|
+
});
|
|
1869
|
+
it('fill: forwards --nth to page.fillText', async () => {
|
|
1870
|
+
const program = createProgram('', '');
|
|
1871
|
+
await program.parseAsync(['node', 'opencli', 'browser', 'fill', '.field', 'x', '--nth', '2']);
|
|
1872
|
+
expect(browserState.page.fillText).toHaveBeenCalledWith('.field', 'x', { nth: 2 });
|
|
1873
|
+
});
|
|
1724
1874
|
});
|
|
1725
1875
|
describe('browser select command', () => {
|
|
1726
1876
|
const { lastJsonLog } = installSelectorFirstTestHarness('select', () => ({
|
|
@@ -89,11 +89,6 @@ export function registerCommandToProgram(siteCmd, cmd) {
|
|
|
89
89
|
const formatExplicit = subCmd.getOptionValueSource('format') === 'cli';
|
|
90
90
|
if (verbose)
|
|
91
91
|
process.env.OPENCLI_VERBOSE = '1';
|
|
92
|
-
if (cmd.deprecated) {
|
|
93
|
-
const message = typeof cmd.deprecated === 'string' ? cmd.deprecated : `${fullName(cmd)} is deprecated.`;
|
|
94
|
-
const replacement = cmd.replacedBy ? ` Use ${cmd.replacedBy} instead.` : '';
|
|
95
|
-
log.warn(`Deprecated: ${message}${replacement}`);
|
|
96
|
-
}
|
|
97
92
|
const globals = typeof subCmd.optsWithGlobals === 'function' ? subCmd.optsWithGlobals() : {};
|
|
98
93
|
const result = await executeCommand(cmd, kwargs, verbose, {
|
|
99
94
|
prepared: true,
|
|
@@ -279,7 +279,6 @@ describe('commanderAdapter error envelope output', () => {
|
|
|
279
279
|
expect(output).toContain('xsec_token');
|
|
280
280
|
expect(output).toContain('--trace=retain-on-failure');
|
|
281
281
|
expect(output).toContain('opencli xiaohongshu note --trace retain-on-failure');
|
|
282
|
-
expect(output).not.toContain('OPENCLI_DIAGNOSTIC');
|
|
283
282
|
stderrSpy.mockRestore();
|
|
284
283
|
});
|
|
285
284
|
it('outputs YAML error envelope for selector errors', async () => {
|
package/dist/src/discovery.js
CHANGED
|
@@ -131,12 +131,11 @@ async function loadFromManifest(manifestPath, clisDir) {
|
|
|
131
131
|
browser: entry.browser,
|
|
132
132
|
args: entry.args ?? [],
|
|
133
133
|
columns: entry.columns,
|
|
134
|
+
defaultFormat: entry.defaultFormat,
|
|
134
135
|
pipeline: entry.pipeline,
|
|
135
|
-
timeoutSeconds: entry.timeout,
|
|
136
136
|
source: entry.sourceFile ? path.resolve(clisDir, entry.sourceFile) : modulePath,
|
|
137
|
-
deprecated: entry.deprecated,
|
|
138
|
-
replacedBy: entry.replacedBy,
|
|
139
137
|
navigateBefore: entry.navigateBefore,
|
|
138
|
+
browserSession: entry.browserSession,
|
|
140
139
|
_lazy: true,
|
|
141
140
|
_modulePath: modulePath,
|
|
142
141
|
};
|
|
@@ -170,7 +169,6 @@ async function discoverClisFromFs(dir) {
|
|
|
170
169
|
await Promise.all(files.map(async (file) => {
|
|
171
170
|
const filePath = path.join(siteDir, file);
|
|
172
171
|
if (file.endsWith('.yaml') || file.endsWith('.yml')) {
|
|
173
|
-
log.warn(`Ignoring YAML adapter ${filePath} — YAML format is no longer supported. Convert to JavaScript using cli() from '@jackwener/opencli/registry'.`);
|
|
174
172
|
return;
|
|
175
173
|
}
|
|
176
174
|
if (file.endsWith('.ts') && !file.endsWith('.d.ts') && !file.endsWith('.test.ts')) {
|
|
@@ -218,7 +216,6 @@ async function discoverPluginDir(dir, site) {
|
|
|
218
216
|
await Promise.all(files.map(async (file) => {
|
|
219
217
|
const filePath = path.join(dir, file);
|
|
220
218
|
if (file.endsWith('.yaml') || file.endsWith('.yml')) {
|
|
221
|
-
log.warn(`Ignoring YAML plugin ${filePath} — YAML format is no longer supported. Convert to JavaScript using cli() from '@jackwener/opencli/registry'.`);
|
|
222
219
|
return;
|
|
223
220
|
}
|
|
224
221
|
if (file.endsWith('.js') && !file.endsWith('.d.js')) {
|
package/dist/src/errors.js
CHANGED
|
@@ -73,7 +73,7 @@ export class AuthRequiredError extends CliError {
|
|
|
73
73
|
}
|
|
74
74
|
export class TimeoutError extends CliError {
|
|
75
75
|
constructor(label, seconds, hint) {
|
|
76
|
-
super('TIMEOUT', `${label} timed out after ${seconds}s`, hint ?? 'Try again, or increase timeout with OPENCLI_BROWSER_COMMAND_TIMEOUT
|
|
76
|
+
super('TIMEOUT', `${label} timed out after ${seconds}s`, hint ?? 'Try again, or increase timeout with --timeout <seconds> (or OPENCLI_BROWSER_COMMAND_TIMEOUT for the global default)', EXIT_CODES.TEMPFAIL);
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
export class ArgumentError extends CliError {
|
package/dist/src/execution.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* This is the single entry point for executing any CLI command. It handles:
|
|
5
5
|
* 1. Argument validation and coercion
|
|
6
6
|
* 2. Browser session lifecycle (if needed)
|
|
7
|
-
* 3. Domain pre-navigation for cookie
|
|
7
|
+
* 3. Domain pre-navigation for cookie strategies
|
|
8
8
|
* 4. Timeout enforcement
|
|
9
9
|
* 5. Lazy-loading of TS modules from manifest
|
|
10
10
|
* 6. Lifecycle hooks (onBeforeExecute / onAfterExecute)
|
package/dist/src/execution.js
CHANGED
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
* This is the single entry point for executing any CLI command. It handles:
|
|
5
5
|
* 1. Argument validation and coercion
|
|
6
6
|
* 2. Browser session lifecycle (if needed)
|
|
7
|
-
* 3. Domain pre-navigation for cookie
|
|
7
|
+
* 3. Domain pre-navigation for cookie strategies
|
|
8
8
|
* 4. Timeout enforcement
|
|
9
9
|
* 5. Lazy-loading of TS modules from manifest
|
|
10
10
|
* 6. Lifecycle hooks (onBeforeExecute / onAfterExecute)
|
|
11
11
|
*/
|
|
12
12
|
import { getRegistry, fullName, } from './registry.js';
|
|
13
13
|
import { pathToFileURL } from 'node:url';
|
|
14
|
+
import * as crypto from 'node:crypto';
|
|
14
15
|
import * as fs from 'node:fs';
|
|
15
16
|
import * as os from 'node:os';
|
|
16
17
|
import { executePipeline } from './pipeline/index.js';
|
|
@@ -28,6 +29,7 @@ const _loadedModules = new Map();
|
|
|
28
29
|
/** Track mtime of loaded user adapter files for hot-reload in daemon mode. */
|
|
29
30
|
const _moduleMtimes = new Map();
|
|
30
31
|
const _userClisDir = `${os.homedir()}/.opencli/clis/`;
|
|
32
|
+
const INTERACTIVE_BROWSER_IDLE_TIMEOUT_SECONDS = 600;
|
|
31
33
|
function normalizeTraceMode(raw) {
|
|
32
34
|
if (raw === undefined || raw === null || raw === '' || raw === 'off')
|
|
33
35
|
return 'off';
|
|
@@ -138,14 +140,37 @@ function resolvePreNav(cmd) {
|
|
|
138
140
|
// strategy → navigateBefore expansion already happened in normalizeCommand().
|
|
139
141
|
return null;
|
|
140
142
|
}
|
|
141
|
-
function
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
143
|
+
function urlMatchesDomain(url, domain) {
|
|
144
|
+
if (!url || !domain)
|
|
145
|
+
return false;
|
|
146
|
+
try {
|
|
147
|
+
const hostname = new URL(url).hostname;
|
|
148
|
+
return hostname === domain || hostname.endsWith(`.${domain}`);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function isDomainRootPreNav(preNavUrl, domain) {
|
|
155
|
+
if (!domain)
|
|
156
|
+
return false;
|
|
157
|
+
try {
|
|
158
|
+
const parsed = new URL(preNavUrl);
|
|
159
|
+
const hostnameMatches = parsed.hostname === domain || parsed.hostname.endsWith(`.${domain}`);
|
|
160
|
+
const rootPath = parsed.pathname === '' || parsed.pathname === '/';
|
|
161
|
+
return hostnameMatches && rootPath && parsed.search === '' && parsed.hash === '';
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async function shouldRunPreNav(cmd, page, reuse, preNavUrl) {
|
|
168
|
+
if (reuse !== 'site' || !cmd.domain)
|
|
169
|
+
return true;
|
|
170
|
+
if (!isDomainRootPreNav(preNavUrl, cmd.domain))
|
|
171
|
+
return true;
|
|
172
|
+
const currentUrl = await page.getCurrentUrl?.().catch(() => null);
|
|
173
|
+
return !urlMatchesDomain(currentUrl, cmd.domain);
|
|
149
174
|
}
|
|
150
175
|
export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
|
|
151
176
|
let kwargs;
|
|
@@ -157,6 +182,7 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
|
|
|
157
182
|
throw err;
|
|
158
183
|
throw new ArgumentError(getErrorMessage(err));
|
|
159
184
|
}
|
|
185
|
+
const userTimeoutSec = readUserTimeoutSeconds(cmd, kwargs);
|
|
160
186
|
const traceMode = normalizeTraceMode(opts.trace);
|
|
161
187
|
const hookCtx = {
|
|
162
188
|
command: fullName(cmd),
|
|
@@ -183,17 +209,19 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
|
|
|
183
209
|
cdpEndpoint = await resolveElectronEndpoint(cmd.site);
|
|
184
210
|
}
|
|
185
211
|
}
|
|
186
|
-
ensureRequiredEnv(cmd);
|
|
187
212
|
const BrowserFactory = getBrowserFactory(cmd.site);
|
|
188
213
|
const contextId = resolveProfileContextId(opts.profile);
|
|
189
214
|
const internal = cmd;
|
|
215
|
+
const browserReuse = resolveBrowserSessionReuse(cmd);
|
|
216
|
+
const workspace = resolveBrowserWorkspace(cmd, browserReuse);
|
|
217
|
+
const idleTimeout = browserReuse === 'site' ? INTERACTIVE_BROWSER_IDLE_TIMEOUT_SECONDS : undefined;
|
|
190
218
|
result = await browserSession(BrowserFactory, async (page) => {
|
|
191
219
|
const observation = traceMode === 'off'
|
|
192
220
|
? null
|
|
193
221
|
: new ObservationSession({
|
|
194
222
|
scope: {
|
|
195
223
|
contextId,
|
|
196
|
-
workspace
|
|
224
|
+
workspace,
|
|
197
225
|
target: page.getActivePage?.(),
|
|
198
226
|
site: cmd.site,
|
|
199
227
|
command: fullName(cmd),
|
|
@@ -210,7 +238,7 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
|
|
|
210
238
|
await page.startNetworkCapture?.().catch(() => false);
|
|
211
239
|
}
|
|
212
240
|
const preNavUrl = resolvePreNav(cmd);
|
|
213
|
-
if (preNavUrl) {
|
|
241
|
+
if (preNavUrl && await shouldRunPreNav(cmd, page, browserReuse, preNavUrl)) {
|
|
214
242
|
observation?.record({
|
|
215
243
|
stream: 'action',
|
|
216
244
|
name: 'pre_navigate',
|
|
@@ -253,12 +281,15 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
|
|
|
253
281
|
throw wrapped;
|
|
254
282
|
}
|
|
255
283
|
}
|
|
256
|
-
// --live / OPENCLI_LIVE=1 keeps the automation
|
|
257
|
-
// command finishes, so agents (or humans) can inspect the page state.
|
|
258
|
-
const keepOpen = process.env.OPENCLI_LIVE === '1' || process.env.OPENCLI_LIVE === 'true';
|
|
284
|
+
// --live / OPENCLI_LIVE=1 keeps the current automation tab lease after
|
|
285
|
+
// the command finishes, so agents (or humans) can inspect the page state.
|
|
286
|
+
const keepOpen = browserReuse !== 'none' || process.env.OPENCLI_LIVE === '1' || process.env.OPENCLI_LIVE === 'true';
|
|
259
287
|
try {
|
|
288
|
+
const browserTimeout = userTimeoutSec !== null
|
|
289
|
+
? userTimeoutSec + RUNTIME_TIMEOUT_PADDING_SECONDS
|
|
290
|
+
: DEFAULT_BROWSER_COMMAND_TIMEOUT;
|
|
260
291
|
const result = await runWithTimeout(runCommand(cmd, page, kwargs, debug), {
|
|
261
|
-
timeout:
|
|
292
|
+
timeout: browserTimeout,
|
|
262
293
|
label: fullName(cmd),
|
|
263
294
|
});
|
|
264
295
|
observation?.record({
|
|
@@ -270,8 +301,9 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
|
|
|
270
301
|
await collectObservationEvidence(observation, page).catch(() => { });
|
|
271
302
|
exportTraceArtifact(observation, 'success', undefined, opts.onTraceExport);
|
|
272
303
|
}
|
|
273
|
-
// Adapter commands are one-shot —
|
|
274
|
-
// instead of waiting for the 30s idle timeout.
|
|
304
|
+
// Adapter commands are one-shot — release the current tab lease immediately
|
|
305
|
+
// instead of waiting for the 30s idle timeout. The automation container
|
|
306
|
+
// window stays open for reuse.
|
|
275
307
|
if (!keepOpen)
|
|
276
308
|
await page.closeWindow?.().catch(() => { });
|
|
277
309
|
return result;
|
|
@@ -294,23 +326,26 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
|
|
|
294
326
|
exportTraceArtifact(observation, 'failure', err, opts.onTraceExport);
|
|
295
327
|
}
|
|
296
328
|
}
|
|
297
|
-
//
|
|
298
|
-
//
|
|
299
|
-
//
|
|
329
|
+
// Release the tab lease on failure too — without this, the lease lingers
|
|
330
|
+
// until the extension's idle timer fires (unreliable on Windows where
|
|
331
|
+
// MV3 service workers may be suspended before setTimeout triggers).
|
|
300
332
|
if (!keepOpen)
|
|
301
333
|
await page.closeWindow?.().catch(() => { });
|
|
302
334
|
throw err;
|
|
303
335
|
}
|
|
304
|
-
}, { workspace
|
|
336
|
+
}, { workspace, cdpEndpoint, contextId, idleTimeout });
|
|
305
337
|
}
|
|
306
338
|
else {
|
|
307
|
-
// Non-browser commands:
|
|
308
|
-
|
|
309
|
-
|
|
339
|
+
// Non-browser commands: enforce a timeout only when the command exposes
|
|
340
|
+
// a `--timeout` arg (and the resolved value is positive). Without that
|
|
341
|
+
// arg there is no meaningful default — non-browser cmds are diverse
|
|
342
|
+
// enough that a hard cap would do more harm than good.
|
|
343
|
+
if (userTimeoutSec !== null) {
|
|
344
|
+
const ceiling = userTimeoutSec + RUNTIME_TIMEOUT_PADDING_SECONDS;
|
|
310
345
|
result = await runWithTimeout(runCommand(cmd, null, kwargs, debug), {
|
|
311
|
-
timeout,
|
|
346
|
+
timeout: ceiling,
|
|
312
347
|
label: fullName(cmd),
|
|
313
|
-
hint: `
|
|
348
|
+
hint: `Pass a higher --timeout value (currently ${userTimeoutSec}s)`,
|
|
314
349
|
});
|
|
315
350
|
}
|
|
316
351
|
else {
|
|
@@ -401,3 +436,52 @@ export function prepareCommandArgs(cmd, rawKwargs) {
|
|
|
401
436
|
cmd.validateArgs?.(kwargs);
|
|
402
437
|
return kwargs;
|
|
403
438
|
}
|
|
439
|
+
/**
|
|
440
|
+
* Runtime ceiling padding (seconds) added on top of the user's `--timeout`.
|
|
441
|
+
* The adapter's polling loop typically uses the full user value; the padding
|
|
442
|
+
* gives us room for the adapter to return + closeWindow + trace export before
|
|
443
|
+
* the runtime kills the Promise.
|
|
444
|
+
*/
|
|
445
|
+
const RUNTIME_TIMEOUT_PADDING_SECONDS = 30;
|
|
446
|
+
function readEnvBrowserSessionReuse() {
|
|
447
|
+
const raw = process.env.OPENCLI_BROWSER_REUSE;
|
|
448
|
+
if (raw === undefined || raw === '')
|
|
449
|
+
return null;
|
|
450
|
+
if (raw === 'none' || raw === 'site')
|
|
451
|
+
return raw;
|
|
452
|
+
throw new ArgumentError(`--reuse must be one of: none, site. Received: "${raw}"`);
|
|
453
|
+
}
|
|
454
|
+
function resolveBrowserSessionReuse(cmd) {
|
|
455
|
+
return readEnvBrowserSessionReuse() ?? cmd.browserSession?.reuse ?? 'none';
|
|
456
|
+
}
|
|
457
|
+
function resolveBrowserWorkspace(cmd, reuse) {
|
|
458
|
+
if (reuse === 'site')
|
|
459
|
+
return `site:${cmd.site}`;
|
|
460
|
+
return `site:${cmd.site}:${crypto.randomUUID()}`;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Resolve the user-controllable `--timeout` arg, in seconds.
|
|
464
|
+
*
|
|
465
|
+
* Convention: a command opts into runtime-enforced timeouts by declaring an
|
|
466
|
+
* arg named `timeout`. The arg's `default` flows through `prepareCommandArgs`
|
|
467
|
+
* into `kwargs.timeout`, so by the time runtime enforcement runs, the value
|
|
468
|
+
* is the merged user-supplied-or-default seconds.
|
|
469
|
+
*
|
|
470
|
+
* Returns the parsed positive integer (seconds), or null if the command does
|
|
471
|
+
* not expose a `timeout` arg. Declaring `timeout` opts into runtime timeout
|
|
472
|
+
* enforcement, so invalid values must fail upfront instead of silently
|
|
473
|
+
* disabling the runtime ceiling.
|
|
474
|
+
*/
|
|
475
|
+
function readUserTimeoutSeconds(cmd, kwargs) {
|
|
476
|
+
if (!cmd.args.some(a => a.name === 'timeout'))
|
|
477
|
+
return null;
|
|
478
|
+
const raw = kwargs.timeout;
|
|
479
|
+
if (raw === undefined || raw === null || raw === '') {
|
|
480
|
+
throw new ArgumentError(`Argument "timeout" must be a positive integer. Received: "${String(raw)}"`);
|
|
481
|
+
}
|
|
482
|
+
const parsed = Number(raw);
|
|
483
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
484
|
+
throw new ArgumentError(`Argument "timeout" must be a positive integer. Received: "${String(raw)}"`);
|
|
485
|
+
}
|
|
486
|
+
return parsed;
|
|
487
|
+
}
|