@viztor/dsh-tinyfish 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of @viztor/dsh-tinyfish might be problematic. Click here for more details.

package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 viztor
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,212 @@
1
+ # dsh-tinyfish
2
+
3
+ <img src="assets/tinyfish.svg" alt="TinyFish logo" width="96" />
4
+
5
+ [![npm](https://img.shields.io/npm/v/dsh-tinyfish.svg)](https://www.npmjs.com/package/dsh-tinyfish) [![downloads](https://img.shields.io/npm/dm/dsh-tinyfish.svg)](https://www.npmjs.com/package/dsh-tinyfish) [![ci](https://github.com/viztor/dsh-tinyfish/actions/workflows/ci.yml/badge.svg)](https://github.com/viztor/dsh-tinyfish/actions/workflows/ci.yml) [![license](https://img.shields.io/npm/l/dsh-tinyfish.svg)](https://github.com/viztor/dsh-tinyfish/blob/main/LICENSE) [![node](https://img.shields.io/badge/node-%3E%3D22.14-5FA04E.svg)](https://nodejs.org)
6
+
7
+ **Search and fetch for the DeepSeek Harness, at $0.**
8
+
9
+ A DSH bundle that makes [TinyFish](https://tinyfish.ai) the implementation of the harness's own `web_search` and `web_fetch` tools. Both endpoints are free, so the web path on your host stops costing money per call.
10
+
11
+ | | `dsh-web`'s default | with `dsh-tinyfish` |
12
+ | --- | --- | --- |
13
+ | search | `deepseek-official` | TinyFish `/search` via Monid, **$0** |
14
+ | fetch | `http` | TinyFish `/fetch`, **$0**, returns clean Markdown |
15
+ | turndown conversion | yes, on every fetch | **no** — the content is already Markdown |
16
+ | reversibility | — | two words, no reinstall |
17
+
18
+ ## Install
19
+
20
+ ```sh
21
+ cd ~/.dsh/profiles/web
22
+ npm install dsh-tinyfish
23
+ # or, identically:
24
+ npm install @viztor/dsh-tinyfish
25
+ ```
26
+
27
+ Both names ship the same content from the same release; `dsh-tinyfish` is the name DSH resolves and the docs use.
28
+
29
+ Add the bundle to that profile's `package.json`, then restart DSH:
30
+
31
+ ```jsonc
32
+ {
33
+ "dependencies": { "dsh-tinyfish": "^0.2.0" },
34
+ "dsh": {
35
+ "profile": {
36
+ "bundles": [
37
+ "@deepseek-ai/dsh-base",
38
+ "@deepseek-ai/dsh-web-app",
39
+ "dsh-tinyfish",
40
+ ],
41
+ },
42
+ },
43
+ }
44
+ ```
45
+
46
+ That is the whole install. The bundle's own `cordis.patch.yml` selects itself:
47
+
48
+ ```yaml
49
+ - id: web
50
+ name: "@deepseek-ai/dsh-web"
51
+ config:
52
+ searchProvider: tinyfish
53
+ fetchProvider: tinyfish
54
+ ```
55
+
56
+ Restarting matters: bundles are resolved when the harness boots, so `patchReload` will not pick up a newly mounted one.
57
+
58
+ **To go back**, set those two back to `deepseek-official` and `http`. The bundle stays mounted and idle — registration and selection are separate, and only your profile decides which provider wins.
59
+
60
+ **Requirements:** DSH **0.2.0+**, Node **22.14+**, and a credential (below). The harness supplies the `@deepseek-ai/*` peer packages; you do not install them.
61
+
62
+ ## Verify
63
+
64
+ Ask the agent to search for something. Or check the wiring without an agent:
65
+
66
+ ```sh
67
+ cd ~/.dsh/profiles/web
68
+ node -e '
69
+ const m = require("dsh-tinyfish");
70
+ const ctx = { web: { registerSearchProvider(){}, registerFetchProvider(){} }, get: () => undefined };
71
+ m.apply(ctx, m.Config({}));
72
+ console.log("registered:", m.name, "| no throw above = wired");
73
+ '
74
+ ```
75
+
76
+ If search reports `WEB_PROVIDER_CREDENTIAL_MISSING` or `WEB_PROVIDER_UNAVAILABLE`, that is the next section.
77
+
78
+ ## Credentials
79
+
80
+ A fresh install has none, and never will — a key inside an npm tarball would be published forever. Both endpoints are free, but both need an account.
81
+
82
+ **In the profile patch (no restart needed).** The `web-tinyfish` row in your profile's `cordis.patch.yml` sets `apiKeyEnv`, which the harness resolves through its credentials service and the launch environment, and re-reads on every call — so a change takes effect on the next search.
83
+
84
+ **By hand:**
85
+
86
+ | channel | get a key | then |
87
+ | --- | --- | --- |
88
+ | `direct` default | [tinyfish.ai](https://tinyfish.ai) → API keys | `tinyfish auth login`, or `echo $KEY \| tinyfish auth set` |
89
+ | `monid` | [app.monid.ai](https://app.monid.ai) | `monid keys add`, or `export MONID_API_KEY` |
90
+
91
+ The default is `direct` because the package is named for TinyFish: a fresh install asks for the credential its own name implies rather than for an account at a different service. If you would rather go through Monid — it reuses a platform key a Monid MCP mount already holds, and costs the same — pin it in your own profile patch, which is a host decision and does not need a new release of this plugin:
92
+
93
+ ```yaml
94
+ - id: web-tinyfish
95
+ config:
96
+ channel: monid
97
+ ```
98
+
99
+ Resolution order, first match wins: a literal `apiKey` in the settings row → the harness credentials service → the launch environment → the live environment → the CLI stores. A failing credential service falls through to the next source rather than failing the search.
100
+
101
+ ## Configuration
102
+
103
+ Everything lives in one row, `web-tinyfish`, edited in your profile's `cordis.patch.yml`. The row is validated, so an out-of-range value is rejected with a message rather than silently clamped.
104
+
105
+ **Settings → Plugins → TinyFish** edits this row, if you prefer a form to a patch file. The plugin ships both halves: the provider the harness loads, and a client bundle that contributes the page. Changes are staged and written on save, and a key you type is stored by the harness rather than in your profile.
106
+
107
+ The patch file is still the honest place for the first edit — it is where a selection that overrides someone else's layer belongs, and it needs no build. Use whichever suits the change.
108
+
109
+ | key | default | meaning |
110
+ | --- | --- | --- |
111
+ | `channel` | `direct` | `monid` or `direct`; see [Credentials](#credentials) |
112
+ | `apiKey` | _(unset)_ | literal credential for either channel; prefer a ref |
113
+ | `apiKeyEnv` | `TINYFISH_API_KEY` | credential reference, or env var, for `direct` |
114
+ | `monidKeyEnv` | `MONID_API_KEY` | credential reference, or env var, for `monid` |
115
+ | `purpose` | _(unset)_ | goal statement; TinyFish ranks on it |
116
+ | `attempts` | `3` | retries for a transient failure or an empty search (1–5) |
117
+ | `filters.domainType` | _(unset)_ | `web` \| `news` \| `research_paper` |
118
+ | `filters.language` / `.location` | _(unset)_ | geo targeting |
119
+ | `filters.includeDomains` / `.excludeDomains` | _(unset)_ | comma-separated |
120
+ | `monidBase` / `searchBase` / `fetchBase` | upstream | endpoint override, for staging |
121
+ | `search` / `fetch` | `true` | offer this kind at all; `false` declines without unregistering |
122
+
123
+ Search and fetch are switched independently. Both always register, so turning one off makes it report _unavailable_ rather than _missing_ — the harness tells those apart, and only the second means "I turned this off" rather than "the install is broken".
124
+
125
+ ```yaml
126
+ - id: web-tinyfish
127
+ config:
128
+ search: true
129
+ fetch: false # keep TinyFish for search, let dsh-web use another fetch
130
+ ```
131
+
132
+ ### Where a value comes from
133
+
134
+ Every setting resolves in the same three rungs — **row, then environment, then built-in default** — so a deployment can be retargeted without writing a patch file. This is the shape the shipped providers use for `$DEEPSEEK_SEARCH_BASE_URL`.
135
+
136
+ | setting | environment variable |
137
+ | ------------ | -------------------------- |
138
+ | `monidBase` | `TINYFISH_MONID_BASE_URL` |
139
+ | `searchBase` | `TINYFISH_SEARCH_BASE_URL` |
140
+ | `fetchBase` | `TINYFISH_FETCH_BASE_URL` |
141
+
142
+ An endpoint that does not parse makes the provider report itself unavailable rather than being trusted.
143
+
144
+ ### The credential, in order
145
+
146
+ Resolved **per call**, so a key rotated anywhere below takes effect on the next search with no restart. First match wins:
147
+
148
+ | # | source | set it by |
149
+ | --- | --- | --- |
150
+ | 1 | the `apiKey` literal | the row — a secret in config; prefer 2–3 |
151
+ | 2 | the credentials service | `apiKeyEnv` (direct) or `monidKeyEnv` (monid) in the settings UI |
152
+ | 3 | the launch environment | exported before DSH started |
153
+ | 4 | the live environment | `MONID_API_KEY` / `TINYFISH_API_KEY` |
154
+ | 5 | the channel's CLI store | `monid keys add` / `tinyfish auth login` |
155
+
156
+ The harness services sit above the environment on purpose: a value someone typed into Settings is a more deliberate choice than one that merely happens to be exported. A failing service falls through to the next source rather than failing the search, and a host that mounts neither still works.
157
+
158
+ ### Two keys, one settings page
159
+
160
+ The two channels authenticate against different services, so each has **its own** credential reference: `apiKeyEnv` (default `TINYFISH_API_KEY`) for `direct`, and `monidKeyEnv` (default `MONID_API_KEY`) for `monid`. Saving one never overwrites the other, so both can be live at once and switching channels back and forth loses nothing.
161
+
162
+ The page shows the key for the **selected** channel only. Showing both at once would invite pasting the Monid platform key into the field TinyFish authenticates with — and a key sent to the wrong service fails as a 401, which reads as "that key is wrong" rather than as "that was the wrong field". The `apiKey` literal still overrides either channel; it exists for a patch file, and the settings page does not write it.
163
+
164
+ ## Why the fetch path is a real improvement
165
+
166
+ `dsh-tool-web` renders a `kind: "html"` body by running **turndown** to convert HTML to Markdown, behind a depth cap with a `"[HTML content omitted]"` fallback. TinyFish already extracts clean Markdown in a browser-grade extractor, so this provider returns `kind: "text"` and the content reaches the model with no conversion step at all.
167
+
168
+ ## Two channels, one payload
169
+
170
+ Monid is a thin envelope whose `output` is the direct response verbatim, and it forwards parameter names unchanged. One transport serves both, and nothing above it branches on which is active — a test asserts the two agree on the top hit for the same query.
171
+
172
+ | | `direct` (default) | `monid` |
173
+ | ------ | ---------------------------- | -------------------------- |
174
+ | search | `GET api.search.tinyfish.ai` | `POST api.monid.ai/v1/run` |
175
+ | fetch | `POST api.fetch.tinyfish.ai` | `POST api.monid.ai/v1/run` |
176
+ | auth | `X-API-Key` | `Authorization: Bearer` |
177
+ | cost | $0, direct | $0, on the Monid wallet |
178
+
179
+ ## Behaviour worth knowing
180
+
181
+ - **A 404 is a result, not an error.** A per-URL fetch failure comes back carrying its status, because that is resource state the model needs.
182
+ - **`publishedAt` is honest.** TinyFish reports dates as human strings (`"Apr 30, 2026"`, `"1 year ago"`). What parses is coerced to ISO-8601; what does not is dropped rather than invented. Unzoned dates are read as UTC, so the same page reports the same day regardless of where the Worker ran.
183
+ - **Search retries an empty result.** The upstream answers a valid query with nothing about one run in three, so a blank result is retried up to `attempts` before it is believed.
184
+ - **A blocked run is terminal.** If a Monid workspace control stops a run, the error says why and links to top up. It is never retried.
185
+
186
+ ## Not included
187
+
188
+ TinyFish's `agent` and `browser` surfaces are **not** exposed. They cost $0.016/step and $0.002/min, are metered against a wallet, and do not fit `ctx.web` — that seam has exactly two provider kinds, and an agent run is an action, not a search or a fetch. Use the `tinyfish` CLI directly when a page genuinely needs a real browser.
189
+
190
+ ## Development
191
+
192
+ The toolchain is [Vite+](https://viteplus.dev): `vp pack` builds the library with tsdown, `vp test` runs Vitest, and `vp lint` / `vp fmt` are Oxlint and Oxfmt, type-aware. Lint and format settings live in the `lint` and `fmt` blocks of `vite.config.ts` — Vite+ disables nested Oxlint/Oxfmt configs, so a standalone `oxlint.config.ts` would be read by nobody.
193
+
194
+ ```sh
195
+ pnpm install
196
+ pnpm test # 78 hermetic tests — no network, no credential
197
+ pnpm run check # format + lint + types
198
+ pnpm run release:gate # build, then the full gate
199
+ pnpm run test:live # the real APIs, still $0, needs credentials
200
+ ```
201
+
202
+ `pnpm run ci` ends in one script, `scripts/check.mjs`, that runs six package checks in a single pass: `lib/` freshness, peer ranges npm can parse, the bundle contract, the harness surfaces still being present, no credentials in the tree, and — the one that earns its keep — packing the tarball, installing it with plain npm, and loading it. Each is proved by planting the regression it guards, and CI runs all of it.
203
+
204
+ Full process and invariants: [`AGENTS.md`](./AGENTS.md).
205
+
206
+ ## Compatibility
207
+
208
+ Requires **DSH 0.2.0+**; tested against 0.2.0-rc.1. The `@deepseek-ai/dsh-*` peers are `^0.2.0-rc.1`, so a DSH patch release will not orphan the plugin, and a 0.3 contract change still fails loudly rather than silently.
209
+
210
+ ## License
211
+
212
+ MIT
@@ -0,0 +1,18 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="TinyFish logo">
2
+ <title>TinyFish</title>
3
+ <defs>
4
+ <linearGradient id="tf-fish" x1="12" y1="20" x2="52" y2="44" gradientUnits="userSpaceOnUse">
5
+ <stop offset="0" stop-color="#4D6BFE"/>
6
+ <stop offset="1" stop-color="#14B8A6"/>
7
+ </linearGradient>
8
+ </defs>
9
+ <!--
10
+ The DeepSeek Harness icon family: a softly tinted rounded square carrying
11
+ one vibrant glyph. Pale blue container, DeepSeek-blue-to-teal fish, white
12
+ knockout eye. Fill only, so it stays crisp at any size.
13
+ -->
14
+ <rect x="2" y="2" width="60" height="60" rx="15" fill="#E8F1FE"/>
15
+ <ellipse cx="27" cy="33" rx="15" ry="11" fill="url(#tf-fish)"/>
16
+ <polygon points="39,33 53,23 53,43" fill="url(#tf-fish)"/>
17
+ <circle cx="21" cy="30.5" r="2.4" fill="#ffffff"/>
18
+ </svg>
@@ -0,0 +1,41 @@
1
+ # Bundle patch for `dsh-tinyfish`.
2
+ #
3
+ # The plugin only *offers* the `tinyfish` provider on both seam kinds. Whether
4
+ # it is actually used is the profile's decision, in `dsh-web`'s config:
5
+ #
6
+ # - id: web
7
+ # name: '@deepseek-ai/dsh-web'
8
+ # config:
9
+ # searchProvider: tinyfish
10
+ # fetchProvider: tinyfish
11
+ #
12
+ # Both rows are kept separate from the plugin row so the id is set in one place
13
+ # and swapping back to `deepseek-official` / `http` is a two-word edit rather
14
+ # than a plugin removal. Registering a provider nobody selects is free.
15
+ #
16
+ # `channel` picks the upstream route and is deliberately NOT set here, so the
17
+ # schema default stands: `direct`, which calls TinyFish's own API with the key
18
+ # the `tinyfish` CLI stored in ~/.tinyfish/config.json. A package named for
19
+ # TinyFish should not open by asking for an account at a different service.
20
+ #
21
+ # A host that prefers the Monid envelope — which reuses the credential a Monid
22
+ # MCP mount already holds, and costs the same — pins it in its own patch layer,
23
+ # where the preference belongs and where it can be changed without a new
24
+ # release:
25
+ #
26
+ # - id: web-tinyfish
27
+ # config:
28
+ # channel: monid
29
+ #
30
+ # `apiKey` is not set here either — a secret belongs in the environment, the
31
+ # CLI's own store, or the harness credentials service, never in a patch file.
32
+ - insert:
33
+ - id: web-tinyfish
34
+ name: "dsh-tinyfish"
35
+ config:
36
+ attempts: 3
37
+ - id: web
38
+ name: "@deepseek-ai/dsh-web"
39
+ config:
40
+ searchProvider: tinyfish
41
+ fetchProvider: tinyfish