dsh-web-search-pro 0.2.0 → 0.2.1
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 +15 -0
- package/README.md +75 -6
- package/assets/skills/dsh-web-search-pro/SKILL.md +1 -1
- package/assets/skills/dsh-web-search-pro/references/sources.md +11 -2
- package/catalog/sources.v1.json +20 -12
- package/lib/actions/search.js +5 -4
- package/lib/actions/search.js.map +1 -1
- package/lib/actions/sources.js +42 -25
- package/lib/actions/sources.js.map +1 -1
- package/lib/cli/builtin-specs.js +306 -0
- package/lib/cli/builtin-specs.js.map +1 -0
- package/lib/cli/chain.js +192 -0
- package/lib/cli/chain.js.map +1 -0
- package/lib/cli/chains-spec.js +98 -0
- package/lib/cli/chains-spec.js.map +1 -0
- package/lib/cli/engine.js +83 -0
- package/lib/cli/engine.js.map +1 -0
- package/lib/cli/opencli.js +105 -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 +374 -0
- package/lib/cli/runner.js.map +1 -0
- package/lib/cli/spec.js +416 -0
- package/lib/cli/spec.js.map +1 -0
- package/lib/client/SettingsCard.js +1 -1
- package/lib/client/SettingsCard.js.map +1 -1
- package/lib/client/form-specs.js +2 -0
- package/lib/client/form-specs.js.map +1 -1
- package/lib/client/form.js +14 -3
- package/lib/client/form.js.map +1 -1
- package/lib/client/locales.js +4 -0
- package/lib/client/locales.js.map +1 -1
- package/lib/client/validators.js +15 -0
- package/lib/client/validators.js.map +1 -1
- package/lib/client.js +1425 -3
- package/lib/client.js.map +1 -1
- package/lib/config.js +4 -0
- package/lib/config.js.map +1 -1
- 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 +31 -114
- package/lib/engines.js.map +1 -1
- package/lib/pipeline/candidates.js.map +1 -1
- package/lib/pipeline/run.js +4 -2
- package/lib/pipeline/run.js.map +1 -1
- package/lib/providers/builtin.js +10 -14
- package/lib/providers/builtin.js.map +1 -1
- package/lib/providers/platforms.js +114 -55
- package/lib/providers/platforms.js.map +1 -1
- package/lib/providers/registry.js.map +1 -1
- package/lib/router.js +63 -6
- package/lib/router.js.map +1 -1
- package/lib/types/cli/builtin-specs.d.ts +30 -0
- package/lib/types/cli/chain.d.ts +83 -0
- package/lib/types/cli/chains-spec.d.ts +39 -0
- package/lib/types/cli/engine.d.ts +43 -0
- package/lib/types/cli/opencli.d.ts +38 -0
- package/lib/types/cli/probe.d.ts +53 -0
- package/lib/types/cli/runner.d.ts +87 -0
- package/lib/types/cli/spec.d.ts +176 -0
- package/lib/types/client/form-specs.d.ts +1 -1
- package/lib/types/client/locales.d.ts +4 -0
- package/lib/types/client/validators.d.ts +6 -0
- package/lib/types/config.d.ts +11 -0
- package/lib/types/deps.d.ts +25 -23
- package/lib/types/engine-error.d.ts +12 -0
- package/lib/types/engines.d.ts +16 -9
- package/lib/types/index.d.ts +4 -0
- package/lib/types/pipeline/candidates.d.ts +2 -0
- package/lib/types/pipeline/run.d.ts +2 -1
- package/lib/types/providers/platforms.d.ts +15 -3
- package/lib/types/providers/registry.d.ts +3 -0
- package/lib/types/router.d.ts +13 -0
- package/lib/types/util.d.ts +6 -1
- package/lib/util.js +7 -3
- package/lib/util.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declarative CLI adapter spec (dev-plan M12): what a standalone command-line tool is, which of its commands the plugin
|
|
3
|
+
* may run, and how its output maps to search sources. Pure data plus validation, no Node imports, so the settings card
|
|
4
|
+
* (client bundle) validates `cliAdapters` with the very function the server uses.
|
|
5
|
+
*
|
|
6
|
+
* The read-only guarantee lives here and applies to built-in and user-defined specs alike:
|
|
7
|
+
* - argv is an array of tokens, never a shell string (the runner spawns without a shell);
|
|
8
|
+
* - a spec declares `allowedSubcommands`, and every command word its argv uses (the leading literal tokens before the
|
|
9
|
+
* first flag or placeholder) must be in that list;
|
|
10
|
+
* - no command word, declared subcommand or flag may match the global deny-list (login, post, delete*, like, ...,
|
|
11
|
+
* and the flags that make a CLI read browser cookies or carry secrets in argv).
|
|
12
|
+
* @module web-search-pro/cli/spec
|
|
13
|
+
*/
|
|
14
|
+
export declare const OUTPUT_FORMATS: readonly ["json", "ndjson", "yaml", "text"];
|
|
15
|
+
export type OutputFormat = typeof OUTPUT_FORMATS[number];
|
|
16
|
+
/** How far a spec has been checked against the real tool (recorded in the spec and shown in `sources.status`). */
|
|
17
|
+
export declare const CLI_VERIFICATIONS: readonly ["live", "contract-only", "docs-only"];
|
|
18
|
+
export type CliVerification = typeof CLI_VERIFICATIONS[number];
|
|
19
|
+
export declare const CLI_ERROR_CODES: readonly ["CLI_NOT_FOUND", "CLI_CONTRACT_MISMATCH", "CLI_NOT_LOGGED_IN", "ENGINE_EMPTY", "CLI_FAILED"];
|
|
20
|
+
export type CliErrorCode = typeof CLI_ERROR_CODES[number];
|
|
21
|
+
/** One piece of a joined field: a path, optionally with text put before / after the value. */
|
|
22
|
+
export type JoinPart = string | {
|
|
23
|
+
path: string;
|
|
24
|
+
prefix?: string;
|
|
25
|
+
suffix?: string;
|
|
26
|
+
};
|
|
27
|
+
/** `{ template }` fills `{path}` placeholders from the item and yields nothing when one is empty; `when` makes it apply to one kind of item. */
|
|
28
|
+
export interface TemplateSource {
|
|
29
|
+
template: string;
|
|
30
|
+
when?: {
|
|
31
|
+
path: string;
|
|
32
|
+
equals: string | number | boolean;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Where a field of a source comes from, for one parsed item:
|
|
37
|
+
* - a dotted path (`data.title`, `items.0.url`);
|
|
38
|
+
* - a list of paths and templates: the first non-empty one wins;
|
|
39
|
+
* - `{ template }`: `{path}` placeholders over the item, dropped when any of them is empty;
|
|
40
|
+
* - `{ join }`: the non-empty parts joined with `sep` (default ` | `);
|
|
41
|
+
* - `{ epoch }`: a Unix timestamp (seconds) as an ISO date; `{ date }`: the date part (first 10 characters) of an ISO timestamp.
|
|
42
|
+
*/
|
|
43
|
+
export type FieldSource = string | readonly (string | TemplateSource)[] | TemplateSource | {
|
|
44
|
+
join: readonly JoinPart[];
|
|
45
|
+
sep?: string;
|
|
46
|
+
} | {
|
|
47
|
+
epoch: string;
|
|
48
|
+
} | {
|
|
49
|
+
date: string;
|
|
50
|
+
};
|
|
51
|
+
export interface CliOutputSpec {
|
|
52
|
+
format: OutputFormat;
|
|
53
|
+
/** json / yaml: dotted path of the item array; absent = the root array, else the first array-valued property. */
|
|
54
|
+
itemsPath?: string;
|
|
55
|
+
/** json / yaml: every entry must hold, else the output is a failure (a versioned envelope's `ok` and `schema_version`). */
|
|
56
|
+
expect?: readonly {
|
|
57
|
+
path: string;
|
|
58
|
+
equals: string | number | boolean;
|
|
59
|
+
}[];
|
|
60
|
+
/** Paths of the tool's own error text, read when `expect` fails (first non-empty one). */
|
|
61
|
+
errorPaths?: readonly string[];
|
|
62
|
+
/** text: how a line becomes an item (`tsv`: tab separated columns; `url-lines`: lines holding a URL, `url` + `title`). */
|
|
63
|
+
lines?: {
|
|
64
|
+
mode: 'tsv';
|
|
65
|
+
columns: readonly string[];
|
|
66
|
+
} | {
|
|
67
|
+
mode: 'url-lines';
|
|
68
|
+
};
|
|
69
|
+
fields: {
|
|
70
|
+
url: FieldSource;
|
|
71
|
+
title?: FieldSource;
|
|
72
|
+
snippet?: FieldSource;
|
|
73
|
+
publishedAt?: FieldSource;
|
|
74
|
+
};
|
|
75
|
+
/** Strip HTML tags from title and snippet. */
|
|
76
|
+
stripTags?: boolean;
|
|
77
|
+
/** Values read as "empty" (`None`). */
|
|
78
|
+
emptyValues?: readonly string[];
|
|
79
|
+
/** Longest snippet kept (default 400). */
|
|
80
|
+
snippetMax?: number;
|
|
81
|
+
/** Longest title kept (default 300). */
|
|
82
|
+
titleMax?: number;
|
|
83
|
+
/** Drop items without a title (a line that is not a result). */
|
|
84
|
+
requireTitle?: boolean;
|
|
85
|
+
}
|
|
86
|
+
export interface CliSearchSpec {
|
|
87
|
+
/** Arguments after the binary. `{query}` and `{count}` are the placeholders; a token may embed one (`ytsearch{count}:{query}`). */
|
|
88
|
+
argv: readonly string[];
|
|
89
|
+
/** Largest `{count}` sent (the search call's count is clamped to 1..maxCount). */
|
|
90
|
+
maxCount?: number;
|
|
91
|
+
output: CliOutputSpec;
|
|
92
|
+
/** Case-insensitive fragments of the tool's output that mean "no results" rather than a failure. */
|
|
93
|
+
emptyWhen?: readonly string[];
|
|
94
|
+
/** Case-insensitive fragments of the tool's output that mean "not logged in" (maps to CLI_NOT_LOGGED_IN). */
|
|
95
|
+
notLoggedInPatterns?: readonly string[];
|
|
96
|
+
}
|
|
97
|
+
export interface CliProbeSpec {
|
|
98
|
+
versionArgs: readonly string[];
|
|
99
|
+
/** `X.Y.Z` the detected version must reach. */
|
|
100
|
+
minVersion?: string;
|
|
101
|
+
helpArgs: readonly string[];
|
|
102
|
+
/** Fragments the help output must contain: the subcommands and flags that prove this is the tool the spec describes. */
|
|
103
|
+
mustContain: readonly string[];
|
|
104
|
+
}
|
|
105
|
+
export interface CliLoginSpec {
|
|
106
|
+
/** What the user runs themselves (`xhs login`); never run by the plugin. */
|
|
107
|
+
command: string;
|
|
108
|
+
/** Home-relative files that exist once the user has logged in. Only their existence is checked, never their content. */
|
|
109
|
+
savedCredentialPaths?: readonly string[];
|
|
110
|
+
/** The tool reads the browser's cookies by itself when it has no saved login: then it is never run without one. */
|
|
111
|
+
readsBrowserCookiesWithoutLogin?: boolean;
|
|
112
|
+
}
|
|
113
|
+
export interface CliAdapterSpec {
|
|
114
|
+
id: string;
|
|
115
|
+
/** Command names tried in order on PATH. */
|
|
116
|
+
bins: readonly string[];
|
|
117
|
+
/** What the package is called, which can differ from the command (`pip install pyzhihu-cli` runs `zhihu`). */
|
|
118
|
+
packageNote: string;
|
|
119
|
+
/** Platform ids the spec can serve (`search.run platform=`); a user spec's platform is `custom-cli:<id>`. */
|
|
120
|
+
platforms: readonly string[];
|
|
121
|
+
probe: CliProbeSpec;
|
|
122
|
+
/** Every command word the argv templates use. */
|
|
123
|
+
allowedSubcommands: readonly string[];
|
|
124
|
+
search: CliSearchSpec;
|
|
125
|
+
/** Per-platform overrides of `search` (gh serves github, github-issues and github-code with different commands). */
|
|
126
|
+
searches?: Readonly<Record<string, CliSearchSpec>>;
|
|
127
|
+
read?: CliSearchSpec;
|
|
128
|
+
env: {
|
|
129
|
+
required?: readonly string[];
|
|
130
|
+
passthrough?: readonly string[];
|
|
131
|
+
set?: Readonly<Record<string, string>>;
|
|
132
|
+
};
|
|
133
|
+
needsLogin: boolean;
|
|
134
|
+
login?: CliLoginSpec;
|
|
135
|
+
timeoutMs: number;
|
|
136
|
+
maxOutputBytes: number;
|
|
137
|
+
/** Built-ins only: how far the spec was checked. User-defined specs carry none (they are unverified by definition). */
|
|
138
|
+
verification?: {
|
|
139
|
+
status: CliVerification;
|
|
140
|
+
version?: string;
|
|
141
|
+
date?: string;
|
|
142
|
+
note?: string;
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/** Command words that write, log in, or change local or remote state. Matched exactly, or as `<word>-...` / `<word>_...`. */
|
|
146
|
+
export declare const DENIED_SUBCOMMANDS: readonly string[];
|
|
147
|
+
/** Command words denied by prefix alone (`delete`, `delete-note`, `deleteNote`, `remove...`). */
|
|
148
|
+
export declare const DENIED_PREFIXES: readonly string[];
|
|
149
|
+
/** Flags (without leading dashes) that read browser cookies or carry credentials in argv. */
|
|
150
|
+
export declare const DENIED_FLAGS: readonly string[];
|
|
151
|
+
/** The command words of an argv: leading literal tokens before the first flag or placeholder token. */
|
|
152
|
+
export declare function commandWords(argv: readonly string[]): string[];
|
|
153
|
+
/** Problems of one argv template with respect to the read-only guard (empty = fine). */
|
|
154
|
+
export declare function argvProblems(argv: readonly string[], allowed: readonly string[], where: string): string[];
|
|
155
|
+
export interface CliSpecValidation {
|
|
156
|
+
ok: boolean;
|
|
157
|
+
errors: string[];
|
|
158
|
+
spec?: CliAdapterSpec;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Validate one adapter spec (built-in or user-defined). Never throws. A user-defined spec (`origin: 'user'`) may not carry a
|
|
162
|
+
* `verification` (it is unverified by definition) and its `platforms` are ignored: it is registered as `custom-cli:<id>`.
|
|
163
|
+
*/
|
|
164
|
+
export declare function validateCliAdapterSpec(raw: unknown, origin?: 'builtin' | 'user'): CliSpecValidation;
|
|
165
|
+
/** Validate the `cliAdapters` setting: the valid specs by id and the problems of the rest (an invalid entry is ignored, never fatal). */
|
|
166
|
+
export declare function resolveCliAdapters(input: unknown): {
|
|
167
|
+
specs: Map<string, CliAdapterSpec>;
|
|
168
|
+
diagnostics: string[];
|
|
169
|
+
};
|
|
170
|
+
/** Search spec of one platform: the platform's override, else the default. */
|
|
171
|
+
export declare function searchSpecFor(spec: CliAdapterSpec, platform: string): CliSearchSpec;
|
|
172
|
+
export declare function compareVersions(left: string, right: string): number;
|
|
173
|
+
/** The first `X.Y.Z` (or `X.Y`) in a tool's version output. */
|
|
174
|
+
export declare function parseVersion(output: string): string | undefined;
|
|
175
|
+
/** `platformBackends`: ordered backend ids per platform. Pure shape check; ids are checked against the known backends by the caller. */
|
|
176
|
+
export declare function platformBackendProblems(input: unknown): string[];
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
export type Json = Record<string, unknown>;
|
|
14
14
|
/** Top-level config fields (one Host write each). */
|
|
15
|
-
export type TopField = 'engines' | 'parallelEngines' | 'searchMaxResults' | 'timeoutMs' | 'fetchDefaultChars' | 'exaContentsPerUrlChars' | 'exaContentsTotalChars' | 'exaApiKeyEnv' | 'jinaApiKeyEnv' | 'githubTokenEnv' | 'bochaApiKeyEnv' | 'bochaBaseUrl' | 'bochaSummary' | 'searxngUrl' | 'openalexMailto' | 'enableCliBackends' | 'opencliEnabled' | 'agentReachEnabled' | 'providerId' | 'registerProvider' | 'toolSurface' | 'playwright' | 'ttlSeconds' | 'memoryCacheEntries' | 'rrfConstant' | 'freshnessBoost' | 'freshnessDays' | 'authorityBoost' | 'authorityDomains' | 'dbPath' | 'allowProxyFakeIp' | 'platformRules' | 'customPlatforms' | 'browserBindings' | 'verbose';
|
|
15
|
+
export type TopField = 'engines' | 'parallelEngines' | 'searchMaxResults' | 'timeoutMs' | 'fetchDefaultChars' | 'exaContentsPerUrlChars' | 'exaContentsTotalChars' | 'exaApiKeyEnv' | 'jinaApiKeyEnv' | 'githubTokenEnv' | 'bochaApiKeyEnv' | 'bochaBaseUrl' | 'bochaSummary' | 'searxngUrl' | 'openalexMailto' | 'enableCliBackends' | 'opencliEnabled' | 'agentReachEnabled' | 'providerId' | 'registerProvider' | 'toolSurface' | 'playwright' | 'ttlSeconds' | 'memoryCacheEntries' | 'rrfConstant' | 'freshnessBoost' | 'freshnessDays' | 'authorityBoost' | 'authorityDomains' | 'dbPath' | 'allowProxyFakeIp' | 'platformRules' | 'customPlatforms' | 'browserBindings' | 'platformBackends' | 'cliAdapters' | 'verbose';
|
|
16
16
|
/** Options inside `evidence` / `provider`, addressed by their dotted config path. */
|
|
17
17
|
export type PathField = 'evidence.autoProviders' | 'evidence.sourcePolicy' | 'evidence.maxRounds' | 'evidence.maxQueries' | 'evidence.judge.mode' | 'evidence.hybridBorderline' | 'evidence.judge.provider' | 'evidence.maxJevQuestions' | 'evidence.judge.allowLlm' | 'evidence.judge.providers' | 'evidence.coverage.mode' | 'evidence.coverage.provider' | 'evidence.coverage.thresholds.weak' | 'evidence.coverage.thresholds.covered' | 'evidence.budget.perSearchInputTokens' | 'evidence.budget.dailyInputTokens' | 'evidence.budget.timezone' | 'evidence.budget.providers' | 'provider.evidence' | 'provider.deadlineMs' | 'sources.priority' | 'sources.disabled';
|
|
18
18
|
/** One rubric override entry (`evidence.rubrics.<id>`) and one keyed-source field. */
|
|
@@ -72,6 +72,10 @@ export declare const zh: {
|
|
|
72
72
|
readonly platformRulesHint: "按平台设置 item/title/link/text 选择器。";
|
|
73
73
|
readonly customPlatforms: "自定义平台(JSON)";
|
|
74
74
|
readonly customPlatformsHint: "按平台定义搜索 URL 与选择器;登录态请通过浏览器绑定引用 AuthProfile。";
|
|
75
|
+
readonly platformBackends: "平台后端顺序(JSON)";
|
|
76
|
+
readonly platformBackendsHint: "每个平台依次尝试的后端,如 {\"xiaohongshu\": [\"xhs\", \"opencli\", \"browser-opencli\"]}。id:各 CLI(xhs、rdt、zhihu、twitter、bili、yt-dlp、omnireach、wx-search-cli、gh、tanso)、opencli、browser-opencli、browser-search、rest、custom-cli:<id>。缺失或未登录的后端会被跳过并说明原因。";
|
|
77
|
+
readonly cliAdapters: "自定义 CLI 适配(JSON)";
|
|
78
|
+
readonly cliAdaptersHint: "按 id 定义只读 CLI 适配规格(argv 数组、输出字段、探测契约、allowedSubcommands)。写操作和读取浏览器 Cookie 的参数会被拒绝;无效条目被忽略并在 sources.status 说明。登录始终由你自己运行 CLI。";
|
|
75
79
|
readonly browserBindings: "浏览器绑定(JSON)";
|
|
76
80
|
readonly browserBindingsHint: "把平台绑定到 dsh-browser AuthProfile 与 RulePack。";
|
|
77
81
|
readonly verbose: "详细诊断日志";
|
|
@@ -48,3 +48,9 @@ export declare function evidenceIssues(ev: Json): Issue[];
|
|
|
48
48
|
export declare function sourcesIssues(sources: Json): Issue[];
|
|
49
49
|
/** Rubric override ids in a settings object that name no built-in rubric (the server ignores them). */
|
|
50
50
|
export declare function unknownRubricIds(rubrics: unknown): string[];
|
|
51
|
+
/**
|
|
52
|
+
* Problems of the CLI adapter settings, from the server's own functions: `cliAdapters` entries the server would ignore
|
|
53
|
+
* (read-only guard, bad argv, secrets in env) and `platformBackends` that name an unknown platform or a backend that
|
|
54
|
+
* cannot serve it.
|
|
55
|
+
*/
|
|
56
|
+
export declare function cliIssues(platformBackends: unknown, cliAdapters: unknown): Issue[];
|
package/lib/types/config.d.ts
CHANGED
|
@@ -214,6 +214,13 @@ export interface Config {
|
|
|
214
214
|
customPlatforms?: Record<string, CustomPlatformSpec>;
|
|
215
215
|
/** Per-platform binding to domain-scoped dsh-browser auth/rule profiles. */
|
|
216
216
|
browserBindings?: Record<string, BrowserBinding>;
|
|
217
|
+
/**
|
|
218
|
+
* Ordered backend chain per platform (dev-plan M12): backend ids such as `xhs`, `opencli` (standalone OpenCLI), `browser-opencli`
|
|
219
|
+
* (dsh-browser), `browser-search`, `rest`, `gh`, `custom-cli:<id>`. A platform not listed uses its default chain.
|
|
220
|
+
*/
|
|
221
|
+
platformBackends?: Record<string, string[]>;
|
|
222
|
+
/** User-defined CLI adapter specs by id (validated like the built-in ones; invalid entries are ignored with a diagnostic in sources.status). */
|
|
223
|
+
cliAdapters?: Record<string, Record<string, unknown>>;
|
|
217
224
|
/** Snapshot options. The browser runtime itself (channel/headless/storageStatePath) is provided by the dsh-browser plugin via the `browser` service. */
|
|
218
225
|
playwright: {
|
|
219
226
|
/** Gate the playwright fallback backend in read.fetch. */
|
|
@@ -313,6 +320,8 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
|
313
320
|
authProfile: z<string, string, "plain">;
|
|
314
321
|
rulePack: z<string, string, "plain">;
|
|
315
322
|
}>>, string>>, "volatile">;
|
|
323
|
+
platformBackends: z<NoInfer<import("@deepseek-ai/cosmokit").Dict<string[], string>>, NoInfer<import("@deepseek-ai/cosmokit").Dict<string[], string>>, "volatile">;
|
|
324
|
+
cliAdapters: z<NoInfer<import("@deepseek-ai/cosmokit").Dict<import("@deepseek-ai/cosmokit").Dict<any, string>, string>>, NoInfer<import("@deepseek-ai/cosmokit").Dict<import("@deepseek-ai/cosmokit").Dict<any, string>, string>>, "volatile">;
|
|
316
325
|
playwright: z<Schemastery.ObjectS<NoInfer<{
|
|
317
326
|
enabled: z<boolean, boolean, "volatile-defined">;
|
|
318
327
|
snapshotDir: z<string, string, "plain">;
|
|
@@ -771,6 +780,8 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
|
771
780
|
authProfile: z<string, string, "plain">;
|
|
772
781
|
rulePack: z<string, string, "plain">;
|
|
773
782
|
}>>, string>>, "volatile">;
|
|
783
|
+
platformBackends: z<NoInfer<import("@deepseek-ai/cosmokit").Dict<string[], string>>, NoInfer<import("@deepseek-ai/cosmokit").Dict<string[], string>>, "volatile">;
|
|
784
|
+
cliAdapters: z<NoInfer<import("@deepseek-ai/cosmokit").Dict<import("@deepseek-ai/cosmokit").Dict<any, string>, string>>, NoInfer<import("@deepseek-ai/cosmokit").Dict<import("@deepseek-ai/cosmokit").Dict<any, string>, string>>, "volatile">;
|
|
774
785
|
playwright: z<Schemastery.ObjectS<NoInfer<{
|
|
775
786
|
enabled: z<boolean, boolean, "volatile-defined">;
|
|
776
787
|
snapshotDir: z<string, string, "plain">;
|
package/lib/types/deps.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External dependency detection and install for the CLI/platform backends.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
3
|
+
* Backends shell out to tools installed outside DSH: the built-in CLI adapter specs (bili, yt-dlp, twitter, xhs, zhihu,
|
|
4
|
+
* rdt, omnireach, gh, wx-search-cli, tanso, the standalone opencli) plus the user's own `cliAdapters`, and mcporter. This
|
|
5
|
+
* module reports which are present, whether the command on PATH really has the contract the adapter needs (a same-named
|
|
6
|
+
* program with another contract is `incompatible`, never `detected`), and how to install them; the sources.deps /
|
|
7
|
+
* sources.install actions expose it to the model. Probes are local, cached with a TTL, and never run a search or a login.
|
|
8
8
|
*
|
|
9
9
|
* Install is intentionally a MODEL-FACING TOOL, not a browser settings button:
|
|
10
10
|
* a browser button running winget/pip/npm would be arbitrary command execution
|
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
* tool-permission/approval pipeline. The card points at this tool instead.
|
|
13
13
|
* @module web-search-pro/deps
|
|
14
14
|
*/
|
|
15
|
+
import { BILI_CLI_INSTALLS, BILI_CLI_REVISION, BILI_CLI_SOURCE, BILI_CLI_VERSION, TWITTER_CLI_INSTALLS } from './cli/builtin-specs.ts';
|
|
16
|
+
import { type CliInstallation } from './cli/probe.ts';
|
|
17
|
+
export { BILI_CLI_INSTALLS, BILI_CLI_REVISION, BILI_CLI_SOURCE, BILI_CLI_VERSION, TWITTER_CLI_INSTALLS };
|
|
15
18
|
export interface DepInfo {
|
|
16
19
|
id: string;
|
|
17
20
|
label: string;
|
|
@@ -27,6 +30,10 @@ export interface DepInfo {
|
|
|
27
30
|
version?: string;
|
|
28
31
|
/** Why a command found on PATH is not compatible. */
|
|
29
32
|
diagnostic?: string;
|
|
33
|
+
/** `missing` (not on PATH), `detected` (present with the contract the adapter needs), `incompatible` (present, wrong contract or too old). */
|
|
34
|
+
installation?: CliInstallation;
|
|
35
|
+
/** How far the adapter was checked against the real tool: `live`, `contract-only`, `docs-only` (absent: user-defined or not a CLI adapter). */
|
|
36
|
+
verification?: string;
|
|
30
37
|
/** No backend of this plugin executes it; it is only an install helper, so its absence is not a gap. */
|
|
31
38
|
optional?: boolean;
|
|
32
39
|
installs: {
|
|
@@ -34,31 +41,26 @@ export interface DepInfo {
|
|
|
34
41
|
command: string;
|
|
35
42
|
}[];
|
|
36
43
|
}
|
|
37
|
-
|
|
38
|
-
export declare const BILI_CLI_REVISION = "489607468f967e0e11f3cdff6efc022d011e982a";
|
|
39
|
-
export declare const BILI_CLI_SOURCE = "git+https://github.com/public-clis/bilibili-cli@489607468f967e0e11f3cdff6efc022d011e982a";
|
|
40
|
-
export declare const BILI_CLI_INSTALLS: {
|
|
41
|
-
installer: string;
|
|
42
|
-
command: string;
|
|
43
|
-
}[];
|
|
44
|
-
interface DepProbeResult {
|
|
44
|
+
interface ProbeResult {
|
|
45
45
|
available: boolean;
|
|
46
46
|
version?: string;
|
|
47
47
|
diagnostic?: string;
|
|
48
48
|
}
|
|
49
49
|
/** Validate the public-clis bili command rather than trusting an ambiguous package name. */
|
|
50
|
-
export declare function evaluateBiliCli(versionOutput: string, searchHelpOutput: string):
|
|
50
|
+
export declare function evaluateBiliCli(versionOutput: string, searchHelpOutput: string): ProbeResult;
|
|
51
51
|
/**
|
|
52
52
|
* The twitter backend runs `twitter search <query> -n N` (twitter-cli). Another program that happens to be
|
|
53
53
|
* called `twitter` must not pass: require a successful `search --help` that actually describes a search command.
|
|
54
54
|
*/
|
|
55
|
-
export declare function evaluateTwitterCli(searchHelpOutput: string, exitCode: number):
|
|
56
|
-
export declare const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
export declare function evaluateTwitterCli(searchHelpOutput: string, exitCode: number): ProbeResult;
|
|
56
|
+
export declare const DEP_IDS: readonly string[];
|
|
57
|
+
/** Detect every dependency: the spec-backed CLIs through their cached contract probes, the rest by presence on PATH. */
|
|
58
|
+
export declare function detectDeps(options?: {
|
|
59
|
+
config?: {
|
|
60
|
+
cliAdapters?: unknown;
|
|
61
|
+
};
|
|
62
|
+
force?: boolean;
|
|
63
|
+
}): Promise<DepInfo[]>;
|
|
62
64
|
/** Run the install command for one backend + installer. */
|
|
63
65
|
export declare function installDep(id: string, installer: string): Promise<{
|
|
64
66
|
code: number;
|
|
@@ -66,5 +68,5 @@ export declare function installDep(id: string, installer: string): Promise<{
|
|
|
66
68
|
stderr: string;
|
|
67
69
|
timedOut: boolean;
|
|
68
70
|
}>;
|
|
69
|
-
|
|
70
|
-
export
|
|
71
|
+
/** The installer `sources.install` uses when none is named: the first one listed for the dependency. */
|
|
72
|
+
export declare function defaultInstaller(id: string): string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The error every search engine and CLI adapter throws. Leaf module (no imports), so both engines.ts and the CLI adapter
|
|
3
|
+
* layer can extend it without an import cycle.
|
|
4
|
+
* @module web-search-pro/engine-error
|
|
5
|
+
*/
|
|
6
|
+
export declare class EngineError extends Error {
|
|
7
|
+
readonly code: string;
|
|
8
|
+
readonly retryable: boolean;
|
|
9
|
+
readonly retryAfterMs?: number | undefined;
|
|
10
|
+
/** `retryAfterMs`: the service's own wait hint (Retry-After); the router uses it as the cooldown. */
|
|
11
|
+
constructor(message: string, code: string, retryable?: boolean, retryAfterMs?: number | undefined);
|
|
12
|
+
}
|
package/lib/types/engines.d.ts
CHANGED
|
@@ -10,16 +10,23 @@ import type { BrowserService } from './browser-service.ts';
|
|
|
10
10
|
import { type BrowserMethod } from './browser-access.ts';
|
|
11
11
|
import type { CustomPlatformSpec } from './config.ts';
|
|
12
12
|
import { type ExaSearchRequest } from './exa-client.ts';
|
|
13
|
+
import { EngineError } from './engine-error.ts';
|
|
13
14
|
export interface SearchOutcome {
|
|
14
15
|
/** Provider-generated answer/summary text, when any. */
|
|
15
16
|
content?: string;
|
|
16
17
|
sources: WebSearchSource[];
|
|
17
18
|
/** Id of the concrete backend that answered (a platform provider with a fallback chain names the leg that ran). */
|
|
18
19
|
via?: string;
|
|
20
|
+
/** The backend id of the platform chain that answered (`bili`, `browser-opencli`), for display and history. */
|
|
21
|
+
backend?: string;
|
|
22
|
+
/** Short notes for the caller: backends skipped on the way, results dropped. */
|
|
23
|
+
notes?: string[];
|
|
19
24
|
}
|
|
20
25
|
export interface Engine {
|
|
21
26
|
id: string;
|
|
22
27
|
label: string;
|
|
28
|
+
/** Backend id in a platform chain (`xhs`, `browser-opencli`); absent for plain engines. */
|
|
29
|
+
backend?: string;
|
|
23
30
|
/** Cheap local availability check; must not do network I/O. */
|
|
24
31
|
available(): boolean;
|
|
25
32
|
/** Browser-service method this engine depends on (dsh-browser is optional). */
|
|
@@ -50,13 +57,7 @@ export interface EngineSearchOptions {
|
|
|
50
57
|
/** Feed URL of the `rss` platform (it has no fixed endpoint). */
|
|
51
58
|
url?: string;
|
|
52
59
|
}
|
|
53
|
-
export
|
|
54
|
-
readonly code: string;
|
|
55
|
-
readonly retryable: boolean;
|
|
56
|
-
readonly retryAfterMs?: number | undefined;
|
|
57
|
-
/** `retryAfterMs`: the service's own wait hint (Retry-After); the router uses it as the cooldown. */
|
|
58
|
-
constructor(message: string, code: string, retryable?: boolean, retryAfterMs?: number | undefined);
|
|
59
|
-
}
|
|
60
|
+
export { EngineError };
|
|
60
61
|
/** One metered request of a non-model provider (Bocha search): counted in the usage ledger, tokens n/a, price unknown. */
|
|
61
62
|
export interface UsageRecorder {
|
|
62
63
|
record(entry: {
|
|
@@ -140,13 +141,19 @@ export declare function githubEngine(deps: EngineDeps): Engine;
|
|
|
140
141
|
export declare function githubCodeEngine(deps: EngineDeps): Engine;
|
|
141
142
|
export declare function githubIssuesEngine(deps: EngineDeps): Engine;
|
|
142
143
|
export declare function bilibiliEngine(deps: EngineDeps): Engine;
|
|
143
|
-
/** Exact argv contract supported by public-clis/bilibili-cli v0.6.2
|
|
144
|
+
/** Exact argv contract supported by public-clis/bilibili-cli v0.6.2+ (the `bili` spec). */
|
|
144
145
|
export declare function biliSearchArgs(query: string, count: number): string[];
|
|
145
|
-
/** Parse and validate bili-cli's versioned JSON envelope. */
|
|
146
|
+
/** Parse and validate bili-cli's versioned JSON envelope (the `bili` spec). */
|
|
146
147
|
export declare function parseBilibiliSearchOutput(output: string): WebSearchSource[];
|
|
147
148
|
export declare function v2exEngine(allowProxyFakeIp?: boolean): Engine;
|
|
148
149
|
export declare function youtubeEngine(deps: EngineDeps, cli?: typeof runCli): Engine;
|
|
150
|
+
/** Platforms the dsh-browser OpenCLI bridge has a site adapter for (platform -> adapter name). */
|
|
151
|
+
export declare const OPENCLI_PLATFORMS: Record<string, string>;
|
|
149
152
|
export declare function opencliEngine(platform: string, deps: EngineDeps): Engine;
|
|
153
|
+
/**
|
|
154
|
+
* The legacy `agentreach-twitter` engine, now a thin view of the `twitter` CLI spec (the platform chain uses the spec
|
|
155
|
+
* directly; this keeps the old engine id and its install message). Credentials are checked by `available()`.
|
|
156
|
+
*/
|
|
150
157
|
export declare function agentReachEngine(platform: string, deps: EngineDeps): Engine;
|
|
151
158
|
export declare function arxivEngine(allowProxyFakeIp?: boolean): Engine;
|
|
152
159
|
export declare function pubmedEngine(allowProxyFakeIp?: boolean): Engine;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -114,6 +114,8 @@ declare const _default: {
|
|
|
114
114
|
authProfile: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
115
115
|
rulePack: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
116
116
|
}>>, string>>, "volatile">;
|
|
117
|
+
platformBackends: import("@deepseek-ai/schemastery").default<NoInfer<import("@deepseek-ai/cosmokit").Dict<string[], string>>, NoInfer<import("@deepseek-ai/cosmokit").Dict<string[], string>>, "volatile">;
|
|
118
|
+
cliAdapters: import("@deepseek-ai/schemastery").default<NoInfer<import("@deepseek-ai/cosmokit").Dict<import("@deepseek-ai/cosmokit").Dict<any, string>, string>>, NoInfer<import("@deepseek-ai/cosmokit").Dict<import("@deepseek-ai/cosmokit").Dict<any, string>, string>>, "volatile">;
|
|
117
119
|
playwright: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<NoInfer<{
|
|
118
120
|
enabled: import("@deepseek-ai/schemastery").default<boolean, boolean, "volatile-defined">;
|
|
119
121
|
snapshotDir: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
@@ -572,6 +574,8 @@ declare const _default: {
|
|
|
572
574
|
authProfile: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
573
575
|
rulePack: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
574
576
|
}>>, string>>, "volatile">;
|
|
577
|
+
platformBackends: import("@deepseek-ai/schemastery").default<NoInfer<import("@deepseek-ai/cosmokit").Dict<string[], string>>, NoInfer<import("@deepseek-ai/cosmokit").Dict<string[], string>>, "volatile">;
|
|
578
|
+
cliAdapters: import("@deepseek-ai/schemastery").default<NoInfer<import("@deepseek-ai/cosmokit").Dict<import("@deepseek-ai/cosmokit").Dict<any, string>, string>>, NoInfer<import("@deepseek-ai/cosmokit").Dict<import("@deepseek-ai/cosmokit").Dict<any, string>, string>>, "volatile">;
|
|
575
579
|
playwright: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<NoInfer<{
|
|
576
580
|
enabled: import("@deepseek-ai/schemastery").default<boolean, boolean, "volatile-defined">;
|
|
577
581
|
snapshotDir: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
@@ -17,6 +17,8 @@ export interface ProviderSource {
|
|
|
17
17
|
/** One provider's ordered result list for one query (index 0 = rank 1). */
|
|
18
18
|
export interface ProviderOutput {
|
|
19
19
|
providerId: string;
|
|
20
|
+
/** The backend of a platform chain that answered (display only). */
|
|
21
|
+
backend?: string;
|
|
20
22
|
query: string;
|
|
21
23
|
sources: readonly ProviderSource[];
|
|
22
24
|
}
|
|
@@ -43,7 +43,8 @@ export interface ProviderCall {
|
|
|
43
43
|
}
|
|
44
44
|
export type ProviderOutcome = {
|
|
45
45
|
state: 'ok';
|
|
46
|
-
sources: readonly ProviderSource[];
|
|
46
|
+
sources: readonly ProviderSource[]; /** The backend of a platform chain that answered. */
|
|
47
|
+
backend?: string;
|
|
47
48
|
}
|
|
48
49
|
/** `detail` carries the provider's own explanation of an empty answer (e.g. a login hint), when it gave one. */
|
|
49
50
|
| {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Platform search backends as registry providers (dev-plan M8b): one provider per site or community, with the same
|
|
3
3
|
* descriptor + adapter shape as the web engines, so a platform is planned, probed, cooled down, singleflighted,
|
|
4
|
-
* persisted and reported exactly like any other source. A platform with several backends (twitter:
|
|
5
|
-
*
|
|
4
|
+
* persisted and reported exactly like any other source. A platform with several backends (twitter: twitter-cli, standalone
|
|
5
|
+
* OpenCLI, dsh-browser OpenCLI) is ONE provider with an internal ordered chain, resolved by src/cli/chain.ts (dev-plan M12). User `customPlatforms` become providers too
|
|
6
6
|
* ({@link customPlatformAdapter}); the router registers and unregisters them as the settings change.
|
|
7
7
|
* The legacy platform ids that already were web providers (github, bilibili, v2ex, youtube, arxiv, pubmed) are
|
|
8
8
|
* declared in builtin.ts and carry `kind: 'platform'` as well.
|
|
@@ -10,9 +10,15 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { type Engine } from '../engines.ts';
|
|
12
12
|
import type { CustomPlatformSpec } from '../config.ts';
|
|
13
|
-
import type
|
|
13
|
+
import { type CliAdapterSpec } from '../cli/spec.ts';
|
|
14
|
+
import type { ProviderAdapter, ProviderDescriptor } from './registry.ts';
|
|
14
15
|
/** Chain `engines` into one provider: the first available leg that answers wins; empty and failing legs fall through in order. */
|
|
15
16
|
export declare function chainEngine(id: string, label: string, chain: readonly Engine[]): Engine;
|
|
17
|
+
/**
|
|
18
|
+
* A platform provider whose backends are an ordered chain (`resolveChain`): the engine is the chain, the local probe is the
|
|
19
|
+
* readiness of every leg (cached contract probes, no search, no login), and `chain` reports each leg for `sources.status`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function chainProvider(platformId: string, d: ProviderDescriptor): ProviderAdapter;
|
|
16
22
|
export declare const platformAdapters: readonly ProviderAdapter[];
|
|
17
23
|
/** Registry id of a custom platform: its key when that is a valid id, else a stable hash of it (the key stays the route alias). */
|
|
18
24
|
export declare function customProviderId(key: string): string;
|
|
@@ -23,3 +29,9 @@ export declare function customKeyProblem(key: string): string | undefined;
|
|
|
23
29
|
* reaches the next call even before the router re-registers the provider.
|
|
24
30
|
*/
|
|
25
31
|
export declare function customPlatformAdapter(key: string, spec: CustomPlatformSpec): ProviderAdapter;
|
|
32
|
+
/** Registry id and route id of a user-defined CLI adapter: `custom-cli:<id>` (also its backend id in `platformBackends`). */
|
|
33
|
+
export declare const customCliProviderId: (id: string) => string;
|
|
34
|
+
/** A user-defined CLI adapter as a platform provider whose chain is the adapter itself (an edit of `platformBackends` can still reorder it). */
|
|
35
|
+
export declare function customCliAdapter(id: string, spec: CliAdapterSpec): ProviderAdapter;
|
|
36
|
+
/** A user spec as validated for registration: the problem text, or undefined when it can be registered. */
|
|
37
|
+
export declare function customCliProblem(id: string, raw: unknown): string | undefined;
|
|
@@ -17,6 +17,7 @@ import type { BrowserMethod } from '../browser-access.ts';
|
|
|
17
17
|
import type { ResolvedConfig } from '../config.ts';
|
|
18
18
|
import type { CompiledQuery } from '../pipeline/compile.ts';
|
|
19
19
|
import type { TaskSpec } from '../pipeline/types.ts';
|
|
20
|
+
import type { ChainReport } from '../cli/chain.ts';
|
|
20
21
|
export type Operation = 'search';
|
|
21
22
|
/** `web`: a general search engine; `platform`: one site or community (addressable as `search.run platform=<route id>`). */
|
|
22
23
|
export type ProviderKind = 'web' | 'platform';
|
|
@@ -126,6 +127,8 @@ export interface ProviderAdapter {
|
|
|
126
127
|
/** Local readiness: declared services, keys, commands. No network, no login, no model call. */
|
|
127
128
|
probeLocal(env: ProbeEnv): Readiness | Promise<Readiness>;
|
|
128
129
|
create(deps: EngineDeps, config: ResolvedConfig): Engine;
|
|
130
|
+
/** Platforms with an ordered backend chain (dev-plan M12): the state of every leg for `sources.status`. Local, no search. */
|
|
131
|
+
chain?(env: ProbeEnv): Promise<ChainReport>;
|
|
129
132
|
/** Optional native query compilation (constraints -> provider options); absent = the core compiler's rules / plain query. */
|
|
130
133
|
compile?(task: Pick<TaskSpec, 'goal' | 'query' | 'needs' | 'constraints'>, now: Date): CompiledQuery;
|
|
131
134
|
}
|
package/lib/types/router.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { WebSearchRequest, WebSearchResult } from '@deepseek-ai/dsh-web';
|
|
|
8
8
|
import type { Store } from './store.ts';
|
|
9
9
|
import type { ResolvedConfig } from './config.ts';
|
|
10
10
|
import { type EngineSearchOptions } from './engines.ts';
|
|
11
|
+
import type { ChainEntryReport, ChainReport } from './cli/chain.ts';
|
|
11
12
|
import { type CostTier, type ProviderDescriptor, type Readiness, type ProviderRegistry } from './providers/index.ts';
|
|
12
13
|
import { type RequestBudgetState } from './pipeline/ledger.ts';
|
|
13
14
|
import { LruCache } from './memory-cache.ts';
|
|
@@ -61,6 +62,8 @@ export interface RouterSearchResult {
|
|
|
61
62
|
availableCount?: number;
|
|
62
63
|
/** Human-readable explanation of why the router fell back to `engine` (P1-1). */
|
|
63
64
|
fallbackNote?: string;
|
|
65
|
+
/** Platform search: the backend of its chain that answered (`bili`, `browser-opencli`). */
|
|
66
|
+
backend?: string;
|
|
64
67
|
}
|
|
65
68
|
/** One provider as `sources.status` reports it: the registry descriptor plus local readiness by dimension. */
|
|
66
69
|
export interface ProviderReport {
|
|
@@ -92,7 +95,10 @@ export interface ProviderReport {
|
|
|
92
95
|
budget?: RequestBudgetState;
|
|
93
96
|
/** Not verified against the live service (descriptor.verification). */
|
|
94
97
|
unverified?: boolean;
|
|
98
|
+
/** Platforms with an ordered backend chain: each backend, in order, with its readiness or the reason it is skipped. */
|
|
99
|
+
chain?: ChainEntryReport[];
|
|
95
100
|
readiness: Readiness & {
|
|
101
|
+
state?: ChainReport['state'];
|
|
96
102
|
lastLocalCheck: string;
|
|
97
103
|
lastRemoteSuccess?: string;
|
|
98
104
|
lastError?: string;
|
|
@@ -115,6 +121,9 @@ export declare class SearchRouter {
|
|
|
115
121
|
private syncedRevision;
|
|
116
122
|
/** Custom platforms (settings `customPlatforms`) this router registered: key -> spec signature + unregister. A key the registry refused stays here with its problem so it is not retried on every call. */
|
|
117
123
|
private readonly custom;
|
|
124
|
+
/** User-defined CLI adapters (settings `cliAdapters`) this router registered as `custom-cli:<id>` platforms, with the diagnostics of the entries it ignored. */
|
|
125
|
+
private readonly customCli;
|
|
126
|
+
private cliAdapterDiagnostics;
|
|
118
127
|
/** Latest local probe per route id (read by providerStatuses for the credential dimension). */
|
|
119
128
|
private readonly readiness;
|
|
120
129
|
/** Last real call per route id: feeds the health dimension (never inferred from a local probe). */
|
|
@@ -128,6 +137,10 @@ export declare class SearchRouter {
|
|
|
128
137
|
* with a provider already registered is not registered (a user platform never replaces a built-in source); see {@link customPlatformProblems}.
|
|
129
138
|
*/
|
|
130
139
|
private syncCustomPlatforms;
|
|
140
|
+
/** The same for `cliAdapters`: valid specs become `custom-cli:<id>` platforms; an invalid entry is ignored and reported. */
|
|
141
|
+
private syncCustomCliAdapters;
|
|
142
|
+
/** Configuration problems of the CLI adapter layer for `sources.status`: ignored `cliAdapters` entries and `platformBackends` that name nothing usable. */
|
|
143
|
+
cliAdapterProblems(): string[];
|
|
131
144
|
/** Custom platforms the registry could not take (key clash, bad key), for `sources.status`. */
|
|
132
145
|
customPlatformProblems(): string[];
|
|
133
146
|
/** Unregister what this router put into the registry (plugin unload). */
|
package/lib/types/util.d.ts
CHANGED
|
@@ -76,6 +76,10 @@ export interface CliResult {
|
|
|
76
76
|
stdout: string;
|
|
77
77
|
stderr: string;
|
|
78
78
|
timedOut: boolean;
|
|
79
|
+
/** The command could not be started at all (not on PATH, not executable). */
|
|
80
|
+
spawnFailed?: boolean;
|
|
81
|
+
/** Output went past `maxOutput` and the rest was dropped. */
|
|
82
|
+
truncated?: boolean;
|
|
79
83
|
}
|
|
80
84
|
/**
|
|
81
85
|
* Run an external CLI (opencli / bili / yt-dlp / agent-reach / npm).
|
|
@@ -85,7 +89,8 @@ export interface CliResult {
|
|
|
85
89
|
export declare function runCli(bin: string, args: string[], opts?: {
|
|
86
90
|
timeoutMs?: number;
|
|
87
91
|
signal: AbortSignal | undefined;
|
|
88
|
-
env?: Record<string, string>;
|
|
92
|
+
env?: Record<string, string>; /** Run with exactly this environment instead of extending the process environment (credentials are not inherited). */
|
|
93
|
+
cleanEnv?: boolean;
|
|
89
94
|
cwd?: string;
|
|
90
95
|
maxOutput?: number;
|
|
91
96
|
outputEncoding?: string;
|
package/lib/util.js
CHANGED
|
@@ -199,6 +199,8 @@ export function runCli(bin, args, opts = { signal: undefined }) {
|
|
|
199
199
|
let stderrBytes = 0;
|
|
200
200
|
let child;
|
|
201
201
|
let settled = false;
|
|
202
|
+
let spawnFailed = false;
|
|
203
|
+
let truncated = false;
|
|
202
204
|
const finish = (code, timedOut) => {
|
|
203
205
|
if (settled)
|
|
204
206
|
return;
|
|
@@ -210,7 +212,7 @@ export function runCli(bin, args, opts = { signal: undefined }) {
|
|
|
210
212
|
const encoding = opts.outputEncoding ?? 'utf-8';
|
|
211
213
|
const stdout = new TextDecoder(encoding).decode(Buffer.concat(stdoutChunks, stdoutBytes));
|
|
212
214
|
const stderr = new TextDecoder(encoding).decode(Buffer.concat(stderrChunks, stderrBytes));
|
|
213
|
-
resolve({ code, stdout, stderr, timedOut });
|
|
215
|
+
resolve({ code, stdout, stderr, timedOut, ...spawnFailed ? { spawnFailed } : {}, ...truncated ? { truncated } : {} });
|
|
214
216
|
};
|
|
215
217
|
const timer = opts.timeoutMs ? setTimeout(() => finish(-1, true), opts.timeoutMs) : undefined;
|
|
216
218
|
const onAbort = () => {
|
|
@@ -219,12 +221,14 @@ export function runCli(bin, args, opts = { signal: undefined }) {
|
|
|
219
221
|
finish(-1, false);
|
|
220
222
|
};
|
|
221
223
|
child = crossSpawn(bin, args, {
|
|
222
|
-
env: { ...process.env, ...opts.env },
|
|
224
|
+
env: opts.cleanEnv ? { ...opts.env } : { ...process.env, ...opts.env },
|
|
223
225
|
cwd: opts.cwd,
|
|
224
226
|
windowsHide: process.platform === 'win32',
|
|
225
227
|
});
|
|
226
228
|
child.stdout?.on('data', (d) => {
|
|
227
229
|
const remaining = maxOutput - stdoutBytes;
|
|
230
|
+
if (d.length > remaining)
|
|
231
|
+
truncated = true;
|
|
228
232
|
if (remaining <= 0)
|
|
229
233
|
return;
|
|
230
234
|
const chunk = d.length <= remaining ? d : d.subarray(0, remaining);
|
|
@@ -239,7 +243,7 @@ export function runCli(bin, args, opts = { signal: undefined }) {
|
|
|
239
243
|
stderrChunks.push(chunk);
|
|
240
244
|
stderrBytes += chunk.length;
|
|
241
245
|
});
|
|
242
|
-
child.on('error', () => finish(-1, false));
|
|
246
|
+
child.on('error', () => { spawnFailed = true; finish(-1, false); });
|
|
243
247
|
child.on('close', (code) => finish(code ?? -1, false));
|
|
244
248
|
if (opts.signal?.aborted)
|
|
245
249
|
onAbort();
|