Web search skill that runs entirely on the user's machine. Scrapes the public SERPs of DuckDuckGo / Bing / Google directly over HTTP, with automatic engine fallback when one backend is rate-limiting or unreachable.
Each result is a SearchFunctionResultItem with url, name, snippet, host_name, rank, date, favicon, plus three extension fields: source_engine, raw_html, score.
One-line install (ClawHub CLI):
npx clawhub install @Sakurakilove/local-searchManual setup (clone this repo):
# 1. install the one runtime dependency
cd local-search
npm install # or: bun install
# 2. run the example
tsx scripts/web_search.ts
# 3. or use the CLI directly
tsx bin/web-search.ts "artificial intelligence" --num 5
tsx bin/web-search.ts "AI news" --recency-days 1 --json -o ai_news.json- No API key — calls the public search engines directly.
- No network hop — your machine → engine, nothing in between.
- Transparent — every result carries a
source_enginefield so you can see who answered. - Resilient — if DDG is rate-limiting you, the orchestrator silently falls through to Bing. Google is excluded from the auto chain (it almost always returns a JS-required wall from datacenter IPs); pass
--engine googleexplicitly if you need it. - Locale-aware — locale is auto-detected from the query (CJK → zh-CN, kana → ja-JP, hangul → ko-KR, Cyrillic → ru-RU, etc.; Latin/default → en-US). Override with
--locale <BCP-47>if needed. Critical for non-English queries — Bing'sensearch=1flag (English SERP) returns garbage for CJK queries, so we only set it for English locales.
local-search/
├── SKILL.md # full documentation (read this first)
├── package.json # declares cheerio as the only runtime dep
├── tsconfig.json
├── LICENSE.txt
├── bin/
│ └── web-search.ts # CLI: tsx bin/web-search.ts <query> [opts]
├── src/
│ ├── index.ts # SDK exports
│ ├── search.ts # orchestrator with auto-fallback
│ ├── types.ts # SearchFunctionResultItem + options
│ └── engines/
│ ├── _shared.ts # fetch/parse helpers
│ ├── duckduckgo.ts
│ ├── bing.ts
│ ├── google.ts
│ └── index.ts # engine registry + AUTO_ENGINE_ORDER
└── scripts/
└── web_search.ts # quick-start example
import { search } from "local-search";
const outcome = await search("What is the capital of France?", { num: 5 });
if (outcome.success) {
console.log(`Engine: ${outcome.engine} (${outcome.elapsedMs}ms)`);
for (const item of outcome.results) {
console.log(`- ${item.name}\n ${item.url}\n ${item.snippet}\n`);
}
} else {
console.error(outcome.error);
}tsx bin/web-search.ts <query> [options]
Options:
--num, -n <N> Number of results (default: 10)
--engine, -e <id> duckduckgo | bing | google | auto (default: auto)
--recency-days, -r <N> Restrict to results from last N days
--locale <BCP-47> Result locale (default: auto-detect from query;
e.g. en-US, zh-CN, ja-JP, ko-KR, ru-RU)
--timeout <ms> Per-engine timeout (default: 8000)
--json Emit JSON
--output, -o <path> Write JSON to file
--quiet, -q Suppress banner
--help, -h Show help| Engine | Endpoint | API key | Residential IP | Datacenter IP | Recency support |
|---|---|---|---|---|---|
| DuckDuckGo | https://html.duckduckgo.com/html/ (GET) |
none | high | medium (rate-limits under load) | df=d<N> (exact days) |
| Bing | https://www.bing.com/search |
none | high | high | freshness=d1|w1|m1 (bucketed) |
https://www.google.com/search |
none | medium | low (enablejs wall) | tbs=qdr:d|w|m|y (bucketed) |
engine: "auto" (the default) tries them in the order DuckDuckGo → Bing, returning the first non-empty result set. Google is excluded from the auto chain — from datacenter IPs it almost always returns a "please enable JS" wall with zero parseable results, so including it just wastes 8s before failing. From a residential IP you can still pass --engine google explicitly.
The chain is configurable — edit AUTO_ENGINE_ORDER in src/engines/index.ts.
After npm install, run the included e2e test to confirm everything works on your network:
tsx scripts/test.tsExpected output: 12 tests pass. The suite doesn't just check "did the engine return something" — it checks "did at least one result mention a query term" (Test 9 / 10 / 11), which catches the relevance regressions that pure "any-results-returned" tests miss.
The result schema and original skill structure were inspired by z-ai-web-dev-sdk's web-search skill. That project's MIT-licensed design shaped the SearchFunctionResultItem shape used here; all engine backend code in this package is original.
MIT. See LICENSE.txt.