Skip to main content

modsearch

190Stars9Forks1Issues0Watchers

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.

Evidence4/5methodologySourceInstallMaintenanceDSH versionSecurity scan
Machine-auditedInstall commandRepo verifieddsh-plugin topicLicenseREADMEAI wiki
Language
TypeScript
License
MIT
Branch
main
agent-skillsagentic-workflowclaude-codeclaude-skillscodexcordisdeepseekdeepseek-harness

Install

cmdweb profile
$ dsh plugin --profile web add @liustack/modsearch

Run 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

Install via your agent

Install the DeepSeek Harness plugin liustack/modsearch for me: review the repository at https://github.com/liustack/modsearch first, then run the install command and verify the plugin loads successfully.

Paste this instruction to the DSH Web GUI assistant — it will install and verify for you.

One-Line Pitch

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.

Core Capabilities

  • In the dsh host, takes over the search backend of dsh's native 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 chain
  • Registers two additional tools that dsh doesn't have: X (Twitter) search via x_search and focused page reading via read_page, which automatically appear in the model's tool list
  • Runs multiple data sources simultaneously (--source web,x): web and X execute concurrently, results returned separately, each with source, requestedSource, engine, status
  • Page fetching (-u) requires no configuration: built-in local engine uses HTTP direct pull, with SSRF protection and per-hop IP pinning; DNS rebinding cannot bypass it
  • Engines are organized by role with priority (antigravity-cli → tavily/exa/firecrawl; X uses grok-cli; fetch falls back to local); quota exhaustion automatically triggers failover; cooldown is enabled by default, temporarily moving quota-exhausted engines to the end of the chain

Technical Implementation

  • Language: TypeScript (Node.js ESM) + pure JS dsh plugin side (dsh/index.js, uses only Node built-in modules, no bundling)
  • Key dependencies: commander (CLI subcommand routing), undici (HTTP client and Agent pool for local fetcher), @biomejs/biome / vite / vitest / typescript (devDependencies only)
  • Architecture pattern: Dual-form package — the same npm package is both a CLI (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 bound
  • Entry files: CLI entry src/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)

Use Cases

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.

Prerequisites and Compatibility

DependencyMinimum VersionDescription
Node.js>= 22.13engines.node in package.json; src/doctor.ts also lists 22.13.0 as the minimum threshold
dshNo specific version declaredInjected 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
PlatformmacOS / Windows / Linuxpackage.json doesn't restrict os/cpu; Antigravity CLI / Grok Build binaries are invoked as subprocesses, target platform follows respective official installers
Native modulesNoneNo node-gyp dependencies introduced; HTTP fetching uses undici's built-in Agent, process communication uses Node's built-in node:child_process
External accountsAt least one for web searchDefault 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

Installation

dsh plugin --profile web add github:liustack/modsearch

Configuration Options

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.

ConfigTypeDescriptionDefault
enginestringPreferred 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 namesempty (auto)
cooldownon / offQuota cooldown failover switch; when enabled, moves quota-exhausted engines to end of chain and writes state file; when disabled, completely skips reading/writing stateon
allowPrivateNetworkbooleanWhether to allow local fetcher to reach reserved and internal network address ranges (for VPN scenarios); Firecrawl fetching is always unaffected by this switchfalse
engines.<name>.apiKeystringAPI key for that engine, <name> ∈ tavily/exa/firecrawl; same-named environment variables (TAVILY_API_KEY/EXA_API_KEY/FIRECRAWL_API_KEY) override file valuesnone
engines.<name>.baseURLstringPoint 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_URLeach engine's official endpoint
engines.<name>.binstringExecutable path for that engine; <name> ∈ antigravity-cli (default agy) / grok-cli (default grok)each engine's default
engines.<name>.modelstringModel name used by that engine; currently applies to antigravity-cli, default gemini-3.6-flash-loweach engine's default
TAVILY_API_KEY / EXA_API_KEY / FIRECRAWL_API_KEYenvironment variablesEnvironment variable form of same-named apiKey, higher priority than config filenone
MODSEARCH_DSH_CLIenvironment variablesCLI 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.

FAQ

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.

Learning Curve

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).

Known Issues and Limitations

  • Local fetcher doesn't execute JavaScript; pages rendered by pure JS frameworks come back very thin; the returned uncertainty field explicitly states this, results should be paraphrased accordingly rather than saying the page is empty
  • Quota is shared by role bucket: Antigravity CLI's free tier is weekly quota, shared with Antigravity desktop App and SDK; concurrent sub-agents can exhaust it quickly
  • Firecrawl fetching always rejects private/reserved addresses even with allowPrivateNetwork enabled (prevents internal hostnames from being sent to third-party cloud)
  • Explicit -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 account
  • Old config files with provider/providers and search/fetch/social three-role structure are automatically migrated to new engine + engines form, no manual file editing needed
  • For schema-enforced subprocess engines (Antigravity CLI, etc.), output is strictly constrained via --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

Read the usage guide →

Install steps, key points, FAQ and compatibility for this plugin — auto-derived from indexed fields.

Listing badge

Listed on deepseek-plugin.org
[![Listed on deepseek-plugin.org](https://img.shields.io/badge/listed_on-deepseek--plugin.org-007EC6)](https://deepseek-plugin.org/plugins/liustack/modsearch)

Paste this markdown into your GitHub README to link back to this listing. The badge only states the listing — not a security endorsement.

← Back to plugin directory