@jackwener/opencli 1.6.0 → 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/CHANGELOG.md +8 -0
- 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/bun.lock +615 -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 +1133 -182
- 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/twitter/search.js +67 -5
- package/dist/clis/twitter/search.test.js +83 -5
- 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/twitter/search.test.ts +88 -5
- package/src/clis/twitter/search.ts +68 -5
- 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/src/launcher.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import { execFileSync, spawn } from 'node:child_process';
|
|
13
13
|
import { request as httpRequest } from 'node:http';
|
|
14
|
+
import * as path from 'node:path';
|
|
14
15
|
import type { ElectronAppEntry } from './electron-apps.js';
|
|
15
16
|
import { getElectronApp } from './electron-apps.js';
|
|
16
17
|
import { confirmPrompt } from './tui.js';
|
|
@@ -46,6 +47,7 @@ export function probeCDP(port: number, timeoutMs: number = PROBE_TIMEOUT_MS): Pr
|
|
|
46
47
|
* Uses pgrep on macOS/Linux.
|
|
47
48
|
*/
|
|
48
49
|
export function detectProcess(processName: string): boolean {
|
|
50
|
+
if (process.platform === 'win32') return false; // pgrep not available on Windows
|
|
49
51
|
try {
|
|
50
52
|
execFileSync('pgrep', ['-x', processName], { encoding: 'utf-8', stdio: 'pipe' });
|
|
51
53
|
return true;
|
|
@@ -58,6 +60,7 @@ export function detectProcess(processName: string): boolean {
|
|
|
58
60
|
* Kill a process by name. Sends SIGTERM first, then SIGKILL after grace period.
|
|
59
61
|
*/
|
|
60
62
|
export function killProcess(processName: string): void {
|
|
63
|
+
if (process.platform === 'win32') return; // pkill not available on Windows
|
|
61
64
|
try {
|
|
62
65
|
execFileSync('pkill', ['-x', processName], { stdio: 'pipe' });
|
|
63
66
|
} catch {
|
|
@@ -101,6 +104,78 @@ function resolveExecutable(appPath: string, processName: string): string {
|
|
|
101
104
|
return `${appPath}/Contents/MacOS/${processName}`;
|
|
102
105
|
}
|
|
103
106
|
|
|
107
|
+
function isMissingExecutableError(err: unknown, label: string): boolean {
|
|
108
|
+
return err instanceof CommandExecutionError
|
|
109
|
+
&& err.message.startsWith(`Could not launch ${label}: executable not found at `);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function resolveExecutableCandidates(appPath: string, app: ElectronAppEntry): string[] {
|
|
113
|
+
const executableNames = app.executableNames?.length ? app.executableNames : [app.processName];
|
|
114
|
+
return [...new Set(executableNames)].map((name) => resolveExecutable(appPath, name));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function launchDetachedApp(executable: string, args: string[], label: string): Promise<void> {
|
|
118
|
+
await new Promise<void>((resolve, reject) => {
|
|
119
|
+
const child = spawn(executable, args, {
|
|
120
|
+
detached: true,
|
|
121
|
+
stdio: 'ignore',
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const onError = (err: NodeJS.ErrnoException): void => {
|
|
125
|
+
if (err.code === 'ENOENT') {
|
|
126
|
+
reject(new CommandExecutionError(
|
|
127
|
+
`Could not launch ${label}: executable not found at ${executable}`,
|
|
128
|
+
`Install ${label}, reinstall it, or register a custom app path in ~/.opencli/apps.yaml`,
|
|
129
|
+
));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
reject(new CommandExecutionError(
|
|
134
|
+
`Failed to launch ${label}`,
|
|
135
|
+
err.message,
|
|
136
|
+
));
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
child.once('error', onError);
|
|
140
|
+
child.once('spawn', () => {
|
|
141
|
+
child.off('error', onError);
|
|
142
|
+
child.unref();
|
|
143
|
+
resolve();
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export async function launchElectronApp(appPath: string, app: ElectronAppEntry, args: string[], label: string): Promise<void> {
|
|
149
|
+
const executables = resolveExecutableCandidates(appPath, app);
|
|
150
|
+
let lastMissingExecutableError: CommandExecutionError | undefined;
|
|
151
|
+
|
|
152
|
+
for (const executable of executables) {
|
|
153
|
+
log.debug(`[launcher] Launching: ${executable} ${args.join(' ')}`);
|
|
154
|
+
try {
|
|
155
|
+
await launchDetachedApp(executable, args, label);
|
|
156
|
+
return;
|
|
157
|
+
} catch (err) {
|
|
158
|
+
if (isMissingExecutableError(err, label)) {
|
|
159
|
+
lastMissingExecutableError = err as CommandExecutionError;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
throw err;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (executables.length > 1) {
|
|
167
|
+
throw new CommandExecutionError(
|
|
168
|
+
`Could not launch ${label}: no compatible executable found in ${path.join(appPath, 'Contents', 'MacOS')}`,
|
|
169
|
+
`Tried: ${executables.map((executable) => path.basename(executable)).join(', ')}. Install ${label}, reinstall it, or register a custom app path in ~/.opencli/apps.yaml`,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
throw lastMissingExecutableError ?? new CommandExecutionError(
|
|
174
|
+
`Could not launch ${label}`,
|
|
175
|
+
`Install ${label}, reinstall it, or register a custom app path in ~/.opencli/apps.yaml`,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
104
179
|
async function pollForReady(port: number): Promise<void> {
|
|
105
180
|
const deadline = Date.now() + POLL_TIMEOUT_MS;
|
|
106
181
|
while (Date.now() < deadline) {
|
|
@@ -138,7 +213,17 @@ export async function resolveElectronEndpoint(site: string): Promise<string> {
|
|
|
138
213
|
return endpoint;
|
|
139
214
|
}
|
|
140
215
|
|
|
141
|
-
// Step 2: Running without CDP?
|
|
216
|
+
// Step 2: Running without CDP? (process detection requires Unix tools)
|
|
217
|
+
if (process.platform !== 'darwin' && process.platform !== 'linux') {
|
|
218
|
+
throw new CommandExecutionError(
|
|
219
|
+
`${label} is not reachable on CDP port ${port}.`,
|
|
220
|
+
`Auto-launch is not yet supported on ${process.platform}.\n` +
|
|
221
|
+
`Start ${label} manually with --remote-debugging-port=${port}, then either:\n` +
|
|
222
|
+
` • Set OPENCLI_CDP_ENDPOINT=http://127.0.0.1:${port}\n` +
|
|
223
|
+
` • Or just re-run the command once ${label} is listening on port ${port}.`,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
142
227
|
const isRunning = detectProcess(processName);
|
|
143
228
|
if (isRunning) {
|
|
144
229
|
log.debug(`[launcher] ${label} is running but CDP not available`);
|
|
@@ -166,15 +251,8 @@ export async function resolveElectronEndpoint(site: string): Promise<string> {
|
|
|
166
251
|
}
|
|
167
252
|
|
|
168
253
|
// Step 4: Launch
|
|
169
|
-
const executable = resolveExecutable(appPath, processName);
|
|
170
254
|
const args = [`--remote-debugging-port=${port}`, ...(app.extraArgs ?? [])];
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const child = spawn(executable, args, {
|
|
174
|
-
detached: true,
|
|
175
|
-
stdio: 'ignore',
|
|
176
|
-
});
|
|
177
|
-
child.unref();
|
|
255
|
+
await launchElectronApp(appPath, app, args, label);
|
|
178
256
|
|
|
179
257
|
// Step 5: Poll for readiness
|
|
180
258
|
process.stderr.write(` Waiting for ${label} on port ${port}...\n`);
|
package/src/output.test.ts
CHANGED
|
@@ -1,109 +1,69 @@
|
|
|
1
|
-
|
|
2
|
-
* Tests for output.ts: render function format coverage.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
1
|
+
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
|
|
6
2
|
import { render } from './output.js';
|
|
7
3
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
describe('render', () => {
|
|
13
|
-
it('renders JSON output', () => {
|
|
14
|
-
const log = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
15
|
-
render([{ title: 'Hello', rank: 1 }], { fmt: 'json' });
|
|
16
|
-
expect(log).toHaveBeenCalledOnce();
|
|
17
|
-
const output = log.mock.calls[0]?.[0];
|
|
18
|
-
const parsed = JSON.parse(output);
|
|
19
|
-
expect(parsed).toEqual([{ title: 'Hello', rank: 1 }]);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('renders Markdown table output', () => {
|
|
23
|
-
const log = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
24
|
-
render([{ name: 'Alice', score: 100 }], { fmt: 'md', columns: ['name', 'score'] });
|
|
25
|
-
const calls = log.mock.calls.map(c => c[0]);
|
|
26
|
-
expect(calls[0]).toContain('| name | score |');
|
|
27
|
-
expect(calls[1]).toContain('| --- | --- |');
|
|
28
|
-
expect(calls[2]).toContain('| Alice | 100 |');
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('renders CSV output with proper quoting', () => {
|
|
32
|
-
const log = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
33
|
-
render([{ name: 'Alice, Bob', value: 'say "hi"' }], { fmt: 'csv' });
|
|
34
|
-
const calls = log.mock.calls.map(c => c[0]);
|
|
35
|
-
// Header
|
|
36
|
-
expect(calls[0]).toBe('name,value');
|
|
37
|
-
// Values with commas/quotes are quoted
|
|
38
|
-
expect(calls[1]).toContain('"Alice, Bob"');
|
|
39
|
-
expect(calls[1]).toContain('"say ""hi"""');
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('handles null and undefined data', () => {
|
|
43
|
-
const log = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
44
|
-
render(null, { fmt: 'json' });
|
|
45
|
-
expect(log).toHaveBeenCalledWith(null);
|
|
46
|
-
});
|
|
4
|
+
describe('output TTY detection', () => {
|
|
5
|
+
const originalIsTTY = process.stdout.isTTY;
|
|
6
|
+
const originalEnv = process.env.OUTPUT;
|
|
7
|
+
let logSpy: ReturnType<typeof vi.spyOn>;
|
|
47
8
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
render({ title: 'Test' }, { fmt: 'json' });
|
|
51
|
-
const output = log.mock.calls[0]?.[0];
|
|
52
|
-
const parsed = JSON.parse(output);
|
|
53
|
-
expect(parsed).toEqual({ title: 'Test' });
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
54
11
|
});
|
|
55
12
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: originalIsTTY, writable: true });
|
|
15
|
+
if (originalEnv === undefined) delete process.env.OUTPUT;
|
|
16
|
+
else process.env.OUTPUT = originalEnv;
|
|
17
|
+
logSpy.mockRestore();
|
|
61
18
|
});
|
|
62
19
|
|
|
63
|
-
it('
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
expect(
|
|
20
|
+
it('outputs YAML in non-TTY when format is default table', () => {
|
|
21
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: false, writable: true });
|
|
22
|
+
// commanderAdapter always passes fmt:'table' as default — this must still trigger downgrade
|
|
23
|
+
render([{ name: 'alice', score: 10 }], { fmt: 'table', columns: ['name', 'score'] });
|
|
24
|
+
const out = logSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
|
|
25
|
+
expect(out).toContain('name: alice');
|
|
26
|
+
expect(out).toContain('score: 10');
|
|
69
27
|
});
|
|
70
28
|
|
|
71
|
-
it('
|
|
72
|
-
|
|
73
|
-
render([{
|
|
74
|
-
|
|
75
|
-
expect(
|
|
76
|
-
expect(log.mock.calls[0]?.[0]).toContain('rank: 1');
|
|
29
|
+
it('outputs table in TTY when format is default table', () => {
|
|
30
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: true, writable: true });
|
|
31
|
+
render([{ name: 'alice', score: 10 }], { fmt: 'table', columns: ['name', 'score'] });
|
|
32
|
+
const out = logSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
|
|
33
|
+
expect(out).toContain('alice');
|
|
77
34
|
});
|
|
78
35
|
|
|
79
|
-
it('
|
|
80
|
-
|
|
81
|
-
render({
|
|
82
|
-
|
|
83
|
-
expect(
|
|
36
|
+
it('respects explicit -f json even in non-TTY', () => {
|
|
37
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: false, writable: true });
|
|
38
|
+
render([{ name: 'alice' }], { fmt: 'json' });
|
|
39
|
+
const out = logSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
|
|
40
|
+
expect(JSON.parse(out)).toEqual([{ name: 'alice' }]);
|
|
84
41
|
});
|
|
85
42
|
|
|
86
|
-
it('
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
43
|
+
it('OUTPUT env var overrides default table in non-TTY', () => {
|
|
44
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: false, writable: true });
|
|
45
|
+
process.env.OUTPUT = 'json';
|
|
46
|
+
render([{ name: 'alice' }], { fmt: 'table' });
|
|
47
|
+
const out = logSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
|
|
48
|
+
expect(JSON.parse(out)).toEqual([{ name: 'alice' }]);
|
|
91
49
|
});
|
|
92
50
|
|
|
93
|
-
it('
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
51
|
+
it('explicit -f flag takes precedence over OUTPUT env var', () => {
|
|
52
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: false, writable: true });
|
|
53
|
+
process.env.OUTPUT = 'json';
|
|
54
|
+
render([{ name: 'alice' }], { fmt: 'csv', fmtExplicit: true });
|
|
55
|
+
const out = logSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
|
|
56
|
+
expect(out).toContain('name');
|
|
57
|
+
expect(out).toContain('alice');
|
|
58
|
+
expect(out).not.toContain('"name"'); // not JSON
|
|
97
59
|
});
|
|
98
60
|
|
|
99
|
-
it('
|
|
100
|
-
|
|
101
|
-
render([{
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
'link: https://example.com',
|
|
107
|
-
]);
|
|
61
|
+
it('explicit -f table overrides non-TTY auto-downgrade', () => {
|
|
62
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: false, writable: true });
|
|
63
|
+
render([{ name: 'alice' }], { fmt: 'table', fmtExplicit: true, columns: ['name'] });
|
|
64
|
+
const out = logSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
|
|
65
|
+
// Should be table output, not YAML
|
|
66
|
+
expect(out).not.toContain('name: alice');
|
|
67
|
+
expect(out).toContain('alice');
|
|
108
68
|
});
|
|
109
69
|
});
|
package/src/output.ts
CHANGED
|
@@ -8,6 +8,8 @@ import yaml from 'js-yaml';
|
|
|
8
8
|
|
|
9
9
|
export interface RenderOptions {
|
|
10
10
|
fmt?: string;
|
|
11
|
+
/** True when the user explicitly passed -f on the command line */
|
|
12
|
+
fmtExplicit?: boolean;
|
|
11
13
|
columns?: string[];
|
|
12
14
|
title?: string;
|
|
13
15
|
elapsed?: number;
|
|
@@ -26,7 +28,14 @@ function resolveColumns(rows: Record<string, unknown>[], opts: RenderOptions): s
|
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
export function render(data: unknown, opts: RenderOptions = {}): void {
|
|
29
|
-
|
|
31
|
+
let fmt = opts.fmt ?? 'table';
|
|
32
|
+
// Non-TTY auto-downgrade only when format was NOT explicitly passed by user.
|
|
33
|
+
// Priority: explicit -f (any value) > OUTPUT env var > TTY auto-detect > table
|
|
34
|
+
if (!opts.fmtExplicit) {
|
|
35
|
+
const envFmt = process.env.OUTPUT?.trim().toLowerCase();
|
|
36
|
+
if (envFmt) fmt = envFmt;
|
|
37
|
+
else if (fmt === 'table' && !process.stdout.isTTY) fmt = 'yaml';
|
|
38
|
+
}
|
|
30
39
|
if (data === null || data === undefined) {
|
|
31
40
|
console.log(data);
|
|
32
41
|
return;
|
|
@@ -20,8 +20,6 @@ function createMockPage(overrides: Partial<IPage> = {}): IPage {
|
|
|
20
20
|
getFormState: vi.fn().mockResolvedValue({}),
|
|
21
21
|
wait: vi.fn(),
|
|
22
22
|
tabs: vi.fn().mockResolvedValue([]),
|
|
23
|
-
closeTab: vi.fn(),
|
|
24
|
-
newTab: vi.fn(),
|
|
25
23
|
selectTab: vi.fn(),
|
|
26
24
|
networkRequests: vi.fn().mockResolvedValue([]),
|
|
27
25
|
consoleMessages: vi.fn().mockResolvedValue(''),
|
|
@@ -34,8 +34,6 @@ function createMockPage(getCookies: IPage['getCookies']): IPage {
|
|
|
34
34
|
getFormState: vi.fn().mockResolvedValue({}),
|
|
35
35
|
wait: vi.fn(),
|
|
36
36
|
tabs: vi.fn().mockResolvedValue([]),
|
|
37
|
-
closeTab: vi.fn(),
|
|
38
|
-
newTab: vi.fn(),
|
|
39
37
|
selectTab: vi.fn(),
|
|
40
38
|
networkRequests: vi.fn().mockResolvedValue([]),
|
|
41
39
|
consoleMessages: vi.fn().mockResolvedValue([]),
|
package/src/registry.ts
CHANGED
|
@@ -17,6 +17,7 @@ export interface Arg {
|
|
|
17
17
|
type?: string;
|
|
18
18
|
default?: unknown;
|
|
19
19
|
required?: boolean;
|
|
20
|
+
valueRequired?: boolean;
|
|
20
21
|
positional?: boolean;
|
|
21
22
|
help?: string;
|
|
22
23
|
choices?: string[];
|
|
@@ -47,6 +48,7 @@ export interface CliCommand {
|
|
|
47
48
|
source?: string;
|
|
48
49
|
footerExtra?: (kwargs: CommandArgs) => string | undefined;
|
|
49
50
|
requiredEnv?: RequiredEnv[];
|
|
51
|
+
validateArgs?: (kwargs: CommandArgs) => void;
|
|
50
52
|
/** Deprecation note shown in help / execution warnings. */
|
|
51
53
|
deprecated?: boolean | string;
|
|
52
54
|
/** Preferred replacement command, if any. */
|
package/src/serialization.ts
CHANGED
|
@@ -14,6 +14,7 @@ export type SerializedArg = {
|
|
|
14
14
|
name: string;
|
|
15
15
|
type: string;
|
|
16
16
|
required: boolean;
|
|
17
|
+
valueRequired: boolean;
|
|
17
18
|
positional: boolean;
|
|
18
19
|
choices: string[];
|
|
19
20
|
default: unknown;
|
|
@@ -26,6 +27,7 @@ export function serializeArg(a: Arg): SerializedArg {
|
|
|
26
27
|
name: a.name,
|
|
27
28
|
type: a.type ?? 'string',
|
|
28
29
|
required: !!a.required,
|
|
30
|
+
valueRequired: !!a.valueRequired,
|
|
29
31
|
positional: !!a.positional,
|
|
30
32
|
choices: a.choices ?? [],
|
|
31
33
|
default: a.default ?? null,
|
package/src/types.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>;
|
|
@@ -67,11 +67,18 @@ export interface IPage {
|
|
|
67
67
|
getInterceptedRequests(): Promise<any[]>;
|
|
68
68
|
waitForCapture(timeout?: number): Promise<void>;
|
|
69
69
|
screenshot(options?: ScreenshotOptions): Promise<string>;
|
|
70
|
+
startNetworkCapture?(pattern?: string): Promise<void>;
|
|
71
|
+
readNetworkCapture?(): Promise<unknown[]>;
|
|
70
72
|
/**
|
|
71
73
|
* Set local file paths on a file input element via CDP DOM.setFileInputFiles.
|
|
72
74
|
* Chrome reads the files directly — no base64 encoding or payload size limits.
|
|
73
75
|
*/
|
|
74
76
|
setFileInput?(files: string[], selector?: string): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Insert text via native CDP Input.insertText into the currently focused element.
|
|
79
|
+
* Useful for rich editors that ignore synthetic DOM value/text mutations.
|
|
80
|
+
*/
|
|
81
|
+
insertText?(text: string): Promise<void>;
|
|
75
82
|
closeWindow?(): Promise<void>;
|
|
76
83
|
/** Returns the current page URL, or null if unavailable. */
|
|
77
84
|
getCurrentUrl?(): Promise<string | null>;
|
|
@@ -105,6 +105,15 @@ describe('login-required commands — graceful failure', () => {
|
|
|
105
105
|
await expectGracefulAuthFailure(['xiaohongshu', 'notifications', '--limit', '3', '-f', 'json']);
|
|
106
106
|
}, 60_000);
|
|
107
107
|
|
|
108
|
+
// ── yuanbao (requires login) ──
|
|
109
|
+
it('yuanbao new fails gracefully without login', async () => {
|
|
110
|
+
await expectGracefulAuthFailure(['yuanbao', 'new', '-f', 'json']);
|
|
111
|
+
}, 60_000);
|
|
112
|
+
|
|
113
|
+
it('yuanbao ask fails gracefully without login', async () => {
|
|
114
|
+
await expectGracefulAuthFailure(['yuanbao', 'ask', '你好', '-f', 'json']);
|
|
115
|
+
}, 60_000);
|
|
116
|
+
|
|
108
117
|
// ── pixiv (requires login) ──
|
|
109
118
|
it('pixiv ranking fails gracefully without login', async () => {
|
|
110
119
|
await expectGracefulAuthFailure(['pixiv', 'ranking', '--limit', '3', '-f', 'json']);
|