Adds web search, X search, and single-page scraping to text models lacking network capability in dsh. Registers x_search and read_page tools, overriding web_search to provide models with structured, source-attributed evidence.
$ dsh plugin --profile web add github:liustack/modsearchRun the command above in your terminal to install this plugin via the dsh CLI. You can switch Profile in the top-right corner. New to dsh? Read the beginner tutorial
modsearch is DeepSeek Harness (dsh)'s web search plugin, adding web search, X (Twitter) search, and page fetch capabilities to text models that originally have no internet access, allowing the model to obtain structured evidence with source links directly in conversations instead of relying only on training data.
web_search; the tool name and schema seen by the model remain unchanged, citation cards are preserved, while the backend is switched to modsearch's engine chainx_search and focused page reading via read_page, which automatically appear in the model's tool list--source web,x): web and X execute concurrently, results returned separately, each with source, requestedSource, engine, status-u) requires no configuration: built-in local engine uses HTTP direct pull, with SSRF protection and per-hop IP pinning; DNS rebinding cannot bypass itcooldown is enabled by default, temporarily moving quota-exhausted engines to the end of the chaindsh/index.js, uses only Node built-in modules, no bundling)commander (CLI subcommand routing), undici (HTTP client and Agent pool for local fetcher), @biomejs/biome / vite / vitest / typescript (devDependencies only)bin: modsearch → dist/main.js, also exposes modsearch <args> externally) and a dsh cordis plugin. The package.json dsh.bundle.patch points to cordis.patch.yml, which injects @liustack/modsearch into the host dsh's cordis layer and points the web searchProvider to this plugin; the plugin side dsh/index.js exports apply(ctx, config), spawns the same package's dist/main.js to invoke CLI, no PATH lookup, plugin and engine versions are tightly boundsrc/main.ts; DSH plugin entry dsh/index.js; schema generated by CLI callers is in src/schema.ts and dsh/search-schema.json / dsh/fetch-schema.json (synchronized by unit tests)When using dsh daily to converse with DeepSeek/GLM and other text models, need to look up latest news, want to see reactions on X about a certain topic, or have a URL you want to read specific content from — after installing this plugin, you don't need to remember any commands, just chat normally or paste a URL. Also suitable for developers doing keyword research, needing citations with clickable source links, or wanting the model to read a complete blog post or API documentation page.
| Dependency | Minimum Version | Description |
|---|---|---|
| Node.js | >= 22.13 | engines.node in package.json; src/doctor.ts also lists 22.13.0 as the minimum threshold |
| dsh | No specific version declared | Injected into host cordis layer via dsh.bundle.patch (cordis.patch.yml); host must expose ctx.web.registerSearchProvider and ctx.tools.register; package.json declares no peerDependencies |
| Platform | macOS / Windows / Linux | package.json doesn't restrict os/cpu; Antigravity CLI / Grok Build binaries are invoked as subprocesses, target platform follows respective official installers |
| Native modules | None | No node-gyp dependencies introduced; HTTP fetching uses undici's built-in Agent, process communication uses Node's built-in node:child_process |
| External accounts | At least one for web search | Default Antigravity CLI (no key required, browser login once); can alternatively configure any of Tavily/Exa/Firecrawl with free keys; X search requires Grok Build + SuperGrok or X Premium subscription |
dsh plugin --profile web add github:liustack/modsearch
Config file is at ~/.modsearch/config.json (written as 0600, keys automatically masked when rendered via modsearch config show). Priority: CLI arguments > environment variables > config file > built-in defaults. File is optional; it runs without it but cannot fix engines or customize endpoints.
| Config | Type | Description | Default |
|---|---|---|---|
engine | string | Preferred engine for web search; empty string means auto-select based on machine conditions. Valid values: antigravity-cli, tavily, exa, firecrawl; aliases agy/antigravity/grok/http/direct will be normalized to canonical names | empty (auto) |
cooldown | on / off | Quota cooldown failover switch; when enabled, moves quota-exhausted engines to end of chain and writes state file; when disabled, completely skips reading/writing state | on |
allowPrivateNetwork | boolean | Whether to allow local fetcher to reach reserved and internal network address ranges (for VPN scenarios); Firecrawl fetching is always unaffected by this switch | false |
engines.<name>.apiKey | string | API key for that engine, <name> ∈ tavily/exa/firecrawl; same-named environment variables (TAVILY_API_KEY/EXA_API_KEY/FIRECRAWL_API_KEY) override file values | none |
engines.<name>.baseURL | string | Point that engine to a compatible third-party gateway/self-hosted endpoint; must be a full http(s) URL, empty string cancels override; <name> ∈ tavily/exa/firecrawl, corresponding env vars TAVILY_BASE_URL/EXA_BASE_URL/FIRECRAWL_BASE_URL | each engine's official endpoint |
engines.<name>.bin | string | Executable path for that engine; <name> ∈ antigravity-cli (default agy) / grok-cli (default grok) | each engine's default |
engines.<name>.model | string | Model name used by that engine; currently applies to antigravity-cli, default gemini-3.6-flash-low | each engine's default |
TAVILY_API_KEY / EXA_API_KEY / FIRECRAWL_API_KEY | environment variables | Environment variable form of same-named apiKey, higher priority than config file | none |
MODSEARCH_DSH_CLI | environment variables | CLI path override used by dsh plugin side when running tests (see src/dshPlugin.test.ts) | none |
Subcommands: modsearch config init creates skeleton, modsearch config set <key> <value> writes values, modsearch config show renders effective config (source tags + key masking), modsearch doctor [--json] health check, modsearch state clear clears all cooldown state.
Q: Can modsearch be used directly after installation, or do I need to configure an API key?
A: Page fetching (-u) works with zero configuration because the built-in local engine is always available; web search (-q) requires at least one search engine. It defaults to Antigravity CLI, which requires no key and only needs one browser login; the three backup engines (Tavily/Exa/Firecrawl) all have free tiers without requiring a card. When nothing is configured, error messages will list all available activation methods.
Q: What changes will appear in dsh after installation?
A: The native web_search tool still uses the same name, the model sees the native schema and citation cards, but the backend is replaced with modsearch's engine chain; additionally, two tools that dsh doesn't have are added: x_search for searching X and read_page for focused page reading, appearing directly in the model's available tool list without manual triggering.
Q: Which account's quota is deducted during search? Which engine processes it?
A: When not forced with -e, all ready engines form a failover chain by role priority. The results[i].engine in results indicates the actual responder, attempts lists the attempt order, and warnings write reasons when an engine fails, gets demoted, or gets cooled down — never silently deducting from another account.
Q: Why does the same X query sometimes return non-X content?
A: When Grok Build is not installed, not logged in, or fails this round, the router automatically falls back to the web search engine to answer, marking source as web, requestedSource as x, status as degraded, and writing the fallback reason in warnings; it won't pretend to be real X results. When --source web,x requires both simultaneously and X is unreachable, a separate empty entry with status: "unavailable" is returned, explicit but as a placeholder.
Q: How do I know what's actually configured on my machine and what's wrong?
A: Run modsearch doctor, which performs purely local checks, doesn't consume quota or send network requests. It reports Node version, readiness status and reasons for each engine, config source and permissions, private network switch, cooldown state, and provides a copy-pasteable fix command for each unready engine; add --json for machine-readable output for script processing.
Q: What if Antigravity CLI says quota is exhausted?
A: Quota cooldown failover is enabled by default: this round moves antigravity-cli to the end of the chain, next time it directly uses other engines first. Wait for weekly reset (error message will state Resets in …) or add a keyed engine (any one of Tavily/Exa/Firecrawl) to recover. modsearch state clear can immediately clear all cooldown.
Q: How is it handled when VPN maps target address to an internal IP?
A: Private/reserved address ranges are rejected by default (SSRF protection). You can allow once with --allow-private-network, or set globally with modsearch config set allowPrivateNetwork true; Firecrawl fetching always rejects private/reserved addresses even with the switch on, preventing internal hostnames from being sent to third-party clouds.
Q: What remains after uninstallation?
A: Just uninstall this plugin. What stays on the machine is only ~/.modsearch/config.json (your explicitly configured keys and endpoints) and ~/.modsearch/state.json (cooldown state); no hooks are left, nor does it modify the host's own configuration.
Introductory — one dsh installation command plus one default engine (Antigravity CLI requires no key, browser login once) lets you use all three capabilities; reading the config manual is only needed for "advanced configuration" (customizing third-party endpoints, switching engines, disabling cooldown).
uncertainty field explicitly states this, results should be paraphrased accordingly rather than saying the page is emptyallowPrivateNetwork enabled (prevents internal hostnames from being sent to third-party cloud)-e/--engine is hard forced: uses only this one engine, doesn't go to fallback or local fallback, fails with error if unable, won't secretly switch to another engine to deduct from another accountprovider/providers and search/fetch/social three-role structure are automatically migrated to new engine + engines form, no manual file editing needed--json-schema; HTTP engines (Tavily/Exa/Firecrawl) are guaranteed structure by their servers, but CLI forcibly strips source/requestedSource/engine/model/status/warnings/attempts/durationSeconds from engine-returned fields to prevent forgery
Give the web to any model without it: search, X, and any page.
🥇 The most powerful web search plugin for DeepSeek Harness (dsh) 🥇
Engines: Antigravity CLI (free, default) · Tavily · Exa · Firecrawl · Grok (X) · local, with automatic failover
简体中文 · Troubleshooting · Configuration · Output contract · Security · ModLens (vision)
Models like DeepSeek-V4-Flash have no web access, or a weak one. ModSearch is a plug-in that greatly strengthens the model's web search, X search, and single-page fetch.
Something broken, or something missing? Open an issue. For everything else, come find me on X: @liustack. What you built with it, which harness you are on, what should come next. New releases land there first, and a proper community space is on the way.
npx -y @deepseek-ai/dsh plugin --profile web add @liustack/modsearch@5.4.3, and dsh's built-in web_search runs on the modsearch engine chain with no API key, keeping its native citation cards. Two tools dsh does not have land beside it: x_search for X (Twitter) and read_page for focused single-page reading. Updating is the same command again. The version is named rather than @latest because pnpm 11 holds back releases published in the last 24 hours and resolves the tag against the older versions that remain. Details in harness setup.Any one of these makes search work. Configure with one command each, keys are stored in ~/.modsearch/config.json (0600, masked when shown):
| Engine | Does | Free tier | Turn it on |
|---|---|---|---|
| Antigravity CLI | web search + page fetch | free, browser sign-in | install agy and sign in |
| Tavily | web search | 1,000 credits/month, no card | modsearch config set tavily.apiKey <key> |
| Exa | web search | $10/month recurring credit (~1,400 searches), no card | modsearch config set exa.apiKey <key> |
| Firecrawl | web search + page fetch | 1,000 credits/month, search even works keyless | modsearch config set firecrawl.apiKey <key> |
| Grok Build | X (Twitter) search | rides SuperGrok or X Premium | install grok and sign in |
| local | page fetch | built in, nothing to install | nothing |
Keys can also come from the environment (TAVILY_API_KEY, EXA_API_KEY, FIRECRAWL_API_KEY). Multiple engines configured means automatic failover, best first. Using a Tavily-, Exa-, or Firecrawl-compatible third-party or self-hosted endpoint? Point the engine at it: modsearch config set tavily.baseURL <url>. Every knob, engine by engine, is in the configuration guide.
Step 1, set up a search engine (the only part that needs your hands). The default engine, Antigravity CLI, requires a browser sign-in that only you can complete:
curl -fsSL https://antigravity.google/cli/install.sh | bash
agy # sign in, then exit
Prefer not to install it? Register a free key with Tavily, Exa, or Firecrawl instead (Tavily 1,000 credits a month, Exa about 1,400 searches a month, Firecrawl 1,000 credits a month, no card required by any of them).
Step 2, hand the rest to your AI. Send it this line, along with the key if you chose one:
Install and configure the modsearch skill following https://github.com/liustack/modsearch/blob/main/INSTALL.md, then run the health check and tell me the result.
Once installed, just chat. Ask anything that needs checking, or paste a URL, and the skill triggers on its own: it picks an engine, runs the search or fetch, and the answer comes back with sources.
Both screenshots are unedited runs from the Codex desktop app, driving a DeepSeek-V4-Flash that has no web access of its own.
Give it a blog link and ask what the post says. Twenty-five seconds later: a structured summary of the whole post, with no browser involved.

Give it no target at all, just "anything interesting in AI today?". Thirty-six seconds later: six sourced stories, with a closing note on which details came from aggregation and deserve a second look. The note comes from the uncertainty field.

| Doc | Read it when |
|---|---|
| INSTALL.md | Installing the skill step by step (written for an agent) |
| CLI manual | The CLI the skill drives: flags, config, doctor |
| Troubleshooting | A command failed and the message needs decoding |
| Configuration | Setting a key, switching engines, fixing config |
| Output contract | Parsing the JSON or building on it |
| Harness setup | Wiring it into Codex, Claude Code, OpenCode, or Pi |
| Security | SSRF guards, DNS-rebinding protection, untrusted input |
| CHANGELOG | Finding what changed in a version |
ModSearch does not accept pull requests. The project is maintained by a single author who reviews every line, which is a deliberate choice for reliability. Two effective ways to contribute:
This project runs on LIUSTACK Skills: shaping before you build, coding while you build, dig when it breaks, snapshot when you hand off. Lighter than Superpowers, and stronger.
npx -y skills add liustack/liustack -g
⭐ If it helps, star ModSearch and liustack. Stars are how the next developer finds them.
ModSearch is MIT-licensed, so use is not restricted. The author gives no warranty and no endorsement for any particular use, commercial or otherwise. The upstream engines it drives (Antigravity CLI, Tavily, Exa, Firecrawl, Grok Build) each carry their own terms and quotas, and complying with them is the user's responsibility.
MIT