dsh-web-tavily 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  All notable changes to this project are documented in this file.
4
4
 
5
+ ## 0.4.2 (2026-09-16)
6
+
7
+ ### Fixed
8
+
9
+ - **`ipaddr.js` is a declared dependency now.** `lib/index.js` imported it while `package.json` declared no `dependencies` at all, so the import resolved only because the dsh installation closure happens to hoist it (it is a dependency of `@deepseek-ai/dsh-web-fetch-http`). It is a module-scope import, so a closure without it would have failed the plugin tree. Declared as `^2.5.0`.
10
+ - **`USER_AGENT` can no longer drift from the package version.** It was the literal `dsh-web-tavily/0.4.0` while the package shipped 0.4.1. It is now read from `package.json` at load time (through `createRequire(import.meta.url)`), with a graceful fallback to the bare plugin name when the manifest is unreadable — loading still never fails because of it.
11
+ - **`diag()` creates the log directory.** `~/.dsh-tui/dsh-web-tavily.log` was appended to without creating `~/.dsh-tui/`, so on a fresh machine every line was swallowed by the outer catch. The write path now creates the parent directory first; the "never throws" contract is unchanged.
12
+ - **Peer ranges widened to `^0.1.2-rc.1 || ^0.1.5-rc.1`.** `^0.1.2-rc.1` cannot match `0.1.5-rc.1`: semver lets a prerelease satisfy a range only when a comparator shares its `[major, minor, patch]` tuple, so every `0.1.x-rc.n` after `0.1.2` fell outside it. Today that is a warning rather than a failure (the profile template pins `autoInstallPeers: false`), but it would become an install error under strict peer enforcement. A future engine prerelease line needs its own clause.
13
+
14
+ ### Documentation
15
+
16
+ - **The install steps now warn that saving `cordis.patch.yml` hot-reloads the running session.** A custom profile defaults to `patchReload: live`, so the edit takes effect immediately — not at the restart the README previously implied. If the `dsh-web-tavily` row has not been loaded yet, the `web` row points at a provider that is not registered and the session's web capability fails with `WEB_PROVIDER_CONFIGURED_MISSING` until the next restart. The safe order (install → `/restart` → edit the patch) is now spelled out.
17
+ - **Getting a Tavily API key is step 1**, with where the key goes (`~/.dsh/.credentials.yaml` under `refs:`, or the launching environment's `TAVILY_API_KEY`) and an explicit note not to commit it.
18
+ - The documented key-resolution order is corrected to match the code: literal `config.apiKey` first, then the credentials service, then the launching environment. The module header comment carried the same wrong order and is fixed too.
19
+
20
+ ## 0.4.1 (2026-09-13)
21
+
22
+ - Documentation only, no code change. Installation now goes through `dsh plugin --profile dsh-tui add dsh-web-tavily` (the package is published) instead of copying files into `node_modules`, both READMEs gain a CI badge, and the release section records the OIDC trusted-publishing path.
23
+
5
24
  ## 0.4.0 (2026-09-13)
6
25
 
7
26
  ### Changed
package/README.md CHANGED
@@ -1,7 +1,11 @@
1
1
  # dsh-web-tavily
2
2
 
3
+ [![ci](https://github.com/VviLliAm-qwq/dsh-web-tavily/actions/workflows/ci.yml/badge.svg)](https://github.com/VviLliAm-qwq/dsh-web-tavily/actions/workflows/ci.yml)
4
+
3
5
  **English** · [中文](README.zh.md)
4
6
 
7
+ Built for [dsh-TUI](https://github.com/ccch1mneyyy/dsh-TUI).
8
+
5
9
  A host-plane Cordis plugin for dsh: it registers the **`tavily`** search provider
6
10
  on dsh's web seam (`ctx.web`), so the `web_search` tool goes to the **Tavily
7
11
  Search API** (`POST https://api.tavily.com/search`).
@@ -11,10 +15,10 @@ Search API** (`POST https://api.tavily.com/search`).
11
15
  - DeepSeek's native search (`web-search-deepseek`) stays available as a fallback
12
16
  you can switch to; set `web.searchProvider` back to `deepseek-official` (or
13
17
  delete the key) to do so.
14
- - The key is always resolved as a **reference** (default `TAVILY_API_KEY`): the
15
- credentials service (`refs:` in `~/.dsh/.credentials.yaml`) first, then the
16
- process environment, and only then a literal `apiKey` in the configuration. The
17
- provider never stores a key.
18
+ - The key is resolved per operation in this order: a literal `apiKey` in the
19
+ configuration first, then the credentials service (`refs:` in
20
+ `~/.dsh/.credentials.yaml`), then the launch environment. The reference
21
+ defaults to `TAVILY_API_KEY`, and the provider never stores a key.
18
22
 
19
23
  ## How it works
20
24
 
@@ -44,11 +48,27 @@ Search API** (`POST https://api.tavily.com/search`).
44
48
 
45
49
  ## Install
46
50
 
47
- 1. Copy the package into
48
- `~/.dsh/profiles/dsh-tui/node_modules/dsh-web-tavily/`.
49
- 2. Append `"dsh-web-tavily"` to `dsh.profile.bundles` in
50
- `~/.dsh/profiles/dsh-tui/package.json`.
51
- 3. **Make tavily the default search backend.** This has to be written in the
51
+ 1. **Get a Tavily account and an API key.** Register at <https://tavily.com/>,
52
+ create a key in the dashboard, and store it under `refs:` in
53
+ `~/.dsh/.credentials.yaml` or export `TAVILY_API_KEY` in the environment
54
+ that launches dsh (see
55
+ [Where the key lives](#where-the-key-lives-local-only--never-in-a-repository)).
56
+ Never commit the key to a repository or paste it into a settings document.
57
+
58
+ 2. Install it into your profile — it is published on npm as `dsh-web-tavily`:
59
+
60
+ ```sh
61
+ dsh plugin --profile dsh-tui add dsh-web-tavily
62
+ # from a local checkout instead:
63
+ # dsh plugin --profile dsh-tui add file:<path to this package>
64
+ ```
65
+
66
+ 3. **Restart dsh-tui before editing the patch** (`/restart`). Adding a plugin
67
+ only rewrites the profile's bundle list; it does **not** load the plugin into
68
+ the running session. Restarting here means the `tavily` provider is already
69
+ registered when the patch below is saved.
70
+
71
+ 4. **Make tavily the default search backend.** This has to be written in the
52
72
  profile's user patch layer (the `web:` section of `settings.yaml` does not
53
73
  affect service configuration) — edit
54
74
  `~/.dsh/profiles/dsh-tui/cordis.patch.yml` and add:
@@ -61,12 +81,21 @@ Search API** (`POST https://api.tavily.com/search`).
61
81
  fetchProvider: http-trusted
62
82
  ```
63
83
 
84
+ ⚠️ **Saving this file hot-reloads the running session — it is not
85
+ "restart to apply".** The profile's patch layer is watched, so the session is
86
+ reconfigured the instant the file is written. If the plugin is not loaded yet
87
+ (step 3 skipped), the `web` row immediately points at a provider that is not
88
+ registered, and `web_search` / web fetch fail in that session with
89
+ `WEB_PROVIDER_CONFIGURED_MISSING`. **A `/restart` recovers it**, because at
90
+ boot the plugin is registered and the patched row is read together. Editing
91
+ the patch first and restarting later is also safe for the boot — it just
92
+ leaves this session's web capability unavailable until that restart.
93
+
64
94
  The `web` row belongs to the `dsh-base` layer, so **do not add a new row with
65
95
  `insert:`** — the duplicate id crashes the boot with
66
96
  `duplicate loader entry id: web`. Override the existing row by id instead, and
67
97
  note that an override **replaces the whole config**, which is why
68
98
  `fetchProvider` is restated above.
69
- 4. Restart dsh-tui (`/restart`).
70
99
 
71
100
  ## Configuration
72
101
 
@@ -100,6 +129,11 @@ refs:
100
129
  TAVILY_API_KEY: 'tvly-...'
101
130
  ```
102
131
 
132
+ Exporting `TAVILY_API_KEY` in the environment that launches dsh works too — the
133
+ credentials store is consulted first, the launch environment second. A literal
134
+ key in `dsh-web-tavily.apiKey` is accepted as well and wins over both, but it
135
+ puts the secret in a settings document, so treat it as a last resort.
136
+
103
137
  ## Switching back to DeepSeek's native search
104
138
 
105
139
  Set `web.searchProvider` to `deepseek-official` (or delete the key and let the
@@ -145,8 +179,10 @@ exchange for the complete safety preflight.
145
179
  - `duplicate loader entry id: web` on boot — the profile's `cordis.patch.yml`
146
180
  used `insert:` for a row that already exists; rewrite it as the by-id override
147
181
  shown above.
148
- - `web_search` answers `WEB_PROVIDER_CONFIGURED_MISSING` — the plugin did not
149
- load: check the bundles list and the patch above.
182
+ - `web_search` answers `WEB_PROVIDER_CONFIGURED_MISSING` — the `web` row points
183
+ at a provider that is not registered: either the plugin did not load (check the
184
+ bundles list), or `cordis.patch.yml` was saved before the plugin was loaded,
185
+ which is a live reload (see [Install](#install)). `/restart` recovers it.
150
186
  - `web_fetch` answers `WEB_BLOCKED_URL ... non-public IP` — the proxy fake-IP
151
187
  preflight refused it: switch to `http-trusted`, or turn the proxy's fake-IP
152
188
  mode off.
@@ -163,6 +199,7 @@ exchange for the complete safety preflight.
163
199
  ## Publishing
164
200
 
165
201
  - **Repository**: <https://github.com/VviLliAm-qwq/dsh-web-tavily> (public)
202
+ - **Release**: `v*` tags drive `.github/workflows/release.yml`, which publishes to npm through **trusted publishing (OIDC)** — no token is stored in the repository.
166
203
 
167
204
  ## Usage
168
205
 
package/README.zh.md CHANGED
@@ -1,12 +1,18 @@
1
1
  # dsh-web-tavily
2
2
 
3
+ [![ci](https://github.com/VviLliAm-qwq/dsh-web-tavily/actions/workflows/ci.yml/badge.svg)](https://github.com/VviLliAm-qwq/dsh-web-tavily/actions/workflows/ci.yml)
4
+
3
5
  **中文** · [English](README.md)
4
6
 
7
+ 为 [dsh-TUI](https://github.com/ccch1mneyyy/dsh-TUI) 构建。
8
+
5
9
  dsh host 平面 cordis 插件:为 dsh 的 web 能力 seam(`ctx.web`)注册搜索提供商 **`tavily`**,让 `web_search` 工具默认走 **Tavily Search API**(`POST https://api.tavily.com/search`)。
6
10
 
7
11
  - 不替换 `web_search` 工具本身:工具、系统提示、展示卡片均保持原样,只有搜索后端换成 Tavily。
8
12
  - 保留 DeepSeek 原生搜索(`web-search-deepseek`)为可切换备选;想切回只需把 `web.searchProvider` 改为 `deepseek-official` 或删掉该键。
9
- - key 一律按**引用**解析(默认 `TAVILY_API_KEY`):优先凭证服务(`~/.dsh/.credentials.yaml` 的 `refs:`),其次启动环境变量,最后才是配置里的字面 `apiKey`。provider 不保留 key。
13
+ - key 按以下顺序解析:配置里的字面 `apiKey` 最优先,其次凭证服务
14
+ (`~/.dsh/.credentials.yaml` 的 `refs:`,引用名默认 `TAVILY_API_KEY`),最后是启动
15
+ 环境变量。provider 不保留 key。
10
16
 
11
17
  ## 工作原理
12
18
 
@@ -30,10 +36,24 @@ dsh host 平面 cordis 插件:为 dsh 的 web 能力 seam(`ctx.web`)注册
30
36
 
31
37
  ## 安装
32
38
 
33
- 1. 把整个包复制到
34
- `~/.dsh/profiles/dsh-tui/node_modules/dsh-web-tavily/`。
35
- 2. 在 `~/.dsh/profiles/dsh-tui/package.json` `dsh.profile.bundles` 追加 `"dsh-web-tavily"`。
36
- 3. **把 web 服务默认搜索改为 tavily**(必须写在 profile 用户补丁层,`settings.yaml` 的 `web:` 段对服务配置无效)——
39
+ 1. **先注册 Tavily 账号并创建 API key。** 到 <https://tavily.com/> 注册,在控制台创建
40
+ key,然后放进 `~/.dsh/.credentials.yaml` 的 `refs:` 下,或在启动 dsh 的环境里导出
41
+ `TAVILY_API_KEY`(详见下文「key 存放」)。**不要把 key 提交进仓库,也不要写进设置
42
+ 文档。**
43
+
44
+ 2. 装进 profile —— 它以 `dsh-web-tavily` 发布在 npm 上:
45
+
46
+ ```sh
47
+ dsh plugin --profile dsh-tui add dsh-web-tavily
48
+ # 从本仓库本地安装(开发):
49
+ # dsh plugin --profile dsh-tui add file:<本包路径>
50
+ ```
51
+
52
+ 3. **先 `/restart` 重启一次**,让插件行载入。`dsh plugin add` 只改写 profile 的 bundles
53
+ 清单,**不会**把插件加载进运行中的会话;先重启,下一步保存补丁时 `tavily` provider
54
+ 才已经注册好。
55
+
56
+ 4. **把 web 服务默认搜索改为 tavily**(必须写在 profile 用户补丁层,`settings.yaml` 的 `web:` 段对服务配置无效)——
37
57
  编辑 `~/.dsh/profiles/dsh-tui/cordis.patch.yml` 追加:
38
58
 
39
59
  ```yaml
@@ -44,10 +64,15 @@ dsh host 平面 cordis 插件:为 dsh 的 web 能力 seam(`ctx.web`)注册
44
64
  fetchProvider: http-trusted
45
65
  ```
46
66
 
67
+ ⚠️ **保存该文件会立即热重载运行中的会话,不是「重启后才生效」。** profile 补丁层被
68
+ 文件监听器盯着,落盘那一刻当前会话就会被重新配置。若插件此时尚未加载(跳过了第 3
69
+ 步),`web` 行立刻指向一个还没注册的 provider,当前会话的 `web_search`/联网能力当场报
70
+ `WEB_PROVIDER_CONFIGURED_MISSING`。**重启即恢复**:启动时插件与补丁一起读入。先改
71
+ 补丁、稍后再重启对启动同样安全,只是当前会话的联网能力要等到那次重启才可用。
72
+
47
73
  `web` 行由基座 dsh-base 提供,**不要用 `insert:` 新增同 id 行**——那会撞 id 直接崩
48
74
  `duplicate loader entry id: web`;这里要按 id 覆盖既有行,且覆盖是**整行重写,需写全
49
75
  `fetchProvider`**。
50
- 4. 重启 dsh-tui(`/restart`)生效。
51
76
 
52
77
  ## 配置
53
78
 
@@ -75,6 +100,10 @@ refs:
75
100
  TAVILY_API_KEY: 'tvly-...'
76
101
  ```
77
102
 
103
+ 也可以在启动 dsh 的环境里导出 `TAVILY_API_KEY`:解析时先查凭证服务、再查启动环境。
104
+ 配置里的字面 `dsh-web-tavily.apiKey` 同样可用,且优先于以上两者,但它等于把密钥写进
105
+ 设置文档,只建议作为最后手段。
106
+
78
107
  ## 切回 DeepSeek 原生搜索
79
108
 
80
109
  把 `web.searchProvider` 改为 `deepseek-official`(或删除该键、让 seam 用唯一可用 provider)。注意 seam 不支持自动回退:Tavily 配置失效时搜索会明确报错,而**不会**悄悄走 DeepSeek。
@@ -99,7 +128,9 @@ config:
99
128
  ## 加载失败排查
100
129
 
101
130
  - 插件启动报 `duplicate loader entry id: web`:profile `cordis.patch.yml` 误用 `insert:` 新增同 id 行,按上文改写为直接行。
102
- - `web_search` 报 `WEB_PROVIDER_CONFIGURED_MISSING`:插件未加载(检查 bundles 列表与上述补丁)。
131
+ - `web_search` 报 `WEB_PROVIDER_CONFIGURED_MISSING`:`web` 行指向的 provider 尚未注册
132
+ ——要么插件没加载(检查 bundles 清单),要么 `cordis.patch.yml` 在插件加载前就被保存
133
+ (保存即热重载,见「安装」);`/restart` 即可恢复。
103
134
  - `web_fetch` 报 `WEB_BLOCKED_URL ... non-public IP`:代理假 IP 预检拒绝,改用 `http-trusted` 或关闭代理 fake-IP 模式。
104
135
 
105
136
  ## 限制说明
@@ -111,6 +142,7 @@ config:
111
142
  ## 发布
112
143
 
113
144
  - **仓库**:<https://github.com/VviLliAm-qwq/dsh-web-tavily>(公开)
145
+ - **发布方式**:`v*` tag 驱动 `.github/workflows/release.yml`,经 npm **可信发布(OIDC)**上传——仓库内不存放任何令牌。
114
146
 
115
147
  ## 使用
116
148
 
package/dsh-plugin.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "manifestVersion": "0.15",
4
4
  "id": "com.dsh-tui.dsh-web-tavily",
5
5
  "name": "dsh-web-tavily",
6
- "version": "0.4.0",
6
+ "version": "0.4.2",
7
7
  "facets": {
8
8
  "host": {
9
9
  "entry": "lib/index.js",
package/lib/diag.js CHANGED
@@ -11,9 +11,9 @@
11
11
  * `~/.dsh-tui/` capped at 128 KiB, and no file writes under `node --test`.
12
12
  */
13
13
 
14
- import { appendFileSync, readFileSync, statSync, writeFileSync } from 'node:fs';
14
+ import { appendFileSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
15
15
  import { homedir } from 'node:os';
16
- import { join } from 'node:path';
16
+ import { dirname, join } from 'node:path';
17
17
 
18
18
  /** Plugin-owned log path. */
19
19
  export const DIAG_LOG = join(homedir(), '.dsh-tui', 'dsh-web-tavily.log');
@@ -31,6 +31,10 @@ function appendLogLine(path, line) {
31
31
  catch {
32
32
  // Missing or unreadable: the append below recreates it.
33
33
  }
34
+ // A fresh machine has no ~/.dsh-tui/ yet; create it first, or the append
35
+ // throws and the caller's catch swallows the line — losing exactly the
36
+ // evidence this log exists to keep.
37
+ mkdirSync(dirname(path), { recursive: true });
34
38
  appendFileSync(path, line);
35
39
  }
36
40
 
package/lib/index.js CHANGED
@@ -3,9 +3,9 @@
3
3
  * capability seam (ctx.web). Registers the `tavily` provider so the
4
4
  * model-facing web_search tool runs against the Tavily Search API
5
5
  * (POST https://api.tavily.com/search). The provider holds no credential
6
- * value: the key is resolved per operation through the credentials service
7
- * (ref, default TAVILY_API_KEY), the launch environment, or a literal
8
- * `apiKey` config, in that order.
6
+ * value: the key is resolved per operation from a literal `apiKey` config
7
+ * first, then the credentials service (ref, default TAVILY_API_KEY), then the
8
+ * launch environment, in that order.
9
9
  *
10
10
  * The bundle additionally registers `http-trusted`, a proxy-friendly HTTP(S)
11
11
  * fetch provider: it reuses the official @deepseek-ai/dsh-web-fetch-http
@@ -26,6 +26,7 @@ import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
26
26
  import { WebError } from "@deepseek-ai/dsh-web";
27
27
  import { HttpFetchProvider } from "@deepseek-ai/dsh-web-fetch-http";
28
28
  import { lookup } from "node:dns/promises";
29
+ import { createRequire } from "node:module";
29
30
  import { isIP } from "node:net";
30
31
  import { diag } from "./diag.js";
31
32
  import { applySettingsCard } from "./settings-card.js";
@@ -55,8 +56,24 @@ const DEFAULT_API_KEY_ENV = "TAVILY_API_KEY";
55
56
  const BASE_URL_ENV = "TAVILY_BASE_URL";
56
57
  /** Settings namespace carrying this provider's endpoint and behavior options. */
57
58
  const SETTINGS_NAMESPACE = "dsh-web-tavily";
58
- /** Attribution header sent on every request. Bump with the package version. */
59
- const USER_AGENT = "dsh-web-tavily/0.4.0";
59
+ /**
60
+ * Build the attribution header from the installed manifest (`../package.json`,
61
+ * beside `lib/`). The version is read at load time so a release can never leave
62
+ * it stale; a missing/unreadable manifest or version degrades to the bare
63
+ * plugin name — attribution is never a reason a module fails to load.
64
+ */
65
+ function userAgent() {
66
+ try {
67
+ const version = createRequire(import.meta.url)("../package.json").version;
68
+ if (typeof version === "string" && version.length > 0) return `${name}/${version}`;
69
+ }
70
+ catch {
71
+ // Fall through to the name-only header.
72
+ }
73
+ return name;
74
+ }
75
+ /** Attribution header sent on every request. */
76
+ const USER_AGENT = userAgent();
60
77
  /** Fallback result cap when the caller sends no maxResults. */
61
78
  const DEFAULT_MAX_RESULTS = 10;
62
79
  /** Tavily's documented per-request result cap. */
package/package.json CHANGED
@@ -1,8 +1,18 @@
1
1
  {
2
2
  "name": "dsh-web-tavily",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Tavily-backed search provider for dsh: registers the `tavily` provider on ctx.web so the web_search tool runs against the Tavily Search API",
5
5
  "license": "MIT",
6
+ "keywords": [
7
+ "dsh",
8
+ "dsh-plugin",
9
+ "dsh-tui",
10
+ "deepseek-harness",
11
+ "plugin",
12
+ "search",
13
+ "tavily",
14
+ "web-search"
15
+ ],
6
16
  "repository": {
7
17
  "type": "git",
8
18
  "url": "git+https://github.com/VviLliAm-qwq/dsh-web-tavily.git"
@@ -29,10 +39,13 @@
29
39
  "peerDependencies": {
30
40
  "@deepseek-ai/cordis": "^4.0.2",
31
41
  "@deepseek-ai/schemastery": "^3.18.2",
32
- "@deepseek-ai/dsh-web": "^0.1.2-rc.1",
33
- "@deepseek-ai/dsh-web-fetch-http": "^0.1.2-rc.1",
34
- "@deepseek-ai/dsh-credentials": "^0.1.2-rc.1",
35
- "@deepseek-ai/dsh-launch-environment": "^0.1.2-rc.1",
36
- "@deepseek-ai/dsh-settings": "^0.1.2-rc.1"
42
+ "@deepseek-ai/dsh-web": "^0.1.2-rc.1 || ^0.1.5-rc.1",
43
+ "@deepseek-ai/dsh-web-fetch-http": "^0.1.2-rc.1 || ^0.1.5-rc.1",
44
+ "@deepseek-ai/dsh-credentials": "^0.1.2-rc.1 || ^0.1.5-rc.1",
45
+ "@deepseek-ai/dsh-launch-environment": "^0.1.2-rc.1 || ^0.1.5-rc.1",
46
+ "@deepseek-ai/dsh-settings": "^0.1.2-rc.1 || ^0.1.5-rc.1"
47
+ },
48
+ "dependencies": {
49
+ "ipaddr.js": "^2.5.0"
37
50
  }
38
51
  }