@jamolpe88/dsh-free-websearch 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.es.md +276 -0
- package/README.md +237 -2
- package/SECURITY.md +36 -0
- package/assets/coverage.png +0 -0
- package/assets/failover.png +0 -0
- package/assets/how-it-works.png +0 -0
- package/assets/tool-card.png +0 -0
- package/cordis.patch.yml +27 -0
- package/lib/fetch.js +161 -0
- package/lib/index.js +120 -0
- package/lib/lanes.js +296 -0
- package/lib/net.js +80 -0
- package/package.json +50 -4
- package/scripts/probe.mjs +70 -0
- package/test/plugin.test.js +323 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 jamolpe88
|
|
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.es.md
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# dsh-free-websearch
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@jamolpe88/dsh-free-websearch)
|
|
4
|
+
[](https://www.npmjs.com/package/@jamolpe88/dsh-free-websearch)
|
|
5
|
+
[](https://github.com/JaMoLpE88/dsh-free-websearch/releases)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](#compatibilidad)
|
|
8
|
+
[](#compatibilidad)
|
|
9
|
+
[](#por-qué-no-hay-ajustes)
|
|
10
|
+
[](https://dsh-plugin.org/plugins/jamolpe88/dsh-free-websearch)
|
|
11
|
+
|
|
12
|
+
**Búsqueda web y lectura de páginas gratis para DeepSeek Harness.** Cinco motores sin
|
|
13
|
+
clave detrás de las propias herramientas `web_search` y `web_fetch` del harness, con
|
|
14
|
+
failover automático y con los proveedores de pago de DeepSeek apagados. Instálalo y la
|
|
15
|
+
plataforma de DeepSeek queda fuera del circuito, uses el modelo que uses.
|
|
16
|
+
|
|
17
|
+
Cero configuración. Sin registro, sin clave, sin página de ajustes y sin respaldo de pago:
|
|
18
|
+
si todos los carriles gratuitos fallan, la búsqueda devuelve un informe en vez de gastar
|
|
19
|
+
un crédito.
|
|
20
|
+
|
|
21
|
+
**English** → [README.md](README.md)
|
|
22
|
+
|
|
23
|
+

|
|
24
|
+
|
|
25
|
+
*Las herramientas no cambian. La costura `ctx.web` elige un proveedor por llamada, este
|
|
26
|
+
bundle fija el suyo y detrás responden cinco carriles sin clave. El mismo parche desactiva
|
|
27
|
+
los proveedores de búsqueda y lectura de DeepSeek, así que no hay camino de vuelta a la API
|
|
28
|
+
de pago por accidente.*
|
|
29
|
+
|
|
30
|
+

|
|
31
|
+
|
|
32
|
+
*Una búsqueda, un carril. La primera respuesta corta la cadena, así que las demás cuotas
|
|
33
|
+
quedan intactas; un carril bloqueado entra en cuarentena y responde el siguiente.*
|
|
34
|
+
|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
*Dos carriles no tienen tope publicado, y por eso son el suelo de la cadena y no la cabeza.
|
|
38
|
+
Las cuotas anónimas se comparten por IP, así que los ilimitados cubren una red ocupada.*
|
|
39
|
+
|
|
40
|
+

|
|
41
|
+
|
|
42
|
+
*La misma tarjeta de citas que pinta el proveedor de pago: títulos, URLs y fragmentos,
|
|
43
|
+
sin una sola clave de API.*
|
|
44
|
+
|
|
45
|
+
## Qué hace
|
|
46
|
+
|
|
47
|
+
- Sirve **`web_search`** a través de cinco motores gratuitos: Keenable, Firecrawl keyless, Bing, DuckDuckGo y You.com.
|
|
48
|
+
- Sirve **`web_fetch`** en local primero (el proveedor HTTP del propio harness) y escala al scrape keyless de Firecrawl sólo cuando la página es un muro, viene vacía o es un shell de JavaScript.
|
|
49
|
+
- **Desactiva los proveedores de pago de DeepSeek** (`deepseek-official`, `deepseek-search-api`, `deepseek-fetch-api`), de modo que ningún ajuste, cadena de respaldo ni interruptor de la UI pueda alcanzarlos.
|
|
50
|
+
- Salta al siguiente carril en orden y se detiene en el primero que responde: una búsqueda gasta la cuota de un carril, no de cinco.
|
|
51
|
+
- Cachea resultados **5 minutos** (50 entradas), así que repetir una pregunta no cuesta nada.
|
|
52
|
+
- Pone en cuarentena un carril tras un muro (5 min) o una cuota mensual agotada (el resto del proceso) en lugar de martillearlo.
|
|
53
|
+
- Nombra todos los carriles que probó cuando se rinde, y avisa de que no se envió nada a una API de pago.
|
|
54
|
+
- Devuelve el comportamiento original de DeepSeek al desactivar o desinstalar el bundle.
|
|
55
|
+
|
|
56
|
+
## Qué no hace
|
|
57
|
+
|
|
58
|
+
- No añade herramientas. Ni `x_search`, ni `read_page`, ni `multi_search`, ni pipeline de investigación.
|
|
59
|
+
- No lee credenciales. Ni las de DeepSeek ni las de nadie: el plugin no importa el servicio de credenciales, así que no puede gastar una clave guardada.
|
|
60
|
+
- No llama a `api.deepseek.com` bajo ninguna circunstancia.
|
|
61
|
+
- No raspa cookies, no guarda historial y no envía telemetría.
|
|
62
|
+
- No renderiza JavaScript en local. Escala, y sólo para destinos públicos.
|
|
63
|
+
|
|
64
|
+
## Por qué no hay ajustes
|
|
65
|
+
|
|
66
|
+
No hay nada que decidir. El orden de carriles es el mismo para todos, todos son sin clave y
|
|
67
|
+
las cuotas son anónimas. Una página de ajustes sólo te pediría elegir un carril cuya cuota
|
|
68
|
+
no puedes ver y cuyo fallo la cadena ya resuelve.
|
|
69
|
+
|
|
70
|
+
La única decisión que importa —"¿esto me puede costar dinero?"— no es una preferencia: la
|
|
71
|
+
responde el parche del bundle. Los proveedores de pago están desactivados, así que la
|
|
72
|
+
respuesta es no.
|
|
73
|
+
|
|
74
|
+
## Qué sustituye
|
|
75
|
+
|
|
76
|
+
| Ruta de pago del harness | Proveedor | Cómo queda |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| Búsqueda por Messages (`web_search_20250305`) | `deepseek-official` | Desactivada. `web_search` usa la cadena gratuita |
|
|
79
|
+
| Search API (`/v1/tools/search`) | `deepseek-search-api` | Desactivada, mismo sustituto |
|
|
80
|
+
| Fetch API (`/v1/tools/fetch`) | `deepseek-fetch-api` | Desactivada. `web_fetch` usa local → scrape keyless |
|
|
81
|
+
| Lectura local | `http` | Se conserva, y se delega en ella en vez de reimplementarla |
|
|
82
|
+
|
|
83
|
+
Todo lo demás que hace el harness —títulos de sesión, compactación, subagentes, workflows—
|
|
84
|
+
va por **tu modelo**, no por un endpoint de DeepSeek. No hay un tercer sitio que arreglar.
|
|
85
|
+
|
|
86
|
+
## Los cinco carriles
|
|
87
|
+
|
|
88
|
+
Se prueban en este orden. Un carril se salta mientras está en cuarentena.
|
|
89
|
+
|
|
90
|
+
| Carril | Endpoint | Cuota anónima | Papel |
|
|
91
|
+
|---|---|---|---|
|
|
92
|
+
| `keenable` | `api.keenable.ai/v1/search/public` | 1.000 peticiones/hora por IP | Absorbe casi todo: JSON, ~0,4 s |
|
|
93
|
+
| `firecrawl` | `api.firecrawl.dev/v2/search` | 1.000 créditos/mes | Mejores fragmentos; escaso, por eso va segundo |
|
|
94
|
+
| `bing` | `www.bing.com/search?format=rss` | Sin tope publicado | El suelo, ~0,3 s, URLs directas |
|
|
95
|
+
| `ddg` | `html.duckduckgo.com/html/` | Sin tope publicado | El otro suelo |
|
|
96
|
+
| `you` | `api.you.com/v1/agents/search` | 100 búsquedas/día por IP | Último recurso |
|
|
97
|
+
|
|
98
|
+
Presupuestos: **7 s por carril**, **30 s por búsqueda**, cadena de cinco y caché de 5 minutos.
|
|
99
|
+
Sin reintentos dentro de un carril: un fallo pasa al siguiente, que es más barato que esperar
|
|
100
|
+
a un servidor que ya ha dicho que no.
|
|
101
|
+
|
|
102
|
+
## Requisitos
|
|
103
|
+
|
|
104
|
+
- **DSH Desktop** `>= 0.2.1-rc.3`, perfil `desktop` (probado en `0.2.1-rc.3`, Windows 11).
|
|
105
|
+
- Salida HTTPS. Nada más.
|
|
106
|
+
- Node 22+ (DSH trae el suyo; el paquete sólo necesita 20.3).
|
|
107
|
+
|
|
108
|
+
Sin clave de DeepSeek. Sin proveedor configurado. Sin cuenta.
|
|
109
|
+
|
|
110
|
+
## Instalar
|
|
111
|
+
|
|
112
|
+
Desde la app de escritorio: **Plugins → Añadir plugin**, y escribe `@jamolpe88/dsh-free-websearch`.
|
|
113
|
+
|
|
114
|
+
Desde npm:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
dsh plugin --profile desktop add @jamolpe88/dsh-free-websearch
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
El perfil `web` instala el mismo paquete:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
dsh plugin --profile web add @jamolpe88/dsh-free-websearch
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Desde el repositorio:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
dsh plugin --profile desktop add github:JaMoLpE88/dsh-free-websearch
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Desde una copia local, mientras desarrollas:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
dsh plugin --profile desktop add link:C:\ruta\a\dsh-free-websearch
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Refresca la ventana de DSH. En un perfil vivo el bundle se aplica sin reiniciar; la página
|
|
139
|
+
de Plugins lista **Free Web Search** y la tarjeta oficial **Web search** deja de aparecer,
|
|
140
|
+
porque sus dos proveedores ya no están montados.
|
|
141
|
+
|
|
142
|
+
## Uso
|
|
143
|
+
|
|
144
|
+
1. Pregunta lo que necesite web, como siempre. El modelo llama a `web_search`; las fuentes vuelven con su tarjeta de citas.
|
|
145
|
+
2. Pega un enlace o pregunta por una página. `web_fetch` la lee en local y sólo escala si la página necesita un navegador.
|
|
146
|
+
3. Nada más. No hay comando, ni píldora, ni panel.
|
|
147
|
+
|
|
148
|
+
Si una búsqueda falla, el error nombra los carriles probados y el motivo, por ejemplo
|
|
149
|
+
`keenable: HTTP 429; bing: no results`. Reintenta, o comprueba si una VPN está reescribiendo
|
|
150
|
+
tu IP de salida.
|
|
151
|
+
|
|
152
|
+
## Cómo funciona
|
|
153
|
+
|
|
154
|
+
El plugin registra dos proveedores en la costura que ya existe, y el parche del bundle los
|
|
155
|
+
selecciona:
|
|
156
|
+
|
|
157
|
+
```js
|
|
158
|
+
ctx.web.registerSearchProvider({ id: 'freeweb', available: () => true, search })
|
|
159
|
+
ctx.web.registerFetchProvider({ id: 'freeweb-fetch', available: () => true, fetch })
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
```yaml
|
|
163
|
+
- id: web
|
|
164
|
+
config:
|
|
165
|
+
searchProvider: freeweb
|
|
166
|
+
fetchProvider: freeweb-fetch
|
|
167
|
+
- id: web-search-deepseek
|
|
168
|
+
disabled: true
|
|
169
|
+
- id: web-deepseek-api
|
|
170
|
+
disabled: true
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
- **La selección va fijada de forma declarativa.** Un parche reemplaza toda la config de la fila objetivo, así que se reescriben las dos claves de la costura. Desinstalar el bundle retira juntos el pin y las desactivaciones; el harness vuelve a `deepseek-official` / `http`.
|
|
174
|
+
- **Un respaldo en runtime cubre la instalación manual.** Si el parche del bundle no llegó a aplicarse (dependencia suelta, perfil antiguo), el plugin escribe la misma selección con `settings.mutate` cuando el servicio de ajustes está montado, y sólo si el valor actual es el de fábrica. Nunca pisa una elección deliberada, y al descargarse escribe de vuelta los ids originales.
|
|
175
|
+
- **`available()` es barato y offline.** El veredicto honesto pertenece a la ejecución, cuyo error lleva cada intento; responder `false` por adelantado convertiría un problema de red pasajero en un error de configuración de la costura.
|
|
176
|
+
- **La cadena de fetch reutiliza el harness.** `freeweb-fetch` delega primero en el proveedor `http` registrado, así que la validación de URL, el anclaje de direcciones, las reglas de redirección y la conversión HTML→markdown siguen siendo del harness. Sólo cuando ese resultado es un muro, está vacío o es un shell de JavaScript, el plugin llama al scrape keyless de Firecrawl.
|
|
177
|
+
- **La divulgación se comprueba antes de ocurrir.** Todas las direcciones resueltas del destino tienen que ser públicas antes de enviar la URL a Firecrawl. `localhost`, rangos privados, CGNAT y link-local no salen de la máquina, y un host que no resuelve tampoco se divulga.
|
|
178
|
+
|
|
179
|
+
## Verificarlo
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
npm test # 16 pruebas: parsers, failover, cuarentena, caché, guarda SSRF, takeover
|
|
183
|
+
npm run probe # comprobación real de los cinco endpoints y del escalado de fetch
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
El probe imprime el estado HTTP de cada carril, lanza búsquedas reales por la cadena con su
|
|
187
|
+
latencia y sus fuentes, y demuestra que un destino privado bloqueado nunca escala. Usa la red;
|
|
188
|
+
las pruebas no.
|
|
189
|
+
|
|
190
|
+
## Desinstalar y revertir
|
|
191
|
+
|
|
192
|
+
Quita el bundle (Plugins → Remove, o `dsh plugin --profile desktop remove @jamolpe88/dsh-free-websearch`).
|
|
193
|
+
La capa de parche desaparece con él:
|
|
194
|
+
|
|
195
|
+
- `web` vuelve a `searchProvider: deepseek-official` / `fetchProvider: http`,
|
|
196
|
+
- los dos proveedores de DeepSeek se reactivan,
|
|
197
|
+
- no quedan ficheros del plugin, ni ajustes propios, ni estado.
|
|
198
|
+
|
|
199
|
+
Desactivar el bundle tiene el mismo efecto. Si se instaló como dependencia suelta, el plugin
|
|
200
|
+
además escribe los ids originales al descargarse.
|
|
201
|
+
|
|
202
|
+
## Compatibilidad
|
|
203
|
+
|
|
204
|
+
| | |
|
|
205
|
+
|---|---|
|
|
206
|
+
| DSH | `>= 0.2.1-rc.3` (la única versión probada por el autor) |
|
|
207
|
+
| Node | `>= 20.3` (el paquete); DSH trae 22+ |
|
|
208
|
+
| SO | Windows 11 (probado); el código es Node puro, macOS y Linux sin probar |
|
|
209
|
+
| Perfil | `desktop` (probado); `web` arranca y busca por la misma costura |
|
|
210
|
+
| Costura | proveedores de search + fetch de `ctx.web` |
|
|
211
|
+
| Catálogo | [listado en dsh-plugin.org](https://dsh-plugin.org/plugins/jamolpe88/dsh-free-websearch) |
|
|
212
|
+
|
|
213
|
+
Estado de validación: **probado en Windows 11 con DSH Desktop 0.2.1-rc.3** (2026-10-28). El
|
|
214
|
+
bundle se aplicó en caliente, los dos proveedores de pago salieron de la composición,
|
|
215
|
+
`web_search` devolvió ocho fuentes citadas desde la cadena gratuita, `web_fetch` devolvió el
|
|
216
|
+
texto de la página y un destino privado bloqueado fue rechazado sin petición a la nube.
|
|
217
|
+
|
|
218
|
+
Sin validar: macOS, Linux, otras versiones de DSH y el perfil `web`.
|
|
219
|
+
|
|
220
|
+
## Límites
|
|
221
|
+
|
|
222
|
+
- **Los carriles son cortesía de terceros.** Las cuotas anónimas pueden cambiar, encogerse o empezar a pedir CAPTCHA sin avisar. Por eso hay cinco y por eso hay failover.
|
|
223
|
+
- **Una VPN puede romper los tramos keyless.** Firecrawl filtra su tramo anónimo por IP de salida y puede responder 403; Keenable y Bing mantienen la búsqueda viva.
|
|
224
|
+
- **Bing a veces sirve un SERP vacío a IPs de datacenter o proxy.** DuckDuckGo es el segundo carril ilimitado para ese caso.
|
|
225
|
+
- **Sin búsqueda de X/Twitter.** No hay camino gratis que no exija una cuenta de pago, así que el plugin no lo finge.
|
|
226
|
+
- **Sin render de JavaScript propio.** El escalado a un scraper en la nube es la única vía tipo navegador, y sólo para páginas públicas.
|
|
227
|
+
- **Las cuotas anónimas se comparten por IP.** Una red de oficina ocupada ve los topes keyless antes que una conexión doméstica.
|
|
228
|
+
|
|
229
|
+
## Privacidad
|
|
230
|
+
|
|
231
|
+
- La consulta va al carril que responde, y a cada carril probado antes que él. Nada más.
|
|
232
|
+
- La URL de una página sale de la máquina sólo por el escalado de `web_fetch`, y sólo después de comprobar que todas las direcciones resueltas son públicas.
|
|
233
|
+
- Sin cookies, sin clave, sin cuenta, sin almacenamiento local, sin telemetría.
|
|
234
|
+
- El plugin nunca lee el almacén de credenciales, así que ninguna clave de API puede filtrarse por él.
|
|
235
|
+
|
|
236
|
+
Política corta en [SECURITY.md](SECURITY.md).
|
|
237
|
+
|
|
238
|
+
## Problemas
|
|
239
|
+
|
|
240
|
+
**Las búsquedas fallan con "every free search lane failed".**
|
|
241
|
+
El mensaje lista cada carril y su error. `HTTP 429` en keenable significa que la reserva
|
|
242
|
+
horaria compartida se agotó; `bot wall` en ddg significa que pide CAPTCHA. Reintenta en unos
|
|
243
|
+
minutos o desactiva la VPN.
|
|
244
|
+
|
|
245
|
+
**`web_fetch` devuelve una página con muy poco texto.**
|
|
246
|
+
La lectura local funcionó pero el sitio se renderiza con JavaScript y el escalado no encontró
|
|
247
|
+
nada mejor. No está roto: es lo que sirve el sitio.
|
|
248
|
+
|
|
249
|
+
**La tarjeta oficial "Web search" ha desaparecido de Plugins.**
|
|
250
|
+
Es lo esperado: sólo existe mientras `web-search-deepseek` sirve su namespace, y este bundle
|
|
251
|
+
lo desactiva. Desinstálalo para recuperarla.
|
|
252
|
+
|
|
253
|
+
**Una página se rechaza como destino privado.**
|
|
254
|
+
La guarda del escalado se negó a enviarla a la nube. La lectura local sí funcionó; sólo se
|
|
255
|
+
bloquea el camino a la nube.
|
|
256
|
+
|
|
257
|
+
## Desarrollo
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
npm test # node --test
|
|
261
|
+
npm run probe # comprobación real de carriles
|
|
262
|
+
python tools/make-assets.py # regenera las imágenes de assets/
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
El paquete es JavaScript plano sin build y sin dependencias de ejecución. Las pruebas
|
|
266
|
+
sustituyen `fetch`, así que corren offline y nunca tocan un endpoint real.
|
|
267
|
+
|
|
268
|
+
## Marcas de terceros
|
|
269
|
+
|
|
270
|
+
DeepSeek, Bing, DuckDuckGo, Firecrawl, Keenable y You.com son marcas de sus respectivos
|
|
271
|
+
propietarios. Se nombran sólo para describir a qué servicio habla cada carril; no se incluye
|
|
272
|
+
ningún logotipo y no se implica afiliación.
|
|
273
|
+
|
|
274
|
+
## Licencia
|
|
275
|
+
|
|
276
|
+
[MIT](LICENSE) © 2026 JaMoLpE88
|
package/README.md
CHANGED
|
@@ -1,3 +1,238 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-free-websearch
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@jamolpe88/dsh-free-websearch)
|
|
4
|
+
[](https://www.npmjs.com/package/@jamolpe88/dsh-free-websearch)
|
|
5
|
+
[](https://github.com/JaMoLpE88/dsh-free-websearch/releases)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](#compatibility)
|
|
8
|
+
[](#compatibility)
|
|
9
|
+
[](#why-there-is-no-settings)
|
|
10
|
+
[](https://dsh-plugin.org/plugins/jamolpe88/dsh-free-websearch)
|
|
11
|
+
|
|
12
|
+
**Free web search and page fetch for the DeepSeek Harness.** Five keyless engines sit behind the harness's own `web_search` and `web_fetch`, with automatic failover, and the paid DeepSeek search and fetch providers are switched off. Install it and the DeepSeek platform is out of the loop — whatever model you are running.
|
|
13
|
+
|
|
14
|
+
Zero configuration. No signup, no API key, no settings page, no paid fallback: if every free lane is down the search fails with a report instead of spending a credit.
|
|
15
|
+
|
|
16
|
+
**Español** → [README.es.md](README.es.md)
|
|
17
|
+
|
|
18
|
+

|
|
19
|
+
|
|
20
|
+
*The tools do not change. The `ctx.web` seam picks a provider per call, this bundle pins its own, and five keyless lanes answer behind it. The DeepSeek search and fetch providers are disabled by the same patch, so there is no path back to the paid API by accident.*
|
|
21
|
+
|
|
22
|
+

|
|
23
|
+
|
|
24
|
+
*One search, one lane. The first answer stops the chain, so the other quotas stay unspent; a walled lane cools down and the next one takes over.*
|
|
25
|
+
|
|
26
|
+

|
|
27
|
+
|
|
28
|
+
*Two lanes have no published cap, which is why they are the floor of the chain rather than the head. The anonymous pools are shared per IP, so the unlimited ones catch a busy network.*
|
|
29
|
+
|
|
30
|
+

|
|
31
|
+
|
|
32
|
+
*The same citation card the paid provider renders: titles, URLs and snippets, produced without an API key.*
|
|
33
|
+
|
|
34
|
+
## What it does
|
|
35
|
+
|
|
36
|
+
- Serves the harness's **`web_search`** through five free engines: Keenable, Firecrawl keyless, Bing, DuckDuckGo and You.com.
|
|
37
|
+
- Serves **`web_fetch`** locally first (the harness's own HTTP provider) and escalates to Firecrawl's keyless scrape only when the page is a wall, empty, or a JavaScript shell.
|
|
38
|
+
- **Disables DeepSeek's paid search and fetch providers** (`deepseek-official`, `deepseek-search-api`, `deepseek-fetch-api`), so no setting, fallback chain or UI switch can reach them.
|
|
39
|
+
- Fails over in order and stops at the first lane that answers: one search spends one lane's quota, not five.
|
|
40
|
+
- Caches results for **5 minutes** (50 entries), so a repeated question costs nothing.
|
|
41
|
+
- Cools a lane down after a wall (5 min) or a spent monthly pool (for the rest of the process) instead of hammering it.
|
|
42
|
+
- Names every lane it tried when it gives up, and says that nothing was sent to a paid API.
|
|
43
|
+
- Restores the shipped DeepSeek behaviour when the bundle is disabled or removed.
|
|
44
|
+
|
|
45
|
+
## What it does not do
|
|
46
|
+
|
|
47
|
+
- It does not add tools. No `x_search`, no `read_page`, no `multi_search`, no research pipeline.
|
|
48
|
+
- It does not read credentials. Not DeepSeek's, not anyone's: the plugin never imports the credential service, so it cannot spend a key that is stored.
|
|
49
|
+
- It does not call `api.deepseek.com` under any circumstance.
|
|
50
|
+
- It does not scrape cookies, keep history, or send telemetry.
|
|
51
|
+
- It does not render JavaScript locally. It escalates instead, and only for public targets.
|
|
52
|
+
|
|
53
|
+
## Why there is no settings
|
|
54
|
+
|
|
55
|
+
There is nothing to decide. The lane order is the same for everyone, every lane is keyless, and the quotas are anonymous. A settings page would only ask you to pick a lane whose quota you cannot see and whose failure the chain already handles.
|
|
56
|
+
|
|
57
|
+
The one knob that matters — "may this cost me money?" — is not a preference. It is answered by the bundle patch: the paid providers are disabled, so the answer is no.
|
|
58
|
+
|
|
59
|
+
## What it replaces
|
|
60
|
+
|
|
61
|
+
| Paid route in the harness | Provider id | How it ends up |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| Search through Messages (`web_search_20250305`) | `deepseek-official` | Disabled. `web_search` runs the free chain |
|
|
64
|
+
| Search API (`/v1/tools/search`) | `deepseek-search-api` | Disabled, same replacement |
|
|
65
|
+
| Fetch API (`/v1/tools/fetch`) | `deepseek-fetch-api` | Disabled. `web_fetch` runs local → keyless scrape |
|
|
66
|
+
| Local fetch | `http` | Kept, and delegated to rather than reimplemented |
|
|
67
|
+
|
|
68
|
+
Everything else the harness does — session titles, compaction, subagents, workflows — runs through **your model**, not through a DeepSeek endpoint. There is no third place to fix.
|
|
69
|
+
|
|
70
|
+
## The five lanes
|
|
71
|
+
|
|
72
|
+
Tried in this order. A lane is skipped while it is cooling down.
|
|
73
|
+
|
|
74
|
+
| Lane | Endpoint | Anonymous quota | Role |
|
|
75
|
+
|---|---|---|---|
|
|
76
|
+
| `keenable` | `api.keenable.ai/v1/search/public` | 1,000 requests/hour per IP | Absorbs most traffic: JSON, ~0.4 s |
|
|
77
|
+
| `firecrawl` | `api.firecrawl.dev/v2/search` | 1,000 credits/month | Best snippets; scarce, so second |
|
|
78
|
+
| `bing` | `www.bing.com/search?format=rss` | No published cap | The floor, ~0.3 s, direct URLs |
|
|
79
|
+
| `ddg` | `html.duckduckgo.com/html/` | No published cap | The other floor |
|
|
80
|
+
| `you` | `api.you.com/v1/agents/search` | 100 searches/day per IP | Last resort |
|
|
81
|
+
|
|
82
|
+
Budgets: **7 s per lane**, **30 s per search**, five-lane chain, 5-minute result cache. No retries inside a lane: a failure moves on immediately, which is cheaper than waiting for a server that already said no.
|
|
83
|
+
|
|
84
|
+
## Requirements
|
|
85
|
+
|
|
86
|
+
- **DSH Desktop** `>= 0.2.1-rc.3`, profile `desktop` (tested on `0.2.1-rc.3`, Windows 11).
|
|
87
|
+
- Outbound HTTPS. That is all.
|
|
88
|
+
- Node 22+ (DSH ships its own runtime; the package itself only needs 20.3).
|
|
89
|
+
|
|
90
|
+
No DeepSeek API key. No provider configured. No account.
|
|
91
|
+
|
|
92
|
+
## Install
|
|
93
|
+
|
|
94
|
+
From the desktop app: **Plugins → Add plugin**, then `@jamolpe88/dsh-free-websearch`.
|
|
95
|
+
|
|
96
|
+
From npm:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
dsh plugin --profile desktop add @jamolpe88/dsh-free-websearch
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The `web` profile takes the same package:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
dsh plugin --profile web add @jamolpe88/dsh-free-websearch
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
From the repository:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
dsh plugin --profile desktop add github:JaMoLpE88/dsh-free-websearch
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
From a local checkout, while developing:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
dsh plugin --profile desktop add link:C:\path\to\dsh-free-websearch
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Refresh the DSH window. In a live profile the bundle applies without a restart; the Plugins page lists **Free Web Search** and the official **Web search** card stops appearing, because its two providers are no longer mounted.
|
|
121
|
+
|
|
122
|
+
## Usage
|
|
123
|
+
|
|
124
|
+
1. Ask anything that needs the web, as usual. The model calls `web_search`; sources come back with a citation card.
|
|
125
|
+
2. Paste a link or ask about a page. `web_fetch` reads it locally and escalates only if the page needs a browser.
|
|
126
|
+
3. Nothing else. There is no command, no pill, no panel.
|
|
127
|
+
|
|
128
|
+
If a search fails, the error names the lanes that were tried and why, for example `keenable: HTTP 429; bing: no results`. Retry, or check whether a VPN is rewriting your egress IP.
|
|
129
|
+
|
|
130
|
+
## How it works
|
|
131
|
+
|
|
132
|
+
The plugin registers two providers into the harness's existing seam, then the bundle patch selects them:
|
|
133
|
+
|
|
134
|
+
```js
|
|
135
|
+
ctx.web.registerSearchProvider({ id: 'freeweb', available: () => true, search })
|
|
136
|
+
ctx.web.registerFetchProvider({ id: 'freeweb-fetch', available: () => true, fetch })
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
```yaml
|
|
140
|
+
- id: web
|
|
141
|
+
config:
|
|
142
|
+
searchProvider: freeweb
|
|
143
|
+
fetchProvider: freeweb-fetch
|
|
144
|
+
- id: web-search-deepseek
|
|
145
|
+
disabled: true
|
|
146
|
+
- id: web-deepseek-api
|
|
147
|
+
disabled: true
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
- **Selection is pinned declaratively.** A patch row replaces the targeted row's whole config, so both seam keys are restated. Uninstalling the bundle removes the pin and the disables together; the harness returns to `deepseek-official` / `http`.
|
|
151
|
+
- **A runtime fallback covers manual installs.** If the bundle patch did not land (a plain dependency, or an older profile), the plugin writes the same selection through `settings.mutate` once the settings service is mounted, and only when the current value is the shipped default. It never overrides a deliberate choice, and it writes the shipped ids back when it unloads.
|
|
152
|
+
- **`available()` is cheap and offline.** The honest verdict belongs to execution, whose error carries every attempt; answering `false` up front would turn a transient network problem into a seam-misconfiguration error.
|
|
153
|
+
- **The fetch chain reuses the harness.** `freeweb-fetch` delegates to the registered `http` provider first, so URL validation, address pinning, redirect rules and HTML→markdown conversion stay the harness's. Only when that result is a wall, empty, or a JavaScript shell does the plugin call Firecrawl's keyless scrape.
|
|
154
|
+
- **Disclosure is checked before it happens.** The target's every resolved address must be public before the URL is sent to Firecrawl. `localhost`, private ranges, CGNAT and link-local addresses never leave the machine, and an unresolvable host is not disclosed either.
|
|
155
|
+
|
|
156
|
+
## Verifying it
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
npm test # 16 tests: parsers, failover, cooldown, cache, SSRF guard, takeover
|
|
160
|
+
npm run probe # live check of the five endpoints and the fetch escalation
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
The probe prints each lane's HTTP status, runs real searches through the chain with their latency and sources, and proves that a blocked private target is never escalated. It uses the network; the tests do not.
|
|
164
|
+
|
|
165
|
+
## Uninstall and revert
|
|
166
|
+
|
|
167
|
+
Remove the bundle (Plugins → Remove, or `dsh plugin --profile desktop remove @jamolpe88/dsh-free-websearch`). The patch layer disappears with it:
|
|
168
|
+
|
|
169
|
+
- `web` returns to the shipped `searchProvider: deepseek-official` / `fetchProvider: http`,
|
|
170
|
+
- both DeepSeek providers re-enable,
|
|
171
|
+
- no plugin files, no settings of its own, no state left behind.
|
|
172
|
+
|
|
173
|
+
Disabling the bundle has the same effect. If it was installed as a plain dependency, the plugin also writes the shipped ids back when it unloads.
|
|
174
|
+
|
|
175
|
+
## Compatibility
|
|
176
|
+
|
|
177
|
+
| | |
|
|
178
|
+
|---|---|
|
|
179
|
+
| DSH | `>= 0.2.1-rc.3` (the only version tested by the author) |
|
|
180
|
+
| Node | `>= 20.3` (the package); DSH ships 22+ |
|
|
181
|
+
| OS | Windows 11 (tested); the code is plain Node, macOS and Linux untested |
|
|
182
|
+
| Profile | `desktop` (tested); `web` boots and searches through the same seam |
|
|
183
|
+
| Seam | `ctx.web` search + fetch providers |
|
|
184
|
+
| Catalog | [listed on dsh-plugin.org](https://dsh-plugin.org/plugins/jamolpe88/dsh-free-websearch) |
|
|
185
|
+
|
|
186
|
+
Validation status: **tested on Windows 11 with DSH Desktop 0.2.1-rc.3** (2026-10-28). The bundle applied live, both paid providers left the composition, `web_search` returned eight cited sources from the free chain, `web_fetch` returned page text, and a blocked private target was refused without a cloud request.
|
|
187
|
+
|
|
188
|
+
Not validated: macOS, Linux, other DSH versions, and the `web` profile.
|
|
189
|
+
|
|
190
|
+
## Limits
|
|
191
|
+
|
|
192
|
+
- **The lanes are third-party goodwill.** Anonymous quotas can change, shrink or start asking for a CAPTCHA without notice. That is why there are five and why the chain fails over.
|
|
193
|
+
- **A VPN can break the keyless tiers.** Firecrawl gates its anonymous tier by egress IP and may answer 403; Keenable and Bing keep the search working.
|
|
194
|
+
- **Bing occasionally serves an empty SERP to data-centre or proxy IPs.** DuckDuckGo is the second unlimited lane for that case.
|
|
195
|
+
- **No X/Twitter search.** There is no free path that does not require a paid account, so the plugin does not pretend otherwise.
|
|
196
|
+
- **No JavaScript rendering of its own.** Escalation to a cloud scraper is the only browser-like path, and only for public pages.
|
|
197
|
+
- **Anonymous pools are shared per IP.** A busy office network sees the keyless ceilings sooner than a home connection.
|
|
198
|
+
|
|
199
|
+
## Privacy
|
|
200
|
+
|
|
201
|
+
- The query goes to the lane that answers, and to each lane tried before it. Nothing else is sent.
|
|
202
|
+
- A page URL leaves the machine only through `web_fetch`'s escalation, only after every resolved address is proven public.
|
|
203
|
+
- No cookies, no key, no account, no local storage, no telemetry.
|
|
204
|
+
- The plugin never reads the credential store, so no API key can leak through it.
|
|
205
|
+
|
|
206
|
+
See [SECURITY.md](SECURITY.md) for the short policy.
|
|
207
|
+
|
|
208
|
+
## Troubleshooting
|
|
209
|
+
|
|
210
|
+
**Searches fail with "every free search lane failed".**
|
|
211
|
+
The message lists each lane and its error. `HTTP 429` on keenable means the shared hourly pool is spent; `bot wall` on ddg means it wants a CAPTCHA. Retry after a few minutes, or disable the VPN.
|
|
212
|
+
|
|
213
|
+
**`web_fetch` returns a page with very little text.**
|
|
214
|
+
The local fetch worked but the site renders with JavaScript and the escalation found nothing better. Nothing is wrong: that is what the site serves.
|
|
215
|
+
|
|
216
|
+
**The official "Web search" card disappeared from Plugins.**
|
|
217
|
+
Expected: it exists only while `web-search-deepseek` serves its namespace, and this bundle disables it. Uninstall the bundle to bring it back.
|
|
218
|
+
|
|
219
|
+
**A page is refused as a private target.**
|
|
220
|
+
The escalation guard declined to send it to the cloud. Local fetch still worked; only the cloud path is blocked.
|
|
221
|
+
|
|
222
|
+
## Development
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
npm test # node --test
|
|
226
|
+
npm run probe # live lane check
|
|
227
|
+
python tools/make-assets.py # regenerate the artwork in assets/
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
The package is plain JavaScript with no build step and no runtime dependencies. The tests stub `fetch`, so they run offline and never touch a real endpoint.
|
|
231
|
+
|
|
232
|
+
## Third-party marks
|
|
233
|
+
|
|
234
|
+
DeepSeek, Bing, DuckDuckGo, Firecrawl, Keenable and You.com are trademarks of their respective owners. They are named only to describe which service a lane talks to; no logo is bundled and no affiliation is implied.
|
|
235
|
+
|
|
236
|
+
## License
|
|
237
|
+
|
|
238
|
+
[MIT](LICENSE) © 2026 JaMoLpE88
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Security policy
|
|
2
|
+
|
|
3
|
+
## Reporting
|
|
4
|
+
|
|
5
|
+
Open a private advisory on GitHub, or an issue if the report contains no sensitive detail.
|
|
6
|
+
Please include the DSH version, the plugin version, and the lane involved.
|
|
7
|
+
|
|
8
|
+
## What this plugin handles
|
|
9
|
+
|
|
10
|
+
- **No credentials.** The plugin never imports `ctx.credentials` and never reads
|
|
11
|
+
`DEEPSEEK_API_KEY`, `TAVILY_API_KEY`, `EXA_API_KEY`, `FIRECRAWL_API_KEY` or any other key.
|
|
12
|
+
It cannot spend one, and it cannot leak one.
|
|
13
|
+
- **No stored state.** No config file, no cache on disk, no history, no cookies, no local
|
|
14
|
+
storage. The result cache lives in memory and dies with the process.
|
|
15
|
+
- **Queries leave the machine** to the search lane that answers, and to each lane tried
|
|
16
|
+
before it: `api.keenable.ai`, `api.firecrawl.dev`, `www.bing.com`, `html.duckduckgo.com`,
|
|
17
|
+
`api.you.com`. All over HTTPS.
|
|
18
|
+
- **Page URLs** are read locally first. A URL reaches `api.firecrawl.dev` only when the local
|
|
19
|
+
read produced a wall, an empty body or a JavaScript shell, **and** every resolved address
|
|
20
|
+
of the host is public. Loopback, private (10/8, 172.16/12, 192.168/16), CGNAT (100.64/10),
|
|
21
|
+
link-local (169.254/16, fe80::/10), unique-local (fc00::/7) and `localhost`-style hostnames
|
|
22
|
+
are never disclosed. An unresolvable host is not disclosed either.
|
|
23
|
+
- **Responses are treated as untrusted.** Search results are data: they are normalized to
|
|
24
|
+
URL, title, snippet and date, stripped of markup, and capped in length. The plugin does not
|
|
25
|
+
execute, evaluate or fetch anything a result suggests.
|
|
26
|
+
|
|
27
|
+
## What it deliberately does not do
|
|
28
|
+
|
|
29
|
+
- No background requests, no telemetry, no analytics, no update check.
|
|
30
|
+
- No remote code, no dynamic import of a URL, no shell.
|
|
31
|
+
- No writes outside the harness's own settings document when the bundle patch did not apply.
|
|
32
|
+
|
|
33
|
+
## Supply chain
|
|
34
|
+
|
|
35
|
+
The package has **zero runtime dependencies**. `lib/` is plain JavaScript with no build step,
|
|
36
|
+
so what the registry serves is exactly what runs.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# dsh-free-websearch — bundle layer for DeepSeek Harness.
|
|
2
|
+
#
|
|
3
|
+
# Three declarative facts, so uninstalling restores the shipped behaviour
|
|
4
|
+
# exactly: this whole file lives in the package, so removing the bundle
|
|
5
|
+
# removes the takeover, the pin, and the disables in one step.
|
|
6
|
+
#
|
|
7
|
+
# 1. Point the web seam at this plugin's providers.
|
|
8
|
+
# A patch row replaces the targeted row's whole config, so every key the
|
|
9
|
+
# base row owns is restated here (it owns only these two).
|
|
10
|
+
# 2. Close the paid door. Without these rows the harness can silently fall
|
|
11
|
+
# back to DeepSeek's paid search/fetch when a free lane is down, and the
|
|
12
|
+
# "Use the new Web Search API" switch can flip the seam back at any time.
|
|
13
|
+
# 3. Mount the plugin.
|
|
14
|
+
- id: web
|
|
15
|
+
config:
|
|
16
|
+
searchProvider: freeweb
|
|
17
|
+
fetchProvider: freeweb-fetch
|
|
18
|
+
|
|
19
|
+
- id: web-search-deepseek
|
|
20
|
+
disabled: true
|
|
21
|
+
|
|
22
|
+
- id: web-deepseek-api
|
|
23
|
+
disabled: true
|
|
24
|
+
|
|
25
|
+
- insert:
|
|
26
|
+
- id: freeweb
|
|
27
|
+
name: '@jamolpe88/dsh-free-websearch'
|