dsh-web-search-pro 0.2.1 → 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.
Files changed (258) hide show
  1. package/CHANGELOG.md +164 -0
  2. package/README.md +154 -58
  3. package/assets/skills/dsh-web-search-pro/SKILL.md +13 -12
  4. package/assets/skills/dsh-web-search-pro/references/config.md +36 -0
  5. package/assets/skills/dsh-web-search-pro/references/sources.md +13 -7
  6. package/assets/skills/dsh-web-search-pro/references/troubleshooting.md +3 -1
  7. package/catalog/sources.v1.json +16 -14
  8. package/lib/actions/approval.js +50 -1
  9. package/lib/actions/approval.js.map +1 -1
  10. package/lib/actions/config.js +274 -0
  11. package/lib/actions/config.js.map +1 -0
  12. package/lib/actions/errors.js +8 -1
  13. package/lib/actions/errors.js.map +1 -1
  14. package/lib/actions/format.js +10 -0
  15. package/lib/actions/format.js.map +1 -1
  16. package/lib/actions/index-view.js +2 -2
  17. package/lib/actions/index-view.js.map +1 -1
  18. package/lib/actions/read.js +7 -3
  19. package/lib/actions/read.js.map +1 -1
  20. package/lib/actions/registry.js +3 -1
  21. package/lib/actions/registry.js.map +1 -1
  22. package/lib/actions/run.js +2 -2
  23. package/lib/actions/run.js.map +1 -1
  24. package/lib/actions/schema.js +18 -0
  25. package/lib/actions/schema.js.map +1 -1
  26. package/lib/actions/search.js +7 -2
  27. package/lib/actions/search.js.map +1 -1
  28. package/lib/actions/sources.js +76 -18
  29. package/lib/actions/sources.js.map +1 -1
  30. package/lib/actions/types.js +19 -1
  31. package/lib/actions/types.js.map +1 -1
  32. package/lib/browser-access.js +25 -0
  33. package/lib/browser-access.js.map +1 -1
  34. package/lib/catalog/recommend.js +11 -3
  35. package/lib/catalog/recommend.js.map +1 -1
  36. package/lib/catalog/schema.js +3 -1
  37. package/lib/catalog/schema.js.map +1 -1
  38. package/lib/cli/builtin-specs.js +57 -22
  39. package/lib/cli/builtin-specs.js.map +1 -1
  40. package/lib/cli/chain.js +13 -12
  41. package/lib/cli/chain.js.map +1 -1
  42. package/lib/cli/chains-spec.js +31 -11
  43. package/lib/cli/chains-spec.js.map +1 -1
  44. package/lib/cli/engine.js +19 -3
  45. package/lib/cli/engine.js.map +1 -1
  46. package/lib/cli/opencli-sites.js +83 -0
  47. package/lib/cli/opencli-sites.js.map +1 -0
  48. package/lib/cli/opencli.js +65 -17
  49. package/lib/cli/opencli.js.map +1 -1
  50. package/lib/cli/probe.js +1 -1
  51. package/lib/cli/probe.js.map +1 -1
  52. package/lib/cli/runner.js +217 -27
  53. package/lib/cli/runner.js.map +1 -1
  54. package/lib/cli/spec.js +100 -20
  55. package/lib/cli/spec.js.map +1 -1
  56. package/lib/cli/time-text.js +79 -0
  57. package/lib/cli/time-text.js.map +1 -0
  58. package/lib/cli/trial.js +57 -0
  59. package/lib/cli/trial.js.map +1 -0
  60. package/lib/client/SettingsCard.js +14 -32
  61. package/lib/client/SettingsCard.js.map +1 -1
  62. package/lib/client/agent-mark.js +27 -0
  63. package/lib/client/agent-mark.js.map +1 -0
  64. package/lib/client/card-frame.js +118 -0
  65. package/lib/client/card-frame.js.map +1 -0
  66. package/lib/client/cli-form.js +257 -0
  67. package/lib/client/cli-form.js.map +1 -0
  68. package/lib/client/field-docs.js +130 -0
  69. package/lib/client/field-docs.js.map +1 -0
  70. package/lib/client/field-keys.js +81 -0
  71. package/lib/client/field-keys.js.map +1 -0
  72. package/lib/client/field-rows.js +71 -0
  73. package/lib/client/field-rows.js.map +1 -0
  74. package/lib/client/fill.js +3 -0
  75. package/lib/client/fill.js.map +1 -0
  76. package/lib/client/form-specs.js +13 -1
  77. package/lib/client/form-specs.js.map +1 -1
  78. package/lib/client/form.js +282 -24
  79. package/lib/client/form.js.map +1 -1
  80. package/lib/client/group-advanced.js +23 -0
  81. package/lib/client/group-advanced.js.map +1 -0
  82. package/lib/client/group-agent.js +28 -0
  83. package/lib/client/group-agent.js.map +1 -0
  84. package/lib/client/group-cli.js +136 -0
  85. package/lib/client/group-cli.js.map +1 -0
  86. package/lib/client/group-common.js +29 -0
  87. package/lib/client/group-common.js.map +1 -0
  88. package/lib/client/group-judge.js +67 -0
  89. package/lib/client/group-judge.js.map +1 -0
  90. package/lib/client/group-order.js +201 -0
  91. package/lib/client/group-order.js.map +1 -0
  92. package/lib/client/group-runtime.js +33 -0
  93. package/lib/client/group-runtime.js.map +1 -0
  94. package/lib/client/group-sources.js +83 -0
  95. package/lib/client/group-sources.js.map +1 -0
  96. package/lib/client/groups-basic.js +36 -0
  97. package/lib/client/groups-basic.js.map +1 -0
  98. package/lib/client/index.js.map +1 -1
  99. package/lib/client/json-list.js +50 -0
  100. package/lib/client/json-list.js.map +1 -0
  101. package/lib/client/locales.js +666 -219
  102. package/lib/client/locales.js.map +1 -1
  103. package/lib/client/order.js +192 -0
  104. package/lib/client/order.js.map +1 -0
  105. package/lib/client/overlay-ops.js +46 -0
  106. package/lib/client/overlay-ops.js.map +1 -0
  107. package/lib/client/sources-table.js +1 -0
  108. package/lib/client/sources-table.js.map +1 -1
  109. package/lib/client/styles.js +74 -47
  110. package/lib/client/styles.js.map +1 -1
  111. package/lib/client/text-view.js +98 -0
  112. package/lib/client/text-view.js.map +1 -0
  113. package/lib/client/ui-kit.js +120 -0
  114. package/lib/client/ui-kit.js.map +1 -0
  115. package/lib/client/validators.js +8 -1
  116. package/lib/client/validators.js.map +1 -1
  117. package/lib/client.js +7313 -1571
  118. package/lib/client.js.map +1 -1
  119. package/lib/config-fillers.js +53 -0
  120. package/lib/config-fillers.js.map +1 -0
  121. package/lib/config-groups.js +49 -0
  122. package/lib/config-groups.js.map +1 -0
  123. package/lib/config.js +84 -108
  124. package/lib/config.js.map +1 -1
  125. package/lib/default-chain.js +66 -0
  126. package/lib/default-chain.js.map +1 -0
  127. package/lib/deps.js +1 -1
  128. package/lib/deps.js.map +1 -1
  129. package/lib/engines.js +3 -3
  130. package/lib/engines.js.map +1 -1
  131. package/lib/fetch.js +23 -4
  132. package/lib/fetch.js.map +1 -1
  133. package/lib/index.js +30 -5
  134. package/lib/index.js.map +1 -1
  135. package/lib/overlay-types.js +88 -0
  136. package/lib/overlay-types.js.map +1 -0
  137. package/lib/overlay.js +683 -0
  138. package/lib/overlay.js.map +1 -0
  139. package/lib/pipeline/candidates.js +27 -1
  140. package/lib/pipeline/candidates.js.map +1 -1
  141. package/lib/pipeline/compile.js +63 -0
  142. package/lib/pipeline/compile.js.map +1 -1
  143. package/lib/pipeline/confidence.js +64 -0
  144. package/lib/pipeline/confidence.js.map +1 -0
  145. package/lib/pipeline/fusion.js +50 -3
  146. package/lib/pipeline/fusion.js.map +1 -1
  147. package/lib/pipeline/plan.js +2 -2
  148. package/lib/pipeline/plan.js.map +1 -1
  149. package/lib/pipeline/render.js +20 -2
  150. package/lib/pipeline/render.js.map +1 -1
  151. package/lib/pipeline/run.js +17 -5
  152. package/lib/pipeline/run.js.map +1 -1
  153. package/lib/pipeline/sources-spec.js +19 -3
  154. package/lib/pipeline/sources-spec.js.map +1 -1
  155. package/lib/providers/builtin.js +3 -0
  156. package/lib/providers/builtin.js.map +1 -1
  157. package/lib/providers/deepseek.js +348 -0
  158. package/lib/providers/deepseek.js.map +1 -0
  159. package/lib/providers/http.js.map +1 -1
  160. package/lib/providers/keyed-meta.js +7 -0
  161. package/lib/providers/keyed-meta.js.map +1 -1
  162. package/lib/providers/platforms.js +18 -17
  163. package/lib/providers/platforms.js.map +1 -1
  164. package/lib/providers/registry.js.map +1 -1
  165. package/lib/restart.js +47 -0
  166. package/lib/restart.js.map +1 -0
  167. package/lib/router.js +145 -26
  168. package/lib/router.js.map +1 -1
  169. package/lib/rpc-types.js +15 -0
  170. package/lib/rpc-types.js.map +1 -0
  171. package/lib/rpc.js +85 -0
  172. package/lib/rpc.js.map +1 -0
  173. package/lib/tools.js +10 -4
  174. package/lib/tools.js.map +1 -1
  175. package/lib/types/actions/approval.d.ts +11 -0
  176. package/lib/types/actions/config.d.ts +54 -0
  177. package/lib/types/actions/format.d.ts +9 -2
  178. package/lib/types/actions/run.d.ts +1 -0
  179. package/lib/types/actions/schema.d.ts +7 -1
  180. package/lib/types/actions/sources.d.ts +29 -3
  181. package/lib/types/actions/types.d.ts +28 -2
  182. package/lib/types/browser-access.d.ts +17 -0
  183. package/lib/types/catalog/recommend.d.ts +4 -0
  184. package/lib/types/catalog/schema.d.ts +2 -0
  185. package/lib/types/cli/builtin-specs.d.ts +11 -3
  186. package/lib/types/cli/chains-spec.d.ts +12 -3
  187. package/lib/types/cli/engine.d.ts +10 -3
  188. package/lib/types/cli/opencli-sites.d.ts +24 -0
  189. package/lib/types/cli/opencli.d.ts +42 -3
  190. package/lib/types/cli/runner.d.ts +47 -8
  191. package/lib/types/cli/spec.d.ts +101 -8
  192. package/lib/types/cli/time-text.d.ts +12 -0
  193. package/lib/types/cli/trial.d.ts +19 -0
  194. package/lib/types/client/SettingsCard.d.ts +17 -1
  195. package/lib/types/client/agent-mark.d.ts +22 -0
  196. package/lib/types/client/card-frame.d.ts +35 -0
  197. package/lib/types/client/cli-form.d.ts +97 -0
  198. package/lib/types/client/field-docs.d.ts +15 -0
  199. package/lib/types/client/field-keys.d.ts +41 -0
  200. package/lib/types/client/field-rows.d.ts +70 -0
  201. package/lib/types/client/fill.d.ts +2 -0
  202. package/lib/types/client/form-specs.d.ts +12 -3
  203. package/lib/types/client/form.d.ts +79 -1
  204. package/lib/types/client/group-advanced.d.ts +9 -0
  205. package/lib/types/client/group-agent.d.ts +20 -0
  206. package/lib/types/client/group-cli.d.ts +52 -0
  207. package/lib/types/client/group-common.d.ts +12 -0
  208. package/lib/types/client/group-judge.d.ts +17 -0
  209. package/lib/types/client/group-order.d.ts +26 -0
  210. package/lib/types/client/group-runtime.d.ts +15 -0
  211. package/lib/types/client/group-sources.d.ts +46 -0
  212. package/lib/types/client/groups-basic.d.ts +18 -0
  213. package/lib/types/client/index.d.ts +8 -0
  214. package/lib/types/client/json-list.d.ts +21 -0
  215. package/lib/types/client/locales.d.ts +1079 -200
  216. package/lib/types/client/order.d.ts +102 -0
  217. package/lib/types/client/overlay-ops.d.ts +37 -0
  218. package/lib/types/client/styles.d.ts +56 -41
  219. package/lib/types/client/text-view.d.ts +22 -0
  220. package/lib/types/client/ui-kit.d.ts +200 -0
  221. package/lib/types/config-fillers.d.ts +9 -0
  222. package/lib/types/config-groups.d.ts +23 -0
  223. package/lib/types/config.d.ts +263 -225
  224. package/lib/types/default-chain.d.ts +63 -0
  225. package/lib/types/engines.d.ts +29 -1
  226. package/lib/types/fetch.d.ts +12 -3
  227. package/lib/types/index.d.ts +164 -150
  228. package/lib/types/overlay-types.d.ts +98 -0
  229. package/lib/types/overlay.d.ts +141 -0
  230. package/lib/types/pipeline/candidates.d.ts +17 -0
  231. package/lib/types/pipeline/compile.d.ts +11 -0
  232. package/lib/types/pipeline/confidence.d.ts +35 -0
  233. package/lib/types/pipeline/fusion.d.ts +23 -1
  234. package/lib/types/pipeline/render.d.ts +16 -0
  235. package/lib/types/pipeline/run.d.ts +2 -1
  236. package/lib/types/pipeline/sources-spec.d.ts +5 -2
  237. package/lib/types/pipeline/types.d.ts +37 -0
  238. package/lib/types/providers/deepseek.d.ts +130 -0
  239. package/lib/types/providers/http.d.ts +2 -1
  240. package/lib/types/providers/keyed-meta.d.ts +5 -0
  241. package/lib/types/providers/registry.d.ts +7 -0
  242. package/lib/types/restart.d.ts +52 -0
  243. package/lib/types/router.d.ts +57 -10
  244. package/lib/types/rpc-types.d.ts +79 -0
  245. package/lib/types/rpc.d.ts +41 -0
  246. package/lib/types/tools.d.ts +6 -0
  247. package/lib/types/util.d.ts +10 -0
  248. package/lib/util.js +10 -1
  249. package/lib/util.js.map +1 -1
  250. package/package.json +50 -42
  251. package/scripts/check-client-bundle.mjs +1 -1
  252. package/scripts/cli-login-check.mjs +214 -0
  253. package/lib/client/RubricEditor.js +0 -47
  254. package/lib/client/RubricEditor.js.map +0 -1
  255. package/lib/client/fields.js +0 -31
  256. package/lib/client/fields.js.map +0 -1
  257. package/lib/types/client/RubricEditor.d.ts +0 -6
  258. package/lib/types/client/fields.d.ts +0 -65
@@ -16,7 +16,9 @@ import type { Store } from '../store.ts';
16
16
  import type { EvidenceService } from '../pipeline/service.ts';
17
17
  import type { BrowserGetter } from '../browser-access.ts';
18
18
  import type { ProviderState } from '../provider.ts';
19
- export declare const ACTION_GROUPS: readonly ["search", "read", "history", "sources", "rules", "cache"];
19
+ import type { OverlayStore } from '../overlay.ts';
20
+ import type { PendingRestart } from '../restart.ts';
21
+ export declare const ACTION_GROUPS: readonly ["search", "read", "history", "sources", "rules", "cache", "config"];
20
22
  export type ActionGroup = typeof ACTION_GROUPS[number];
21
23
  /**
22
24
  * One parameter node. A subset of the Host's tool parameter DSL, so the same
@@ -51,8 +53,10 @@ export interface OutputSchema {
51
53
  * - `none`: runs directly.
52
54
  * - `install`: runs an external installer (`sources.install`).
53
55
  * - `local-write`: changes or deletes locally stored data (cache, history rows, extraction rules).
56
+ * - `config`: changes the agent overlay (search order, confidence, platform chains); runs without asking, denied in read-only mode.
57
+ * - `run-program`: lets the plugin run a local program (a custom CLI spec); always asks the user, denied in read-only mode.
54
58
  */
55
- export type ApprovalClass = 'none' | 'install' | 'local-write';
59
+ export type ApprovalClass = 'none' | 'install' | 'local-write' | 'config' | 'run-program';
56
60
  export interface ActionExample {
57
61
  args: Record<string, unknown>;
58
62
  note?: string;
@@ -74,9 +78,15 @@ export interface ActionServices {
74
78
  providerState?: () => ProviderState;
75
79
  /** Default characters one text exit may return (config `fetchDefaultChars`). */
76
80
  outputCap: () => number;
81
+ /** The agent overlay (run-time settings an agent may change); absent where the host of the actions has none (`config.*` then reports it). */
82
+ overlay?: OverlayStore;
83
+ /** Settings changed in the configuration that the running plugin only reads at start-up. */
84
+ pendingRestart?: () => PendingRestart[];
77
85
  }
78
86
  export interface ActionContext extends ActionServices {
79
87
  signal: AbortSignal;
88
+ /** The session the call belongs to (recorded in the overlay audit trail); undefined outside a session. */
89
+ sessionId?: string;
80
90
  }
81
91
  export interface ActionDef {
82
92
  /** `group.action`. */
@@ -103,6 +113,12 @@ export interface ActionDef {
103
113
  unavailable?(env: {
104
114
  browserReady: boolean;
105
115
  }): string | undefined;
116
+ /**
117
+ * Checks beyond the parameter schema that need no state and run no side effect, one message per problem ([] when fine).
118
+ * Run on the schema-validated arguments by `runAction` before `execute`, and by the approval hook before the user is asked,
119
+ * so a call that would be refused as INVALID_ARGS never becomes an approval prompt. Keep it a pure function of `args`.
120
+ */
121
+ precheck?(args: Record<string, any>): string[];
106
122
  execute(args: Record<string, any>, ctx: ActionContext): Promise<unknown>;
107
123
  /** The result as the text the model reads. */
108
124
  render(result: any, args: Record<string, any>): string;
@@ -117,6 +133,16 @@ export declare class ActionNotFoundError extends Error {
117
133
  readonly hint?: string | undefined;
118
134
  constructor(message: string, hint?: string | undefined);
119
135
  }
136
+ /** Raised when something the call needs is not set up here (a broken overlay file, say); mapped to CAPABILITY_UNAVAILABLE. */
137
+ export declare class ActionUnavailableError extends Error {
138
+ readonly hint?: string | undefined;
139
+ constructor(message: string, hint?: string | undefined);
140
+ }
141
+ /** Raised when a write is refused because the stored state moved on since the caller read it (revision mismatch); mapped to ACTION_FAILED with the hint to read again. */
142
+ export declare class ActionConflictError extends Error {
143
+ readonly hint?: string | undefined;
144
+ constructor(message: string, hint?: string | undefined);
145
+ }
120
146
  export declare const ERROR_CODES: readonly ["INVALID_ARGS", "UNKNOWN_ACTION", "CAPABILITY_UNAVAILABLE", "POLICY_DENIED", "DEADLINE", "CANCELLED", "NOT_FOUND", "ACTION_FAILED"];
121
147
  export type ErrorCode = typeof ERROR_CODES[number];
122
148
  export interface ActionErrorBody {
@@ -42,3 +42,20 @@ export declare function browserState(browser: BrowserService | undefined): {
42
42
  state: 'ready' | 'incomplete' | 'legacy' | 'missing';
43
43
  reason?: string;
44
44
  };
45
+ /** What a launch of the browser would do on this machine, as dsh-browser reports it (0.2.2+ `status().browser`). */
46
+ export interface BrowserLaunchReadiness {
47
+ ready: boolean;
48
+ /** Which browser a launch will use (`playwright-chromium`, `system-browser`, ...). */
49
+ source?: string;
50
+ /** Where it is. */
51
+ path?: string;
52
+ /** The one command (or step) that fixes `ready: false`, in dsh-browser's own words. */
53
+ advice?: string;
54
+ }
55
+ /**
56
+ * Whether the browser service can actually launch a browser. The service being present only means the plugin is loaded: a new user
57
+ * has no Playwright Chromium, and whether a system Chrome stands in is dsh-browser's decision. It is asked, never guessed, and
58
+ * only a dsh-browser that reports it (`status().browser`) is believed; any other answer (older plugin, a failing status call)
59
+ * is `undefined`, which callers treat as unknown and say nothing about.
60
+ */
61
+ export declare function browserLaunchReadiness(browser: BrowserService | undefined): Promise<BrowserLaunchReadiness | undefined>;
@@ -60,6 +60,10 @@ export interface Suggestion {
60
60
  /** First counter-example of the entry. */
61
61
  notFor?: string;
62
62
  verified: boolean;
63
+ /** "常用": the plugin's recommended source (it leads once its key is configured). */
64
+ common?: boolean;
65
+ /** The CLI backend this source runs through, when its adapter was not verified with a real search (`twitter: contract-only — …`). */
66
+ backend?: string;
63
67
  sourceFamily?: string;
64
68
  }
65
69
  export interface Recommendation {
@@ -62,6 +62,8 @@ export interface CatalogEntry {
62
62
  invoke?: string;
63
63
  /** Curation order among equals (lower first, default 50). */
64
64
  rank?: number;
65
+ /** "常用": the plugin's recommended source. Once its key is configured it leads the recommendations, and `search.recommend` marks it. Mirrors the provider descriptor's `common`. */
66
+ common?: boolean;
65
67
  /** Short install or configuration text. */
66
68
  install: string;
67
69
  license: string;
@@ -2,10 +2,11 @@
2
2
  * The built-in CLI adapter specs (dev-plan M12). Every argv, flag and output field was derived from the tool's own
3
3
  * `--help` (and, where the help does not show the output shape, from the installed package source or the upstream
4
4
  * README), and `verification` records how far each was checked:
5
- * - `live`: a real read-only search was run and its output parsed (bili, gh, omnireach/wechat);
5
+ * - `live`: a real read-only search was run and its output parsed (bili, gh, omnireach, yt-dlp, wx-search-cli, xhs; the standalone
6
+ * opencli for the twitter and zhihu sites, see opencli-sites.ts; and Exa through mcporter, which is not a spec);
6
7
  * - `contract-only`: help and version probed on a real install, output shape taken from the tool's own source,
7
- * README or documented output option, because a search needs the user's login or reads browser cookies, or was outside
8
- * the checks allowed (xhs, zhihu, rdt, twitter, opencli sites, yt-dlp, wx-search-cli);
8
+ * README or documented output option, because a search needs the user's login or reads browser cookies
9
+ * (zhihu, rdt, twitter, the other opencli sites);
9
10
  * - `docs-only`: not installed here; derived from the upstream README (tanso).
10
11
  * Fixtures for the contract-only and docs-only parsers are marked "constructed from help/docs" in test/fixtures/cli.
11
12
  * @module web-search-pro/cli/builtin-specs
@@ -24,7 +25,14 @@ export declare const TWITTER_CLI_INSTALLS: {
24
25
  }[];
25
26
  /** The date of the checks recorded below. */
26
27
  export declare const SPEC_CHECK_DATE = "2026-10-06";
28
+ /** The omnireach sources that need neither a key, a login nor a browser (the others are `heavy` / booster tiers or need a URL query). */
29
+ export declare const OMNIREACH_KEYLESS_SOURCES = "hackernews,youtube,github,wechat,bilibili";
27
30
  /** The `wechat` platform's two tools and the multi-source `omnireach` platform come from the same specs; ids are the backend ids. */
28
31
  export declare const BUILTIN_CLI_SPECS: readonly CliAdapterSpec[];
32
+ /**
33
+ * How far the adapter of a command (by its first bin name) was verified: a built-in spec's own record, and for the standalone
34
+ * OpenCLI that of the site of `platform` (`live` for twitter and zhihu), else `contract-only`.
35
+ */
36
+ export declare function cliVerificationOf(bin: string, platform?: string): string | undefined;
29
37
  export declare const builtinSpecById: (id: string) => CliAdapterSpec | undefined;
30
38
  export type { CliSearchSpec };
@@ -4,12 +4,17 @@
4
4
  * ids exist and which platforms each can serve.
5
5
  * @module web-search-pro/cli/chains-spec
6
6
  */
7
- /** The default chain of each platform: first usable backend answers, empty or failing ones fall through in order. */
7
+ import { OPENCLI_SITE_OF } from './opencli-sites.ts';
8
+ /**
9
+ * The default chain of each platform: first usable backend answers, empty or failing ones fall through in order. The platforms
10
+ * that need a login (xiaohongshu, reddit, twitter, zhihu) try the standalone OpenCLI first: it reads the login of the user's
11
+ * own day-to-day Chrome through the OpenCLI Browser Bridge, so nothing is logged in by the plugin. The standalone CLIs
12
+ * (xhs, rdt, twitter, zhihu) stay as the optional fallback for users who logged them in themselves.
13
+ */
8
14
  export declare const DEFAULT_CHAINS: Readonly<Record<string, readonly string[]>>;
9
15
  /** Platforms whose provider is a chain (everything in the default table). */
10
16
  export declare const CHAIN_PLATFORMS: readonly string[];
11
- /** Platform -> site of the standalone OpenCLI (`opencli <site> search`). */
12
- export declare const OPENCLI_SITE_OF: Readonly<Record<string, string>>;
17
+ export { OPENCLI_SITE_OF };
13
18
  /** Platforms the dsh-browser OpenCLI bridge has a site adapter for (pinned to engines.ts by a test). */
14
19
  export declare const BROWSER_OPENCLI_PLATFORMS: readonly string[];
15
20
  /** Platforms with a built-in rendered-search-page spec in dsh-browser's searchResults (pinned to platform-search.ts by a test). */
@@ -18,6 +23,8 @@ export declare const BROWSER_SEARCH_PLATFORMS: readonly string[];
18
23
  export declare const REST_PLATFORMS: readonly string[];
19
24
  /** Backend ids that are not CLI specs. */
20
25
  export declare const FIXED_BACKENDS: readonly ["opencli", "browser-opencli", "browser-search", "rest"];
26
+ /** Whether `id` names a platform backend: a built-in CLI spec, a fixed backend, or a custom CLI (`custom-cli:<id>`). */
27
+ export declare function isBackendId(id: string): boolean;
21
28
  export interface BackendServing {
22
29
  ok: boolean;
23
30
  why?: string;
@@ -32,6 +39,8 @@ export declare function specPlatformsOf(cliAdapters: unknown): {
32
39
  platforms: Map<string, readonly string[]>;
33
40
  diagnostics: string[];
34
41
  };
42
+ /** The declared platforms of the user's CLI adapters that no backend chain exists for (the spec would serve nothing there). */
43
+ export declare function customCliPlatformIssues(cliAdapters: unknown): string[];
35
44
  /**
36
45
  * Every problem of the `platformBackends` setting (shape, unknown platform, unknown or foreign backend id) given the
37
46
  * `cliAdapters` setting. The settings card and `sources.status` both call this.
@@ -7,10 +7,15 @@
7
7
  import type { Engine, EngineDeps } from '../engines.ts';
8
8
  import { CliAdapterError } from './runner.ts';
9
9
  import { type CliProbeResult } from './probe.ts';
10
- import type { CliAdapterSpec } from './spec.ts';
10
+ import { type CliAdapterSpec } from './spec.ts';
11
11
  import type { runCli } from '../util.ts';
12
12
  /** Why a spec is switched off by settings, or undefined when it may run. */
13
13
  export declare function disabledBySettings(spec: Pick<CliAdapterSpec, 'id'>, deps: Pick<EngineDeps, 'enableCli' | 'agentReachEnabled'>): string | undefined;
14
+ /**
15
+ * The environment a spec's credential variables are read from: the process environment overlaid with the values the router
16
+ * resolved (settings literal -> DSH credentials -> process environment). Undefined = the process environment as it is.
17
+ */
18
+ export declare function specEnv(deps: Pick<EngineDeps, 'cliEnv'>): NodeJS.ProcessEnv | undefined;
14
19
  export interface CliLegOptions {
15
20
  /** Engine id (default: the spec id). */
16
21
  id?: string;
@@ -21,10 +26,12 @@ export interface CliLegOptions {
21
26
  home?: string;
22
27
  /** The credentials are checked by the caller (the legacy twitter engine). */
23
28
  skipCredentialGate?: boolean;
29
+ /** A user-defined spec: every row it returns says which CLI it came from and that nothing here verified it. */
30
+ userDefined?: boolean;
24
31
  }
25
32
  /** The probe verdict as the structured error a search would raise, or undefined when the command is usable. */
26
33
  export declare function probeError(spec: Pick<CliAdapterSpec, 'bins' | 'packageNote'>, probe: CliProbeResult): CliAdapterError | undefined;
27
- export declare function cliSpecEngine(spec: CliAdapterSpec, platform: string, deps: Pick<EngineDeps, 'enableCli' | 'agentReachEnabled'>, options?: CliLegOptions): Engine;
34
+ export declare function cliSpecEngine(spec: CliAdapterSpec, platform: string, deps: Pick<EngineDeps, 'enableCli' | 'agentReachEnabled' | 'cliEnv'>, options?: CliLegOptions): Engine;
28
35
  export interface SpecReadiness {
29
36
  ready: boolean;
30
37
  installation: 'missing' | 'detected' | 'incompatible';
@@ -37,7 +44,7 @@ export interface SpecReadiness {
37
44
  code: 'ok' | 'disabled' | 'cli_missing' | 'cli_incompatible' | 'not_logged_in';
38
45
  }
39
46
  /** Local readiness of one spec: settings, contract probe, then the credentials it needs. Never runs a search. */
40
- export declare function specReadiness(spec: CliAdapterSpec, deps: Pick<EngineDeps, 'enableCli' | 'agentReachEnabled'>, options?: {
47
+ export declare function specReadiness(spec: CliAdapterSpec, deps: Pick<EngineDeps, 'enableCli' | 'agentReachEnabled' | 'cliEnv'>, options?: {
41
48
  env?: NodeJS.ProcessEnv;
42
49
  home?: string;
43
50
  }): Promise<SpecReadiness>;
@@ -0,0 +1,24 @@
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
+ import type { CliAdapterSpec, CliOutputSpec } from './spec.ts';
8
+ /** Platform -> site of the standalone OpenCLI (`opencli <site> search`). */
9
+ export declare const OPENCLI_SITE_OF: Readonly<Record<string, string>>;
10
+ export declare const GENERIC_OUTPUT: CliOutputSpec;
11
+ /**
12
+ * 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`.
13
+ * - twitter, zhihu: MEASURED (opencli 1.8.8, 2026-10-27, fixtures live-opencli-*). twitter rows are `id, author (handle), bio, text,
14
+ * created_at (X API format), likes (number), views (string), url (https://x.com/i/status/<id>), has_media, media_urls, ...`; the
15
+ * title and meta line follow the standalone twitter backend (text, then author · likes · views · time). zhihu rows are
16
+ * `rank, title, type, author, votes, url` and carry no excerpt and no time.
17
+ * - xiaohongshu, reddit: NOT MEASURED. The only runs failed (login wall; an HTML page instead of JSON), so the mapping is the
18
+ * documented columns of clis/xiaohongshu/search.js (`rank, title, author, likes, published_at (date from the note id), url`,
19
+ * the url is a signed search_result link) and clis/reddit/search.js (`id, title, subreddit (r/x), author, score, comments, url,
20
+ * created_utc (epoch seconds), selftext, ...`), read from the source of 1.8.8.
21
+ */
22
+ export declare const SITE_OUTPUT: Readonly<Record<string, CliOutputSpec>>;
23
+ /** How far the search of one OpenCLI site was checked (`live`, `contract-only`). */
24
+ export declare function opencliSiteVerification(site: string): NonNullable<CliAdapterSpec['verification']>;
@@ -8,10 +8,22 @@
8
8
  */
9
9
  import { runCli } from '../util.ts';
10
10
  import { type ProbeSubject } from './probe.ts';
11
- import { type CliAdapterSpec } from './spec.ts';
11
+ import { type CliAdapterSpec, type CliErrorCodesSpec } from './spec.ts';
12
+ import { opencliSiteVerification } from './opencli-sites.ts';
12
13
  export { OPENCLI_SITE_OF } from './chains-spec.ts';
14
+ export { opencliSiteVerification };
13
15
  /** The command that proves this is the OpenCLI whose `list` output the adapter understands. Verified on 1.8.8. */
14
16
  export declare const OPENCLI_PROBE: ProbeSubject;
17
+ /**
18
+ * The browser options every browser-backed site command of OpenCLI accepts (read from `opencli <site> search --help` and
19
+ * 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
20
+ * ephemeral site session whose tab is released when it finishes. These are also its defaults; the plugin passes them
21
+ * explicitly so a changed `OPENCLI_WINDOW` / `OPENCLI_SITE_SESSION` environment cannot bring a window to the front, and only
22
+ * when the command itself lists the three options (a command without a browser rejects unknown options).
23
+ */
24
+ export declare const OPENCLI_SILENT_ARGS: readonly string[];
25
+ /** The probe of one site's search command: it must list the three silent browser options (`opencli <site> search --help`). Local, cached, no browser. */
26
+ export declare function opencliSiteProbe(site: string): ProbeSubject;
15
27
  export interface OpencliSite {
16
28
  site: string;
17
29
  /** `cookie` sites use the user's logged-in Chrome; `public` sites need no login. */
@@ -19,7 +31,8 @@ export interface OpencliSite {
19
31
  /** Whether the command drives the browser at all. */
20
32
  browser: boolean;
21
33
  hasLimit: boolean;
22
- loginCommand?: string;
34
+ /** The site's domain as the command table gives it (`www.zhihu.com`), for the "log in to ..." hint. */
35
+ domain?: string;
23
36
  }
24
37
  /** Sites with a read `search` command taking a positional query, from the JSON of `opencli list -f json`. Pure. */
25
38
  export declare function parseOpencliList(json: string): Map<string, OpencliSite>;
@@ -34,5 +47,31 @@ export declare function listOpencliSites(options?: {
34
47
  sites?: Map<string, OpencliSite>;
35
48
  problem?: string;
36
49
  }>;
50
+ /**
51
+ * The failure format of OpenCLI 1.8.8 (dist/src/errors.js `toEnvelope`, printed as YAML on stderr whatever `-f` says):
52
+ * `ok: false` / `error: { code, message, help?, exitCode }`. Codes, from that file and the site adapters:
53
+ * - AUTH_REQUIRED (exit 77): the Chrome has no login for the site -> not logged in (measured on xiaohongshu);
54
+ * - LOGIN_WALL (77), SECURITY_BLOCK, RATE_LIMITED, FORBIDDEN: an HTML page where JSON was expected, a risk-control block -> blocked;
55
+ * UNKNOWN with a JSON parse error on an HTML body (`Unexpected token '<'`) is the same thing from an adapter that does not
56
+ * check (measured on reddit through a proxy);
57
+ * - EMPTY_RESULT (66), NO_DATA, EMPTY_RESPONSE: nothing found; BROWSER_CONNECT (69): no Chrome / bridge; TIMEOUT (75);
58
+ * - ARGUMENT, INVALID_ARGUMENT, ...: the arguments of this adapter are not accepted by that version.
59
+ * Anything else (COMMAND_EXEC, FETCH_ERROR, HTTP_ERROR, ...) keeps OpenCLI's own message.
60
+ */
61
+ export declare const OPENCLI_ERROR_CODES: CliErrorCodesSpec;
37
62
  /** 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). */
38
- export declare function opencliSearchSpec(platform: string, entry: OpencliSite): CliAdapterSpec | undefined;
63
+ export declare function opencliSearchSpec(platform: string, entry: OpencliSite, options?: {
64
+ silent?: boolean;
65
+ }): CliAdapterSpec | undefined;
66
+ export interface OpencliSearchPlan {
67
+ spec?: CliAdapterSpec;
68
+ entry?: OpencliSite;
69
+ problem?: string;
70
+ hint?: string;
71
+ silent: boolean;
72
+ }
73
+ /**
74
+ * Everything a search of one platform through the standalone OpenCLI needs, checked locally (the command table and the site's
75
+ * `--help`, never a search): the site's entry, whether its command takes the silent browser options, and the generated spec.
76
+ */
77
+ export declare function planOpencliSearch(platform: string, site: string): Promise<OpencliSearchPlan>;
@@ -5,14 +5,20 @@
5
5
  * @module web-search-pro/cli/runner
6
6
  */
7
7
  import type { WebSearchSource } from '@deepseek-ai/dsh-web';
8
- import { runCli } from '../util.ts';
8
+ import { runCli, type CliResult } from '../util.ts';
9
9
  import { EngineError } from '../engine-error.ts';
10
- import { type CliAdapterSpec, type CliErrorCode, type CliOutputSpec, type CliSearchSpec, type FieldSource } from './spec.ts';
10
+ import { type CliAdapterSpec, type CliErrorCode, type CliErrorCodesSpec, type CliOutputSpec, type CliSearchSpec, type FieldSource } from './spec.ts';
11
11
  /** A structured failure of a CLI adapter: `code` is one of {@link CliErrorCode}; `hint` says what the user can do. */
12
12
  export declare class CliAdapterError extends EngineError {
13
13
  readonly hint?: string | undefined;
14
14
  constructor(message: string, code: CliErrorCode, hint?: string | undefined, retryable?: boolean);
15
15
  }
16
+ /**
17
+ * A NO_PROXY list with its IPv6 entries written without brackets (`[::1]` / `[::1]:8080` -> `::1`). Python's httpx (omnireach, and
18
+ * any tool built on it) refuses a bracketed entry with "Invalid port: ':1]'" and the whole search fails; the DSH Host sets
19
+ * `NO_PROXY` with `[::1]`, which a plain terminal does not (found live 2026-10-26). Every tool that takes NO_PROXY accepts the bare form.
20
+ */
21
+ export declare function normalizeNoProxy(list: string): string;
16
22
  /** The environment a spec's command runs with: the base set, what the spec lists, and the fixed UTF-8 / `env.set` values. */
17
23
  export declare function buildCliEnv(spec: Pick<CliAdapterSpec, 'env'>, source?: NodeJS.ProcessEnv): Record<string, string>;
18
24
  /** Names of the credential variables a spec relies on that are not set. */
@@ -22,15 +28,29 @@ export declare function savedLoginPresent(spec: Pick<CliAdapterSpec, 'login'>, o
22
28
  home?: string;
23
29
  exists?: (file: string) => boolean;
24
30
  }): boolean | undefined;
31
+ /** Credential variables the plugin also resolves from its own settings / DSH credentials (config literal -> credentials ref -> environment). */
32
+ export declare const SETTINGS_BACKED_ENV: Readonly<Record<string, string>>;
25
33
  /** What the user must do to log in: the tool's own command run by them, or the environment variables they set. Never run by the plugin. */
26
34
  export declare function loginHint(spec: Pick<CliAdapterSpec, 'bins' | 'login' | 'env'>): string;
27
35
  export declare function getPath(root: unknown, dotted: string): unknown;
28
- export declare function resolveField(source: FieldSource | undefined, item: unknown, empty?: ReadonlySet<string>): string | undefined;
36
+ export declare function resolveField(source: FieldSource | undefined, item: unknown, empty?: ReadonlySet<string>, now?: number): string | undefined;
37
+ /**
38
+ * Whether the whole stdout of a text-mode command is a JSON object / array or a YAML mapping / sequence document: the tool then
39
+ * printed a structured format the text parsers must not guess at (twitter-cli prints YAML when piped, issue #38).
40
+ */
41
+ export declare function structuredKind(text: string): 'JSON' | 'YAML' | undefined;
29
42
  /**
30
43
  * Parse a tool's stdout into the items its output spec describes. A broken payload is CLI_CONTRACT_MISMATCH (the tool is not
31
44
  * what the spec says); a payload that says it failed (`expect` not met) is CLI_FAILED with the tool's own message.
32
45
  */
33
46
  export declare function parseCliItems(spec: Pick<CliAdapterSpec, 'bins'>, output: CliOutputSpec, stdout: string): unknown[];
47
+ /** The failure payload (`ok: false`, `error: { code, message }`) a tool prints on stderr or stdout: JSON, or a YAML document after any notices. */
48
+ export declare function parseFailurePayload(text: string): unknown;
49
+ /**
50
+ * A failed command run (non-zero exit) as the structured error its own code and message call for, or undefined when the
51
+ * spec lists no codes or the output carries nothing readable (the generic handling then applies).
52
+ */
53
+ export declare function mapToolFailure(codes: CliErrorCodesSpec | undefined, result: Pick<CliResult, 'code' | 'stdout' | 'stderr'>, spec: Pick<CliAdapterSpec, 'bins' | 'login' | 'env'>, redact?: (text: string) => string): CliAdapterError | EngineError | undefined;
34
54
  /** The tool's own error text from a parsed json / yaml payload (`errorPaths`), when it carries one. */
35
55
  export declare function payloadError(output: CliOutputSpec, stdout: string): string | undefined;
36
56
  /**
@@ -42,15 +62,26 @@ export declare function stripMarkup(input: string): string;
42
62
  * Map parsed items to sources by the output spec: http(s) URLs only, snippets and titles capped, tags stripped when asked.
43
63
  * `skipped` counts the items (objects) that had no usable link, so the caller can say so instead of silently returning fewer.
44
64
  */
45
- export declare function mapCliItemsDetailed(output: CliOutputSpec, items: readonly unknown[], count: number): {
65
+ export declare function mapCliItemsDetailed(output: CliOutputSpec, items: readonly unknown[], count: number, now?: number): {
46
66
  sources: WebSearchSource[];
47
67
  skipped: number;
48
68
  };
49
- export declare function mapCliItems(output: CliOutputSpec, items: readonly unknown[], count: number): WebSearchSource[];
69
+ export declare function mapCliItems(output: CliOutputSpec, items: readonly unknown[], count: number, now?: number): WebSearchSource[];
70
+ /**
71
+ * The result of a `lines: { mode: 'whole' }` command: the whole stdout as ONE source. The text is cut at `maxChars` with a note that
72
+ * says how much was shown; the title is "<id>: <query>"; the link is the stand-in `cli://<id>?q=<query>` (it is no web page, so the
73
+ * evidence pipeline lists it but never fetches it). Undefined when the tool printed nothing.
74
+ */
75
+ export declare function wholeOutputSource(spec: Pick<CliAdapterSpec, 'id'>, output: CliOutputSpec, query: string, stdout: string): WebSearchSource | undefined;
50
76
  /** Parse and map in one go (what the fixtures tests call). */
51
- export declare function parseCliOutput(spec: Pick<CliAdapterSpec, 'bins'>, search: CliSearchSpec, stdout: string, count?: number): WebSearchSource[];
52
- /** Fill the placeholders of an argv template. A value that would read as a flag gets a leading space (the tool then takes it as the query). */
53
- export declare function buildArgv(search: Pick<CliSearchSpec, 'argv' | 'maxCount'>, input: {
77
+ export declare function parseCliOutput(spec: Pick<CliAdapterSpec, 'bins'>, search: CliSearchSpec, stdout: string, count?: number, now?: number): WebSearchSource[];
78
+ /** Split a query into words; double quotes keep a phrase in one word (`a "b c" d` -> `a`, `b c`, `d`) and are dropped. */
79
+ export declare function splitTerms(query: string): string[];
80
+ /**
81
+ * Fill the placeholders of an argv template. A value that would read as a flag gets a leading space (the tool then takes it as the query)
82
+ * unless a `--` precedes it. A `{terms}` token becomes one token per word of the query.
83
+ */
84
+ export declare function buildArgv(search: Pick<CliSearchSpec, 'argv' | 'maxCount' | 'overfetch'>, input: {
54
85
  query: string;
55
86
  count: number;
56
87
  }): string[];
@@ -73,6 +104,14 @@ export interface CliRunOptions {
73
104
  exists?: (file: string) => boolean;
74
105
  /** The caller checks the credentials itself (the legacy twitter engine): skip the pre-run credential gate. */
75
106
  skipCredentialGate?: boolean;
107
+ /** Test seam: the clock `{ time }` fields read relative texts (`3小时前`) against. */
108
+ now?: number;
109
+ /** Sees what the command printed (secrets redacted) before it is interpreted: the settings card's trial shows the tool's own words. */
110
+ observe?: (result: {
111
+ code: number;
112
+ stdout: string;
113
+ stderr: string;
114
+ }) => void;
76
115
  }
77
116
  /**
78
117
  * Run one spec command for a query and return its sources. Throws {@link CliAdapterError} (CLI_NOT_FOUND,
@@ -16,13 +16,38 @@ export type OutputFormat = typeof OUTPUT_FORMATS[number];
16
16
  /** How far a spec has been checked against the real tool (recorded in the spec and shown in `sources.status`). */
17
17
  export declare const CLI_VERIFICATIONS: readonly ["live", "contract-only", "docs-only"];
18
18
  export type CliVerification = typeof CLI_VERIFICATIONS[number];
19
+ /**
20
+ * The short honest tag shown next to a backend that was not verified with a real search ("detected and configured" is not "works"),
21
+ * or undefined for `live` and for backends that are not CLI specs.
22
+ */
23
+ export declare function verificationTag(status: string): string | undefined;
24
+ /** The verification word of a user-defined spec: it is unverified by definition and says so in every result it returns. */
25
+ export declare const USER_DEFINED = "user-defined";
26
+ /** The honest label a custom CLI's results and chain entries carry. */
27
+ export declare const USER_DEFINED_TAG = "user-defined, not verified by the plugin (\u7528\u6237\u81EA\u5B9A\u4E49\uFF0C\u672A\u7ECF\u63D2\u4EF6\u9A8C\u8BC1)";
28
+ /** Confidence of a custom CLI whose spec gives none: below the built-in sources (1), because nothing here checked its output. */
29
+ export declare const DEFAULT_CUSTOM_CONFIDENCE = 0.5;
30
+ /** Default and largest size of a whole-output result's text, in characters (`output.lines.maxChars`). */
31
+ export declare const WHOLE_OUTPUT_DEFAULT_CHARS = 2000;
32
+ export declare const WHOLE_OUTPUT_MAX_CHARS = 8000;
33
+ /** Scheme of the stand-in link of a whole-output result (`cli://<id>?q=<query>`): not a web page, so nothing fetches it. */
34
+ export declare const WHOLE_OUTPUT_SCHEME = "cli://";
19
35
  export declare const CLI_ERROR_CODES: readonly ["CLI_NOT_FOUND", "CLI_CONTRACT_MISMATCH", "CLI_NOT_LOGGED_IN", "ENGINE_EMPTY", "CLI_FAILED"];
20
36
  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. */
37
+ /**
38
+ * One piece of a joined field: a path, optionally with text put before / after the value, a `{ template }` over the item (dropped
39
+ * when a placeholder is empty), or a `{ time }` path whose text is read as a time (see {@link FieldSource}) and dropped when it is not one.
40
+ */
22
41
  export type JoinPart = string | {
23
42
  path: string;
24
43
  prefix?: string;
25
44
  suffix?: string;
45
+ } | {
46
+ template: string;
47
+ } | {
48
+ time: string;
49
+ prefix?: string;
50
+ suffix?: string;
26
51
  };
27
52
  /** `{ template }` fills `{path}` placeholders from the item and yields nothing when one is empty; `when` makes it apply to one kind of item. */
28
53
  export interface TemplateSource {
@@ -38,7 +63,11 @@ export interface TemplateSource {
38
63
  * - a list of paths and templates: the first non-empty one wins;
39
64
  * - `{ template }`: `{path}` placeholders over the item, dropped when any of them is empty;
40
65
  * - `{ 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.
66
+ * - `{ epoch }`: a Unix timestamp (seconds) as an ISO date; `{ date }`: the date part (first 10 characters) of an ISO timestamp;
67
+ * - `{ time }`: a time text the tool prints (ISO, the X API's `created_at`, `2026-10-27`, `08-26`, `3小时前`, `昨天 10:20`, ...) as an
68
+ * ISO date or timestamp, and nothing when the text is none of the exactly known formats (see cli/time-text.ts).
69
+ * A dotted path may hold a `key=value` segment that picks the first element of an array with that field value
70
+ * (`note_card.corner_tag_info.type=publish_time.text`).
42
71
  */
43
72
  export type FieldSource = string | readonly (string | TemplateSource)[] | TemplateSource | {
44
73
  join: readonly JoinPart[];
@@ -47,6 +76,8 @@ export type FieldSource = string | readonly (string | TemplateSource)[] | Templa
47
76
  epoch: string;
48
77
  } | {
49
78
  date: string;
79
+ } | {
80
+ time: string;
50
81
  };
51
82
  export interface CliOutputSpec {
52
83
  format: OutputFormat;
@@ -59,19 +90,32 @@ export interface CliOutputSpec {
59
90
  }[];
60
91
  /** Paths of the tool's own error text, read when `expect` fails (first non-empty one). */
61
92
  errorPaths?: readonly string[];
62
- /** text: how a line becomes an item (`tsv`: tab separated columns; `url-lines`: lines holding a URL, `url` + `title`). */
93
+ /**
94
+ * text: how a line becomes an item (`tsv`: tab separated columns; `url-lines`: lines holding a URL, `url` + `title`; `whole`: the
95
+ * whole stdout is ONE result, for a tool that prints just a block of text: its text capped at `maxChars` (default
96
+ * {@link WHOLE_OUTPUT_DEFAULT_CHARS}) with a note when it was cut, the title "<id>: <query>" and the link `cli://<id>?q=<query>`).
97
+ */
63
98
  lines?: {
64
99
  mode: 'tsv';
65
100
  columns: readonly string[];
66
101
  } | {
67
102
  mode: 'url-lines';
103
+ } | {
104
+ mode: 'whole';
105
+ maxChars?: number;
68
106
  };
69
- fields: {
70
- url: FieldSource;
107
+ /** Required (with `url`) for every mode except `whole`, which maps nothing. */
108
+ fields?: {
109
+ url?: FieldSource;
71
110
  title?: FieldSource;
72
111
  snippet?: FieldSource;
73
112
  publishedAt?: FieldSource;
74
113
  };
114
+ /** Items whose `path` equals the value are not results at all and are not counted as skipped (xhs: `model_type` is `hot_query` for the suggestion cards its result list also carries). */
115
+ skipWhen?: {
116
+ path: string;
117
+ equals: string | number | boolean;
118
+ };
75
119
  /** Strip HTML tags from title and snippet. */
76
120
  stripTags?: boolean;
77
121
  /** Values read as "empty" (`None`). */
@@ -83,16 +127,54 @@ export interface CliOutputSpec {
83
127
  /** Drop items without a title (a line that is not a result). */
84
128
  requireTitle?: boolean;
85
129
  }
130
+ /**
131
+ * How a tool reports a failed command in its machine-readable format (a YAML / JSON payload on stderr, else stdout), and what
132
+ * each of its error codes means. A listed code is mapped to a structured plugin error; a code that is not listed keeps the
133
+ * tool's own message (`<tool> search failed: [CODE] message`).
134
+ */
135
+ export interface CliErrorCodesSpec {
136
+ /** Dotted path of the tool's error code in the failure payload (`error.code`). */
137
+ codePath: string;
138
+ /** Dotted path of its message (`error.message`). */
139
+ messagePath?: string;
140
+ /** Codes that mean "not logged in" (CLI_NOT_LOGGED_IN). */
141
+ notLoggedIn?: readonly string[];
142
+ /** Exit codes that mean "not logged in" when the output carries no readable code. */
143
+ notLoggedInExit?: readonly number[];
144
+ /** Codes that mean "no results" (ENGINE_EMPTY). */
145
+ empty?: readonly string[];
146
+ /** Codes that mean the site answered with a web page or blocked the request: risk control, IP block, login wall (CLI_FAILED, said so). */
147
+ blocked?: readonly string[];
148
+ /** Case-insensitive fragments of the tool's message that mean the same (a JSON parse error on an HTML page: `Unexpected token '<'`). */
149
+ blockedPatterns?: readonly string[];
150
+ /** Codes that mean the tool cannot reach its browser (CLI_FAILED, not retryable). */
151
+ unavailable?: readonly string[];
152
+ /** Codes that mean a timeout (ENGINE_TIMEOUT). */
153
+ timeout?: readonly string[];
154
+ /** Codes that mean the tool rejected its arguments (CLI_CONTRACT_MISMATCH: the adapter does not match this version). */
155
+ usage?: readonly string[];
156
+ }
86
157
  export interface CliSearchSpec {
87
- /** Arguments after the binary. `{query}` and `{count}` are the placeholders; a token may embed one (`ytsearch{count}:{query}`). */
158
+ /**
159
+ * Arguments after the binary. `{query}` and `{count}` are the placeholders; a token may embed one (`ytsearch{count}:{query}`).
160
+ * A token that is exactly `{terms}` expands to the query split into one argv token per word (double quotes keep a phrase together):
161
+ * for tools that join their positional arguments themselves and would otherwise quote a one-token query as an exact phrase (`gh search`).
162
+ */
88
163
  argv: readonly string[];
89
164
  /** Largest `{count}` sent (the search call's count is clamped to 1..maxCount). */
90
165
  maxCount?: number;
166
+ /**
167
+ * Extra results asked of the tool beyond the count (`{count}` becomes count + overfetch), for tools whose list can hold entries the
168
+ * mapper must drop (bili: courses and series without a link). The mapper still returns at most `count`.
169
+ */
170
+ overfetch?: number;
91
171
  output: CliOutputSpec;
92
172
  /** Case-insensitive fragments of the tool's output that mean "no results" rather than a failure. */
93
173
  emptyWhen?: readonly string[];
94
174
  /** Case-insensitive fragments of the tool's output that mean "not logged in" (maps to CLI_NOT_LOGGED_IN). */
95
175
  notLoggedInPatterns?: readonly string[];
176
+ /** The tool's structured error codes (checked before the fragments above). */
177
+ errorCodes?: CliErrorCodesSpec;
96
178
  }
97
179
  export interface CliProbeSpec {
98
180
  versionArgs: readonly string[];
@@ -101,10 +183,14 @@ export interface CliProbeSpec {
101
183
  helpArgs: readonly string[];
102
184
  /** Fragments the help output must contain: the subcommands and flags that prove this is the tool the spec describes. */
103
185
  mustContain: readonly string[];
186
+ /** Appended to the reason when `mustContain` is not met (which version is known to have the contract, for example). */
187
+ contractHint?: string;
104
188
  }
105
189
  export interface CliLoginSpec {
106
190
  /** What the user runs themselves (`xhs login`); never run by the plugin. */
107
- command: string;
191
+ command?: string;
192
+ /** What the user does, in words, when the tool takes its login from somewhere the plugin cannot run (the login of the user's own Chrome): shown instead of a command. One of `command` / `hint` is required. */
193
+ hint?: string;
108
194
  /** Home-relative files that exist once the user has logged in. Only their existence is checked, never their content. */
109
195
  savedCredentialPaths?: readonly string[];
110
196
  /** The tool reads the browser's cookies by itself when it has no saved login: then it is never run without one. */
@@ -116,8 +202,15 @@ export interface CliAdapterSpec {
116
202
  bins: readonly string[];
117
203
  /** What the package is called, which can differ from the command (`pip install pyzhihu-cli` runs `zhihu`). */
118
204
  packageNote: string;
119
- /** Platform ids the spec can serve (`search.run platform=`); a user spec's platform is `custom-cli:<id>`. */
205
+ /**
206
+ * Platform ids the spec serves: the platforms whose backend chain it may sit in (`platformBackends.<platform>`). A user spec is
207
+ * also a platform of its own, `custom-cli:<id>`, and may list none when it is a general `engine`.
208
+ */
120
209
  platforms: readonly string[];
210
+ /** User specs only: also a general search engine, listed in `engines` as `custom-cli:<id>` (fused and de-duplicated with the others). */
211
+ engine?: boolean;
212
+ /** User specs only: 0..1, default {@link DEFAULT_CUSTOM_CONFIDENCE}. `sources.confidence.custom-cli:<id>` overrides it. */
213
+ confidence?: number;
121
214
  probe: CliProbeSpec;
122
215
  /** Every command word the argv templates use. */
123
216
  allowedSubcommands: readonly string[];
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Conservative parsing of the time texts CLI tools print (a field source `{ time }` of the adapter spec): an exact format is
3
+ * turned into an ISO date or timestamp, anything else gives nothing, so a result never carries a guessed time. Pure; the clock is
4
+ * a parameter. Chinese relative and calendar texts are read in China time (UTC+8), which is what the sites print.
5
+ * - ISO 8601 (`2026-10-27T08:38:08Z`, with or without a zone offset or fraction) -> normalized timestamp;
6
+ * - the `created_at` of the X API (`Tue Oct 27 08:38:08 +0000 2026`) -> timestamp;
7
+ * - `YYYY-MM-DD` -> that date; `MM-DD` -> the latest such date that is not after today (a date in the future means last year);
8
+ * - `N分钟前` / `N小时前` -> timestamp (now minus N); `N天前` -> date; `昨天 HH:MM` -> timestamp (China time).
9
+ * @module web-search-pro/cli/time-text
10
+ */
11
+ /** The text as an ISO date (`YYYY-MM-DD`) or timestamp, or undefined when it is not one of the formats above. */
12
+ export declare function normalizeTimeText(raw: string, now?: number): string | undefined;