dsh-web-search-pro 0.2.0 → 0.2.3
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 +179 -0
- package/README.md +197 -32
- package/assets/skills/dsh-web-search-pro/SKILL.md +14 -13
- package/assets/skills/dsh-web-search-pro/references/config.md +36 -0
- package/assets/skills/dsh-web-search-pro/references/sources.md +19 -4
- package/assets/skills/dsh-web-search-pro/references/troubleshooting.md +3 -1
- package/catalog/sources.v1.json +22 -12
- package/lib/actions/approval.js +50 -1
- package/lib/actions/approval.js.map +1 -1
- package/lib/actions/config.js +274 -0
- package/lib/actions/config.js.map +1 -0
- package/lib/actions/errors.js +8 -1
- package/lib/actions/errors.js.map +1 -1
- package/lib/actions/format.js +10 -0
- package/lib/actions/format.js.map +1 -1
- package/lib/actions/index-view.js +2 -2
- package/lib/actions/index-view.js.map +1 -1
- package/lib/actions/read.js +7 -3
- package/lib/actions/read.js.map +1 -1
- package/lib/actions/registry.js +3 -1
- package/lib/actions/registry.js.map +1 -1
- package/lib/actions/run.js +2 -2
- package/lib/actions/run.js.map +1 -1
- package/lib/actions/schema.js +18 -0
- package/lib/actions/schema.js.map +1 -1
- package/lib/actions/search.js +12 -6
- package/lib/actions/search.js.map +1 -1
- package/lib/actions/sources.js +116 -41
- package/lib/actions/sources.js.map +1 -1
- package/lib/actions/types.js +19 -1
- package/lib/actions/types.js.map +1 -1
- package/lib/browser-access.js +25 -0
- package/lib/browser-access.js.map +1 -1
- package/lib/catalog/recommend.js +11 -3
- package/lib/catalog/recommend.js.map +1 -1
- package/lib/catalog/schema.js +3 -1
- package/lib/catalog/schema.js.map +1 -1
- package/lib/cli/builtin-specs.js +341 -0
- package/lib/cli/builtin-specs.js.map +1 -0
- package/lib/cli/chain.js +193 -0
- package/lib/cli/chain.js.map +1 -0
- package/lib/cli/chains-spec.js +118 -0
- package/lib/cli/chains-spec.js.map +1 -0
- package/lib/cli/engine.js +99 -0
- package/lib/cli/engine.js.map +1 -0
- package/lib/cli/opencli-sites.js +83 -0
- package/lib/cli/opencli-sites.js.map +1 -0
- package/lib/cli/opencli.js +153 -0
- package/lib/cli/opencli.js.map +1 -0
- package/lib/cli/probe.js +128 -0
- package/lib/cli/probe.js.map +1 -0
- package/lib/cli/runner.js +564 -0
- package/lib/cli/runner.js.map +1 -0
- package/lib/cli/spec.js +496 -0
- package/lib/cli/spec.js.map +1 -0
- package/lib/cli/time-text.js +79 -0
- package/lib/cli/time-text.js.map +1 -0
- package/lib/cli/trial.js +57 -0
- package/lib/cli/trial.js.map +1 -0
- package/lib/client/SettingsCard.js +14 -32
- package/lib/client/SettingsCard.js.map +1 -1
- package/lib/client/agent-mark.js +27 -0
- package/lib/client/agent-mark.js.map +1 -0
- package/lib/client/card-frame.js +118 -0
- package/lib/client/card-frame.js.map +1 -0
- package/lib/client/cli-form.js +257 -0
- package/lib/client/cli-form.js.map +1 -0
- package/lib/client/field-docs.js +130 -0
- package/lib/client/field-docs.js.map +1 -0
- package/lib/client/field-keys.js +81 -0
- package/lib/client/field-keys.js.map +1 -0
- package/lib/client/field-rows.js +71 -0
- package/lib/client/field-rows.js.map +1 -0
- package/lib/client/fill.js +3 -0
- package/lib/client/fill.js.map +1 -0
- package/lib/client/form-specs.js +15 -1
- package/lib/client/form-specs.js.map +1 -1
- package/lib/client/form.js +296 -27
- package/lib/client/form.js.map +1 -1
- package/lib/client/group-advanced.js +23 -0
- package/lib/client/group-advanced.js.map +1 -0
- package/lib/client/group-agent.js +28 -0
- package/lib/client/group-agent.js.map +1 -0
- package/lib/client/group-cli.js +136 -0
- package/lib/client/group-cli.js.map +1 -0
- package/lib/client/group-common.js +29 -0
- package/lib/client/group-common.js.map +1 -0
- package/lib/client/group-judge.js +67 -0
- package/lib/client/group-judge.js.map +1 -0
- package/lib/client/group-order.js +201 -0
- package/lib/client/group-order.js.map +1 -0
- package/lib/client/group-runtime.js +33 -0
- package/lib/client/group-runtime.js.map +1 -0
- package/lib/client/group-sources.js +83 -0
- package/lib/client/group-sources.js.map +1 -0
- package/lib/client/groups-basic.js +36 -0
- package/lib/client/groups-basic.js.map +1 -0
- package/lib/client/index.js.map +1 -1
- package/lib/client/json-list.js +50 -0
- package/lib/client/json-list.js.map +1 -0
- package/lib/client/locales.js +666 -215
- package/lib/client/locales.js.map +1 -1
- package/lib/client/order.js +192 -0
- package/lib/client/order.js.map +1 -0
- package/lib/client/overlay-ops.js +46 -0
- package/lib/client/overlay-ops.js.map +1 -0
- package/lib/client/sources-table.js +1 -0
- package/lib/client/sources-table.js.map +1 -1
- package/lib/client/styles.js +74 -47
- package/lib/client/styles.js.map +1 -1
- package/lib/client/text-view.js +98 -0
- package/lib/client/text-view.js.map +1 -0
- package/lib/client/ui-kit.js +120 -0
- package/lib/client/ui-kit.js.map +1 -0
- package/lib/client/validators.js +22 -0
- package/lib/client/validators.js.map +1 -1
- package/lib/client.js +9151 -1987
- package/lib/client.js.map +1 -1
- package/lib/config-fillers.js +53 -0
- package/lib/config-fillers.js.map +1 -0
- package/lib/config-groups.js +49 -0
- package/lib/config-groups.js.map +1 -0
- package/lib/config.js +87 -107
- package/lib/config.js.map +1 -1
- package/lib/default-chain.js +66 -0
- package/lib/default-chain.js.map +1 -0
- package/lib/deps.js +109 -102
- package/lib/deps.js.map +1 -1
- package/lib/engine-error.js +19 -0
- package/lib/engine-error.js.map +1 -0
- package/lib/engines.js +32 -115
- package/lib/engines.js.map +1 -1
- package/lib/fetch.js +23 -4
- package/lib/fetch.js.map +1 -1
- package/lib/index.js +30 -5
- package/lib/index.js.map +1 -1
- package/lib/overlay-types.js +88 -0
- package/lib/overlay-types.js.map +1 -0
- package/lib/overlay.js +683 -0
- package/lib/overlay.js.map +1 -0
- package/lib/pipeline/candidates.js +27 -1
- package/lib/pipeline/candidates.js.map +1 -1
- package/lib/pipeline/compile.js +63 -0
- package/lib/pipeline/compile.js.map +1 -1
- package/lib/pipeline/confidence.js +64 -0
- package/lib/pipeline/confidence.js.map +1 -0
- package/lib/pipeline/fusion.js +50 -3
- package/lib/pipeline/fusion.js.map +1 -1
- package/lib/pipeline/plan.js +2 -2
- package/lib/pipeline/plan.js.map +1 -1
- package/lib/pipeline/render.js +20 -2
- package/lib/pipeline/render.js.map +1 -1
- package/lib/pipeline/run.js +21 -7
- package/lib/pipeline/run.js.map +1 -1
- package/lib/pipeline/sources-spec.js +19 -3
- package/lib/pipeline/sources-spec.js.map +1 -1
- package/lib/providers/builtin.js +13 -14
- package/lib/providers/builtin.js.map +1 -1
- package/lib/providers/deepseek.js +348 -0
- package/lib/providers/deepseek.js.map +1 -0
- package/lib/providers/http.js.map +1 -1
- package/lib/providers/keyed-meta.js +7 -0
- package/lib/providers/keyed-meta.js.map +1 -1
- package/lib/providers/platforms.js +118 -58
- package/lib/providers/platforms.js.map +1 -1
- package/lib/providers/registry.js.map +1 -1
- package/lib/restart.js +47 -0
- package/lib/restart.js.map +1 -0
- package/lib/router.js +203 -27
- package/lib/router.js.map +1 -1
- package/lib/rpc-types.js +15 -0
- package/lib/rpc-types.js.map +1 -0
- package/lib/rpc.js +85 -0
- package/lib/rpc.js.map +1 -0
- package/lib/tools.js +10 -4
- package/lib/tools.js.map +1 -1
- package/lib/types/actions/approval.d.ts +11 -0
- package/lib/types/actions/config.d.ts +54 -0
- package/lib/types/actions/format.d.ts +9 -2
- package/lib/types/actions/run.d.ts +1 -0
- package/lib/types/actions/schema.d.ts +7 -1
- package/lib/types/actions/sources.d.ts +29 -3
- package/lib/types/actions/types.d.ts +28 -2
- package/lib/types/browser-access.d.ts +17 -0
- package/lib/types/catalog/recommend.d.ts +4 -0
- package/lib/types/catalog/schema.d.ts +2 -0
- package/lib/types/cli/builtin-specs.d.ts +38 -0
- package/lib/types/cli/chain.d.ts +83 -0
- package/lib/types/cli/chains-spec.d.ts +48 -0
- package/lib/types/cli/engine.d.ts +50 -0
- package/lib/types/cli/opencli-sites.d.ts +24 -0
- package/lib/types/cli/opencli.d.ts +77 -0
- package/lib/types/cli/probe.d.ts +53 -0
- package/lib/types/cli/runner.d.ts +126 -0
- package/lib/types/cli/spec.d.ts +269 -0
- package/lib/types/cli/time-text.d.ts +12 -0
- package/lib/types/cli/trial.d.ts +19 -0
- package/lib/types/client/SettingsCard.d.ts +17 -1
- package/lib/types/client/agent-mark.d.ts +22 -0
- package/lib/types/client/card-frame.d.ts +35 -0
- package/lib/types/client/cli-form.d.ts +97 -0
- package/lib/types/client/field-docs.d.ts +15 -0
- package/lib/types/client/field-keys.d.ts +41 -0
- package/lib/types/client/field-rows.d.ts +70 -0
- package/lib/types/client/fill.d.ts +2 -0
- package/lib/types/client/form-specs.d.ts +12 -3
- package/lib/types/client/form.d.ts +79 -1
- package/lib/types/client/group-advanced.d.ts +9 -0
- package/lib/types/client/group-agent.d.ts +20 -0
- package/lib/types/client/group-cli.d.ts +52 -0
- package/lib/types/client/group-common.d.ts +12 -0
- package/lib/types/client/group-judge.d.ts +17 -0
- package/lib/types/client/group-order.d.ts +26 -0
- package/lib/types/client/group-runtime.d.ts +15 -0
- package/lib/types/client/group-sources.d.ts +46 -0
- package/lib/types/client/groups-basic.d.ts +18 -0
- package/lib/types/client/index.d.ts +8 -0
- package/lib/types/client/json-list.d.ts +21 -0
- package/lib/types/client/locales.d.ts +1079 -196
- package/lib/types/client/order.d.ts +102 -0
- package/lib/types/client/overlay-ops.d.ts +37 -0
- package/lib/types/client/styles.d.ts +56 -41
- package/lib/types/client/text-view.d.ts +22 -0
- package/lib/types/client/ui-kit.d.ts +200 -0
- package/lib/types/client/validators.d.ts +6 -0
- package/lib/types/config-fillers.d.ts +9 -0
- package/lib/types/config-groups.d.ts +23 -0
- package/lib/types/config.d.ts +265 -216
- package/lib/types/default-chain.d.ts +63 -0
- package/lib/types/deps.d.ts +25 -23
- package/lib/types/engine-error.d.ts +12 -0
- package/lib/types/engines.d.ts +45 -10
- package/lib/types/fetch.d.ts +12 -3
- package/lib/types/index.d.ts +164 -146
- package/lib/types/overlay-types.d.ts +98 -0
- package/lib/types/overlay.d.ts +141 -0
- package/lib/types/pipeline/candidates.d.ts +19 -0
- package/lib/types/pipeline/compile.d.ts +11 -0
- package/lib/types/pipeline/confidence.d.ts +35 -0
- package/lib/types/pipeline/fusion.d.ts +23 -1
- package/lib/types/pipeline/render.d.ts +16 -0
- package/lib/types/pipeline/run.d.ts +3 -1
- package/lib/types/pipeline/sources-spec.d.ts +5 -2
- package/lib/types/pipeline/types.d.ts +37 -0
- package/lib/types/providers/deepseek.d.ts +130 -0
- package/lib/types/providers/http.d.ts +2 -1
- package/lib/types/providers/keyed-meta.d.ts +5 -0
- package/lib/types/providers/platforms.d.ts +15 -3
- package/lib/types/providers/registry.d.ts +10 -0
- package/lib/types/restart.d.ts +52 -0
- package/lib/types/router.d.ts +69 -9
- package/lib/types/rpc-types.d.ts +79 -0
- package/lib/types/rpc.d.ts +41 -0
- package/lib/types/tools.d.ts +6 -0
- package/lib/types/util.d.ts +16 -1
- package/lib/util.js +17 -4
- package/lib/util.js.map +1 -1
- package/package.json +51 -43
- package/scripts/check-client-bundle.mjs +1 -1
- package/scripts/cli-login-check.mjs +214 -0
- package/lib/client/RubricEditor.js +0 -47
- package/lib/client/RubricEditor.js.map +0 -1
- package/lib/client/fields.js +0 -31
- package/lib/client/fields.js.map +0 -1
- package/lib/types/client/RubricEditor.d.ts +0 -6
- package/lib/types/client/fields.d.ts +0 -65
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The static facts of the platform backend chains (dev-plan M12), free of Node imports so the settings card validates
|
|
3
|
+
* `platformBackends` with the same code the server resolves it with: the default chain of each platform, which backend
|
|
4
|
+
* ids exist and which platforms each can serve.
|
|
5
|
+
* @module web-search-pro/cli/chains-spec
|
|
6
|
+
*/
|
|
7
|
+
import { BUILTIN_CLI_SPECS } from "./builtin-specs.js";
|
|
8
|
+
import { OPENCLI_SITE_OF } from "./opencli-sites.js";
|
|
9
|
+
import { platformBackendProblems, resolveCliAdapters } from "./spec.js";
|
|
10
|
+
/**
|
|
11
|
+
* The default chain of each platform: first usable backend answers, empty or failing ones fall through in order. The platforms
|
|
12
|
+
* that need a login (xiaohongshu, reddit, twitter, zhihu) try the standalone OpenCLI first: it reads the login of the user's
|
|
13
|
+
* own day-to-day Chrome through the OpenCLI Browser Bridge, so nothing is logged in by the plugin. The standalone CLIs
|
|
14
|
+
* (xhs, rdt, twitter, zhihu) stay as the optional fallback for users who logged them in themselves.
|
|
15
|
+
*/
|
|
16
|
+
export const DEFAULT_CHAINS = {
|
|
17
|
+
xiaohongshu: ['opencli', 'xhs', 'browser-opencli'],
|
|
18
|
+
reddit: ['opencli', 'rdt', 'browser-opencli'],
|
|
19
|
+
twitter: ['opencli', 'twitter', 'browser-opencli'],
|
|
20
|
+
zhihu: ['opencli', 'zhihu', 'browser-search'],
|
|
21
|
+
bilibili: ['bili'],
|
|
22
|
+
youtube: ['yt-dlp'],
|
|
23
|
+
github: ['rest', 'gh'],
|
|
24
|
+
'github-issues': ['rest', 'gh'],
|
|
25
|
+
'github-code': ['rest', 'gh'],
|
|
26
|
+
wechat: ['omnireach', 'wx-search-cli'],
|
|
27
|
+
omnireach: ['omnireach'],
|
|
28
|
+
tanso: ['tanso'],
|
|
29
|
+
instagram: ['browser-opencli'],
|
|
30
|
+
facebook: ['browser-opencli'],
|
|
31
|
+
weibo: ['browser-search'],
|
|
32
|
+
douban: ['browser-search'],
|
|
33
|
+
tieba: ['browser-search'],
|
|
34
|
+
douyin: ['browser-search'],
|
|
35
|
+
kuaishou: ['browser-search'],
|
|
36
|
+
};
|
|
37
|
+
/** Platforms whose provider is a chain (everything in the default table). */
|
|
38
|
+
export const CHAIN_PLATFORMS = Object.keys(DEFAULT_CHAINS);
|
|
39
|
+
export { OPENCLI_SITE_OF };
|
|
40
|
+
/** Platforms the dsh-browser OpenCLI bridge has a site adapter for (pinned to engines.ts by a test). */
|
|
41
|
+
export const BROWSER_OPENCLI_PLATFORMS = ['xiaohongshu', 'twitter', 'reddit', 'instagram', 'facebook'];
|
|
42
|
+
/** Platforms with a built-in rendered-search-page spec in dsh-browser's searchResults (pinned to platform-search.ts by a test). */
|
|
43
|
+
export const BROWSER_SEARCH_PLATFORMS = ['zhihu', 'weibo', 'douban', 'tieba', 'douyin', 'kuaishou'];
|
|
44
|
+
/** Platforms the plugin's own REST client serves. */
|
|
45
|
+
export const REST_PLATFORMS = ['github', 'github-issues', 'github-code'];
|
|
46
|
+
/** Backend ids that are not CLI specs. */
|
|
47
|
+
export const FIXED_BACKENDS = ['opencli', 'browser-opencli', 'browser-search', 'rest'];
|
|
48
|
+
/** Whether `id` names a platform backend: a built-in CLI spec, a fixed backend, or a custom CLI (`custom-cli:<id>`). */
|
|
49
|
+
export function isBackendId(id) {
|
|
50
|
+
return FIXED_BACKENDS.includes(id) || BUILTIN_CLI_SPECS.some(spec => spec.id === id) || id.startsWith('custom-cli:');
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Whether backend `id` can serve `platform`. `specPlatforms` maps a CLI spec id (built-in, or `custom-cli:<id>` of the user)
|
|
54
|
+
* to the platforms it lists; a user spec serves any platform its chain names.
|
|
55
|
+
*/
|
|
56
|
+
export function backendServes(id, platform, specPlatforms) {
|
|
57
|
+
if (id === 'opencli')
|
|
58
|
+
return OPENCLI_SITE_OF[platform] ? { ok: true } : { ok: false, why: 'has no site for platform ' + platform };
|
|
59
|
+
if (id === 'browser-opencli')
|
|
60
|
+
return BROWSER_OPENCLI_PLATFORMS.includes(platform) ? { ok: true } : { ok: false, why: 'does not serve platform ' + platform };
|
|
61
|
+
if (id === 'browser-search')
|
|
62
|
+
return BROWSER_SEARCH_PLATFORMS.includes(platform) ? { ok: true } : { ok: false, why: 'does not serve platform ' + platform };
|
|
63
|
+
if (id === 'rest')
|
|
64
|
+
return REST_PLATFORMS.includes(platform) ? { ok: true } : { ok: false, why: 'does not serve platform ' + platform };
|
|
65
|
+
const platforms = specPlatforms.get(id);
|
|
66
|
+
if (!platforms)
|
|
67
|
+
return { ok: false, why: 'is not a known backend (built-in ids: ' + [...specPlatforms.keys()].filter(k => !k.startsWith('custom-cli:')).join(', ') + ', ' + FIXED_BACKENDS.join(', ') + ')' };
|
|
68
|
+
// A custom CLI is the platform `custom-cli:<id>` of its own and serves exactly the platforms its spec declares.
|
|
69
|
+
if (id === platform)
|
|
70
|
+
return { ok: true };
|
|
71
|
+
if (!platforms.includes(platform))
|
|
72
|
+
return { ok: false, why: 'does not serve platform ' + platform + (platforms.length ? ' (it serves ' + platforms.join(', ') + ')' : ' (it declares no platform' + (id.startsWith('custom-cli:') ? ': add ' + platform + ' to its "platforms"' : '') + ')') };
|
|
73
|
+
return { ok: true };
|
|
74
|
+
}
|
|
75
|
+
/** The CLI specs a chain may name: the built-ins by id and the user's valid `cliAdapters` as `custom-cli:<id>`. */
|
|
76
|
+
export function specPlatformsOf(cliAdapters) {
|
|
77
|
+
const platforms = new Map(BUILTIN_CLI_SPECS.map(spec => [spec.id, spec.platforms]));
|
|
78
|
+
const user = resolveCliAdapters(cliAdapters);
|
|
79
|
+
for (const [id, spec] of user.specs)
|
|
80
|
+
platforms.set('custom-cli:' + id, spec.platforms);
|
|
81
|
+
return { platforms, diagnostics: user.diagnostics };
|
|
82
|
+
}
|
|
83
|
+
/** The declared platforms of the user's CLI adapters that no backend chain exists for (the spec would serve nothing there). */
|
|
84
|
+
export function customCliPlatformIssues(cliAdapters) {
|
|
85
|
+
const { specs } = resolveCliAdapters(cliAdapters);
|
|
86
|
+
const out = [];
|
|
87
|
+
for (const [id, spec] of specs) {
|
|
88
|
+
const unknown = spec.platforms.filter(platform => !CHAIN_PLATFORMS.includes(platform));
|
|
89
|
+
if (unknown.length)
|
|
90
|
+
out.push('cliAdapters.' + id + '.platforms: no backend chain exists for ' + unknown.join(', ') + ' (platforms with a chain: ' + CHAIN_PLATFORMS.join(', ') + ')');
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Every problem of the `platformBackends` setting (shape, unknown platform, unknown or foreign backend id) given the
|
|
96
|
+
* `cliAdapters` setting. The settings card and `sources.status` both call this.
|
|
97
|
+
*/
|
|
98
|
+
export function platformBackendIssues(platformBackends, cliAdapters) {
|
|
99
|
+
const shape = platformBackendProblems(platformBackends);
|
|
100
|
+
if (shape.length || !platformBackends || typeof platformBackends !== 'object')
|
|
101
|
+
return shape;
|
|
102
|
+
const { platforms } = specPlatformsOf(cliAdapters);
|
|
103
|
+
const known = new Set([...CHAIN_PLATFORMS, ...[...platforms.keys()].filter(id => id.startsWith('custom-cli:'))]);
|
|
104
|
+
const out = [];
|
|
105
|
+
for (const [platform, ids] of Object.entries(platformBackends)) {
|
|
106
|
+
if (!known.has(platform)) {
|
|
107
|
+
out.push('platformBackends.' + platform + ': no such platform with a backend chain (known: ' + [...known].join(', ') + ')');
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
for (const id of ids) {
|
|
111
|
+
const served = backendServes(id, platform, platforms);
|
|
112
|
+
if (!served.ok)
|
|
113
|
+
out.push('platformBackends.' + platform + ': "' + id + '" ' + served.why);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=chains-spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chains-spec.js","sourceRoot":"","sources":["../../src/cli/chains-spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Engine of a CLI adapter spec (dev-plan M12): one search leg that probes the command's contract (cached), then runs
|
|
3
|
+
* the spec through the shared runner. `available()` is synchronous and local (settings gates plus the probe cache);
|
|
4
|
+
* every failure reaches the caller as a structured error naming what to install or run.
|
|
5
|
+
* @module web-search-pro/cli/engine
|
|
6
|
+
*/
|
|
7
|
+
import { CliAdapterError, loginHint, missingEnv, runCliSearchDetailed, savedLoginPresent } from "./runner.js";
|
|
8
|
+
import { cachedProbe, probeCli } from "./probe.js";
|
|
9
|
+
import { USER_DEFINED_TAG } from "./spec.js";
|
|
10
|
+
/** Settings that gate a spec on top of `enableCliBackends` (twitter-cli kept its own `agentReachEnabled` switch). */
|
|
11
|
+
const SETTING_GATE = {
|
|
12
|
+
twitter: { key: 'agentReachEnabled', label: 'the twitter-cli backend is disabled in settings (agentReachEnabled)' },
|
|
13
|
+
};
|
|
14
|
+
/** Why a spec is switched off by settings, or undefined when it may run. */
|
|
15
|
+
export function disabledBySettings(spec, deps) {
|
|
16
|
+
if (!deps.enableCli)
|
|
17
|
+
return 'CLI backends are disabled in settings (enableCliBackends)';
|
|
18
|
+
const gate = SETTING_GATE[spec.id];
|
|
19
|
+
return gate && !deps[gate.key] ? gate.label : undefined;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The environment a spec's credential variables are read from: the process environment overlaid with the values the router
|
|
23
|
+
* resolved (settings literal -> DSH credentials -> process environment). Undefined = the process environment as it is.
|
|
24
|
+
*/
|
|
25
|
+
export function specEnv(deps) {
|
|
26
|
+
return deps.cliEnv && Object.keys(deps.cliEnv).length ? { ...process.env, ...deps.cliEnv } : undefined;
|
|
27
|
+
}
|
|
28
|
+
/** The probe verdict as the structured error a search would raise, or undefined when the command is usable. */
|
|
29
|
+
export function probeError(spec, probe) {
|
|
30
|
+
if (probe.state === 'detected')
|
|
31
|
+
return undefined;
|
|
32
|
+
if (probe.state === 'missing')
|
|
33
|
+
return new CliAdapterError('the ' + spec.bins[0] + ' command is not installed: install it (' + spec.packageNote + ')', 'CLI_NOT_FOUND', 'install: ' + spec.packageNote);
|
|
34
|
+
return new CliAdapterError(spec.bins[0] + ' is installed but does not match this adapter: ' + (probe.reason ?? 'contract mismatch'), 'CLI_CONTRACT_MISMATCH', 'a different program with the same name, or a version the adapter does not know: check `sources.deps`');
|
|
35
|
+
}
|
|
36
|
+
export function cliSpecEngine(spec, platform, deps, options = {}) {
|
|
37
|
+
return {
|
|
38
|
+
id: options.id ?? spec.id,
|
|
39
|
+
backend: options.id ?? spec.id,
|
|
40
|
+
label: options.label ?? spec.bins[0],
|
|
41
|
+
available: () => {
|
|
42
|
+
if (disabledBySettings(spec, deps))
|
|
43
|
+
return false;
|
|
44
|
+
// Before the first probe the command is assumed present; the search itself probes and reports.
|
|
45
|
+
return options.run ? true : (cachedProbe(spec)?.state ?? 'detected') === 'detected';
|
|
46
|
+
},
|
|
47
|
+
unavailableReason: () => {
|
|
48
|
+
const off = disabledBySettings(spec, deps);
|
|
49
|
+
if (off)
|
|
50
|
+
return off;
|
|
51
|
+
const probe = options.run ? undefined : cachedProbe(spec);
|
|
52
|
+
return probe && probe.state !== 'detected' ? probeError(spec, probe)?.message : undefined;
|
|
53
|
+
},
|
|
54
|
+
async search(query, count, signal) {
|
|
55
|
+
const why = disabledBySettings(spec, deps);
|
|
56
|
+
if (why)
|
|
57
|
+
throw new CliAdapterError(why, 'CLI_NOT_FOUND', undefined, false);
|
|
58
|
+
let bin = spec.bins[0];
|
|
59
|
+
if (!options.run) {
|
|
60
|
+
const probe = await probeCli(spec);
|
|
61
|
+
const failure = probeError(spec, probe);
|
|
62
|
+
if (failure)
|
|
63
|
+
throw failure;
|
|
64
|
+
bin = probe.path ?? bin;
|
|
65
|
+
}
|
|
66
|
+
const runOptions = {
|
|
67
|
+
platform, bin,
|
|
68
|
+
...options.run ? { run: options.run } : {},
|
|
69
|
+
...(options.env ?? specEnv(deps)) ? { env: (options.env ?? specEnv(deps)) } : {},
|
|
70
|
+
...options.home ? { home: options.home } : {},
|
|
71
|
+
...options.skipCredentialGate ? { skipCredentialGate: true } : {},
|
|
72
|
+
};
|
|
73
|
+
const { sources, skipped } = await runCliSearchDetailed(spec, { query, count, signal }, runOptions);
|
|
74
|
+
const rows = options.userDefined ? sources.map(source => ({ ...source, via: options.id ?? spec.id, tag: USER_DEFINED_TAG })) : sources;
|
|
75
|
+
return { sources: rows, via: options.id ?? spec.id, backend: options.id ?? spec.id, ...skipped ? { notes: [skipped + ' result' + (skipped === 1 ? '' : 's') + ' without a link skipped'] } : {} };
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/** Local readiness of one spec: settings, contract probe, then the credentials it needs. Never runs a search. */
|
|
80
|
+
export async function specReadiness(spec, deps, options = {}) {
|
|
81
|
+
const off = disabledBySettings(spec, deps);
|
|
82
|
+
if (off)
|
|
83
|
+
return { ready: false, installation: 'missing', credential: 'not_required', reason: spec.id + ': ' + off, code: 'disabled' };
|
|
84
|
+
const probe = await probeCli(spec);
|
|
85
|
+
const base = { installation: probe.state, ...probe.version ? { version: probe.version } : {}, ...probe.path ? { path: probe.path } : {} };
|
|
86
|
+
if (probe.state === 'missing')
|
|
87
|
+
return { ...base, ready: false, installation: 'missing', credential: 'not_required', reason: spec.id + ': ' + (probe.reason ?? 'not installed'), code: 'cli_missing' };
|
|
88
|
+
if (probe.state === 'incompatible')
|
|
89
|
+
return { ...base, ready: false, installation: 'incompatible', credential: 'not_required', reason: spec.id + ': ' + (probe.reason ?? 'contract mismatch'), code: 'cli_incompatible' };
|
|
90
|
+
const missing = missingEnv(spec, options.env ?? specEnv(deps));
|
|
91
|
+
if (missing.length)
|
|
92
|
+
return { ...base, ready: false, installation: 'detected', credential: 'missing', reason: spec.id + ': ' + missing.join(' / ') + ' not set (' + loginHint(spec) + ')', code: 'not_logged_in' };
|
|
93
|
+
const saved = savedLoginPresent(spec, options);
|
|
94
|
+
if (saved === false)
|
|
95
|
+
return { ...base, ready: false, installation: 'detected', credential: 'missing', reason: spec.id + ': no saved login (' + loginHint(spec) + ')', code: 'not_logged_in' };
|
|
96
|
+
const credential = saved === true || (spec.env.required?.length ?? 0) > 0 ? 'configured' : spec.needsLogin ? undefined : 'not_required';
|
|
97
|
+
return { ...base, ready: true, installation: 'detected', ...credential ? { credential } : {}, code: 'ok' };
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/cli/engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The static facts of the standalone OpenCLI sites the plugin searches (free of Node imports, so the catalog and the chain
|
|
3
|
+
* specs can read them): which site serves which platform, how each site's `search` rows map to sources, and how far each
|
|
4
|
+
* mapping was verified. The spec generation itself (src/cli/opencli.ts) reads the live command table.
|
|
5
|
+
* @module web-search-pro/cli/opencli-sites
|
|
6
|
+
*/
|
|
7
|
+
/** Platform -> site of the standalone OpenCLI (`opencli <site> search`). */
|
|
8
|
+
export const OPENCLI_SITE_OF = {
|
|
9
|
+
xiaohongshu: 'xiaohongshu', twitter: 'twitter', reddit: 'reddit', instagram: 'instagram', facebook: 'facebook',
|
|
10
|
+
zhihu: 'zhihu', weibo: 'weibo', douban: 'douban', tieba: 'tieba', douyin: 'douyin', bilibili: 'bilibili', youtube: 'youtube', wechat: 'weixin',
|
|
11
|
+
};
|
|
12
|
+
/** Generic field mapping over the columns OpenCLI commands emit (rank, title, url, summary, ...), for the sites without a measured or documented mapping below. */
|
|
13
|
+
const FIELDS = {
|
|
14
|
+
url: ['url', 'link', 'href', 'projectUrl', 'item_url'],
|
|
15
|
+
title: ['title', 'name', 'text', 'desc', 'label'],
|
|
16
|
+
snippet: ['summary', 'snippet', 'description', 'desc', 'content', 'selftext', 'abstract', 'author', 'user'],
|
|
17
|
+
publishedAt: ['published_at', 'publish_time', 'created_at', 'date', 'time', 'published', 'postTime'],
|
|
18
|
+
};
|
|
19
|
+
export const GENERIC_OUTPUT = {
|
|
20
|
+
format: 'json', stripTags: true,
|
|
21
|
+
fields: { url: [...FIELDS.url], title: [...FIELDS.title], snippet: [...FIELDS.snippet], publishedAt: [...FIELDS.publishedAt] },
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* The output mapping of the sites the plugin knows by their real columns. All four print a JSON array of flat rows under `-f json`.
|
|
25
|
+
* - twitter, zhihu: MEASURED (opencli 1.8.8, 2026-10-27, fixtures live-opencli-*). twitter rows are `id, author (handle), bio, text,
|
|
26
|
+
* created_at (X API format), likes (number), views (string), url (https://x.com/i/status/<id>), has_media, media_urls, ...`; the
|
|
27
|
+
* title and meta line follow the standalone twitter backend (text, then author · likes · views · time). zhihu rows are
|
|
28
|
+
* `rank, title, type, author, votes, url` and carry no excerpt and no time.
|
|
29
|
+
* - xiaohongshu, reddit: NOT MEASURED. The only runs failed (login wall; an HTML page instead of JSON), so the mapping is the
|
|
30
|
+
* documented columns of clis/xiaohongshu/search.js (`rank, title, author, likes, published_at (date from the note id), url`,
|
|
31
|
+
* the url is a signed search_result link) and clis/reddit/search.js (`id, title, subreddit (r/x), author, score, comments, url,
|
|
32
|
+
* created_utc (epoch seconds), selftext, ...`), read from the source of 1.8.8.
|
|
33
|
+
*/
|
|
34
|
+
export const SITE_OUTPUT = {
|
|
35
|
+
twitter: {
|
|
36
|
+
format: 'json', titleMax: 200,
|
|
37
|
+
fields: {
|
|
38
|
+
url: [{ template: 'https://x.com/{author}/status/{id}' }, 'url'],
|
|
39
|
+
title: ['text', { template: '@{author}' }],
|
|
40
|
+
snippet: { join: [{ template: '@{author}' }, { path: 'likes', prefix: 'likes ' }, { path: 'views', prefix: 'views ' }, { time: 'created_at' }], sep: ' · ' },
|
|
41
|
+
publishedAt: { time: 'created_at' },
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
zhihu: {
|
|
45
|
+
format: 'json', stripTags: true,
|
|
46
|
+
fields: {
|
|
47
|
+
url: 'url',
|
|
48
|
+
title: 'title',
|
|
49
|
+
snippet: { join: ['type', 'author', { path: 'votes', prefix: '赞同 ' }], sep: ' · ' },
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
xiaohongshu: {
|
|
53
|
+
format: 'json',
|
|
54
|
+
fields: {
|
|
55
|
+
url: 'url',
|
|
56
|
+
title: 'title',
|
|
57
|
+
snippet: { join: [{ path: 'author', prefix: '作者: ' }, { path: 'likes', prefix: '赞: ' }] },
|
|
58
|
+
publishedAt: { time: 'published_at' },
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
reddit: {
|
|
62
|
+
format: 'json',
|
|
63
|
+
fields: {
|
|
64
|
+
url: 'url',
|
|
65
|
+
title: 'title',
|
|
66
|
+
snippet: { join: ['subreddit', { path: 'score', prefix: 'score ' }, { path: 'comments', suffix: ' comments' }, 'selftext'], sep: ' · ' },
|
|
67
|
+
publishedAt: { epoch: 'created_utc' },
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
const CHECKED = { version: '1.8.8', date: '2026-10-27' };
|
|
72
|
+
const SITE_VERIFICATION = {
|
|
73
|
+
twitter: { status: 'live', ...CHECKED, note: '`opencli twitter search -f json` run on the user\'s own Chrome (OpenCLI Browser Bridge) and parsed: a JSON array of tweets (fixture live-opencli-twitter-search.json); created_at is in the X API format and is converted to ISO' },
|
|
74
|
+
zhihu: { status: 'live', ...CHECKED, note: '`opencli zhihu search -f json` run on the user\'s own Chrome and parsed: a JSON array of { rank, title, type, author, votes, url } without excerpt or time (fixture live-opencli-zhihu-search.json)' },
|
|
75
|
+
xiaohongshu: { status: 'contract-only', ...CHECKED, note: 'not measured: the only run answered AUTH_REQUIRED (exit 77, the Chrome had no xiaohongshu login); columns read from clis/xiaohongshu/search.js of 1.8.8. The AUTH_REQUIRED failure itself is mapped (fixture live-opencli-xiaohongshu-auth-required.yaml)' },
|
|
76
|
+
reddit: { status: 'contract-only', ...CHECKED, note: 'not measured: the only run got an HTML page where the command expects JSON (exit 1, code UNKNOWN; Reddit blocked the request on this proxied network); columns read from clis/reddit/search.js of 1.8.8. That failure is mapped (fixture live-opencli-reddit-html-error.yaml)' },
|
|
77
|
+
};
|
|
78
|
+
const GENERIC_VERIFICATION = { status: 'contract-only', ...CHECKED, note: 'command table from `opencli list -f json`; -f json is the documented output option; this site\'s search was not run (it uses your Chrome session)' };
|
|
79
|
+
/** How far the search of one OpenCLI site was checked (`live`, `contract-only`). */
|
|
80
|
+
export function opencliSiteVerification(site) {
|
|
81
|
+
return SITE_VERIFICATION[site] ?? GENERIC_VERIFICATION;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=opencli-sites.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opencli-sites.js","sourceRoot":"","sources":["../../src/cli/opencli-sites.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standalone OpenCLI (dev-plan M12): the `opencli` command the user installed themselves, which drives their own Chrome
|
|
3
|
+
* through its Browser Bridge, independent of the dsh-browser plugin. It has one command per site and the plugin only
|
|
4
|
+
* calls `<site> search`, and only for sites that `opencli list -f json` itself reports with a read-access `search`
|
|
5
|
+
* command taking a positional query (the catalog is the tool's own, so a site without search, such as douyin or v2ex,
|
|
6
|
+
* is never assumed to have one). The list is a local call: no browser, no login.
|
|
7
|
+
* @module web-search-pro/cli/opencli
|
|
8
|
+
*/
|
|
9
|
+
import { runCli } from "../util.js";
|
|
10
|
+
import { buildCliEnv } from "./runner.js";
|
|
11
|
+
import { findOnPath, probeCli } from "./probe.js";
|
|
12
|
+
import { validateCliAdapterSpec } from "./spec.js";
|
|
13
|
+
import { GENERIC_OUTPUT, opencliSiteVerification, SITE_OUTPUT } from "./opencli-sites.js";
|
|
14
|
+
export { OPENCLI_SITE_OF } from "./chains-spec.js";
|
|
15
|
+
export { opencliSiteVerification };
|
|
16
|
+
/** The command that proves this is the OpenCLI whose `list` output the adapter understands. Verified on 1.8.8. */
|
|
17
|
+
export const OPENCLI_PROBE = {
|
|
18
|
+
id: 'opencli',
|
|
19
|
+
bins: ['opencli'],
|
|
20
|
+
packageNote: 'OpenCLI (npm i -g @jackwener/opencli; needs its Browser Bridge extension in your own Chrome)',
|
|
21
|
+
probe: { versionArgs: ['--version'], minVersion: '1.8.0', helpArgs: ['list', '--help'], mustContain: ['--format', 'json'] },
|
|
22
|
+
env: {},
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* The browser options every browser-backed site command of OpenCLI accepts (read from `opencli <site> search --help` and
|
|
26
|
+
* dist/src/execution.js of 1.8.8): the command runs in a background window (no focus is taken from the user's Chrome), with an
|
|
27
|
+
* ephemeral site session whose tab is released when it finishes. These are also its defaults; the plugin passes them
|
|
28
|
+
* explicitly so a changed `OPENCLI_WINDOW` / `OPENCLI_SITE_SESSION` environment cannot bring a window to the front, and only
|
|
29
|
+
* when the command itself lists the three options (a command without a browser rejects unknown options).
|
|
30
|
+
*/
|
|
31
|
+
export const OPENCLI_SILENT_ARGS = ['--window', 'background', '--site-session', 'ephemeral', '--keep-tab', 'false'];
|
|
32
|
+
const SILENT_OPTIONS = ['--window', '--site-session', '--keep-tab'];
|
|
33
|
+
/** The probe of one site's search command: it must list the three silent browser options (`opencli <site> search --help`). Local, cached, no browser. */
|
|
34
|
+
export function opencliSiteProbe(site) {
|
|
35
|
+
return {
|
|
36
|
+
id: 'opencli:' + site,
|
|
37
|
+
bins: ['opencli'],
|
|
38
|
+
packageNote: OPENCLI_PROBE.packageNote,
|
|
39
|
+
probe: { versionArgs: ['--version'], minVersion: '1.8.0', helpArgs: [site, 'search', '--help'], mustContain: [...SILENT_OPTIONS] },
|
|
40
|
+
env: {},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** Sites with a read `search` command taking a positional query, from the JSON of `opencli list -f json`. Pure. */
|
|
44
|
+
export function parseOpencliList(json) {
|
|
45
|
+
const rows = JSON.parse(json);
|
|
46
|
+
if (!Array.isArray(rows))
|
|
47
|
+
throw new Error('opencli list did not return an array');
|
|
48
|
+
const sites = new Map();
|
|
49
|
+
for (const row of rows) {
|
|
50
|
+
if (!row || typeof row.site !== 'string' || row.name !== 'search' || row.access !== 'read' || !Array.isArray(row.args))
|
|
51
|
+
continue;
|
|
52
|
+
const args = row.args;
|
|
53
|
+
if (!args.some(a => a.positional === true && a.name === 'query') && !args.some(a => a.positional === true))
|
|
54
|
+
continue;
|
|
55
|
+
sites.set(row.site, {
|
|
56
|
+
site: row.site,
|
|
57
|
+
strategy: typeof row.strategy === 'string' ? row.strategy : 'cookie',
|
|
58
|
+
browser: row.browser === true,
|
|
59
|
+
hasLimit: args.some(a => a.name === 'limit'),
|
|
60
|
+
...typeof row.domain === 'string' && row.domain ? { domain: row.domain } : {},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return sites;
|
|
64
|
+
}
|
|
65
|
+
const cache = new Map();
|
|
66
|
+
export function clearOpencliCache() { cache.clear(); }
|
|
67
|
+
/** The searchable sites of the installed OpenCLI (cached 5 minutes); `undefined` when the command cannot be listed. */
|
|
68
|
+
export async function listOpencliSites(options = {}) {
|
|
69
|
+
const found = findOnPath('opencli');
|
|
70
|
+
if (!found)
|
|
71
|
+
return { problem: 'opencli not found on PATH' };
|
|
72
|
+
const now = options.now ?? Date.now;
|
|
73
|
+
const key = found + '\u0000' + (process.env.PATH ?? '');
|
|
74
|
+
const hit = cache.get(key);
|
|
75
|
+
if (!options.force && hit && now() - hit.at <= (options.ttlMs ?? 300_000))
|
|
76
|
+
return { sites: hit.sites };
|
|
77
|
+
const run = options.run ?? runCli;
|
|
78
|
+
const res = await run(found, ['list', '-f', 'json'], { timeoutMs: 20_000, signal: undefined, env: buildCliEnv({ env: {} }), cleanEnv: true, maxOutput: 16 * 1024 * 1024, outputEncoding: 'utf-8' });
|
|
79
|
+
if (res.code !== 0 || res.truncated)
|
|
80
|
+
return { problem: res.truncated ? 'opencli list output exceeded the size cap' : 'opencli list failed with exit ' + res.code };
|
|
81
|
+
try {
|
|
82
|
+
const sites = parseOpencliList(res.stdout);
|
|
83
|
+
cache.set(key, { at: now(), sites });
|
|
84
|
+
return { sites };
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
return { problem: 'opencli list returned an unexpected payload: ' + (error instanceof Error ? error.message : String(error)) };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The failure format of OpenCLI 1.8.8 (dist/src/errors.js `toEnvelope`, printed as YAML on stderr whatever `-f` says):
|
|
92
|
+
* `ok: false` / `error: { code, message, help?, exitCode }`. Codes, from that file and the site adapters:
|
|
93
|
+
* - AUTH_REQUIRED (exit 77): the Chrome has no login for the site -> not logged in (measured on xiaohongshu);
|
|
94
|
+
* - LOGIN_WALL (77), SECURITY_BLOCK, RATE_LIMITED, FORBIDDEN: an HTML page where JSON was expected, a risk-control block -> blocked;
|
|
95
|
+
* UNKNOWN with a JSON parse error on an HTML body (`Unexpected token '<'`) is the same thing from an adapter that does not
|
|
96
|
+
* check (measured on reddit through a proxy);
|
|
97
|
+
* - EMPTY_RESULT (66), NO_DATA, EMPTY_RESPONSE: nothing found; BROWSER_CONNECT (69): no Chrome / bridge; TIMEOUT (75);
|
|
98
|
+
* - ARGUMENT, INVALID_ARGUMENT, ...: the arguments of this adapter are not accepted by that version.
|
|
99
|
+
* Anything else (COMMAND_EXEC, FETCH_ERROR, HTTP_ERROR, ...) keeps OpenCLI's own message.
|
|
100
|
+
*/
|
|
101
|
+
export const OPENCLI_ERROR_CODES = {
|
|
102
|
+
codePath: 'error.code',
|
|
103
|
+
messagePath: 'error.message',
|
|
104
|
+
notLoggedIn: ['AUTH_REQUIRED'],
|
|
105
|
+
notLoggedInExit: [77],
|
|
106
|
+
blocked: ['LOGIN_WALL', 'SECURITY_BLOCK', 'RATE_LIMITED', 'FORBIDDEN'],
|
|
107
|
+
blockedPatterns: ["unexpected token '<'", 'is not valid json', '<!doctype', '<html', '<body'],
|
|
108
|
+
empty: ['EMPTY_RESULT', 'NO_DATA', 'EMPTY_RESPONSE'],
|
|
109
|
+
unavailable: ['BROWSER_CONNECT'],
|
|
110
|
+
timeout: ['TIMEOUT'],
|
|
111
|
+
usage: ['ARGUMENT', 'INVALID_ARGUMENT', 'INVALID_ARG', 'INVALID_ARGS', 'INVALID_INPUT'],
|
|
112
|
+
};
|
|
113
|
+
const NOT_LOGGED_IN = ['not logged in', 'login required', 'please log in', 'please login', 'sign in', 'auth_required', 'requires login', '未登录', '请先登录'];
|
|
114
|
+
/** The spec that runs `opencli <site> search` for one platform, or undefined when the generated spec is invalid (a site named like a write command). */
|
|
115
|
+
export function opencliSearchSpec(platform, entry, options = {}) {
|
|
116
|
+
const spec = {
|
|
117
|
+
id: 'opencli',
|
|
118
|
+
bins: ['opencli'],
|
|
119
|
+
packageNote: OPENCLI_PROBE.packageNote,
|
|
120
|
+
platforms: [platform],
|
|
121
|
+
probe: OPENCLI_PROBE.probe,
|
|
122
|
+
allowedSubcommands: ['list', entry.site, 'search'],
|
|
123
|
+
search: {
|
|
124
|
+
argv: [entry.site, 'search', '{query}', ...entry.hasLimit ? ['--limit', '{count}'] : [], '-f', 'json', ...entry.browser && options.silent ? OPENCLI_SILENT_ARGS : []],
|
|
125
|
+
maxCount: 20,
|
|
126
|
+
output: SITE_OUTPUT[entry.site] ?? GENERIC_OUTPUT,
|
|
127
|
+
notLoggedInPatterns: NOT_LOGGED_IN,
|
|
128
|
+
errorCodes: OPENCLI_ERROR_CODES,
|
|
129
|
+
},
|
|
130
|
+
env: {},
|
|
131
|
+
needsLogin: entry.strategy !== 'public',
|
|
132
|
+
// The login is the one of the user's own Chrome; the plugin never suggests a login command for it.
|
|
133
|
+
...entry.strategy !== 'public' ? { login: { hint: '在你的 Chrome 里登录 ' + (entry.domain ?? entry.site) + ' 后重试(opencli 使用 Chrome 的登录态)' } } : {},
|
|
134
|
+
timeoutMs: 60_000,
|
|
135
|
+
maxOutputBytes: 8 * 1024 * 1024,
|
|
136
|
+
verification: opencliSiteVerification(entry.site),
|
|
137
|
+
};
|
|
138
|
+
return validateCliAdapterSpec(spec, 'builtin').ok ? spec : undefined;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Everything a search of one platform through the standalone OpenCLI needs, checked locally (the command table and the site's
|
|
142
|
+
* `--help`, never a search): the site's entry, whether its command takes the silent browser options, and the generated spec.
|
|
143
|
+
*/
|
|
144
|
+
export async function planOpencliSearch(platform, site) {
|
|
145
|
+
const listed = await listOpencliSites();
|
|
146
|
+
const entry = listed.sites?.get(site);
|
|
147
|
+
if (!entry)
|
|
148
|
+
return { silent: false, problem: listed.problem ?? 'opencli has no read `search` command for the site ' + site + ' (opencli list)', hint: 'run `opencli list` to see the sites your version supports' };
|
|
149
|
+
const silent = entry.browser && (await probeCli(opencliSiteProbe(site))).state === 'detected';
|
|
150
|
+
const spec = opencliSearchSpec(platform, entry, { silent });
|
|
151
|
+
return spec ? { spec, entry, silent } : { entry, silent, problem: 'opencli site ' + site + ' cannot be used by this adapter' };
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=opencli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opencli.js","sourceRoot":"","sources":["../../src/cli/opencli.ts"],"names":[],"mappings":"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"}
|
package/lib/cli/probe.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract probes of CLI adapter specs (dev-plan M12, design §4.3 / §4.4): local only, cached with a TTL. A probe finds
|
|
3
|
+
* the command on PATH and runs ONLY the spec's version and help commands, then checks the contract the spec needs: a
|
|
4
|
+
* minimum version and the subcommands / flags that must appear in the help text. A same-named program with another
|
|
5
|
+
* contract is `incompatible`, not `detected`. A probe never runs a search and never logs in.
|
|
6
|
+
* @module web-search-pro/cli/probe
|
|
7
|
+
*/
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { runCli } from "../util.js";
|
|
11
|
+
import { buildCliEnv } from "./runner.js";
|
|
12
|
+
import { compareVersions, parseVersion } from "./spec.js";
|
|
13
|
+
const DEFAULT_TTL_MS = 60_000;
|
|
14
|
+
const PROBE_TIMEOUT_MS = 8_000;
|
|
15
|
+
const cache = new Map();
|
|
16
|
+
const flights = new Map();
|
|
17
|
+
export function clearProbeCache() {
|
|
18
|
+
cache.clear();
|
|
19
|
+
flights.clear();
|
|
20
|
+
}
|
|
21
|
+
/** The executable `cmd` resolves to on PATH (no process is spawned; Windows tries PATHEXT). */
|
|
22
|
+
export function findOnPath(cmd, env = process.env) {
|
|
23
|
+
const win = process.platform === 'win32';
|
|
24
|
+
const dirs = (env.PATH ?? env.Path ?? '').split(path.delimiter).filter(Boolean);
|
|
25
|
+
const exts = win ? ['', ...(env.PATHEXT ?? '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean)] : [''];
|
|
26
|
+
for (const dir of dirs) {
|
|
27
|
+
for (const ext of exts) {
|
|
28
|
+
const file = path.join(dir, cmd + ext);
|
|
29
|
+
try {
|
|
30
|
+
const stat = fs.statSync(file);
|
|
31
|
+
if (stat.isFile() && (win || (stat.mode & 0o111) !== 0))
|
|
32
|
+
return file;
|
|
33
|
+
}
|
|
34
|
+
catch { /* not here */ }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
const keyOf = (spec) => [spec.id, spec.bins.join(','), JSON.stringify(spec.probe), process.env.PATH ?? ''].join('\u0000');
|
|
40
|
+
/** The cached probe of a spec, when one is fresh (no spawn: `Engine.available()` is synchronous). */
|
|
41
|
+
export function cachedProbe(spec, ttlMs = DEFAULT_TTL_MS, now = Date.now) {
|
|
42
|
+
const hit = cache.get(keyOf(spec));
|
|
43
|
+
return hit && now() - hit.at <= ttlMs ? hit.result : undefined;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Decide whether version and help output satisfy a spec's contract. Pure (the fixtures tests call it directly).
|
|
47
|
+
* The version command may fail or print nothing unless the spec asks for a minimum version.
|
|
48
|
+
*/
|
|
49
|
+
export function evaluateCliContract(spec, bin, version, help) {
|
|
50
|
+
const { probe } = spec;
|
|
51
|
+
const versionCmd = [bin, ...probe.versionArgs].join(' ');
|
|
52
|
+
const detected = version.code === 0 ? parseVersion(version.output) : undefined;
|
|
53
|
+
if (probe.minVersion !== undefined) {
|
|
54
|
+
if (version.code !== 0)
|
|
55
|
+
return { state: 'incompatible', reason: versionCmd + ' failed with exit ' + version.code };
|
|
56
|
+
if (!detected)
|
|
57
|
+
return { state: 'incompatible', reason: versionCmd + ' returned no semantic version' };
|
|
58
|
+
if (compareVersions(detected, probe.minVersion) < 0)
|
|
59
|
+
return { state: 'incompatible', version: detected, reason: bin + ' ' + detected + ' is older than required ' + probe.minVersion };
|
|
60
|
+
}
|
|
61
|
+
const withVersion = detected ? { version: detected } : {};
|
|
62
|
+
const helpCmd = [bin, ...probe.helpArgs].join(' ');
|
|
63
|
+
if (help.code !== 0)
|
|
64
|
+
return { state: 'incompatible', ...withVersion, reason: helpCmd + ' failed with exit ' + help.code };
|
|
65
|
+
const lower = help.output.toLowerCase();
|
|
66
|
+
const missing = probe.mustContain.filter(fragment => !lower.includes(fragment.toLowerCase()));
|
|
67
|
+
if (missing.length) {
|
|
68
|
+
const words = probe.helpArgs.filter(arg => !arg.startsWith('-')).join(' ') || 'help';
|
|
69
|
+
return { state: 'incompatible', ...withVersion, reason: bin + ' ' + words + ' contract missing ' + missing.join(', ') + ' (a different program with the same name' + (probe.contractHint ? ', or an older version: ' + probe.contractHint : '?') + ')' };
|
|
70
|
+
}
|
|
71
|
+
return { state: 'detected', ...withVersion };
|
|
72
|
+
}
|
|
73
|
+
async function probeUncached(spec, options) {
|
|
74
|
+
const run = options.run ?? runCli;
|
|
75
|
+
const now = options.now ?? Date.now;
|
|
76
|
+
const env = buildCliEnv(spec);
|
|
77
|
+
const call = async (bin, args) => {
|
|
78
|
+
const res = await run(bin, [...args], { timeoutMs: PROBE_TIMEOUT_MS, signal: undefined, env, cleanEnv: true, maxOutput: 128 * 1024, outputEncoding: 'utf-8' });
|
|
79
|
+
return { code: res.timedOut || res.spawnFailed ? -1 : res.code, output: res.stdout + '\n' + res.stderr };
|
|
80
|
+
};
|
|
81
|
+
let firstBad;
|
|
82
|
+
for (const bin of spec.bins) {
|
|
83
|
+
const found = findOnPath(bin);
|
|
84
|
+
if (!found)
|
|
85
|
+
continue;
|
|
86
|
+
const version = await call(found, spec.probe.versionArgs);
|
|
87
|
+
const help = await call(found, spec.probe.helpArgs);
|
|
88
|
+
const verdict = evaluateCliContract(spec, bin, version, help);
|
|
89
|
+
const result = { state: verdict.state, bin, path: found, ...verdict.version ? { version: verdict.version } : {}, ...verdict.reason ? { reason: verdict.reason } : {}, checkedAt: now() };
|
|
90
|
+
if (verdict.state === 'detected')
|
|
91
|
+
return result;
|
|
92
|
+
firstBad ??= result;
|
|
93
|
+
}
|
|
94
|
+
return firstBad ?? { state: 'missing', reason: spec.bins.join(' / ') + ' not found on PATH (' + spec.packageNote + ')', checkedAt: now() };
|
|
95
|
+
}
|
|
96
|
+
/** Probe one spec (cached; concurrent calls for the same spec share one run). */
|
|
97
|
+
export async function probeCli(spec, options = {}) {
|
|
98
|
+
const key = keyOf(spec);
|
|
99
|
+
const now = options.now ?? Date.now;
|
|
100
|
+
if (!options.force) {
|
|
101
|
+
const hit = cache.get(key);
|
|
102
|
+
if (hit && now() - hit.at <= (options.ttlMs ?? DEFAULT_TTL_MS))
|
|
103
|
+
return hit.result;
|
|
104
|
+
const flight = flights.get(key);
|
|
105
|
+
if (flight)
|
|
106
|
+
return flight;
|
|
107
|
+
}
|
|
108
|
+
const flight = probeUncached(spec, options).then(result => { cache.set(key, { at: now(), result }); return result; }).finally(() => { if (flights.get(key) === flight)
|
|
109
|
+
flights.delete(key); });
|
|
110
|
+
flights.set(key, flight);
|
|
111
|
+
return flight;
|
|
112
|
+
}
|
|
113
|
+
/** Probe many specs with a small concurrency bound (a settings refresh must not spawn dozens of processes at once). */
|
|
114
|
+
export async function probeAll(specs, options = {}, concurrency = 4) {
|
|
115
|
+
const out = new Map();
|
|
116
|
+
let next = 0;
|
|
117
|
+
const worker = async () => {
|
|
118
|
+
for (;;) {
|
|
119
|
+
const spec = specs[next++];
|
|
120
|
+
if (!spec)
|
|
121
|
+
return;
|
|
122
|
+
out.set(spec.id, await probeCli(spec, options));
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, specs.length) }, worker));
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=probe.js.map
|