@jackwener/opencli 1.6.1 → 1.6.2
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/CONTRIBUTING.md +1 -1
- package/README.md +27 -45
- package/README.zh-CN.md +32 -34
- package/autoresearch/browse-tasks.json +18 -20
- package/autoresearch/commands/debug.ts +163 -0
- package/autoresearch/commands/fix.ts +145 -0
- package/autoresearch/commands/plan.ts +88 -0
- package/autoresearch/commands/run.ts +138 -0
- package/autoresearch/config.ts +82 -0
- package/autoresearch/engine.ts +359 -0
- package/autoresearch/eval-all.ts +127 -0
- package/autoresearch/eval-browse.ts +1 -1
- package/autoresearch/eval-publish.ts +238 -0
- package/autoresearch/eval-save.ts +249 -0
- package/autoresearch/eval-skill.ts +14 -8
- package/autoresearch/eval-v2ex.ts +220 -0
- package/autoresearch/eval-zhihu.ts +230 -0
- package/autoresearch/logger.ts +69 -0
- package/autoresearch/presets/combined-reliability.ts +27 -0
- package/autoresearch/presets/index.ts +23 -0
- package/autoresearch/presets/operate-reliability.ts +24 -0
- package/autoresearch/presets/save-reliability.ts +26 -0
- package/autoresearch/presets/skill-quality.ts +20 -0
- package/autoresearch/presets/v2ex-reliability.ts +24 -0
- package/autoresearch/presets/zhihu-reliability.ts +25 -0
- package/autoresearch/publish-tasks.json +345 -0
- package/autoresearch/run-save.sh +11 -0
- package/autoresearch/save-adapters/xhs-explore-deep.ts +64 -0
- package/autoresearch/save-adapters/xhs-note-comments.ts +61 -0
- package/autoresearch/save-adapters/xhs-search-full.ts +62 -0
- package/autoresearch/save-adapters/zhihu-hot-detail.ts +52 -0
- package/autoresearch/save-adapters/zhihu-question-full.ts +57 -0
- package/autoresearch/save-adapters/zhihu-search-detail.ts +53 -0
- package/autoresearch/save-tasks.json +281 -0
- package/autoresearch/v2ex-tasks.json +899 -0
- package/autoresearch/zhihu-tasks.json +848 -0
- package/dist/browser/base-page.d.ts +4 -2
- package/dist/browser/base-page.js +37 -4
- package/dist/browser/bridge.js +10 -8
- package/dist/browser/cdp.js +2 -6
- package/dist/browser/daemon-client.d.ts +11 -1
- package/dist/browser/daemon-client.js +3 -0
- package/dist/browser/dom-helpers.d.ts +4 -2
- package/dist/browser/dom-helpers.js +42 -31
- package/dist/browser/dom-snapshot.js +23 -1
- package/dist/browser/page.d.ts +7 -2
- package/dist/browser/page.js +112 -30
- package/dist/browser.test.js +1 -1
- package/dist/build-manifest.d.ts +1 -0
- package/dist/build-manifest.js +1 -0
- package/dist/cli-manifest.json +1135 -184
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +48 -7
- package/dist/cli.test.d.ts +1 -0
- package/dist/cli.test.js +88 -0
- package/dist/clis/1688/item.d.ts +70 -0
- package/dist/clis/1688/item.js +187 -0
- package/dist/clis/1688/item.test.d.ts +1 -0
- package/dist/clis/1688/item.test.js +67 -0
- package/dist/clis/1688/search.d.ts +56 -0
- package/dist/clis/1688/search.js +309 -0
- package/dist/clis/1688/search.test.d.ts +1 -0
- package/dist/clis/1688/search.test.js +75 -0
- package/dist/clis/1688/shared.d.ts +112 -0
- package/dist/clis/1688/shared.js +514 -0
- package/dist/clis/1688/shared.test.d.ts +1 -0
- package/dist/clis/1688/shared.test.js +57 -0
- package/dist/clis/1688/store.d.ts +45 -0
- package/dist/clis/1688/store.js +226 -0
- package/dist/clis/1688/store.test.d.ts +1 -0
- package/dist/clis/1688/store.test.js +62 -0
- package/dist/clis/amazon/bestsellers.d.ts +0 -20
- package/dist/clis/amazon/bestsellers.js +6 -129
- package/dist/clis/amazon/bestsellers.test.js +12 -3
- package/dist/clis/amazon/movers-shakers.d.ts +1 -0
- package/dist/clis/amazon/movers-shakers.js +7 -0
- package/dist/clis/amazon/new-releases.d.ts +1 -0
- package/dist/clis/amazon/new-releases.js +7 -0
- package/dist/clis/amazon/rankings.d.ts +59 -0
- package/dist/clis/amazon/rankings.js +226 -0
- package/dist/clis/amazon/rankings.test.d.ts +1 -0
- package/dist/clis/amazon/rankings.test.js +41 -0
- package/dist/clis/amazon/shared.d.ts +11 -0
- package/dist/clis/amazon/shared.js +121 -11
- package/dist/clis/amazon/shared.test.js +11 -0
- package/dist/clis/bilibili/comments.js +2 -2
- package/dist/clis/bilibili/comments.test.js +3 -2
- package/dist/clis/bilibili/download.js +2 -1
- package/dist/clis/bilibili/subtitle.js +4 -3
- package/dist/clis/bilibili/subtitle.test.js +2 -1
- package/dist/clis/bilibili/utils.d.ts +5 -0
- package/dist/clis/bilibili/utils.js +30 -0
- package/dist/clis/bilibili/utils.test.d.ts +1 -0
- package/dist/clis/bilibili/utils.test.js +17 -0
- package/dist/clis/douban/marks.js +1 -1
- package/dist/clis/douban/subject.yaml +50 -19
- package/dist/clis/doubao/utils.js +32 -12
- package/dist/clis/douyin/_shared/browser-fetch.test.js +0 -1
- package/dist/clis/douyin/_shared/transcode.test.js +0 -2
- package/dist/clis/douyin/draft.test.js +0 -2
- package/dist/clis/facebook/search.test.js +0 -2
- package/dist/clis/gemini/ask.js +9 -3
- package/dist/clis/gemini/ask.test.d.ts +1 -0
- package/dist/clis/gemini/ask.test.js +100 -0
- package/dist/clis/gemini/reply-state.test.d.ts +1 -0
- package/dist/clis/gemini/reply-state.test.js +641 -0
- package/dist/clis/gemini/utils.d.ts +44 -1
- package/dist/clis/gemini/utils.js +528 -61
- package/dist/clis/gemini/utils.test.js +149 -2
- package/dist/clis/hupu/detail.d.ts +1 -0
- package/dist/clis/hupu/detail.js +72 -0
- package/dist/clis/hupu/hot.yaml +43 -0
- package/dist/clis/hupu/like.d.ts +1 -0
- package/dist/clis/hupu/like.js +75 -0
- package/dist/clis/hupu/reply.d.ts +1 -0
- package/dist/clis/hupu/reply.js +71 -0
- package/dist/clis/hupu/search.d.ts +1 -0
- package/dist/clis/hupu/search.js +59 -0
- package/dist/clis/hupu/unlike.d.ts +1 -0
- package/dist/clis/hupu/unlike.js +75 -0
- package/dist/clis/hupu/utils.d.ts +20 -0
- package/dist/clis/hupu/utils.js +319 -0
- package/dist/clis/instagram/_shared/private-publish.d.ts +138 -0
- package/dist/clis/instagram/_shared/private-publish.js +1030 -0
- package/dist/clis/instagram/_shared/private-publish.test.d.ts +1 -0
- package/dist/clis/instagram/_shared/private-publish.test.js +705 -0
- package/dist/clis/instagram/_shared/protocol-capture.d.ts +26 -0
- package/dist/clis/instagram/_shared/protocol-capture.js +282 -0
- package/dist/clis/instagram/_shared/protocol-capture.test.d.ts +1 -0
- package/dist/clis/instagram/_shared/protocol-capture.test.js +114 -0
- package/dist/clis/instagram/_shared/runtime-info.d.ts +9 -0
- package/dist/clis/instagram/_shared/runtime-info.js +81 -0
- package/dist/clis/instagram/note.d.ts +1 -0
- package/dist/clis/instagram/note.js +222 -0
- package/dist/clis/instagram/note.test.d.ts +1 -0
- package/dist/clis/instagram/note.test.js +81 -0
- package/dist/clis/instagram/post.d.ts +4 -0
- package/dist/clis/instagram/post.js +1496 -0
- package/dist/clis/instagram/post.test.d.ts +1 -0
- package/dist/clis/instagram/post.test.js +1647 -0
- package/dist/clis/instagram/reel.d.ts +1 -0
- package/dist/clis/instagram/reel.js +826 -0
- package/dist/clis/instagram/reel.test.d.ts +1 -0
- package/dist/clis/instagram/reel.test.js +167 -0
- package/dist/clis/instagram/story.d.ts +1 -0
- package/dist/clis/instagram/story.js +115 -0
- package/dist/clis/instagram/story.test.d.ts +1 -0
- package/dist/clis/instagram/story.test.js +167 -0
- package/dist/clis/sinafinance/stock-rank.d.ts +4 -0
- package/dist/clis/sinafinance/stock-rank.js +65 -0
- package/dist/clis/substack/utils.test.js +0 -2
- package/dist/clis/twitter/post.js +72 -45
- package/dist/clis/twitter/post.test.d.ts +1 -0
- package/dist/clis/twitter/post.test.js +116 -0
- package/dist/clis/twitter/reply.d.ts +12 -0
- package/dist/clis/twitter/reply.js +257 -35
- package/dist/clis/twitter/reply.test.d.ts +1 -0
- package/dist/clis/twitter/reply.test.js +151 -0
- package/dist/clis/xianyu/chat.d.ts +7 -0
- package/dist/clis/xianyu/chat.js +146 -0
- package/dist/clis/xianyu/chat.test.d.ts +1 -0
- package/dist/clis/xianyu/chat.test.js +15 -0
- package/dist/clis/xianyu/item.d.ts +7 -0
- package/dist/clis/xianyu/item.js +152 -0
- package/dist/clis/xianyu/item.test.d.ts +1 -0
- package/dist/clis/xianyu/item.test.js +56 -0
- package/dist/clis/xianyu/search.d.ts +10 -0
- package/dist/clis/xianyu/search.js +134 -0
- package/dist/clis/xianyu/search.test.d.ts +1 -0
- package/dist/clis/xianyu/search.test.js +17 -0
- package/dist/clis/xianyu/utils.d.ts +1 -0
- package/dist/clis/xianyu/utils.js +8 -0
- package/dist/clis/xiaoe/catalog.yaml +129 -0
- package/dist/clis/xiaoe/content.yaml +43 -0
- package/dist/clis/xiaoe/courses.yaml +73 -0
- package/dist/clis/xiaoe/detail.yaml +39 -0
- package/dist/clis/xiaoe/play-url.yaml +124 -0
- package/dist/clis/xiaohongshu/comments.test.js +0 -2
- package/dist/clis/xiaohongshu/creator-note-detail.test.js +0 -2
- package/dist/clis/xiaohongshu/creator-notes.test.js +0 -2
- package/dist/clis/xiaohongshu/download.test.js +0 -2
- package/dist/clis/xiaohongshu/note.test.js +0 -2
- package/dist/clis/xiaohongshu/publish.test.js +0 -2
- package/dist/clis/xiaohongshu/search.js +29 -20
- package/dist/clis/xiaohongshu/search.test.js +56 -48
- package/dist/clis/yuanbao/ask.d.ts +21 -0
- package/dist/clis/yuanbao/ask.js +427 -0
- package/dist/clis/yuanbao/ask.test.d.ts +1 -0
- package/dist/clis/yuanbao/ask.test.js +124 -0
- package/dist/clis/yuanbao/new.d.ts +1 -0
- package/dist/clis/yuanbao/new.js +70 -0
- package/dist/clis/yuanbao/new.test.d.ts +1 -0
- package/dist/clis/yuanbao/new.test.js +30 -0
- package/dist/clis/yuanbao/shared.d.ts +13 -0
- package/dist/clis/yuanbao/shared.js +49 -0
- package/dist/clis/zhihu/question.js +30 -19
- package/dist/clis/zhihu/question.test.js +34 -16
- package/dist/commanderAdapter.js +8 -4
- package/dist/commanderAdapter.test.js +42 -0
- package/dist/completion.js +3 -1
- package/dist/completion.test.d.ts +1 -0
- package/dist/completion.test.js +23 -0
- package/dist/doctor.js +1 -1
- package/dist/electron-apps.d.ts +2 -0
- package/dist/electron-apps.js +7 -1
- package/dist/errors.js +1 -1
- package/dist/execution.js +25 -35
- package/dist/explore.js +1 -1
- package/dist/launcher.d.ts +4 -0
- package/dist/launcher.js +64 -8
- package/dist/launcher.test.js +88 -7
- package/dist/output.d.ts +2 -0
- package/dist/output.js +10 -1
- package/dist/output.test.d.ts +0 -3
- package/dist/output.test.js +59 -92
- package/dist/pipeline/executor.test.js +0 -2
- package/dist/pipeline/steps/download.test.js +0 -2
- package/dist/registry.d.ts +2 -0
- package/dist/serialization.d.ts +1 -0
- package/dist/serialization.js +1 -0
- package/dist/types.d.ts +9 -2
- package/docs/.vitepress/config.mts +4 -0
- package/docs/adapters/browser/1688.md +52 -0
- package/docs/adapters/browser/36kr.md +2 -1
- package/docs/adapters/browser/doubao.md +5 -1
- package/docs/adapters/browser/hupu.md +53 -0
- package/docs/adapters/browser/sinafinance.md +32 -2
- package/docs/adapters/browser/weibo.md +6 -1
- package/docs/adapters/browser/wikipedia.md +2 -0
- package/docs/adapters/browser/xianyu.md +42 -0
- package/docs/adapters/browser/xiaoe.md +44 -0
- package/docs/adapters/browser/yuanbao.md +64 -0
- package/docs/adapters/index.md +14 -5
- package/docs/comparison.md +1 -1
- package/docs/developer/ai-workflow.md +2 -2
- package/docs/developer/contributing.md +1 -1
- package/docs/developer/testing.md +2 -0
- package/docs/guide/plugins.md +1 -0
- package/docs/guide/troubleshooting.md +11 -0
- package/docs/superpowers/specs/2026-04-03-v2ex-autoresearch-design.md +41 -0
- package/docs/zh/guide/plugins.md +1 -0
- package/extension/dist/background.js +1127 -0
- package/extension/src/background.test.ts +39 -0
- package/extension/src/background.ts +223 -34
- package/extension/src/cdp.ts +194 -4
- package/extension/src/protocol.ts +22 -1
- package/package.json +3 -2
- package/scripts/postinstall.js +1 -1
- package/skills/opencli-explorer/SKILL.md +1 -1
- package/skills/opencli-oneshot/SKILL.md +2 -2
- package/skills/opencli-operate/SKILL.md +120 -27
- package/skills/opencli-usage/SKILL.md +31 -20
- package/skills/opencli-usage/browser.md +114 -16
- package/skills/opencli-usage/public-api.md +32 -3
- package/skills/smart-search/SKILL.md +156 -0
- package/skills/smart-search/references/sources-ai.md +74 -0
- package/skills/smart-search/references/sources-info.md +43 -0
- package/skills/smart-search/references/sources-media.md +50 -0
- package/skills/smart-search/references/sources-other.md +42 -0
- package/skills/smart-search/references/sources-shopping.md +31 -0
- package/skills/smart-search/references/sources-social.md +51 -0
- package/skills/smart-search/references/sources-tech.md +42 -0
- package/skills/smart-search/references/sources-travel.md +20 -0
- package/src/browser/base-page.ts +41 -6
- package/src/browser/bridge.ts +11 -8
- package/src/browser/cdp.ts +1 -8
- package/src/browser/daemon-client.ts +11 -1
- package/src/browser/dom-helpers.ts +43 -31
- package/src/browser/dom-snapshot.ts +23 -1
- package/src/browser/page.ts +115 -31
- package/src/browser.test.ts +1 -1
- package/src/build-manifest.ts +2 -0
- package/src/cli.test.ts +133 -0
- package/src/cli.ts +73 -11
- package/src/clis/1688/item.test.ts +69 -0
- package/src/clis/1688/item.ts +282 -0
- package/src/clis/1688/search.test.ts +81 -0
- package/src/clis/1688/search.ts +402 -0
- package/src/clis/1688/shared.test.ts +75 -0
- package/src/clis/1688/shared.ts +623 -0
- package/src/clis/1688/store.test.ts +69 -0
- package/src/clis/1688/store.ts +300 -0
- package/src/clis/amazon/bestsellers.test.ts +12 -3
- package/src/clis/amazon/bestsellers.ts +6 -178
- package/src/clis/amazon/movers-shakers.ts +8 -0
- package/src/clis/amazon/new-releases.ts +8 -0
- package/src/clis/amazon/rankings.test.ts +47 -0
- package/src/clis/amazon/rankings.ts +312 -0
- package/src/clis/amazon/shared.test.ts +16 -0
- package/src/clis/amazon/shared.ts +134 -12
- package/src/clis/bilibili/comments.test.ts +4 -3
- package/src/clis/bilibili/comments.ts +2 -2
- package/src/clis/bilibili/download.ts +2 -1
- package/src/clis/bilibili/subtitle.test.ts +2 -1
- package/src/clis/bilibili/subtitle.ts +4 -3
- package/src/clis/bilibili/utils.test.ts +21 -0
- package/src/clis/bilibili/utils.ts +27 -0
- package/src/clis/douban/marks.ts +1 -1
- package/src/clis/douban/subject.yaml +50 -19
- package/src/clis/doubao/utils.ts +32 -12
- package/src/clis/douyin/_shared/browser-fetch.test.ts +0 -1
- package/src/clis/douyin/_shared/transcode.test.ts +0 -2
- package/src/clis/douyin/draft.test.ts +0 -2
- package/src/clis/facebook/search.test.ts +0 -2
- package/src/clis/gemini/ask.test.ts +116 -0
- package/src/clis/gemini/ask.ts +10 -3
- package/src/clis/gemini/reply-state.test.ts +708 -0
- package/src/clis/gemini/utils.test.ts +184 -2
- package/src/clis/gemini/utils.ts +588 -60
- package/src/clis/hupu/detail.ts +126 -0
- package/src/clis/hupu/hot.yaml +43 -0
- package/src/clis/hupu/like.ts +76 -0
- package/src/clis/hupu/reply.ts +76 -0
- package/src/clis/hupu/search.ts +95 -0
- package/src/clis/hupu/unlike.ts +76 -0
- package/src/clis/hupu/utils.ts +381 -0
- package/src/clis/instagram/_shared/private-publish.test.ts +827 -0
- package/src/clis/instagram/_shared/private-publish.ts +1303 -0
- package/src/clis/instagram/_shared/protocol-capture.test.ts +148 -0
- package/src/clis/instagram/_shared/protocol-capture.ts +321 -0
- package/src/clis/instagram/_shared/runtime-info.ts +91 -0
- package/src/clis/instagram/note.test.ts +96 -0
- package/src/clis/instagram/note.ts +254 -0
- package/src/clis/instagram/post.test.ts +1716 -0
- package/src/clis/instagram/post.ts +1620 -0
- package/src/clis/instagram/reel.test.ts +191 -0
- package/src/clis/instagram/reel.ts +886 -0
- package/src/clis/instagram/story.test.ts +191 -0
- package/src/clis/instagram/story.ts +151 -0
- package/src/clis/sinafinance/stock-rank.ts +68 -0
- package/src/clis/substack/utils.test.ts +0 -2
- package/src/clis/twitter/post.test.ts +157 -0
- package/src/clis/twitter/post.ts +82 -48
- package/src/clis/twitter/reply.test.ts +177 -0
- package/src/clis/twitter/reply.ts +285 -39
- package/src/clis/xianyu/chat.test.ts +20 -0
- package/src/clis/xianyu/chat.ts +175 -0
- package/src/clis/xianyu/item.test.ts +67 -0
- package/src/clis/xianyu/item.ts +172 -0
- package/src/clis/xianyu/search.test.ts +22 -0
- package/src/clis/xianyu/search.ts +151 -0
- package/src/clis/xianyu/utils.ts +9 -0
- package/src/clis/xiaoe/catalog.yaml +129 -0
- package/src/clis/xiaoe/content.yaml +43 -0
- package/src/clis/xiaoe/courses.yaml +73 -0
- package/src/clis/xiaoe/detail.yaml +39 -0
- package/src/clis/xiaoe/play-url.yaml +124 -0
- package/src/clis/xiaohongshu/comments.test.ts +0 -2
- package/src/clis/xiaohongshu/creator-note-detail.test.ts +0 -2
- package/src/clis/xiaohongshu/creator-notes.test.ts +0 -2
- package/src/clis/xiaohongshu/download.test.ts +0 -2
- package/src/clis/xiaohongshu/note.test.ts +0 -2
- package/src/clis/xiaohongshu/publish.test.ts +0 -2
- package/src/clis/xiaohongshu/search.test.ts +59 -48
- package/src/clis/xiaohongshu/search.ts +31 -21
- package/src/clis/yuanbao/ask.test.ts +156 -0
- package/src/clis/yuanbao/ask.ts +522 -0
- package/src/clis/yuanbao/new.test.ts +36 -0
- package/src/clis/yuanbao/new.ts +81 -0
- package/src/clis/yuanbao/shared.ts +57 -0
- package/src/clis/zhihu/question.test.ts +42 -17
- package/src/clis/zhihu/question.ts +31 -26
- package/src/commanderAdapter.test.ts +51 -0
- package/src/commanderAdapter.ts +8 -4
- package/src/completion.test.ts +30 -0
- package/src/completion.ts +3 -1
- package/src/doctor.ts +1 -1
- package/src/electron-apps.ts +9 -1
- package/src/errors.ts +1 -1
- package/src/execution.ts +26 -30
- package/src/explore.ts +1 -1
- package/src/launcher.test.ts +121 -7
- package/src/launcher.ts +87 -9
- package/src/output.test.ts +50 -90
- package/src/output.ts +10 -1
- package/src/pipeline/executor.test.ts +0 -2
- package/src/pipeline/steps/download.test.ts +0 -2
- package/src/registry.ts +2 -0
- package/src/serialization.ts +2 -0
- package/src/types.ts +9 -2
- package/tests/e2e/browser-auth.test.ts +9 -0
- package/CLI-EXPLORER.md +0 -724
- package/CLI-ONESHOT.md +0 -216
- package/SKILL.md +0 -59
package/dist/execution.js
CHANGED
|
@@ -19,7 +19,7 @@ import { emitHook } from './hooks.js';
|
|
|
19
19
|
import { checkDaemonStatus } from './browser/discover.js';
|
|
20
20
|
import { log } from './logger.js';
|
|
21
21
|
import { isElectronApp } from './electron-apps.js';
|
|
22
|
-
import { resolveElectronEndpoint } from './launcher.js';
|
|
22
|
+
import { probeCDP, resolveElectronEndpoint } from './launcher.js';
|
|
23
23
|
const _loadedModules = new Set();
|
|
24
24
|
export function coerceAndValidateArgs(cmdArgs, kwargs) {
|
|
25
25
|
const result = { ...kwargs };
|
|
@@ -111,29 +111,11 @@ function ensureRequiredEnv(cmd) {
|
|
|
111
111
|
return;
|
|
112
112
|
throw new CommandExecutionError(`Command ${fullName(cmd)} requires environment variable ${missing.name}.`, missing.help ?? `Set ${missing.name} before running ${fullName(cmd)}.`);
|
|
113
113
|
}
|
|
114
|
-
/**
|
|
115
|
-
* Check if the browser is already on the target domain, avoiding redundant navigation.
|
|
116
|
-
* Returns true if current page hostname matches the pre-nav URL hostname.
|
|
117
|
-
*/
|
|
118
|
-
async function isAlreadyOnDomain(page, targetUrl) {
|
|
119
|
-
if (!page.getCurrentUrl)
|
|
120
|
-
return false;
|
|
121
|
-
try {
|
|
122
|
-
const currentUrl = await page.getCurrentUrl();
|
|
123
|
-
if (!currentUrl)
|
|
124
|
-
return false;
|
|
125
|
-
const currentHost = new URL(currentUrl).hostname;
|
|
126
|
-
const targetHost = new URL(targetUrl).hostname;
|
|
127
|
-
return currentHost === targetHost;
|
|
128
|
-
}
|
|
129
|
-
catch {
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
114
|
export async function executeCommand(cmd, rawKwargs, debug = false) {
|
|
134
115
|
let kwargs;
|
|
135
116
|
try {
|
|
136
117
|
kwargs = coerceAndValidateArgs(cmd.args, rawKwargs);
|
|
118
|
+
cmd.validateArgs?.(kwargs);
|
|
137
119
|
}
|
|
138
120
|
catch (err) {
|
|
139
121
|
if (err instanceof ArgumentError)
|
|
@@ -152,8 +134,18 @@ export async function executeCommand(cmd, rawKwargs, debug = false) {
|
|
|
152
134
|
const electron = isElectronApp(cmd.site);
|
|
153
135
|
let cdpEndpoint;
|
|
154
136
|
if (electron) {
|
|
155
|
-
// Electron apps:
|
|
156
|
-
|
|
137
|
+
// Electron apps: respect manual endpoint override, then try auto-detect
|
|
138
|
+
const manualEndpoint = process.env.OPENCLI_CDP_ENDPOINT;
|
|
139
|
+
if (manualEndpoint) {
|
|
140
|
+
const port = Number(new URL(manualEndpoint).port);
|
|
141
|
+
if (!await probeCDP(port)) {
|
|
142
|
+
throw new CommandExecutionError(`CDP not reachable at ${manualEndpoint}`, 'Check that the app is running with --remote-debugging-port and the endpoint is correct.');
|
|
143
|
+
}
|
|
144
|
+
cdpEndpoint = manualEndpoint;
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
cdpEndpoint = await resolveElectronEndpoint(cmd.site);
|
|
148
|
+
}
|
|
157
149
|
}
|
|
158
150
|
else {
|
|
159
151
|
// Browser Bridge: fail-fast when daemon is up but extension is missing.
|
|
@@ -162,7 +154,7 @@ export async function executeCommand(cmd, rawKwargs, debug = false) {
|
|
|
162
154
|
if (status.running && !status.extensionConnected) {
|
|
163
155
|
throw new BrowserConnectError('Browser Bridge extension not connected', 'Install the Browser Bridge:\n' +
|
|
164
156
|
' 1. Download: https://github.com/jackwener/opencli/releases\n' +
|
|
165
|
-
' 2. chrome://extensions → Developer Mode → Load unpacked\n' +
|
|
157
|
+
' 2. In Chrome or Chromium, open chrome://extensions → Developer Mode → Load unpacked\n' +
|
|
166
158
|
' Then run: opencli doctor');
|
|
167
159
|
}
|
|
168
160
|
}
|
|
@@ -171,19 +163,17 @@ export async function executeCommand(cmd, rawKwargs, debug = false) {
|
|
|
171
163
|
result = await browserSession(BrowserFactory, async (page) => {
|
|
172
164
|
const preNavUrl = resolvePreNav(cmd);
|
|
173
165
|
if (preNavUrl) {
|
|
174
|
-
|
|
175
|
-
if
|
|
176
|
-
|
|
177
|
-
|
|
166
|
+
// Navigate directly — the extension's handleNavigate already has a fast-path
|
|
167
|
+
// that skips navigation if the tab is already at the target URL.
|
|
168
|
+
// This avoids an extra exec round-trip (getCurrentUrl) on first command and
|
|
169
|
+
// lets the extension create the automation window with the target URL directly
|
|
170
|
+
// instead of about:blank.
|
|
171
|
+
try {
|
|
172
|
+
await page.goto(preNavUrl);
|
|
178
173
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
catch (err) {
|
|
184
|
-
if (debug)
|
|
185
|
-
log.debug(`[pre-nav] Failed to navigate to ${preNavUrl}: ${err instanceof Error ? err.message : err}`);
|
|
186
|
-
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
if (debug)
|
|
176
|
+
log.debug(`[pre-nav] Failed to navigate to ${preNavUrl}: ${err instanceof Error ? err.message : err}`);
|
|
187
177
|
}
|
|
188
178
|
}
|
|
189
179
|
return runWithTimeout(runCommand(cmd, page, kwargs, debug), {
|
package/dist/explore.js
CHANGED
|
@@ -263,7 +263,7 @@ export async function exploreUrl(url, opts) {
|
|
|
263
263
|
await page.wait(2); // wait for XHRs to settle
|
|
264
264
|
}
|
|
265
265
|
catch (e) {
|
|
266
|
-
log.
|
|
266
|
+
log.verbose(`Interactive fuzzing skipped: ${e instanceof Error ? e.message : String(e)}`);
|
|
267
267
|
}
|
|
268
268
|
}
|
|
269
269
|
// Step 3: Read page metadata
|
package/dist/launcher.d.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* 4. Launch with --remote-debugging-port
|
|
9
9
|
* 5. Poll /json until ready
|
|
10
10
|
*/
|
|
11
|
+
import type { ElectronAppEntry } from './electron-apps.js';
|
|
11
12
|
/**
|
|
12
13
|
* Probe whether a CDP endpoint is listening on the given port.
|
|
13
14
|
* Returns true if http://127.0.0.1:{port}/json responds successfully.
|
|
@@ -28,6 +29,9 @@ export declare function killProcess(processName: string): void;
|
|
|
28
29
|
* Returns null if the app is not installed.
|
|
29
30
|
*/
|
|
30
31
|
export declare function discoverAppPath(displayName: string): string | null;
|
|
32
|
+
export declare function resolveExecutableCandidates(appPath: string, app: ElectronAppEntry): string[];
|
|
33
|
+
export declare function launchDetachedApp(executable: string, args: string[], label: string): Promise<void>;
|
|
34
|
+
export declare function launchElectronApp(appPath: string, app: ElectronAppEntry, args: string[], label: string): Promise<void>;
|
|
31
35
|
/**
|
|
32
36
|
* Main entry point: resolve an Electron app to a CDP endpoint URL.
|
|
33
37
|
*
|
package/dist/launcher.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { execFileSync, spawn } from 'node:child_process';
|
|
12
12
|
import { request as httpRequest } from 'node:http';
|
|
13
|
+
import * as path from 'node:path';
|
|
13
14
|
import { getElectronApp } from './electron-apps.js';
|
|
14
15
|
import { confirmPrompt } from './tui.js';
|
|
15
16
|
import { CommandExecutionError } from './errors.js';
|
|
@@ -38,6 +39,8 @@ export function probeCDP(port, timeoutMs = PROBE_TIMEOUT_MS) {
|
|
|
38
39
|
* Uses pgrep on macOS/Linux.
|
|
39
40
|
*/
|
|
40
41
|
export function detectProcess(processName) {
|
|
42
|
+
if (process.platform === 'win32')
|
|
43
|
+
return false; // pgrep not available on Windows
|
|
41
44
|
try {
|
|
42
45
|
execFileSync('pgrep', ['-x', processName], { encoding: 'utf-8', stdio: 'pipe' });
|
|
43
46
|
return true;
|
|
@@ -50,6 +53,8 @@ export function detectProcess(processName) {
|
|
|
50
53
|
* Kill a process by name. Sends SIGTERM first, then SIGKILL after grace period.
|
|
51
54
|
*/
|
|
52
55
|
export function killProcess(processName) {
|
|
56
|
+
if (process.platform === 'win32')
|
|
57
|
+
return; // pkill not available on Windows
|
|
53
58
|
try {
|
|
54
59
|
execFileSync('pkill', ['-x', processName], { stdio: 'pipe' });
|
|
55
60
|
}
|
|
@@ -91,6 +96,57 @@ export function discoverAppPath(displayName) {
|
|
|
91
96
|
function resolveExecutable(appPath, processName) {
|
|
92
97
|
return `${appPath}/Contents/MacOS/${processName}`;
|
|
93
98
|
}
|
|
99
|
+
function isMissingExecutableError(err, label) {
|
|
100
|
+
return err instanceof CommandExecutionError
|
|
101
|
+
&& err.message.startsWith(`Could not launch ${label}: executable not found at `);
|
|
102
|
+
}
|
|
103
|
+
export function resolveExecutableCandidates(appPath, app) {
|
|
104
|
+
const executableNames = app.executableNames?.length ? app.executableNames : [app.processName];
|
|
105
|
+
return [...new Set(executableNames)].map((name) => resolveExecutable(appPath, name));
|
|
106
|
+
}
|
|
107
|
+
export async function launchDetachedApp(executable, args, label) {
|
|
108
|
+
await new Promise((resolve, reject) => {
|
|
109
|
+
const child = spawn(executable, args, {
|
|
110
|
+
detached: true,
|
|
111
|
+
stdio: 'ignore',
|
|
112
|
+
});
|
|
113
|
+
const onError = (err) => {
|
|
114
|
+
if (err.code === 'ENOENT') {
|
|
115
|
+
reject(new CommandExecutionError(`Could not launch ${label}: executable not found at ${executable}`, `Install ${label}, reinstall it, or register a custom app path in ~/.opencli/apps.yaml`));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
reject(new CommandExecutionError(`Failed to launch ${label}`, err.message));
|
|
119
|
+
};
|
|
120
|
+
child.once('error', onError);
|
|
121
|
+
child.once('spawn', () => {
|
|
122
|
+
child.off('error', onError);
|
|
123
|
+
child.unref();
|
|
124
|
+
resolve();
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
export async function launchElectronApp(appPath, app, args, label) {
|
|
129
|
+
const executables = resolveExecutableCandidates(appPath, app);
|
|
130
|
+
let lastMissingExecutableError;
|
|
131
|
+
for (const executable of executables) {
|
|
132
|
+
log.debug(`[launcher] Launching: ${executable} ${args.join(' ')}`);
|
|
133
|
+
try {
|
|
134
|
+
await launchDetachedApp(executable, args, label);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
if (isMissingExecutableError(err, label)) {
|
|
139
|
+
lastMissingExecutableError = err;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
throw err;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (executables.length > 1) {
|
|
146
|
+
throw new CommandExecutionError(`Could not launch ${label}: no compatible executable found in ${path.join(appPath, 'Contents', 'MacOS')}`, `Tried: ${executables.map((executable) => path.basename(executable)).join(', ')}. Install ${label}, reinstall it, or register a custom app path in ~/.opencli/apps.yaml`);
|
|
147
|
+
}
|
|
148
|
+
throw lastMissingExecutableError ?? new CommandExecutionError(`Could not launch ${label}`, `Install ${label}, reinstall it, or register a custom app path in ~/.opencli/apps.yaml`);
|
|
149
|
+
}
|
|
94
150
|
async function pollForReady(port) {
|
|
95
151
|
const deadline = Date.now() + POLL_TIMEOUT_MS;
|
|
96
152
|
while (Date.now() < deadline) {
|
|
@@ -119,7 +175,13 @@ export async function resolveElectronEndpoint(site) {
|
|
|
119
175
|
log.debug(`[launcher] CDP already available on port ${port}`);
|
|
120
176
|
return endpoint;
|
|
121
177
|
}
|
|
122
|
-
// Step 2: Running without CDP?
|
|
178
|
+
// Step 2: Running without CDP? (process detection requires Unix tools)
|
|
179
|
+
if (process.platform !== 'darwin' && process.platform !== 'linux') {
|
|
180
|
+
throw new CommandExecutionError(`${label} is not reachable on CDP port ${port}.`, `Auto-launch is not yet supported on ${process.platform}.\n` +
|
|
181
|
+
`Start ${label} manually with --remote-debugging-port=${port}, then either:\n` +
|
|
182
|
+
` • Set OPENCLI_CDP_ENDPOINT=http://127.0.0.1:${port}\n` +
|
|
183
|
+
` • Or just re-run the command once ${label} is listening on port ${port}.`);
|
|
184
|
+
}
|
|
123
185
|
const isRunning = detectProcess(processName);
|
|
124
186
|
if (isRunning) {
|
|
125
187
|
log.debug(`[launcher] ${label} is running but CDP not available`);
|
|
@@ -136,14 +198,8 @@ export async function resolveElectronEndpoint(site) {
|
|
|
136
198
|
throw new CommandExecutionError(`Could not find ${label} on this machine.`, `Install ${label} or register a custom path in ~/.opencli/apps.yaml`);
|
|
137
199
|
}
|
|
138
200
|
// Step 4: Launch
|
|
139
|
-
const executable = resolveExecutable(appPath, processName);
|
|
140
201
|
const args = [`--remote-debugging-port=${port}`, ...(app.extraArgs ?? [])];
|
|
141
|
-
|
|
142
|
-
const child = spawn(executable, args, {
|
|
143
|
-
detached: true,
|
|
144
|
-
stdio: 'ignore',
|
|
145
|
-
});
|
|
146
|
-
child.unref();
|
|
202
|
+
await launchElectronApp(appPath, app, args, label);
|
|
147
203
|
// Step 5: Poll for readiness
|
|
148
204
|
process.stderr.write(` Waiting for ${label} on port ${port}...\n`);
|
|
149
205
|
await pollForReady(port);
|
package/dist/launcher.test.js
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import { detectProcess, discoverAppPath, launchDetachedApp, launchElectronApp, probeCDP, resolveExecutableCandidates } from './launcher.js';
|
|
3
|
+
function createMockChildProcess() {
|
|
4
|
+
const listeners = new Map();
|
|
5
|
+
return {
|
|
6
|
+
once: vi.fn((event, handler) => {
|
|
7
|
+
listeners.set(event, [...(listeners.get(event) ?? []), handler]);
|
|
8
|
+
}),
|
|
9
|
+
off: vi.fn((event, handler) => {
|
|
10
|
+
listeners.set(event, (listeners.get(event) ?? []).filter((listener) => listener !== handler));
|
|
11
|
+
}),
|
|
12
|
+
unref: vi.fn(),
|
|
13
|
+
emit: (event, value) => {
|
|
14
|
+
for (const listener of listeners.get(event) ?? [])
|
|
15
|
+
listener(value);
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
3
19
|
vi.mock('node:child_process', () => ({
|
|
4
20
|
execFileSync: vi.fn(),
|
|
5
|
-
spawn: vi.fn(
|
|
6
|
-
unref: vi.fn(),
|
|
7
|
-
pid: 12345,
|
|
8
|
-
on: vi.fn(),
|
|
9
|
-
})),
|
|
21
|
+
spawn: vi.fn(),
|
|
10
22
|
}));
|
|
11
23
|
const cp = vi.mocked(await import('node:child_process'));
|
|
12
24
|
describe('probeCDP', () => {
|
|
@@ -28,7 +40,7 @@ describe('detectProcess', () => {
|
|
|
28
40
|
const result = detectProcess('NonExistentApp');
|
|
29
41
|
expect(result).toBe(false);
|
|
30
42
|
});
|
|
31
|
-
it('returns true when pgrep finds a process', () => {
|
|
43
|
+
it.skipIf(process.platform === 'win32')('returns true when pgrep finds a process', () => {
|
|
32
44
|
cp.execFileSync.mockReturnValue('12345\n');
|
|
33
45
|
const result = detectProcess('Cursor');
|
|
34
46
|
expect(result).toBe(true);
|
|
@@ -55,3 +67,72 @@ describe('discoverAppPath', () => {
|
|
|
55
67
|
expect(result).toBeNull();
|
|
56
68
|
});
|
|
57
69
|
});
|
|
70
|
+
describe('launchDetachedApp', () => {
|
|
71
|
+
beforeEach(() => {
|
|
72
|
+
vi.restoreAllMocks();
|
|
73
|
+
cp.spawn.mockReset();
|
|
74
|
+
});
|
|
75
|
+
it('unrefs the process after spawn succeeds', async () => {
|
|
76
|
+
const child = createMockChildProcess();
|
|
77
|
+
cp.spawn.mockImplementation(() => {
|
|
78
|
+
queueMicrotask(() => child.emit('spawn'));
|
|
79
|
+
return child;
|
|
80
|
+
});
|
|
81
|
+
await expect(launchDetachedApp('/Applications/Antigravity.app/Contents/MacOS/Antigravity', ['--remote-debugging-port=9234'], 'Antigravity'))
|
|
82
|
+
.resolves
|
|
83
|
+
.toBeUndefined();
|
|
84
|
+
expect(child.unref).toHaveBeenCalledTimes(1);
|
|
85
|
+
});
|
|
86
|
+
it('converts ENOENT into a controlled launch error', async () => {
|
|
87
|
+
const child = createMockChildProcess();
|
|
88
|
+
cp.spawn.mockImplementation(() => {
|
|
89
|
+
queueMicrotask(() => child.emit('error', Object.assign(new Error('missing binary'), { code: 'ENOENT' })));
|
|
90
|
+
return child;
|
|
91
|
+
});
|
|
92
|
+
await expect(launchDetachedApp('/Applications/Antigravity.app/Contents/MacOS/Antigravity', ['--remote-debugging-port=9234'], 'Antigravity'))
|
|
93
|
+
.rejects
|
|
94
|
+
.toThrow('Could not launch Antigravity');
|
|
95
|
+
expect(child.unref).not.toHaveBeenCalled();
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
describe('resolveExecutableCandidates', () => {
|
|
99
|
+
it('prefers explicit executable candidates over processName', () => {
|
|
100
|
+
const app = {
|
|
101
|
+
port: 9234,
|
|
102
|
+
processName: 'Antigravity',
|
|
103
|
+
executableNames: ['Electron', 'Antigravity'],
|
|
104
|
+
};
|
|
105
|
+
expect(resolveExecutableCandidates('/Applications/Antigravity.app', app)).toEqual([
|
|
106
|
+
'/Applications/Antigravity.app/Contents/MacOS/Electron',
|
|
107
|
+
'/Applications/Antigravity.app/Contents/MacOS/Antigravity',
|
|
108
|
+
]);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
describe('launchElectronApp', () => {
|
|
112
|
+
beforeEach(() => {
|
|
113
|
+
vi.restoreAllMocks();
|
|
114
|
+
cp.spawn.mockReset();
|
|
115
|
+
});
|
|
116
|
+
it('falls back to the next executable candidate when the first is missing', async () => {
|
|
117
|
+
const firstChild = createMockChildProcess();
|
|
118
|
+
const secondChild = createMockChildProcess();
|
|
119
|
+
const app = {
|
|
120
|
+
port: 9234,
|
|
121
|
+
processName: 'Antigravity',
|
|
122
|
+
executableNames: ['Electron', 'Antigravity'],
|
|
123
|
+
};
|
|
124
|
+
cp.spawn
|
|
125
|
+
.mockImplementationOnce(() => {
|
|
126
|
+
queueMicrotask(() => firstChild.emit('error', Object.assign(new Error('missing binary'), { code: 'ENOENT' })));
|
|
127
|
+
return firstChild;
|
|
128
|
+
})
|
|
129
|
+
.mockImplementationOnce(() => {
|
|
130
|
+
queueMicrotask(() => secondChild.emit('spawn'));
|
|
131
|
+
return secondChild;
|
|
132
|
+
});
|
|
133
|
+
await expect(launchElectronApp('/Applications/Antigravity.app', app, ['--remote-debugging-port=9234'], 'Antigravity')).resolves.toBeUndefined();
|
|
134
|
+
expect(cp.spawn).toHaveBeenNthCalledWith(1, '/Applications/Antigravity.app/Contents/MacOS/Electron', ['--remote-debugging-port=9234'], { detached: true, stdio: 'ignore' });
|
|
135
|
+
expect(cp.spawn).toHaveBeenNthCalledWith(2, '/Applications/Antigravity.app/Contents/MacOS/Antigravity', ['--remote-debugging-port=9234'], { detached: true, stdio: 'ignore' });
|
|
136
|
+
expect(secondChild.unref).toHaveBeenCalledTimes(1);
|
|
137
|
+
});
|
|
138
|
+
});
|
package/dist/output.d.ts
CHANGED
package/dist/output.js
CHANGED
|
@@ -15,7 +15,16 @@ function resolveColumns(rows, opts) {
|
|
|
15
15
|
return opts.columns ?? Object.keys(rows[0] ?? {});
|
|
16
16
|
}
|
|
17
17
|
export function render(data, opts = {}) {
|
|
18
|
-
|
|
18
|
+
let fmt = opts.fmt ?? 'table';
|
|
19
|
+
// Non-TTY auto-downgrade only when format was NOT explicitly passed by user.
|
|
20
|
+
// Priority: explicit -f (any value) > OUTPUT env var > TTY auto-detect > table
|
|
21
|
+
if (!opts.fmtExplicit) {
|
|
22
|
+
const envFmt = process.env.OUTPUT?.trim().toLowerCase();
|
|
23
|
+
if (envFmt)
|
|
24
|
+
fmt = envFmt;
|
|
25
|
+
else if (fmt === 'table' && !process.stdout.isTTY)
|
|
26
|
+
fmt = 'yaml';
|
|
27
|
+
}
|
|
19
28
|
if (data === null || data === undefined) {
|
|
20
29
|
console.log(data);
|
|
21
30
|
return;
|
package/dist/output.test.d.ts
CHANGED
package/dist/output.test.js
CHANGED
|
@@ -1,95 +1,62 @@
|
|
|
1
|
-
|
|
2
|
-
* Tests for output.ts: render function format coverage.
|
|
3
|
-
*/
|
|
4
|
-
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
1
|
+
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
|
|
5
2
|
import { render } from './output.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
expect(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
expect(
|
|
40
|
-
});
|
|
41
|
-
it('
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
expect(
|
|
47
|
-
});
|
|
48
|
-
it('
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
expect(log).toHaveBeenCalledOnce();
|
|
65
|
-
expect(log.mock.calls[0]?.[0]).toContain('- title: Hello');
|
|
66
|
-
expect(log.mock.calls[0]?.[0]).toContain('rank: 1');
|
|
67
|
-
});
|
|
68
|
-
it('renders yml alias as YAML output', () => {
|
|
69
|
-
const log = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
70
|
-
render({ title: 'Hello' }, { fmt: 'yml' });
|
|
71
|
-
expect(log).toHaveBeenCalledOnce();
|
|
72
|
-
expect(log.mock.calls[0]?.[0]).toContain('title: Hello');
|
|
73
|
-
});
|
|
74
|
-
it('handles null values in CSV cells', () => {
|
|
75
|
-
const log = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
76
|
-
render([{ name: 'test', value: null }], { fmt: 'csv' });
|
|
77
|
-
const calls = log.mock.calls.map(c => c[0]);
|
|
78
|
-
expect(calls[1]).toBe('test,');
|
|
79
|
-
});
|
|
80
|
-
it('renders single-field rows in plain mode as the bare value', () => {
|
|
81
|
-
const log = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
82
|
-
render([{ response: 'Gemini says hi' }], { fmt: 'plain' });
|
|
83
|
-
expect(log).toHaveBeenCalledWith('Gemini says hi');
|
|
84
|
-
});
|
|
85
|
-
it('renders multi-field rows in plain mode as key-value lines', () => {
|
|
86
|
-
const log = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
87
|
-
render([{ status: 'ok', file: '~/tmp/a.png', link: 'https://example.com' }], { fmt: 'plain' });
|
|
88
|
-
const calls = log.mock.calls.map(c => c[0]);
|
|
89
|
-
expect(calls).toEqual([
|
|
90
|
-
'status: ok',
|
|
91
|
-
'file: ~/tmp/a.png',
|
|
92
|
-
'link: https://example.com',
|
|
93
|
-
]);
|
|
3
|
+
describe('output TTY detection', () => {
|
|
4
|
+
const originalIsTTY = process.stdout.isTTY;
|
|
5
|
+
const originalEnv = process.env.OUTPUT;
|
|
6
|
+
let logSpy;
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
9
|
+
});
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: originalIsTTY, writable: true });
|
|
12
|
+
if (originalEnv === undefined)
|
|
13
|
+
delete process.env.OUTPUT;
|
|
14
|
+
else
|
|
15
|
+
process.env.OUTPUT = originalEnv;
|
|
16
|
+
logSpy.mockRestore();
|
|
17
|
+
});
|
|
18
|
+
it('outputs YAML in non-TTY when format is default table', () => {
|
|
19
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: false, writable: true });
|
|
20
|
+
// commanderAdapter always passes fmt:'table' as default — this must still trigger downgrade
|
|
21
|
+
render([{ name: 'alice', score: 10 }], { fmt: 'table', columns: ['name', 'score'] });
|
|
22
|
+
const out = logSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
23
|
+
expect(out).toContain('name: alice');
|
|
24
|
+
expect(out).toContain('score: 10');
|
|
25
|
+
});
|
|
26
|
+
it('outputs table in TTY when format is default table', () => {
|
|
27
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: true, writable: true });
|
|
28
|
+
render([{ name: 'alice', score: 10 }], { fmt: 'table', columns: ['name', 'score'] });
|
|
29
|
+
const out = logSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
30
|
+
expect(out).toContain('alice');
|
|
31
|
+
});
|
|
32
|
+
it('respects explicit -f json even in non-TTY', () => {
|
|
33
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: false, writable: true });
|
|
34
|
+
render([{ name: 'alice' }], { fmt: 'json' });
|
|
35
|
+
const out = logSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
36
|
+
expect(JSON.parse(out)).toEqual([{ name: 'alice' }]);
|
|
37
|
+
});
|
|
38
|
+
it('OUTPUT env var overrides default table in non-TTY', () => {
|
|
39
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: false, writable: true });
|
|
40
|
+
process.env.OUTPUT = 'json';
|
|
41
|
+
render([{ name: 'alice' }], { fmt: 'table' });
|
|
42
|
+
const out = logSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
43
|
+
expect(JSON.parse(out)).toEqual([{ name: 'alice' }]);
|
|
44
|
+
});
|
|
45
|
+
it('explicit -f flag takes precedence over OUTPUT env var', () => {
|
|
46
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: false, writable: true });
|
|
47
|
+
process.env.OUTPUT = 'json';
|
|
48
|
+
render([{ name: 'alice' }], { fmt: 'csv', fmtExplicit: true });
|
|
49
|
+
const out = logSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
50
|
+
expect(out).toContain('name');
|
|
51
|
+
expect(out).toContain('alice');
|
|
52
|
+
expect(out).not.toContain('"name"'); // not JSON
|
|
53
|
+
});
|
|
54
|
+
it('explicit -f table overrides non-TTY auto-downgrade', () => {
|
|
55
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: false, writable: true });
|
|
56
|
+
render([{ name: 'alice' }], { fmt: 'table', fmtExplicit: true, columns: ['name'] });
|
|
57
|
+
const out = logSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
58
|
+
// Should be table output, not YAML
|
|
59
|
+
expect(out).not.toContain('name: alice');
|
|
60
|
+
expect(out).toContain('alice');
|
|
94
61
|
});
|
|
95
62
|
});
|
|
@@ -17,8 +17,6 @@ function createMockPage(overrides = {}) {
|
|
|
17
17
|
getFormState: vi.fn().mockResolvedValue({}),
|
|
18
18
|
wait: vi.fn(),
|
|
19
19
|
tabs: vi.fn().mockResolvedValue([]),
|
|
20
|
-
closeTab: vi.fn(),
|
|
21
|
-
newTab: vi.fn(),
|
|
22
20
|
selectTab: vi.fn(),
|
|
23
21
|
networkRequests: vi.fn().mockResolvedValue([]),
|
|
24
22
|
consoleMessages: vi.fn().mockResolvedValue(''),
|
|
@@ -29,8 +29,6 @@ function createMockPage(getCookies) {
|
|
|
29
29
|
getFormState: vi.fn().mockResolvedValue({}),
|
|
30
30
|
wait: vi.fn(),
|
|
31
31
|
tabs: vi.fn().mockResolvedValue([]),
|
|
32
|
-
closeTab: vi.fn(),
|
|
33
|
-
newTab: vi.fn(),
|
|
34
32
|
selectTab: vi.fn(),
|
|
35
33
|
networkRequests: vi.fn().mockResolvedValue([]),
|
|
36
34
|
consoleMessages: vi.fn().mockResolvedValue([]),
|
package/dist/registry.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export interface Arg {
|
|
|
14
14
|
type?: string;
|
|
15
15
|
default?: unknown;
|
|
16
16
|
required?: boolean;
|
|
17
|
+
valueRequired?: boolean;
|
|
17
18
|
positional?: boolean;
|
|
18
19
|
help?: string;
|
|
19
20
|
choices?: string[];
|
|
@@ -40,6 +41,7 @@ export interface CliCommand {
|
|
|
40
41
|
source?: string;
|
|
41
42
|
footerExtra?: (kwargs: CommandArgs) => string | undefined;
|
|
42
43
|
requiredEnv?: RequiredEnv[];
|
|
44
|
+
validateArgs?: (kwargs: CommandArgs) => void;
|
|
43
45
|
/** Deprecation note shown in help / execution warnings. */
|
|
44
46
|
deprecated?: boolean | string;
|
|
45
47
|
/** Preferred replacement command, if any. */
|
package/dist/serialization.d.ts
CHANGED
package/dist/serialization.js
CHANGED
package/dist/types.d.ts
CHANGED
|
@@ -56,8 +56,8 @@ export interface IPage {
|
|
|
56
56
|
getFormState(): Promise<any>;
|
|
57
57
|
wait(options: number | WaitOptions): Promise<void>;
|
|
58
58
|
tabs(): Promise<any>;
|
|
59
|
-
closeTab(index?: number): Promise<void>;
|
|
60
|
-
newTab(): Promise<void>;
|
|
59
|
+
closeTab?(index?: number): Promise<void>;
|
|
60
|
+
newTab?(): Promise<void>;
|
|
61
61
|
selectTab(index: number): Promise<void>;
|
|
62
62
|
networkRequests(includeStatic?: boolean): Promise<any>;
|
|
63
63
|
consoleMessages(level?: string): Promise<any>;
|
|
@@ -70,11 +70,18 @@ export interface IPage {
|
|
|
70
70
|
getInterceptedRequests(): Promise<any[]>;
|
|
71
71
|
waitForCapture(timeout?: number): Promise<void>;
|
|
72
72
|
screenshot(options?: ScreenshotOptions): Promise<string>;
|
|
73
|
+
startNetworkCapture?(pattern?: string): Promise<void>;
|
|
74
|
+
readNetworkCapture?(): Promise<unknown[]>;
|
|
73
75
|
/**
|
|
74
76
|
* Set local file paths on a file input element via CDP DOM.setFileInputFiles.
|
|
75
77
|
* Chrome reads the files directly — no base64 encoding or payload size limits.
|
|
76
78
|
*/
|
|
77
79
|
setFileInput?(files: string[], selector?: string): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Insert text via native CDP Input.insertText into the currently focused element.
|
|
82
|
+
* Useful for rich editors that ignore synthetic DOM value/text mutations.
|
|
83
|
+
*/
|
|
84
|
+
insertText?(text: string): Promise<void>;
|
|
78
85
|
closeWindow?(): Promise<void>;
|
|
79
86
|
/** Returns the current page URL, or null if unavailable. */
|
|
80
87
|
getCurrentUrl?(): Promise<string | null>;
|