@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/browser/cdp.js
CHANGED
|
@@ -202,16 +202,58 @@ class CDPPage extends BasePage {
|
|
|
202
202
|
: cookies;
|
|
203
203
|
}
|
|
204
204
|
async screenshot(options = {}) {
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
205
|
+
const fullPage = options.fullPage === true;
|
|
206
|
+
const overrideWidth = options.width && options.width > 0 ? Math.ceil(options.width) : undefined;
|
|
207
|
+
// height is ignored under fullPage so the captureBeyondViewport path stays unchanged for users who pass --height alongside --full-page.
|
|
208
|
+
const overrideHeight = !fullPage && options.height && options.height > 0 ? Math.ceil(options.height) : undefined;
|
|
209
|
+
const needsOverride = overrideWidth !== undefined || overrideHeight !== undefined;
|
|
210
|
+
if (needsOverride) {
|
|
211
|
+
if (overrideWidth !== undefined && fullPage) {
|
|
212
|
+
await this.bridge.send('Emulation.setDeviceMetricsOverride', {
|
|
213
|
+
mobile: false,
|
|
214
|
+
width: overrideWidth,
|
|
215
|
+
height: 0,
|
|
216
|
+
deviceScaleFactor: 1,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
let finalWidth = overrideWidth ?? 0;
|
|
220
|
+
let finalHeight = overrideHeight ?? 0;
|
|
221
|
+
if (fullPage) {
|
|
222
|
+
const metrics = await this.bridge.send('Page.getLayoutMetrics');
|
|
223
|
+
const m = isRecord(metrics) ? metrics : {};
|
|
224
|
+
const css = isRecord(m.cssContentSize) ? m.cssContentSize : undefined;
|
|
225
|
+
const fb = isRecord(m.contentSize) ? m.contentSize : undefined;
|
|
226
|
+
const size = css ?? fb;
|
|
227
|
+
if (size && typeof size.width === 'number' && typeof size.height === 'number') {
|
|
228
|
+
if (finalWidth === 0)
|
|
229
|
+
finalWidth = Math.ceil(size.width);
|
|
230
|
+
finalHeight = Math.ceil(size.height);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
await this.bridge.send('Emulation.setDeviceMetricsOverride', {
|
|
234
|
+
mobile: false,
|
|
235
|
+
width: finalWidth,
|
|
236
|
+
height: finalHeight,
|
|
237
|
+
deviceScaleFactor: 1,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
const result = await this.bridge.send('Page.captureScreenshot', {
|
|
242
|
+
format: options.format ?? 'png',
|
|
243
|
+
quality: options.format === 'jpeg' ? (options.quality ?? 80) : undefined,
|
|
244
|
+
captureBeyondViewport: !needsOverride && fullPage,
|
|
245
|
+
});
|
|
246
|
+
const base64 = isRecord(result) && typeof result.data === 'string' ? result.data : '';
|
|
247
|
+
if (options.path) {
|
|
248
|
+
await saveBase64ToFile(base64, options.path);
|
|
249
|
+
}
|
|
250
|
+
return base64;
|
|
251
|
+
}
|
|
252
|
+
finally {
|
|
253
|
+
if (needsOverride) {
|
|
254
|
+
await this.bridge.send('Emulation.clearDeviceMetricsOverride').catch(() => { });
|
|
255
|
+
}
|
|
213
256
|
}
|
|
214
|
-
return base64;
|
|
215
257
|
}
|
|
216
258
|
async startNetworkCapture(pattern = '') {
|
|
217
259
|
// Always update the filter pattern
|
|
@@ -20,6 +20,10 @@ export interface DaemonCommand {
|
|
|
20
20
|
format?: 'png' | 'jpeg';
|
|
21
21
|
quality?: number;
|
|
22
22
|
fullPage?: boolean;
|
|
23
|
+
/** Override viewport width in CSS pixels for screenshot (0 / undefined = use current) */
|
|
24
|
+
width?: number;
|
|
25
|
+
/** Override viewport height in CSS pixels for screenshot (0 / undefined = use current; ignored when fullPage) */
|
|
26
|
+
height?: number;
|
|
23
27
|
/** Local file paths for set-file-input action */
|
|
24
28
|
files?: string[];
|
|
25
29
|
/** CSS selector for file input element (set-file-input action) */
|
|
@@ -65,7 +65,7 @@ export function isTransientBrowserError(err) {
|
|
|
65
65
|
export function formatBrowserConnectError(kind, detail) {
|
|
66
66
|
switch (kind) {
|
|
67
67
|
case 'daemon-not-running':
|
|
68
|
-
return new BrowserConnectError('Cannot connect to opencli daemon.' + (detail ? `\n\n${detail}` : ''), `
|
|
68
|
+
return new BrowserConnectError('Cannot connect to opencli daemon.' + (detail ? `\n\n${detail}` : ''), `Run \`opencli doctor\` to diagnose, or \`opencli daemon restart\` to force a fresh daemon. Default port is ${DEFAULT_DAEMON_PORT}.`, kind);
|
|
69
69
|
case 'extension-not-connected':
|
|
70
70
|
return new BrowserConnectError('Browser Bridge extension is not connected.' + (detail ? `\n\n${detail}` : ''), 'Install the extension from GitHub Releases, then reload.', kind);
|
|
71
71
|
case 'command-failed':
|
|
@@ -41,7 +41,7 @@ export declare class Page extends BasePage {
|
|
|
41
41
|
domain?: string;
|
|
42
42
|
url?: string;
|
|
43
43
|
}): Promise<BrowserCookie[]>;
|
|
44
|
-
/**
|
|
44
|
+
/** Release the current automation tab lease in the extension */
|
|
45
45
|
closeWindow(): Promise<void>;
|
|
46
46
|
tabs(): Promise<unknown[]>;
|
|
47
47
|
newTab(url?: string): Promise<string | undefined>;
|
package/dist/src/browser/page.js
CHANGED
|
@@ -143,7 +143,7 @@ export class Page extends BasePage {
|
|
|
143
143
|
const result = await sendCommand('cookies', { ...this._wsOpt(), ...opts });
|
|
144
144
|
return Array.isArray(result) ? result : [];
|
|
145
145
|
}
|
|
146
|
-
/**
|
|
146
|
+
/** Release the current automation tab lease in the extension */
|
|
147
147
|
async closeWindow() {
|
|
148
148
|
try {
|
|
149
149
|
await sendCommand('close-window', { ...this._wsOpt() });
|
|
@@ -205,6 +205,8 @@ export class Page extends BasePage {
|
|
|
205
205
|
format: options.format,
|
|
206
206
|
quality: options.quality,
|
|
207
207
|
fullPage: options.fullPage,
|
|
208
|
+
width: options.width,
|
|
209
|
+
height: options.height,
|
|
208
210
|
});
|
|
209
211
|
if (options.path) {
|
|
210
212
|
await saveBase64ToFile(base64, options.path);
|
|
@@ -216,3 +216,32 @@ describe('Page active target tracking', () => {
|
|
|
216
216
|
expect(evalCall?.[1]).not.toHaveProperty('page');
|
|
217
217
|
});
|
|
218
218
|
});
|
|
219
|
+
describe('Page.screenshot', () => {
|
|
220
|
+
beforeEach(() => {
|
|
221
|
+
sendCommandMock.mockReset();
|
|
222
|
+
sendCommandFullMock.mockReset();
|
|
223
|
+
warnMock.mockReset();
|
|
224
|
+
});
|
|
225
|
+
it('forwards width / height / fullPage options to the bridge', async () => {
|
|
226
|
+
sendCommandMock.mockResolvedValueOnce('BASE64');
|
|
227
|
+
const page = new Page('browser:default');
|
|
228
|
+
const data = await page.screenshot({ fullPage: true, width: 1080 });
|
|
229
|
+
expect(data).toBe('BASE64');
|
|
230
|
+
expect(sendCommandMock).toHaveBeenCalledWith('screenshot', expect.objectContaining({
|
|
231
|
+
workspace: 'browser:default',
|
|
232
|
+
fullPage: true,
|
|
233
|
+
width: 1080,
|
|
234
|
+
}));
|
|
235
|
+
});
|
|
236
|
+
it('omits viewport overrides when none are set', async () => {
|
|
237
|
+
sendCommandMock.mockResolvedValueOnce('BASE64');
|
|
238
|
+
const page = new Page('browser:default');
|
|
239
|
+
await page.screenshot();
|
|
240
|
+
const call = sendCommandMock.mock.calls.at(-1);
|
|
241
|
+
expect(call?.[0]).toBe('screenshot');
|
|
242
|
+
const args = call?.[1];
|
|
243
|
+
expect(args.width).toBeUndefined();
|
|
244
|
+
expect(args.height).toBeUndefined();
|
|
245
|
+
expect(args.fullPage).toBeUndefined();
|
|
246
|
+
});
|
|
247
|
+
});
|
|
@@ -15,8 +15,9 @@
|
|
|
15
15
|
* - selector_not_found: 0 matches
|
|
16
16
|
* - selector_ambiguous: >1 matches for a write op without --nth
|
|
17
17
|
* - selector_nth_out_of_range: --nth beyond matches_n
|
|
18
|
+
* - not_editable: target exists but cannot accept text input
|
|
18
19
|
*/
|
|
19
|
-
export type TargetErrorCode = 'not_found' | 'stale_ref' | 'invalid_selector' | 'selector_not_found' | 'selector_ambiguous' | 'selector_nth_out_of_range';
|
|
20
|
+
export type TargetErrorCode = 'not_found' | 'stale_ref' | 'invalid_selector' | 'selector_not_found' | 'selector_ambiguous' | 'selector_nth_out_of_range' | 'not_editable';
|
|
20
21
|
export interface TargetErrorInfo {
|
|
21
22
|
code: TargetErrorCode;
|
|
22
23
|
message: string;
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* - selector_not_found: 0 matches
|
|
16
16
|
* - selector_ambiguous: >1 matches for a write op without --nth
|
|
17
17
|
* - selector_nth_out_of_range: --nth beyond matches_n
|
|
18
|
+
* - not_editable: target exists but cannot accept text input
|
|
18
19
|
*/
|
|
19
20
|
export class TargetError extends Error {
|
|
20
21
|
code;
|
|
@@ -79,6 +79,25 @@ export declare function clickResolvedJs(opts?: {
|
|
|
79
79
|
* Generate JS for type that uses the unified resolver.
|
|
80
80
|
*/
|
|
81
81
|
export declare function typeResolvedJs(text: string): string;
|
|
82
|
+
export type FillResolvedResult = {
|
|
83
|
+
ok: true;
|
|
84
|
+
actual: string;
|
|
85
|
+
expected: string;
|
|
86
|
+
length: number;
|
|
87
|
+
mode: 'input' | 'textarea' | 'contenteditable';
|
|
88
|
+
} | {
|
|
89
|
+
ok: false;
|
|
90
|
+
actual: string;
|
|
91
|
+
expected: string;
|
|
92
|
+
length: number;
|
|
93
|
+
mode: 'input' | 'textarea' | 'contenteditable';
|
|
94
|
+
} | {
|
|
95
|
+
ok: false;
|
|
96
|
+
reason: string;
|
|
97
|
+
tag?: string;
|
|
98
|
+
type?: string;
|
|
99
|
+
role?: string;
|
|
100
|
+
};
|
|
82
101
|
/**
|
|
83
102
|
* Prepare the resolved element for native CDP Input.insertText.
|
|
84
103
|
*
|
|
@@ -90,6 +109,12 @@ export declare function prepareNativeTypeResolvedJs(opts?: {
|
|
|
90
109
|
skipScroll?: boolean;
|
|
91
110
|
skipFocus?: boolean;
|
|
92
111
|
}): string;
|
|
112
|
+
/**
|
|
113
|
+
* Verify the exact value/text currently held by the resolved editable target.
|
|
114
|
+
* Assumes resolveTargetJs and prepareNativeTypeResolvedJs have already set
|
|
115
|
+
* `window.__resolved` to the normalized editable host.
|
|
116
|
+
*/
|
|
117
|
+
export declare function verifyFilledResolvedJs(expected: string): string;
|
|
93
118
|
/**
|
|
94
119
|
* Generate JS for scrollTo that uses the unified resolver.
|
|
95
120
|
* Assumes resolveTargetJs has been called and __resolved is set.
|
|
@@ -411,6 +411,49 @@ export function prepareNativeTypeResolvedJs(opts = {}) {
|
|
|
411
411
|
})()
|
|
412
412
|
`;
|
|
413
413
|
}
|
|
414
|
+
/**
|
|
415
|
+
* Verify the exact value/text currently held by the resolved editable target.
|
|
416
|
+
* Assumes resolveTargetJs and prepareNativeTypeResolvedJs have already set
|
|
417
|
+
* `window.__resolved` to the normalized editable host.
|
|
418
|
+
*/
|
|
419
|
+
export function verifyFilledResolvedJs(expected) {
|
|
420
|
+
const safeText = JSON.stringify(expected);
|
|
421
|
+
return `
|
|
422
|
+
(() => {
|
|
423
|
+
const el = window.__resolved;
|
|
424
|
+
if (!el) return { ok: false, reason: 'no_resolved_element' };
|
|
425
|
+
|
|
426
|
+
const tag = el.tagName ? el.tagName.toLowerCase() : '';
|
|
427
|
+
const isInput = el instanceof HTMLInputElement;
|
|
428
|
+
const isTextarea = el instanceof HTMLTextAreaElement;
|
|
429
|
+
const mode = el.isContentEditable
|
|
430
|
+
? 'contenteditable'
|
|
431
|
+
: isTextarea
|
|
432
|
+
? 'textarea'
|
|
433
|
+
: isInput
|
|
434
|
+
? 'input'
|
|
435
|
+
: '';
|
|
436
|
+
|
|
437
|
+
if (!mode) {
|
|
438
|
+
return {
|
|
439
|
+
ok: false,
|
|
440
|
+
reason: 'not_editable',
|
|
441
|
+
tag,
|
|
442
|
+
role: el.getAttribute ? (el.getAttribute('role') || '') : '',
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const actual = mode === 'contenteditable' ? (el.innerText || '') : String(el.value || '');
|
|
447
|
+
return {
|
|
448
|
+
ok: actual === ${safeText},
|
|
449
|
+
actual,
|
|
450
|
+
expected: ${safeText},
|
|
451
|
+
length: actual.length,
|
|
452
|
+
mode,
|
|
453
|
+
};
|
|
454
|
+
})()
|
|
455
|
+
`;
|
|
456
|
+
}
|
|
414
457
|
/**
|
|
415
458
|
* Generate JS for scrollTo that uses the unified resolver.
|
|
416
459
|
* Assumes resolveTargetJs has been called and __resolved is set.
|
|
@@ -32,7 +32,13 @@ const PACKAGE_ROOT = findPackageRoot(fileURLToPath(import.meta.url));
|
|
|
32
32
|
const CLIS_DIR = path.join(PACKAGE_ROOT, 'clis');
|
|
33
33
|
// Write manifest next to clis/ so both dev and installed runtime can find it.
|
|
34
34
|
const OUTPUT = getCliManifestPath(CLIS_DIR);
|
|
35
|
-
|
|
35
|
+
// Module is treated as a CLI command source if it either:
|
|
36
|
+
// 1. Calls `cli(...)` directly (the common case), or
|
|
37
|
+
// 2. Calls a factory `make<Pascal>Command(...)` from clis/_shared/ that
|
|
38
|
+
// wraps `cli(...)`. Without (2), shared-factory adapters
|
|
39
|
+
// (codex/cursor/chatwise new/status/dump/screenshot) match no `cli(`
|
|
40
|
+
// token at the top level and silently drop out of the manifest.
|
|
41
|
+
const CLI_MODULE_PATTERN = /\bcli\s*\(|\bmake[A-Z]\w*Command\s*\(/;
|
|
36
42
|
/**
|
|
37
43
|
* Thrown by `loadManifestEntries` when an adapter file looks like a CLI
|
|
38
44
|
* module (matches CLI_MODULE_PATTERN) but cannot be imported. Callers
|
|
@@ -91,13 +97,12 @@ function toManifestEntry(cmd, modulePath, sourceFile) {
|
|
|
91
97
|
browser: cmd.browser ?? true,
|
|
92
98
|
args: toManifestArgs(cmd.args),
|
|
93
99
|
columns: cmd.columns,
|
|
94
|
-
|
|
95
|
-
deprecated: cmd.deprecated,
|
|
96
|
-
replacedBy: cmd.replacedBy,
|
|
100
|
+
defaultFormat: cmd.defaultFormat,
|
|
97
101
|
type: 'js',
|
|
98
102
|
modulePath,
|
|
99
103
|
sourceFile,
|
|
100
104
|
navigateBefore: cmd.navigateBefore,
|
|
105
|
+
browserSession: cmd.browserSession,
|
|
101
106
|
};
|
|
102
107
|
}
|
|
103
108
|
/**
|
|
@@ -46,8 +46,7 @@ describe('manifest helper rules', () => {
|
|
|
46
46
|
],
|
|
47
47
|
domain: 'localhost',
|
|
48
48
|
navigateBefore: 'https://example.com/session',
|
|
49
|
-
|
|
50
|
-
replacedBy: 'opencli demo new',
|
|
49
|
+
defaultFormat: 'plain',
|
|
51
50
|
}),
|
|
52
51
|
}));
|
|
53
52
|
expect(entries).toMatchObject([
|
|
@@ -74,8 +73,7 @@ describe('manifest helper rules', () => {
|
|
|
74
73
|
type: 'js',
|
|
75
74
|
modulePath: `${site}/${site}.js`,
|
|
76
75
|
navigateBefore: 'https://example.com/session',
|
|
77
|
-
|
|
78
|
-
replacedBy: 'opencli demo new',
|
|
76
|
+
defaultFormat: 'plain',
|
|
79
77
|
},
|
|
80
78
|
]);
|
|
81
79
|
// Verify sourceFile is included and stable for manifest consumers.
|
|
@@ -96,8 +94,6 @@ describe('manifest helper rules', () => {
|
|
|
96
94
|
name: 'legacy',
|
|
97
95
|
access: 'read',
|
|
98
96
|
description: 'legacy command',
|
|
99
|
-
deprecated: 'legacy is deprecated',
|
|
100
|
-
replacedBy: 'opencli demo new',
|
|
101
97
|
});
|
|
102
98
|
return {};
|
|
103
99
|
});
|
|
@@ -112,8 +108,6 @@ describe('manifest helper rules', () => {
|
|
|
112
108
|
args: [],
|
|
113
109
|
type: 'js',
|
|
114
110
|
modulePath: `${site}/${site}.js`,
|
|
115
|
-
deprecated: 'legacy is deprecated',
|
|
116
|
-
replacedBy: 'opencli demo new',
|
|
117
111
|
},
|
|
118
112
|
]);
|
|
119
113
|
// Verify sourceFile is included
|
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
import type { CliCommand } from './registry.js';
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Pipeline steps that require a live browser session.
|
|
4
|
+
*
|
|
5
|
+
* Note: this is the *subset* of registered pipeline steps that need a page;
|
|
6
|
+
* non-browser steps (fetch, select, map, filter, sort, limit, download)
|
|
7
|
+
* deliberately stay out. The full registered-step list lives in
|
|
8
|
+
* `src/pipeline/registry.ts` and is re-derived elsewhere via
|
|
9
|
+
* `getRegisteredStepNames()` (e.g. in `validate.ts`). When you add a new
|
|
10
|
+
* pipeline step, decide whether it belongs here based on whether its handler
|
|
11
|
+
* touches the IPage object — and `src/capabilityRouting.test.ts` verifies the
|
|
12
|
+
* subset relationship is intact.
|
|
13
|
+
*/
|
|
3
14
|
export declare const BROWSER_ONLY_STEPS: Set<string>;
|
|
15
|
+
/** Internal helper: ensure BROWSER_ONLY_STEPS is a subset of registered pipeline steps. */
|
|
16
|
+
export declare function _validateBrowserOnlyStepsAgainstRegistry(): {
|
|
17
|
+
extras: string[];
|
|
18
|
+
};
|
|
4
19
|
export declare function shouldUseBrowserSession(cmd: CliCommand): boolean;
|
|
@@ -1,8 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
import { getRegisteredStepNames } from './pipeline/registry.js';
|
|
2
|
+
/**
|
|
3
|
+
* Pipeline steps that require a live browser session.
|
|
4
|
+
*
|
|
5
|
+
* Note: this is the *subset* of registered pipeline steps that need a page;
|
|
6
|
+
* non-browser steps (fetch, select, map, filter, sort, limit, download)
|
|
7
|
+
* deliberately stay out. The full registered-step list lives in
|
|
8
|
+
* `src/pipeline/registry.ts` and is re-derived elsewhere via
|
|
9
|
+
* `getRegisteredStepNames()` (e.g. in `validate.ts`). When you add a new
|
|
10
|
+
* pipeline step, decide whether it belongs here based on whether its handler
|
|
11
|
+
* touches the IPage object — and `src/capabilityRouting.test.ts` verifies the
|
|
12
|
+
* subset relationship is intact.
|
|
13
|
+
*/
|
|
2
14
|
export const BROWSER_ONLY_STEPS = new Set([
|
|
3
15
|
'navigate',
|
|
4
16
|
'click',
|
|
5
17
|
'type',
|
|
18
|
+
'fill',
|
|
6
19
|
'wait',
|
|
7
20
|
'press',
|
|
8
21
|
'snapshot',
|
|
@@ -10,6 +23,16 @@ export const BROWSER_ONLY_STEPS = new Set([
|
|
|
10
23
|
'intercept',
|
|
11
24
|
'tap',
|
|
12
25
|
]);
|
|
26
|
+
/** Internal helper: ensure BROWSER_ONLY_STEPS is a subset of registered pipeline steps. */
|
|
27
|
+
export function _validateBrowserOnlyStepsAgainstRegistry() {
|
|
28
|
+
const registered = new Set(getRegisteredStepNames());
|
|
29
|
+
const extras = [];
|
|
30
|
+
for (const step of BROWSER_ONLY_STEPS) {
|
|
31
|
+
if (!registered.has(step))
|
|
32
|
+
extras.push(step);
|
|
33
|
+
}
|
|
34
|
+
return { extras };
|
|
35
|
+
}
|
|
13
36
|
function pipelineNeedsBrowserSession(pipeline) {
|
|
14
37
|
return pipeline.some((step) => {
|
|
15
38
|
if (!step || typeof step !== 'object')
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import { Strategy } from './registry.js';
|
|
3
|
-
import { shouldUseBrowserSession } from './capabilityRouting.js';
|
|
3
|
+
import { BROWSER_ONLY_STEPS, _validateBrowserOnlyStepsAgainstRegistry, shouldUseBrowserSession } from './capabilityRouting.js';
|
|
4
|
+
import { getRegisteredStepNames } from './pipeline/registry.js';
|
|
4
5
|
function makeCmd(partial) {
|
|
5
6
|
return {
|
|
6
7
|
site: 'test',
|
|
@@ -42,4 +43,21 @@ describe('shouldUseBrowserSession', () => {
|
|
|
42
43
|
func: async () => [],
|
|
43
44
|
}))).toBe(true);
|
|
44
45
|
});
|
|
46
|
+
it('routes pipelines containing the fill step into a browser session', () => {
|
|
47
|
+
expect(shouldUseBrowserSession(makeCmd({
|
|
48
|
+
browser: true,
|
|
49
|
+
strategy: Strategy.PUBLIC,
|
|
50
|
+
pipeline: [{ navigate: 'https://example.com' }, { fill: { ref: '#q', text: 'hello' } }],
|
|
51
|
+
}))).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
describe('BROWSER_ONLY_STEPS / pipeline registry alignment', () => {
|
|
55
|
+
it('is a subset of registered pipeline step names', () => {
|
|
56
|
+
const { extras } = _validateBrowserOnlyStepsAgainstRegistry();
|
|
57
|
+
expect(extras).toEqual([]);
|
|
58
|
+
});
|
|
59
|
+
it('includes fill (DOM-touching step added in PR #1222)', () => {
|
|
60
|
+
expect(BROWSER_ONLY_STEPS.has('fill')).toBe(true);
|
|
61
|
+
expect(getRegisteredStepNames()).toContain('fill');
|
|
62
|
+
});
|
|
45
63
|
});
|
package/dist/src/cli.js
CHANGED
|
@@ -8,7 +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 { Command } from 'commander';
|
|
11
|
+
import { Command, InvalidArgumentError } from 'commander';
|
|
12
12
|
import { styleText } from 'node:util';
|
|
13
13
|
import { findPackageRoot, getBuiltEntryCandidates } from './package-paths.js';
|
|
14
14
|
import { fullName, getRegistry, strategyLabel } from './registry.js';
|
|
@@ -18,7 +18,7 @@ import { PKG_VERSION } from './version.js';
|
|
|
18
18
|
import { printCompletionScript } from './completion.js';
|
|
19
19
|
import { loadExternalClis, executeExternalCli, installExternalCli, registerExternalCli, isBinaryInstalled } from './external.js';
|
|
20
20
|
import { registerAllCommands } from './commanderAdapter.js';
|
|
21
|
-
import { formatRootAdapterHelpText, installStructuredHelp, rootHelpData } from './help.js';
|
|
21
|
+
import { classifyAdapter, formatRootAdapterHelpText, installStructuredHelp, rootHelpData } from './help.js';
|
|
22
22
|
import { EXIT_CODES, getErrorMessage, BrowserConnectError } from './errors.js';
|
|
23
23
|
import { TargetError } from './browser/target-errors.js';
|
|
24
24
|
import { resolveTargetJs, getTextResolvedJs, getValueResolvedJs, getAttributesResolvedJs, selectResolvedJs, isAutocompleteResolvedJs } from './browser/target-resolver.js';
|
|
@@ -316,7 +316,9 @@ async function resolveStoredBrowserTarget(page, scope = DEFAULT_BROWSER_WORKSPAC
|
|
|
316
316
|
async function getBrowserPage(targetPage, workspace = DEFAULT_BROWSER_WORKSPACE, contextId) {
|
|
317
317
|
const { BrowserBridge } = await import('./browser/index.js');
|
|
318
318
|
const bridge = new BrowserBridge();
|
|
319
|
-
|
|
319
|
+
// Idle timeout: how long the browser workspace lease stays alive between commands
|
|
320
|
+
// (controls when the automation tab is released). Not the per-command runtime timeout.
|
|
321
|
+
const envTimeout = process.env.OPENCLI_BROWSER_IDLE_TIMEOUT;
|
|
320
322
|
const idleTimeout = envTimeout ? parseInt(envTimeout, 10) : undefined;
|
|
321
323
|
const page = await bridge.connect({
|
|
322
324
|
timeout: 30,
|
|
@@ -389,6 +391,16 @@ function parsePositiveIntOption(val, label, fallback) {
|
|
|
389
391
|
}
|
|
390
392
|
return parsed;
|
|
391
393
|
}
|
|
394
|
+
function parseScreenshotDim(val, label) {
|
|
395
|
+
if (!/^\d+$/.test(val)) {
|
|
396
|
+
throw new InvalidArgumentError(`--${label} must be a positive integer (got "${val}")`);
|
|
397
|
+
}
|
|
398
|
+
const parsed = parseInt(val, 10);
|
|
399
|
+
if (parsed <= 0) {
|
|
400
|
+
throw new InvalidArgumentError(`--${label} must be a positive integer (got "${val}")`);
|
|
401
|
+
}
|
|
402
|
+
return parsed;
|
|
403
|
+
}
|
|
392
404
|
function applyVerbose(opts) {
|
|
393
405
|
if (opts.verbose)
|
|
394
406
|
process.env.OPENCLI_VERBOSE = '1';
|
|
@@ -888,14 +900,22 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
|
|
|
888
900
|
console.log(JSON.stringify(frames, null, 2));
|
|
889
901
|
}));
|
|
890
902
|
addBrowserTabOption(browser.command('screenshot').argument('[path]', 'Save to file (base64 if omitted)'))
|
|
903
|
+
.option('--full-page', 'Capture the full scrollable page, not just the viewport', false)
|
|
904
|
+
.option('--width <n>', 'Override viewport width in CSS pixels for this screenshot only', (v) => parseScreenshotDim(v, 'width'))
|
|
905
|
+
.option('--height <n>', 'Override viewport height in CSS pixels for this screenshot only (ignored with --full-page)', (v) => parseScreenshotDim(v, 'height'))
|
|
891
906
|
.description('Take screenshot')
|
|
892
|
-
.action(browserAction(async (page, path) => {
|
|
907
|
+
.action(browserAction(async (page, path, opts) => {
|
|
908
|
+
const shotOpts = {
|
|
909
|
+
fullPage: opts.fullPage === true,
|
|
910
|
+
width: opts.width,
|
|
911
|
+
height: opts.height,
|
|
912
|
+
};
|
|
893
913
|
if (path) {
|
|
894
|
-
await page.screenshot({ path });
|
|
914
|
+
await page.screenshot({ ...shotOpts, path });
|
|
895
915
|
console.log(`Screenshot saved to: ${path}`);
|
|
896
916
|
}
|
|
897
917
|
else {
|
|
898
|
-
console.log(await page.screenshot({ format: 'png' }));
|
|
918
|
+
console.log(await page.screenshot({ ...shotOpts, format: 'png' }));
|
|
899
919
|
}
|
|
900
920
|
}));
|
|
901
921
|
addBrowserTabOption(browser.command('console'))
|
|
@@ -1313,6 +1333,33 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
|
|
|
1313
1333
|
autocomplete: !!isAutocomplete,
|
|
1314
1334
|
}, null, 2));
|
|
1315
1335
|
}));
|
|
1336
|
+
addBrowserTabOption(browser.command('fill')
|
|
1337
|
+
.argument('<target>', 'Numeric ref (from browser state / find) or CSS selector')
|
|
1338
|
+
.argument('<text>', 'Text to set exactly')
|
|
1339
|
+
.option('--nth <n>', 'When <target> is a multi-match CSS selector, pick the nth match (0-based)')
|
|
1340
|
+
.description('Set input/textarea/contenteditable text exactly and verify the value — JSON envelope {filled, verified, text, actual}'))
|
|
1341
|
+
.action(browserAction(async (page, target, text, opts) => {
|
|
1342
|
+
const parsed = nthToResolveOpts(opts?.nth);
|
|
1343
|
+
if ('error' in parsed) {
|
|
1344
|
+
console.log(JSON.stringify({ error: { code: 'usage_error', message: parsed.error } }, null, 2));
|
|
1345
|
+
process.exitCode = EXIT_CODES.USAGE_ERROR;
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
const result = await page.fillText(String(target), String(text), parsed.opts);
|
|
1349
|
+
if (!result.verified)
|
|
1350
|
+
process.exitCode = EXIT_CODES.GENERIC_ERROR;
|
|
1351
|
+
console.log(JSON.stringify({
|
|
1352
|
+
filled: result.filled,
|
|
1353
|
+
verified: result.verified,
|
|
1354
|
+
target: String(target),
|
|
1355
|
+
text: String(text),
|
|
1356
|
+
actual: result.actual,
|
|
1357
|
+
length: result.length,
|
|
1358
|
+
matches_n: result.matches_n,
|
|
1359
|
+
match_level: result.match_level,
|
|
1360
|
+
...(result.mode ? { mode: result.mode } : {}),
|
|
1361
|
+
}, null, 2));
|
|
1362
|
+
}));
|
|
1316
1363
|
addBrowserTabOption(browser.command('select')
|
|
1317
1364
|
.argument('<target>', 'Numeric ref (from browser state / find) or CSS selector of a <select> element')
|
|
1318
1365
|
.argument('<option>', 'Option text (or value) to select')
|
|
@@ -2036,10 +2083,10 @@ cli({
|
|
|
2036
2083
|
}
|
|
2037
2084
|
});
|
|
2038
2085
|
// ── Session ──
|
|
2039
|
-
browser.command('close').description('
|
|
2086
|
+
browser.command('close').description('Release the current automation tab lease')
|
|
2040
2087
|
.action(browserAction(async (page) => {
|
|
2041
2088
|
await page.closeWindow?.();
|
|
2042
|
-
console.log('Automation
|
|
2089
|
+
console.log('Automation tab lease released');
|
|
2043
2090
|
}));
|
|
2044
2091
|
// ── Built-in: doctor / completion ──────────────────────────────────────────
|
|
2045
2092
|
program
|
|
@@ -2539,11 +2586,29 @@ cli({
|
|
|
2539
2586
|
siteGroups.set('antigravity', antigravityCmd);
|
|
2540
2587
|
const siteNames = registerAllCommands(program, siteGroups);
|
|
2541
2588
|
applyRootSubcommandSummaries(program);
|
|
2542
|
-
|
|
2589
|
+
// ── Help-text grouping: External CLIs / App adapters / Site adapters ──
|
|
2590
|
+
// Classification derives from each adapter's `domain` field — see classifyAdapter.
|
|
2591
|
+
// External CLIs are taken from the externalClis registry (passthrough binaries).
|
|
2592
|
+
const externalNames = externalClis.map(ext => ext.name);
|
|
2593
|
+
const siteDomains = new Map();
|
|
2594
|
+
for (const [, cmd] of getRegistry()) {
|
|
2595
|
+
if (!siteDomains.has(cmd.site))
|
|
2596
|
+
siteDomains.set(cmd.site, cmd.domain);
|
|
2597
|
+
}
|
|
2598
|
+
const apps = [];
|
|
2599
|
+
const sites = [];
|
|
2600
|
+
for (const site of siteNames) {
|
|
2601
|
+
if (classifyAdapter(siteDomains.get(site)) === 'app')
|
|
2602
|
+
apps.push(site);
|
|
2603
|
+
else
|
|
2604
|
+
sites.push(site);
|
|
2605
|
+
}
|
|
2606
|
+
const adapterGroups = { external: externalNames, apps, sites };
|
|
2607
|
+
const adapterNameSet = new Set([...externalNames, ...siteNames]);
|
|
2543
2608
|
program.configureHelp({
|
|
2544
|
-
visibleCommands: (command) => command.commands.filter(child => command !== program || !
|
|
2609
|
+
visibleCommands: (command) => command.commands.filter(child => command !== program || !adapterNameSet.has(child.name())),
|
|
2545
2610
|
});
|
|
2546
|
-
installStructuredHelp(program, () => rootHelpData(program,
|
|
2611
|
+
installStructuredHelp(program, () => rootHelpData(program, adapterGroups), () => formatRootAdapterHelpText(adapterGroups));
|
|
2547
2612
|
// ── Unknown command fallback ──────────────────────────────────────────────
|
|
2548
2613
|
// Security: do NOT auto-discover and register arbitrary system binaries.
|
|
2549
2614
|
// Only explicitly registered external CLIs are allowed.
|