@jackwener/opencli 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/build-extension.yml +2 -6
- package/.github/workflows/ci.yml +21 -1
- package/README.md +35 -6
- package/README.zh-CN.md +12 -5
- package/SKILL.md +2 -0
- package/dist/browser/cdp.d.ts +2 -1
- package/dist/browser/discover.d.ts +4 -1
- package/dist/browser/discover.js +6 -2
- package/dist/browser/errors.d.ts +2 -2
- package/dist/browser/errors.js +4 -12
- package/dist/browser/mcp.d.ts +2 -1
- package/dist/build-manifest.d.ts +2 -0
- package/dist/build-manifest.js +39 -14
- package/dist/build-manifest.test.js +21 -0
- package/dist/capabilityRouting.d.ts +2 -0
- package/dist/capabilityRouting.js +2 -1
- package/dist/cli-manifest.json +1111 -112
- package/dist/cli.js +34 -3
- package/dist/clis/36kr/article.d.ts +1 -0
- package/dist/clis/36kr/article.js +62 -0
- package/dist/clis/36kr/hot.d.ts +3 -0
- package/dist/clis/36kr/hot.js +80 -0
- package/dist/clis/36kr/hot.test.d.ts +1 -0
- package/dist/clis/36kr/hot.test.js +15 -0
- package/dist/clis/36kr/news.d.ts +1 -0
- package/dist/clis/36kr/news.js +51 -0
- package/dist/clis/36kr/news.test.d.ts +1 -0
- package/dist/clis/36kr/news.test.js +85 -0
- package/dist/clis/36kr/search.d.ts +1 -0
- package/dist/clis/36kr/search.js +72 -0
- package/dist/clis/bilibili/comments.d.ts +5 -0
- package/dist/clis/bilibili/comments.js +40 -0
- package/dist/clis/bilibili/comments.test.d.ts +1 -0
- package/dist/clis/bilibili/comments.test.js +82 -0
- package/dist/clis/chatgpt/ask.js +29 -14
- package/dist/clis/chatgpt/ax.d.ts +6 -0
- package/dist/clis/chatgpt/ax.js +172 -1
- package/dist/clis/chatgpt/model.d.ts +1 -0
- package/dist/clis/chatgpt/model.js +24 -0
- package/dist/clis/chatgpt/send.js +12 -3
- package/dist/clis/douban/download.d.ts +1 -0
- package/dist/clis/douban/download.js +67 -0
- package/dist/clis/douban/download.test.d.ts +1 -0
- package/dist/clis/douban/download.test.js +170 -0
- package/dist/clis/douban/photos.d.ts +1 -0
- package/dist/clis/douban/photos.js +34 -0
- package/dist/clis/douban/utils.d.ts +25 -0
- package/dist/clis/douban/utils.js +190 -1
- package/dist/clis/douban/utils.test.d.ts +1 -0
- package/dist/clis/douban/utils.test.js +64 -0
- package/dist/clis/imdb/person.d.ts +1 -0
- package/dist/clis/imdb/person.js +203 -0
- package/dist/clis/imdb/reviews.d.ts +1 -0
- package/dist/clis/imdb/reviews.js +88 -0
- package/dist/clis/imdb/search.d.ts +1 -0
- package/dist/clis/imdb/search.js +161 -0
- package/dist/clis/imdb/title.d.ts +1 -0
- package/dist/clis/imdb/title.js +93 -0
- package/dist/clis/imdb/top.d.ts +1 -0
- package/dist/clis/imdb/top.js +53 -0
- package/dist/clis/imdb/trending.d.ts +1 -0
- package/dist/clis/imdb/trending.js +52 -0
- package/dist/clis/imdb/utils.d.ts +46 -0
- package/dist/clis/imdb/utils.js +285 -0
- package/dist/clis/imdb/utils.test.d.ts +1 -0
- package/dist/clis/imdb/utils.test.js +88 -0
- package/dist/clis/jd/item.d.ts +4 -0
- package/dist/clis/jd/item.js +16 -15
- package/dist/clis/jd/item.test.js +16 -1
- package/dist/clis/linux-do/categories.yaml +38 -9
- package/dist/clis/linux-do/category.d.ts +1 -0
- package/dist/clis/linux-do/category.js +36 -0
- package/dist/clis/linux-do/feed.d.ts +45 -0
- package/dist/clis/linux-do/feed.js +397 -0
- package/dist/clis/linux-do/feed.test.d.ts +1 -0
- package/dist/clis/linux-do/feed.test.js +118 -0
- package/dist/clis/linux-do/hot.d.ts +1 -0
- package/dist/clis/linux-do/hot.js +25 -0
- package/dist/clis/linux-do/latest.d.ts +1 -0
- package/dist/clis/linux-do/latest.js +18 -0
- package/dist/clis/linux-do/tags.yaml +41 -0
- package/dist/clis/linux-do/topic.yaml +41 -3
- package/dist/clis/linux-do/user-posts.yaml +67 -0
- package/dist/clis/linux-do/user-topics.yaml +54 -0
- package/dist/clis/paperreview/commands.test.d.ts +3 -0
- package/dist/clis/paperreview/commands.test.js +243 -0
- package/dist/clis/paperreview/feedback.d.ts +1 -0
- package/dist/clis/paperreview/feedback.js +52 -0
- package/dist/clis/paperreview/review.d.ts +1 -0
- package/dist/clis/paperreview/review.js +37 -0
- package/dist/clis/paperreview/submit.d.ts +1 -0
- package/dist/clis/paperreview/submit.js +85 -0
- package/dist/clis/paperreview/utils.d.ts +46 -0
- package/dist/clis/paperreview/utils.js +197 -0
- package/dist/clis/paperreview/utils.test.d.ts +1 -0
- package/dist/clis/paperreview/utils.test.js +49 -0
- package/dist/clis/producthunt/browse.d.ts +1 -0
- package/dist/clis/producthunt/browse.js +99 -0
- package/dist/clis/producthunt/hot.d.ts +1 -0
- package/dist/clis/producthunt/hot.js +110 -0
- package/dist/clis/producthunt/posts.d.ts +1 -0
- package/dist/clis/producthunt/posts.js +28 -0
- package/dist/clis/producthunt/today.d.ts +1 -0
- package/dist/clis/producthunt/today.js +35 -0
- package/dist/clis/producthunt/utils.d.ts +29 -0
- package/dist/clis/producthunt/utils.js +99 -0
- package/dist/clis/producthunt/utils.test.d.ts +1 -0
- package/dist/clis/producthunt/utils.test.js +64 -0
- package/dist/clis/twitter/article.js +4 -28
- package/dist/clis/twitter/likes.d.ts +24 -0
- package/dist/clis/twitter/likes.js +217 -0
- package/dist/clis/twitter/likes.test.d.ts +1 -0
- package/dist/clis/twitter/likes.test.js +85 -0
- package/dist/clis/twitter/profile.js +4 -28
- package/dist/clis/twitter/search.js +2 -1
- package/dist/clis/twitter/search.test.js +2 -0
- package/dist/clis/twitter/shared.d.ts +6 -0
- package/dist/clis/twitter/shared.js +35 -0
- package/dist/clis/twitter/timeline.js +2 -13
- package/dist/clis/weixin/download.d.ts +17 -0
- package/dist/clis/weixin/download.js +88 -20
- package/dist/clis/weread/book.js +2 -2
- package/dist/clis/weread/commands.test.d.ts +3 -0
- package/dist/clis/weread/commands.test.js +43 -0
- package/dist/clis/weread/highlights.js +2 -2
- package/dist/clis/weread/notebooks.js +2 -2
- package/dist/clis/weread/notes.js +3 -3
- package/dist/clis/weread/shelf.js +2 -2
- package/dist/clis/weread/utils.d.ts +4 -4
- package/dist/clis/weread/utils.js +32 -14
- package/dist/clis/weread/utils.test.js +1 -28
- package/dist/clis/xiaohongshu/comments.d.ts +5 -0
- package/dist/clis/xiaohongshu/comments.js +74 -0
- package/dist/clis/xiaohongshu/comments.test.d.ts +1 -0
- package/dist/clis/xiaohongshu/comments.test.js +79 -0
- package/dist/clis/xiaohongshu/publish.js +114 -18
- package/dist/clis/xiaohongshu/publish.test.d.ts +1 -0
- package/dist/clis/xiaohongshu/publish.test.js +119 -0
- package/dist/commanderAdapter.d.ts +1 -0
- package/dist/commanderAdapter.js +176 -29
- package/dist/commanderAdapter.test.d.ts +1 -0
- package/dist/commanderAdapter.test.js +62 -0
- package/dist/daemon.js +17 -1
- package/dist/discovery.js +8 -14
- package/dist/doctor.d.ts +1 -0
- package/dist/doctor.js +9 -2
- package/dist/download/index.js +63 -51
- package/dist/download/index.test.js +17 -4
- package/dist/errors.d.ts +3 -1
- package/dist/errors.js +15 -32
- package/dist/execution.d.ts +1 -3
- package/dist/execution.js +21 -1
- package/dist/hooks.js +2 -0
- package/dist/main.js +5 -0
- package/dist/output.js +5 -1
- package/dist/pipeline/executor.js +3 -4
- package/dist/plugin-manifest.d.ts +70 -0
- package/dist/plugin-manifest.js +160 -0
- package/dist/plugin-manifest.test.d.ts +4 -0
- package/dist/plugin-manifest.test.js +179 -0
- package/dist/plugin.d.ts +38 -5
- package/dist/plugin.js +267 -33
- package/dist/plugin.test.js +220 -3
- package/dist/registry.d.ts +4 -0
- package/dist/registry.js +2 -0
- package/dist/runtime-detect.d.ts +21 -0
- package/dist/runtime-detect.js +32 -0
- package/dist/runtime-detect.test.d.ts +1 -0
- package/dist/runtime-detect.test.js +27 -0
- package/dist/runtime.js +1 -1
- package/dist/serialization.d.ts +2 -0
- package/dist/serialization.js +6 -0
- package/dist/types.d.ts +1 -0
- package/dist/update-check.d.ts +22 -0
- package/dist/update-check.js +112 -0
- package/dist/weixin-download.test.d.ts +1 -0
- package/dist/weixin-download.test.js +30 -0
- package/dist/weread-private-api-regression.test.d.ts +1 -0
- package/dist/weread-private-api-regression.test.js +122 -0
- package/dist/yaml-schema.d.ts +3 -0
- package/dist/yaml-schema.js +18 -1
- package/docs/.vitepress/config.mts +4 -0
- package/docs/adapters/browser/36kr.md +47 -0
- package/docs/adapters/browser/douban.md +14 -0
- package/docs/adapters/browser/imdb.md +47 -0
- package/docs/adapters/browser/jd.md +2 -2
- package/docs/adapters/browser/linux-do.md +181 -20
- package/docs/adapters/browser/paperreview.md +43 -0
- package/docs/adapters/browser/producthunt.md +49 -0
- package/docs/adapters/desktop/chatgpt.md +5 -0
- package/docs/adapters/index.md +6 -2
- package/docs/advanced/download.md +4 -0
- package/docs/advanced/rate-limiter-plugin.md +99 -0
- package/docs/guide/electron-app-cli.md +200 -0
- package/docs/guide/getting-started.md +1 -0
- package/docs/guide/plugins.md +87 -0
- package/docs/zh/guide/electron-app-cli.md +188 -0
- package/docs/zh/guide/getting-started.md +1 -0
- package/docs/zh/guide/plugins.md +65 -0
- package/extension/package.json +1 -0
- package/extension/scripts/package-release.mjs +179 -0
- package/extension/src/background.ts +2 -0
- package/package.json +4 -1
- package/scripts/postinstall.js +10 -0
- package/src/browser/cdp.ts +2 -1
- package/src/browser/discover.ts +8 -3
- package/src/browser/errors.ts +13 -14
- package/src/browser/mcp.ts +2 -1
- package/src/build-manifest.test.ts +23 -0
- package/src/build-manifest.ts +40 -15
- package/src/capabilityRouting.ts +2 -1
- package/src/cli.ts +35 -3
- package/src/clis/36kr/article.ts +69 -0
- package/src/clis/36kr/hot.test.ts +19 -0
- package/src/clis/36kr/hot.ts +100 -0
- package/src/clis/36kr/news.test.ts +90 -0
- package/src/clis/36kr/news.ts +54 -0
- package/src/clis/36kr/search.ts +78 -0
- package/src/clis/bilibili/comments.test.ts +102 -0
- package/src/clis/bilibili/comments.ts +44 -0
- package/src/clis/chatgpt/ask.ts +28 -14
- package/src/clis/chatgpt/ax.ts +180 -1
- package/src/clis/chatgpt/model.ts +27 -0
- package/src/clis/chatgpt/send.ts +16 -6
- package/src/clis/douban/download.test.ts +196 -0
- package/src/clis/douban/download.ts +78 -0
- package/src/clis/douban/photos.ts +36 -0
- package/src/clis/douban/utils.test.ts +97 -0
- package/src/clis/douban/utils.ts +232 -1
- package/src/clis/imdb/person.ts +232 -0
- package/src/clis/imdb/reviews.ts +111 -0
- package/src/clis/imdb/search.ts +179 -0
- package/src/clis/imdb/title.ts +121 -0
- package/src/clis/imdb/top.ts +67 -0
- package/src/clis/imdb/trending.ts +66 -0
- package/src/clis/imdb/utils.test.ts +117 -0
- package/src/clis/imdb/utils.ts +305 -0
- package/src/clis/jd/item.test.ts +18 -1
- package/src/clis/jd/item.ts +18 -15
- package/src/clis/linux-do/categories.yaml +38 -9
- package/src/clis/linux-do/category.ts +37 -0
- package/src/clis/linux-do/feed.test.ts +132 -0
- package/src/clis/linux-do/feed.ts +501 -0
- package/src/clis/linux-do/hot.ts +26 -0
- package/src/clis/linux-do/latest.ts +19 -0
- package/src/clis/linux-do/tags.yaml +41 -0
- package/src/clis/linux-do/topic.yaml +41 -3
- package/src/clis/linux-do/user-posts.yaml +67 -0
- package/src/clis/linux-do/user-topics.yaml +54 -0
- package/src/clis/paperreview/commands.test.ts +283 -0
- package/src/clis/paperreview/feedback.ts +64 -0
- package/src/clis/paperreview/review.ts +47 -0
- package/src/clis/paperreview/submit.ts +119 -0
- package/src/clis/paperreview/utils.test.ts +68 -0
- package/src/clis/paperreview/utils.ts +276 -0
- package/src/clis/producthunt/browse.ts +109 -0
- package/src/clis/producthunt/hot.ts +127 -0
- package/src/clis/producthunt/posts.ts +29 -0
- package/src/clis/producthunt/today.ts +37 -0
- package/src/clis/producthunt/utils.test.ts +72 -0
- package/src/clis/producthunt/utils.ts +122 -0
- package/src/clis/twitter/article.ts +5 -28
- package/src/clis/twitter/likes.test.ts +91 -0
- package/src/clis/twitter/likes.ts +256 -0
- package/src/clis/twitter/profile.ts +5 -28
- package/src/clis/twitter/search.test.ts +2 -0
- package/src/clis/twitter/search.ts +3 -1
- package/src/clis/twitter/shared.ts +45 -0
- package/src/clis/twitter/timeline.ts +2 -13
- package/src/clis/weixin/download.ts +114 -20
- package/src/clis/weread/book.ts +2 -2
- package/src/clis/weread/commands.test.ts +57 -0
- package/src/clis/weread/highlights.ts +2 -2
- package/src/clis/weread/notebooks.ts +2 -2
- package/src/clis/weread/notes.ts +3 -3
- package/src/clis/weread/shelf.ts +2 -2
- package/src/clis/weread/utils.test.ts +1 -32
- package/src/clis/weread/utils.ts +41 -16
- package/src/clis/xiaohongshu/comments.test.ts +96 -0
- package/src/clis/xiaohongshu/comments.ts +81 -0
- package/src/clis/xiaohongshu/publish.test.ts +137 -0
- package/src/clis/xiaohongshu/publish.ts +129 -18
- package/src/commanderAdapter.test.ts +78 -0
- package/src/commanderAdapter.ts +188 -24
- package/src/daemon.ts +19 -1
- package/src/discovery.ts +8 -15
- package/src/doctor.ts +13 -2
- package/src/download/index.test.ts +14 -4
- package/src/download/index.ts +67 -55
- package/src/errors.ts +25 -66
- package/src/execution.ts +28 -3
- package/src/hooks.ts +1 -0
- package/src/main.ts +6 -0
- package/src/output.ts +3 -1
- package/src/pipeline/executor.ts +4 -6
- package/src/plugin-manifest.test.ts +223 -0
- package/src/plugin-manifest.ts +206 -0
- package/src/plugin.test.ts +246 -2
- package/src/plugin.ts +338 -36
- package/src/registry.ts +6 -1
- package/src/runtime-detect.test.ts +30 -0
- package/src/runtime-detect.ts +36 -0
- package/src/runtime.ts +1 -1
- package/src/serialization.ts +4 -0
- package/src/types.ts +1 -0
- package/src/update-check.ts +114 -0
- package/src/weixin-download.test.ts +64 -0
- package/src/weread-private-api-regression.test.ts +150 -0
- package/src/yaml-schema.ts +20 -0
- package/tests/e2e/browser-auth.test.ts +13 -9
- package/tests/e2e/browser-public-extended.test.ts +1 -1
- package/tests/e2e/browser-public.test.ts +62 -4
- package/tests/e2e/helpers.ts +2 -1
- package/tests/e2e/public-commands.test.ts +37 -3
- package/tests/smoke/api-health.test.ts +1 -1
- package/vitest.config.ts +10 -0
- package/dist/clis/linux-do/category.yaml +0 -51
- package/dist/clis/linux-do/hot.yaml +0 -50
- package/dist/clis/linux-do/latest.yaml +0 -40
- package/src/clis/linux-do/category.yaml +0 -51
- package/src/clis/linux-do/hot.yaml +0 -50
- package/src/clis/linux-do/latest.yaml +0 -40
package/dist/discovery.js
CHANGED
|
@@ -19,6 +19,7 @@ import { log } from './logger.js';
|
|
|
19
19
|
export const PLUGINS_DIR = path.join(os.homedir(), '.opencli', 'plugins');
|
|
20
20
|
/** Matches files that register commands via cli() or lifecycle hooks */
|
|
21
21
|
const PLUGIN_MODULE_PATTERN = /\b(?:cli|onStartup|onBeforeExecute|onAfterExecute)\s*\(/;
|
|
22
|
+
import { parseYamlArgs } from './yaml-schema.js';
|
|
22
23
|
function parseStrategy(rawStrategy, fallback = Strategy.COOKIE) {
|
|
23
24
|
if (!rawStrategy)
|
|
24
25
|
return fallback;
|
|
@@ -71,6 +72,8 @@ async function loadFromManifest(manifestPath, clisDir) {
|
|
|
71
72
|
pipeline: entry.pipeline,
|
|
72
73
|
timeoutSeconds: entry.timeout,
|
|
73
74
|
source: `manifest:${entry.site}/${entry.name}`,
|
|
75
|
+
deprecated: entry.deprecated,
|
|
76
|
+
replacedBy: entry.replacedBy,
|
|
74
77
|
navigateBefore: entry.navigateBefore,
|
|
75
78
|
};
|
|
76
79
|
registerCommand(cmd);
|
|
@@ -91,6 +94,8 @@ async function loadFromManifest(manifestPath, clisDir) {
|
|
|
91
94
|
columns: entry.columns,
|
|
92
95
|
timeoutSeconds: entry.timeout,
|
|
93
96
|
source: modulePath,
|
|
97
|
+
deprecated: entry.deprecated,
|
|
98
|
+
replacedBy: entry.replacedBy,
|
|
94
99
|
navigateBefore: entry.navigateBefore,
|
|
95
100
|
_lazy: true,
|
|
96
101
|
_modulePath: modulePath,
|
|
@@ -153,20 +158,7 @@ async function registerYamlCli(filePath, defaultSite) {
|
|
|
153
158
|
const strategyStr = cliDef.strategy ?? (cliDef.browser === false ? 'public' : 'cookie');
|
|
154
159
|
const strategy = parseStrategy(strategyStr);
|
|
155
160
|
const browser = cliDef.browser ?? (strategy !== Strategy.PUBLIC);
|
|
156
|
-
const args =
|
|
157
|
-
if (cliDef.args && typeof cliDef.args === 'object') {
|
|
158
|
-
for (const [argName, argDef] of Object.entries(cliDef.args)) {
|
|
159
|
-
args.push({
|
|
160
|
-
name: argName,
|
|
161
|
-
type: argDef?.type ?? 'str',
|
|
162
|
-
default: argDef?.default,
|
|
163
|
-
required: argDef?.required ?? false,
|
|
164
|
-
positional: argDef?.positional ?? false,
|
|
165
|
-
help: argDef?.description ?? argDef?.help ?? '',
|
|
166
|
-
choices: argDef?.choices,
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
}
|
|
161
|
+
const args = parseYamlArgs(cliDef.args);
|
|
170
162
|
const cmd = {
|
|
171
163
|
site,
|
|
172
164
|
name,
|
|
@@ -179,6 +171,8 @@ async function registerYamlCli(filePath, defaultSite) {
|
|
|
179
171
|
pipeline: cliDef.pipeline,
|
|
180
172
|
timeoutSeconds: cliDef.timeout,
|
|
181
173
|
source: filePath,
|
|
174
|
+
deprecated: cliDef.deprecated,
|
|
175
|
+
replacedBy: cliDef.replacedBy,
|
|
182
176
|
navigateBefore: cliDef.navigateBefore,
|
|
183
177
|
};
|
|
184
178
|
registerCommand(cmd);
|
package/dist/doctor.d.ts
CHANGED
package/dist/doctor.js
CHANGED
|
@@ -10,6 +10,7 @@ import { checkDaemonStatus } from './browser/discover.js';
|
|
|
10
10
|
import { BrowserBridge } from './browser/index.js';
|
|
11
11
|
import { listSessions } from './browser/daemon-client.js';
|
|
12
12
|
import { getErrorMessage } from './errors.js';
|
|
13
|
+
import { getRuntimeLabel } from './runtime-detect.js';
|
|
13
14
|
/**
|
|
14
15
|
* Test connectivity by attempting a real browser command.
|
|
15
16
|
*/
|
|
@@ -64,23 +65,29 @@ export async function runBrowserDoctor(opts = {}) {
|
|
|
64
65
|
if (connectivity && !connectivity.ok) {
|
|
65
66
|
issues.push(`Browser connectivity test failed: ${connectivity.error ?? 'unknown'}`);
|
|
66
67
|
}
|
|
68
|
+
if (status.extensionVersion && opts.cliVersion && status.extensionVersion !== opts.cliVersion) {
|
|
69
|
+
issues.push(`Extension version mismatch: extension v${status.extensionVersion} ≠ CLI v${opts.cliVersion}\n` +
|
|
70
|
+
' Download the latest extension from: https://github.com/jackwener/opencli/releases');
|
|
71
|
+
}
|
|
67
72
|
return {
|
|
68
73
|
cliVersion: opts.cliVersion,
|
|
69
74
|
daemonRunning: status.running,
|
|
70
75
|
extensionConnected: status.extensionConnected,
|
|
76
|
+
extensionVersion: status.extensionVersion,
|
|
71
77
|
connectivity,
|
|
72
78
|
sessions,
|
|
73
79
|
issues,
|
|
74
80
|
};
|
|
75
81
|
}
|
|
76
82
|
export function renderBrowserDoctorReport(report) {
|
|
77
|
-
const lines = [chalk.bold(`opencli v${report.cliVersion ?? 'unknown'} doctor`), ''];
|
|
83
|
+
const lines = [chalk.bold(`opencli v${report.cliVersion ?? 'unknown'} doctor`) + chalk.dim(` (${getRuntimeLabel()})`), ''];
|
|
78
84
|
// Daemon status
|
|
79
85
|
const daemonIcon = report.daemonRunning ? chalk.green('[OK]') : chalk.red('[MISSING]');
|
|
80
86
|
lines.push(`${daemonIcon} Daemon: ${report.daemonRunning ? `running on port ${DEFAULT_DAEMON_PORT}` : 'not running'}`);
|
|
81
87
|
// Extension status
|
|
82
88
|
const extIcon = report.extensionConnected ? chalk.green('[OK]') : chalk.yellow('[MISSING]');
|
|
83
|
-
|
|
89
|
+
const extVersion = report.extensionVersion ? chalk.dim(` (v${report.extensionVersion})`) : '';
|
|
90
|
+
lines.push(`${extIcon} Extension: ${report.extensionConnected ? 'connected' : 'not connected'}${extVersion}`);
|
|
84
91
|
// Connectivity
|
|
85
92
|
if (report.connectivity) {
|
|
86
93
|
const connIcon = report.connectivity.ok ? chalk.green('[OK]') : chalk.red('[FAIL]');
|
package/dist/download/index.js
CHANGED
|
@@ -7,6 +7,8 @@ import * as path from 'node:path';
|
|
|
7
7
|
import * as https from 'node:https';
|
|
8
8
|
import * as http from 'node:http';
|
|
9
9
|
import * as os from 'node:os';
|
|
10
|
+
import { Transform } from 'node:stream';
|
|
11
|
+
import { pipeline } from 'node:stream/promises';
|
|
10
12
|
import { URL } from 'node:url';
|
|
11
13
|
import { isBinaryInstalled } from '../external.js';
|
|
12
14
|
import { getErrorMessage } from '../errors.js';
|
|
@@ -68,65 +70,75 @@ export async function httpDownload(url, destPath, options = {}, redirectCount =
|
|
|
68
70
|
if (cookies) {
|
|
69
71
|
requestHeaders['Cookie'] = cookies;
|
|
70
72
|
}
|
|
71
|
-
// Ensure directory exists
|
|
72
|
-
const dir = path.dirname(destPath);
|
|
73
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
74
73
|
const tempPath = `${destPath}.tmp`;
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
79
|
-
file.close();
|
|
80
|
-
if (fs.existsSync(tempPath))
|
|
81
|
-
fs.unlinkSync(tempPath);
|
|
82
|
-
if (redirectCount >= maxRedirects) {
|
|
83
|
-
resolve({ success: false, size: 0, error: `Too many redirects (> ${maxRedirects})` });
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
const redirectUrl = resolveRedirectUrl(url, response.headers.location);
|
|
87
|
-
const originalHost = new URL(url).hostname;
|
|
88
|
-
const redirectHost = new URL(redirectUrl).hostname;
|
|
89
|
-
// Do not forward cookies when a redirect crosses host boundaries.
|
|
90
|
-
const redirectOptions = originalHost === redirectHost
|
|
91
|
-
? options
|
|
92
|
-
: { ...options, cookies: undefined, headers: stripCookieHeaders(options.headers) };
|
|
93
|
-
httpDownload(redirectUrl, destPath, redirectOptions, redirectCount + 1).then(resolve);
|
|
74
|
+
let settled = false;
|
|
75
|
+
const finish = (result) => {
|
|
76
|
+
if (settled)
|
|
94
77
|
return;
|
|
78
|
+
settled = true;
|
|
79
|
+
resolve(result);
|
|
80
|
+
};
|
|
81
|
+
const cleanupTempFile = async () => {
|
|
82
|
+
try {
|
|
83
|
+
await fs.promises.rm(tempPath, { force: true });
|
|
95
84
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (fs.existsSync(tempPath))
|
|
99
|
-
fs.unlinkSync(tempPath);
|
|
100
|
-
resolve({ success: false, size: 0, error: `HTTP ${response.statusCode}` });
|
|
101
|
-
return;
|
|
85
|
+
catch {
|
|
86
|
+
// Ignore cleanup errors so the original failure is preserved.
|
|
102
87
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
88
|
+
};
|
|
89
|
+
const request = protocol.get(url, { headers: requestHeaders, timeout }, (response) => {
|
|
90
|
+
void (async () => {
|
|
91
|
+
// Handle redirects before creating any file handles.
|
|
92
|
+
if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
93
|
+
response.resume();
|
|
94
|
+
if (redirectCount >= maxRedirects) {
|
|
95
|
+
finish({ success: false, size: 0, error: `Too many redirects (> ${maxRedirects})` });
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const redirectUrl = resolveRedirectUrl(url, response.headers.location);
|
|
99
|
+
const originalHost = new URL(url).hostname;
|
|
100
|
+
const redirectHost = new URL(redirectUrl).hostname;
|
|
101
|
+
const redirectOptions = originalHost === redirectHost
|
|
102
|
+
? options
|
|
103
|
+
: { ...options, cookies: undefined, headers: stripCookieHeaders(options.headers) };
|
|
104
|
+
finish(await httpDownload(redirectUrl, destPath, redirectOptions, redirectCount + 1));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (response.statusCode !== 200) {
|
|
108
|
+
response.resume();
|
|
109
|
+
finish({ success: false, size: 0, error: `HTTP ${response.statusCode}` });
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const totalSize = parseInt(response.headers['content-length'] || '0', 10);
|
|
113
|
+
let received = 0;
|
|
114
|
+
const progressStream = new Transform({
|
|
115
|
+
transform(chunk, _encoding, callback) {
|
|
116
|
+
received += chunk.length;
|
|
117
|
+
if (onProgress)
|
|
118
|
+
onProgress(received, totalSize);
|
|
119
|
+
callback(null, chunk);
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
try {
|
|
123
|
+
await fs.promises.mkdir(path.dirname(destPath), { recursive: true });
|
|
124
|
+
await pipeline(response, progressStream, fs.createWriteStream(tempPath));
|
|
125
|
+
await fs.promises.rename(tempPath, destPath);
|
|
126
|
+
finish({ success: true, size: received });
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
await cleanupTempFile();
|
|
130
|
+
finish({ success: false, size: 0, error: getErrorMessage(err) });
|
|
131
|
+
}
|
|
132
|
+
})();
|
|
117
133
|
});
|
|
118
134
|
request.on('error', (err) => {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
135
|
+
void (async () => {
|
|
136
|
+
await cleanupTempFile();
|
|
137
|
+
finish({ success: false, size: 0, error: err.message });
|
|
138
|
+
})();
|
|
123
139
|
});
|
|
124
140
|
request.on('timeout', () => {
|
|
125
|
-
request.destroy();
|
|
126
|
-
file.close();
|
|
127
|
-
if (fs.existsSync(tempPath))
|
|
128
|
-
fs.unlinkSync(tempPath);
|
|
129
|
-
resolve({ success: false, size: 0, error: 'Timeout' });
|
|
141
|
+
request.destroy(new Error('Timeout'));
|
|
130
142
|
});
|
|
131
143
|
});
|
|
132
144
|
}
|
|
@@ -5,11 +5,19 @@ import * as path from 'node:path';
|
|
|
5
5
|
import { afterEach, describe, expect, it } from 'vitest';
|
|
6
6
|
import { formatCookieHeader, httpDownload, resolveRedirectUrl } from './index.js';
|
|
7
7
|
const servers = [];
|
|
8
|
+
const tempDirs = [];
|
|
8
9
|
afterEach(async () => {
|
|
9
10
|
await Promise.all(servers.map((server) => new Promise((resolve, reject) => {
|
|
10
11
|
server.close((err) => (err ? reject(err) : resolve()));
|
|
11
12
|
})));
|
|
12
13
|
servers.length = 0;
|
|
14
|
+
for (const dir of tempDirs) {
|
|
15
|
+
try {
|
|
16
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
17
|
+
}
|
|
18
|
+
catch { /* ignore */ }
|
|
19
|
+
}
|
|
20
|
+
tempDirs.length = 0;
|
|
13
21
|
});
|
|
14
22
|
async function startServer(handler, hostname = '127.0.0.1') {
|
|
15
23
|
const server = http.createServer(handler);
|
|
@@ -21,7 +29,9 @@ async function startServer(handler, hostname = '127.0.0.1') {
|
|
|
21
29
|
}
|
|
22
30
|
return `http://${hostname}:${address.port}`;
|
|
23
31
|
}
|
|
24
|
-
|
|
32
|
+
// Windows Defender can briefly lock newly-written .tmp files, causing EPERM.
|
|
33
|
+
// Retry once to handle this flakiness.
|
|
34
|
+
describe('download helpers', { retry: process.platform === 'win32' ? 2 : 0 }, () => {
|
|
25
35
|
it('resolves relative redirects against the original URL', () => {
|
|
26
36
|
expect(resolveRedirectUrl('https://example.com/a/file', '/cdn/file.bin')).toBe('https://example.com/cdn/file.bin');
|
|
27
37
|
expect(resolveRedirectUrl('https://example.com/a/file', '../next')).toBe('https://example.com/next');
|
|
@@ -38,7 +48,8 @@ describe('download helpers', () => {
|
|
|
38
48
|
res.setHeader('Location', '/loop');
|
|
39
49
|
res.end();
|
|
40
50
|
});
|
|
41
|
-
const tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'opencli-
|
|
51
|
+
const tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'opencli-dl-'));
|
|
52
|
+
tempDirs.push(tempDir);
|
|
42
53
|
const destPath = path.join(tempDir, 'file.txt');
|
|
43
54
|
const result = await httpDownload(`${baseUrl}/loop`, destPath, { maxRedirects: 2 });
|
|
44
55
|
expect(result).toEqual({
|
|
@@ -60,7 +71,8 @@ describe('download helpers', () => {
|
|
|
60
71
|
res.setHeader('Location', targetUrl);
|
|
61
72
|
res.end();
|
|
62
73
|
});
|
|
63
|
-
const tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'opencli-
|
|
74
|
+
const tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'opencli-dl-'));
|
|
75
|
+
tempDirs.push(tempDir);
|
|
64
76
|
const destPath = path.join(tempDir, 'redirect.txt');
|
|
65
77
|
const result = await httpDownload(`${redirectUrl}/start`, destPath, { cookies: 'sid=abc' });
|
|
66
78
|
expect(result).toEqual({ success: true, size: 2 });
|
|
@@ -79,7 +91,8 @@ describe('download helpers', () => {
|
|
|
79
91
|
res.setHeader('Location', targetUrl);
|
|
80
92
|
res.end();
|
|
81
93
|
});
|
|
82
|
-
const tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'opencli-
|
|
94
|
+
const tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'opencli-dl-'));
|
|
95
|
+
tempDirs.push(tempDir);
|
|
83
96
|
const destPath = path.join(tempDir, 'redirect-header.txt');
|
|
84
97
|
const result = await httpDownload(`${redirectUrl}/start`, destPath, {
|
|
85
98
|
headers: { Cookie: 'sid=header-cookie' },
|
package/dist/errors.d.ts
CHANGED
|
@@ -12,8 +12,10 @@ export declare class CliError extends Error {
|
|
|
12
12
|
readonly hint?: string;
|
|
13
13
|
constructor(code: string, message: string, hint?: string);
|
|
14
14
|
}
|
|
15
|
+
export type BrowserConnectKind = 'daemon-not-running' | 'extension-not-connected' | 'command-failed' | 'unknown';
|
|
15
16
|
export declare class BrowserConnectError extends CliError {
|
|
16
|
-
|
|
17
|
+
readonly kind: BrowserConnectKind;
|
|
18
|
+
constructor(message: string, hint?: string, kind?: BrowserConnectKind);
|
|
17
19
|
}
|
|
18
20
|
export declare class AdapterLoadError extends CliError {
|
|
19
21
|
constructor(message: string, hint?: string);
|
package/dist/errors.js
CHANGED
|
@@ -12,74 +12,50 @@ export class CliError extends Error {
|
|
|
12
12
|
hint;
|
|
13
13
|
constructor(code, message, hint) {
|
|
14
14
|
super(message);
|
|
15
|
-
this.name =
|
|
15
|
+
this.name = new.target.name;
|
|
16
16
|
this.code = code;
|
|
17
17
|
this.hint = hint;
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
-
// ── Browser / Connection ────────────────────────────────────────────────────
|
|
21
20
|
export class BrowserConnectError extends CliError {
|
|
22
|
-
|
|
21
|
+
kind;
|
|
22
|
+
constructor(message, hint, kind = 'unknown') {
|
|
23
23
|
super('BROWSER_CONNECT', message, hint);
|
|
24
|
-
this.
|
|
24
|
+
this.kind = kind;
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
|
-
// ── Adapter loading ─────────────────────────────────────────────────────────
|
|
28
27
|
export class AdapterLoadError extends CliError {
|
|
29
|
-
constructor(message, hint) {
|
|
30
|
-
super('ADAPTER_LOAD', message, hint);
|
|
31
|
-
this.name = 'AdapterLoadError';
|
|
32
|
-
}
|
|
28
|
+
constructor(message, hint) { super('ADAPTER_LOAD', message, hint); }
|
|
33
29
|
}
|
|
34
|
-
// ── Command execution ───────────────────────────────────────────────────────
|
|
35
30
|
export class CommandExecutionError extends CliError {
|
|
36
|
-
constructor(message, hint) {
|
|
37
|
-
super('COMMAND_EXEC', message, hint);
|
|
38
|
-
this.name = 'CommandExecutionError';
|
|
39
|
-
}
|
|
31
|
+
constructor(message, hint) { super('COMMAND_EXEC', message, hint); }
|
|
40
32
|
}
|
|
41
|
-
// ── Configuration ───────────────────────────────────────────────────────────
|
|
42
33
|
export class ConfigError extends CliError {
|
|
43
|
-
constructor(message, hint) {
|
|
44
|
-
super('CONFIG', message, hint);
|
|
45
|
-
this.name = 'ConfigError';
|
|
46
|
-
}
|
|
34
|
+
constructor(message, hint) { super('CONFIG', message, hint); }
|
|
47
35
|
}
|
|
48
|
-
// ── Authentication / Login ──────────────────────────────────────────────────
|
|
49
36
|
export class AuthRequiredError extends CliError {
|
|
50
37
|
domain;
|
|
51
38
|
constructor(domain, message) {
|
|
52
39
|
super('AUTH_REQUIRED', message ?? `Not logged in to ${domain}`, `Please open Chrome and log in to https://${domain}`);
|
|
53
|
-
this.name = 'AuthRequiredError';
|
|
54
40
|
this.domain = domain;
|
|
55
41
|
}
|
|
56
42
|
}
|
|
57
|
-
// ── Timeout ─────────────────────────────────────────────────────────────────
|
|
58
43
|
export class TimeoutError extends CliError {
|
|
59
44
|
constructor(label, seconds) {
|
|
60
45
|
super('TIMEOUT', `${label} timed out after ${seconds}s`, 'Try again, or increase timeout with OPENCLI_BROWSER_COMMAND_TIMEOUT env var');
|
|
61
|
-
this.name = 'TimeoutError';
|
|
62
46
|
}
|
|
63
47
|
}
|
|
64
|
-
// ── Argument validation ─────────────────────────────────────────────────────
|
|
65
48
|
export class ArgumentError extends CliError {
|
|
66
|
-
constructor(message, hint) {
|
|
67
|
-
super('ARGUMENT', message, hint);
|
|
68
|
-
this.name = 'ArgumentError';
|
|
69
|
-
}
|
|
49
|
+
constructor(message, hint) { super('ARGUMENT', message, hint); }
|
|
70
50
|
}
|
|
71
|
-
// ── Empty result ────────────────────────────────────────────────────────────
|
|
72
51
|
export class EmptyResultError extends CliError {
|
|
73
52
|
constructor(command, hint) {
|
|
74
53
|
super('EMPTY_RESULT', `${command} returned no data`, hint ?? 'The page structure may have changed, or you may need to log in');
|
|
75
|
-
this.name = 'EmptyResultError';
|
|
76
54
|
}
|
|
77
55
|
}
|
|
78
|
-
// ── Selector / DOM ──────────────────────────────────────────────────────────
|
|
79
56
|
export class SelectorError extends CliError {
|
|
80
57
|
constructor(selector, hint) {
|
|
81
58
|
super('SELECTOR', `Could not find element: ${selector}`, hint ?? 'The page UI may have changed. Please report this issue.');
|
|
82
|
-
this.name = 'SelectorError';
|
|
83
59
|
}
|
|
84
60
|
}
|
|
85
61
|
// ── Utilities ───────────────────────────────────────────────────────────
|
|
@@ -95,4 +71,11 @@ export const ERROR_ICONS = {
|
|
|
95
71
|
ARGUMENT: '❌',
|
|
96
72
|
EMPTY_RESULT: '📭',
|
|
97
73
|
SELECTOR: '🔍',
|
|
74
|
+
COMMAND_EXEC: '💥',
|
|
75
|
+
ADAPTER_LOAD: '📦',
|
|
76
|
+
NETWORK: '🌐',
|
|
77
|
+
API_ERROR: '🚫',
|
|
78
|
+
RATE_LIMITED: '⏳',
|
|
79
|
+
PAGE_CHANGED: '🔄',
|
|
80
|
+
CONFIG: '⚙️ ',
|
|
98
81
|
};
|
package/dist/execution.d.ts
CHANGED
|
@@ -9,8 +9,6 @@
|
|
|
9
9
|
* 5. Lazy-loading of TS modules from manifest
|
|
10
10
|
* 6. Lifecycle hooks (onBeforeExecute / onAfterExecute)
|
|
11
11
|
*/
|
|
12
|
-
import { type CliCommand, type Arg } from './registry.js';
|
|
13
|
-
type CommandArgs = Record<string, unknown>;
|
|
12
|
+
import { type CliCommand, type Arg, type CommandArgs } from './registry.js';
|
|
14
13
|
export declare function coerceAndValidateArgs(cmdArgs: Arg[], kwargs: CommandArgs): CommandArgs;
|
|
15
14
|
export declare function executeCommand(cmd: CliCommand, rawKwargs: CommandArgs, debug?: boolean): Promise<unknown>;
|
|
16
|
-
export {};
|
package/dist/execution.js
CHANGED
|
@@ -12,10 +12,13 @@
|
|
|
12
12
|
import { Strategy, getRegistry, fullName } from './registry.js';
|
|
13
13
|
import { pathToFileURL } from 'node:url';
|
|
14
14
|
import { executePipeline } from './pipeline/index.js';
|
|
15
|
-
import { AdapterLoadError, ArgumentError, CommandExecutionError, getErrorMessage } from './errors.js';
|
|
15
|
+
import { AdapterLoadError, ArgumentError, BrowserConnectError, CommandExecutionError, getErrorMessage } from './errors.js';
|
|
16
16
|
import { shouldUseBrowserSession } from './capabilityRouting.js';
|
|
17
17
|
import { getBrowserFactory, browserSession, runWithTimeout, DEFAULT_BROWSER_COMMAND_TIMEOUT } from './runtime.js';
|
|
18
18
|
import { emitHook } from './hooks.js';
|
|
19
|
+
import { checkDaemonStatus } from './browser/discover.js';
|
|
20
|
+
import { PKG_VERSION } from './version.js';
|
|
21
|
+
import chalk from 'chalk';
|
|
19
22
|
const _loadedModules = new Set();
|
|
20
23
|
export function coerceAndValidateArgs(cmdArgs, kwargs) {
|
|
21
24
|
const result = { ...kwargs };
|
|
@@ -126,6 +129,23 @@ export async function executeCommand(cmd, rawKwargs, debug = false) {
|
|
|
126
129
|
let result;
|
|
127
130
|
try {
|
|
128
131
|
if (shouldUseBrowserSession(cmd)) {
|
|
132
|
+
// ── Fail-fast: only when daemon is UP but extension is not connected ──
|
|
133
|
+
// If daemon is not running, let browserSession() handle auto-start as usual.
|
|
134
|
+
// We only short-circuit when the daemon confirms the extension is missing —
|
|
135
|
+
// that's a clear setup gap, not a transient startup state.
|
|
136
|
+
// Use a short timeout: localhost responds in <50ms when running.
|
|
137
|
+
// 300ms avoids a full 2s wait on cold-start (daemon not yet running).
|
|
138
|
+
const status = await checkDaemonStatus({ timeout: 300 });
|
|
139
|
+
if (status.running && !status.extensionConnected) {
|
|
140
|
+
throw new BrowserConnectError('Browser Bridge extension not connected', 'Install the Browser Bridge:\n' +
|
|
141
|
+
' 1. Download: https://github.com/jackwener/opencli/releases\n' +
|
|
142
|
+
' 2. chrome://extensions → Developer Mode → Load unpacked\n' +
|
|
143
|
+
' Then run: opencli doctor');
|
|
144
|
+
}
|
|
145
|
+
// ── Version mismatch: warn but don't block ──
|
|
146
|
+
if (status.extensionVersion && status.extensionVersion !== PKG_VERSION) {
|
|
147
|
+
process.stderr.write(chalk.yellow(`⚠ Extension v${status.extensionVersion} ≠ CLI v${PKG_VERSION} — consider updating the extension.\n`));
|
|
148
|
+
}
|
|
129
149
|
ensureRequiredEnv(cmd);
|
|
130
150
|
const BrowserFactory = getBrowserFactory();
|
|
131
151
|
result = await browserSession(BrowserFactory, async (page) => {
|
package/dist/hooks.js
CHANGED
|
@@ -15,6 +15,8 @@ const _hooks = globalThis.__opencli_hooks__ ??= new Map();
|
|
|
15
15
|
// ── Registration API (used by plugins) ─────────────────────────────────────
|
|
16
16
|
function addHook(name, fn) {
|
|
17
17
|
const list = _hooks.get(name) ?? [];
|
|
18
|
+
if (list.includes(fn))
|
|
19
|
+
return;
|
|
18
20
|
list.push(fn);
|
|
19
21
|
_hooks.set(name, list);
|
|
20
22
|
}
|
package/dist/main.js
CHANGED
|
@@ -19,12 +19,17 @@ import { discoverClis, discoverPlugins } from './discovery.js';
|
|
|
19
19
|
import { getCompletions } from './completion.js';
|
|
20
20
|
import { runCli } from './cli.js';
|
|
21
21
|
import { emitHook } from './hooks.js';
|
|
22
|
+
import { registerUpdateNoticeOnExit, checkForUpdateBackground } from './update-check.js';
|
|
22
23
|
const __filename = fileURLToPath(import.meta.url);
|
|
23
24
|
const __dirname = path.dirname(__filename);
|
|
24
25
|
const BUILTIN_CLIS = path.resolve(__dirname, 'clis');
|
|
25
26
|
const USER_CLIS = path.join(os.homedir(), '.opencli', 'clis');
|
|
26
27
|
await discoverClis(BUILTIN_CLIS, USER_CLIS);
|
|
27
28
|
await discoverPlugins();
|
|
29
|
+
// Register exit hook: notice appears after command output (same as npm/gh/yarn)
|
|
30
|
+
registerUpdateNoticeOnExit();
|
|
31
|
+
// Kick off background fetch for next run (non-blocking)
|
|
32
|
+
checkForUpdateBackground();
|
|
28
33
|
// ── Fast-path: handle --get-completions before commander parses ─────────
|
|
29
34
|
// Usage: opencli --get-completions --cursor <N> [word1 word2 ...]
|
|
30
35
|
const getCompIdx = process.argv.indexOf('--get-completions');
|
package/dist/output.js
CHANGED
|
@@ -5,7 +5,11 @@ import chalk from 'chalk';
|
|
|
5
5
|
import Table from 'cli-table3';
|
|
6
6
|
import yaml from 'js-yaml';
|
|
7
7
|
function normalizeRows(data) {
|
|
8
|
-
|
|
8
|
+
if (Array.isArray(data))
|
|
9
|
+
return data;
|
|
10
|
+
if (data && typeof data === 'object')
|
|
11
|
+
return [data];
|
|
12
|
+
return [{ value: data }];
|
|
9
13
|
}
|
|
10
14
|
function resolveColumns(rows, opts) {
|
|
11
15
|
return opts.columns ?? Object.keys(rows[0] ?? {});
|
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
import { getStep } from './registry.js';
|
|
5
5
|
import { log } from '../logger.js';
|
|
6
6
|
import { ConfigError } from '../errors.js';
|
|
7
|
-
|
|
8
|
-
const BROWSER_STEPS = new Set(['navigate', 'evaluate', 'click', 'type', 'press', 'wait', 'snapshot']);
|
|
7
|
+
import { BROWSER_ONLY_STEPS } from '../capabilityRouting.js';
|
|
9
8
|
export async function executePipeline(page, pipeline, ctx = {}) {
|
|
10
9
|
const args = ctx.args ?? {};
|
|
11
10
|
const debug = ctx.debug ?? false;
|
|
@@ -33,7 +32,7 @@ export async function executePipeline(page, pipeline, ctx = {}) {
|
|
|
33
32
|
}
|
|
34
33
|
catch (err) {
|
|
35
34
|
// Attempt cleanup: close automation window on pipeline failure
|
|
36
|
-
if (page
|
|
35
|
+
if (page?.closeWindow) {
|
|
37
36
|
try {
|
|
38
37
|
await page.closeWindow();
|
|
39
38
|
}
|
|
@@ -44,7 +43,7 @@ export async function executePipeline(page, pipeline, ctx = {}) {
|
|
|
44
43
|
return data;
|
|
45
44
|
}
|
|
46
45
|
async function executeStepWithRetry(handler, page, params, data, args, op, configRetries) {
|
|
47
|
-
const maxRetries = configRetries ?? (
|
|
46
|
+
const maxRetries = configRetries ?? (BROWSER_ONLY_STEPS.has(op) ? 2 : 0);
|
|
48
47
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
49
48
|
try {
|
|
50
49
|
return await handler(page, params, data, args);
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin manifest: reads and validates opencli-plugin.json files.
|
|
3
|
+
*
|
|
4
|
+
* Supports two modes:
|
|
5
|
+
* 1. Single plugin: repo root IS the plugin directory.
|
|
6
|
+
* 2. Monorepo: repo contains multiple plugins declared in `plugins` field.
|
|
7
|
+
*/
|
|
8
|
+
export interface SubPluginEntry {
|
|
9
|
+
/** Relative path from repo root to the sub-plugin directory. */
|
|
10
|
+
path: string;
|
|
11
|
+
version?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
/** Semver range for opencli compatibility (overrides top-level). */
|
|
14
|
+
opencli?: string;
|
|
15
|
+
/** When true, this sub-plugin is skipped during install. */
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface PluginManifest {
|
|
19
|
+
/** Plugin name (single-plugin mode). */
|
|
20
|
+
name?: string;
|
|
21
|
+
/** Semantic version of the plugin (single-plugin mode). */
|
|
22
|
+
version?: string;
|
|
23
|
+
/** Semver range for opencli compatibility, e.g. ">=1.0.0". */
|
|
24
|
+
opencli?: string;
|
|
25
|
+
/** Human-readable description. */
|
|
26
|
+
description?: string;
|
|
27
|
+
/** Monorepo sub-plugins. Key = logical plugin name. */
|
|
28
|
+
plugins?: Record<string, SubPluginEntry>;
|
|
29
|
+
}
|
|
30
|
+
export declare const MANIFEST_FILENAME = "opencli-plugin.json";
|
|
31
|
+
/**
|
|
32
|
+
* Read and parse opencli-plugin.json from a directory.
|
|
33
|
+
* Returns null if the file does not exist or is unparseable.
|
|
34
|
+
*/
|
|
35
|
+
export declare function readPluginManifest(dir: string): PluginManifest | null;
|
|
36
|
+
/** Returns true when the manifest declares a monorepo (has `plugins` field). */
|
|
37
|
+
export declare function isMonorepo(manifest: PluginManifest): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Get the list of enabled sub-plugins from a monorepo manifest.
|
|
40
|
+
* Returns entries sorted by key name.
|
|
41
|
+
*/
|
|
42
|
+
export declare function getEnabledPlugins(manifest: PluginManifest): Array<{
|
|
43
|
+
name: string;
|
|
44
|
+
entry: SubPluginEntry;
|
|
45
|
+
}>;
|
|
46
|
+
/**
|
|
47
|
+
* Check if the current opencli version satisfies a semver range string.
|
|
48
|
+
*
|
|
49
|
+
* Supports a simplified subset of semver ranges:
|
|
50
|
+
* ">=1.0.0" – greater than or equal
|
|
51
|
+
* "<=1.5.0" – less than or equal
|
|
52
|
+
* ">1.0.0" – strictly greater
|
|
53
|
+
* "<2.0.0" – strictly less
|
|
54
|
+
* "^1.2.0" – compatible (>=1.2.0 and <2.0.0)
|
|
55
|
+
* "~1.2.0" – patch-level (>=1.2.0 and <1.3.0)
|
|
56
|
+
* "1.2.0" – exact match
|
|
57
|
+
* ">=1.0.0 <2.0.0" – multiple constraints (space-separated, all must match)
|
|
58
|
+
*
|
|
59
|
+
* Returns true if compatible, false if not, and true for empty/undefined
|
|
60
|
+
* ranges (no constraint = always compatible).
|
|
61
|
+
*/
|
|
62
|
+
export declare function checkCompatibility(range: string | undefined): boolean;
|
|
63
|
+
/** Parse a version string ("1.2.3") into [major, minor, patch]. */
|
|
64
|
+
export declare function parseVersion(version: string): [number, number, number] | null;
|
|
65
|
+
/**
|
|
66
|
+
* Check if a version string satisfies a range expression.
|
|
67
|
+
* Space-separated constraints are ANDed together.
|
|
68
|
+
*/
|
|
69
|
+
export declare function satisfiesRange(versionStr: string, range: string): boolean;
|
|
70
|
+
export { readPluginManifest as _readPluginManifest, isMonorepo as _isMonorepo, getEnabledPlugins as _getEnabledPlugins, checkCompatibility as _checkCompatibility, parseVersion as _parseVersion, satisfiesRange as _satisfiesRange, };
|