searxng-web-search
Search the web using a self-hosted SearXNG metasearch engine. Use when the user asks to search the web, find information online, look up recent news, research a topic, or needs current data from the internet. Also use when the agent needs to gather external context to answer a question. Requires a running SearXNG instance with JSON API enabled.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install openclaw-skills-searxng-web-search
Repository
Skill path: skills/gangtao/searxng-web-search
Search the web using a self-hosted SearXNG metasearch engine. Use when the user asks to search the web, find information online, look up recent news, research a topic, or needs current data from the internet. Also use when the agent needs to gather external context to answer a question. Requires a running SearXNG instance with JSON API enabled.
Open repositoryBest for
Primary workflow: Research & Ops.
Technical facets: Full Stack, Backend, Data / AI.
Target audience: everyone.
License: Apache-2.0.
Original source
Catalog source: SkillHub Club.
Repository owner: openclaw.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install searxng-web-search into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/openclaw/skills before adding searxng-web-search to shared team environments
- Use searxng-web-search for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: searxng-web-search
description: >
Search the web using a self-hosted SearXNG metasearch engine. Use when the user asks
to search the web, find information online, look up recent news, research a topic,
or needs current data from the internet. Also use when the agent needs to gather
external context to answer a question. Requires a running SearXNG instance with
JSON API enabled.
license: Apache-2.0
compatibility: >
Requires Python 3.9+, requests library, and a running SearXNG instance with
format: [html, json] enabled in settings.yml. Network access to the SearXNG endpoint.
metadata:
author: timeplus-io
version: "1.0.0"
tags: search web searxng metasearch privacy
category: web
---
# SearXNG Web Search
Privacy-respecting web search skill powered by SearXNG, a free metasearch engine
that aggregates results from 243+ search services without tracking users.
Rewritten from PulseBot's built-in web search skill to use SearXNG as the backend,
packaged as a standalone agentskills.io skill.
## Prerequisites
1. A running SearXNG instance (self-hosted or accessible endpoint)
2. JSON format must be enabled in your SearXNG `settings.yml`:
```yaml
search:
formats:
- html
- json
```
3. Python `requests` library installed
## Configuration
The skill uses environment variables for configuration:
| Variable | Default | Description |
|---|---|---|
| `SEARXNG_BASE_URL` | `http://localhost:8080` | SearXNG instance URL |
| `SEARXNG_MAX_RESULTS` | `10` | Maximum results to return |
| `SEARXNG_LANGUAGE` | `all` | Default search language (e.g. `en`, `zh`, `all`) |
| `SEARXNG_SAFESEARCH` | `0` | Safe search level: 0=off, 1=moderate, 2=strict |
| `SEARXNG_TIMEOUT` | `15` | Request timeout in seconds |
| `SEARXNG_CATEGORIES` | `general` | Default categories (comma-separated) |
## Usage
Run the search script:
```bash
python scripts/searxng_search.py "your search query"
```
With options:
```bash
python scripts/searxng_search.py "latest AI news" \
--categories news \
--language en \
--time-range day \
--max-results 5
```
### Output Format
The script outputs JSON to stdout with the following structure:
```json
{
"query": "search query",
"results": [
{
"title": "Result Title",
"url": "https://example.com",
"snippet": "Text snippet from the page...",
"engines": ["google", "bing"],
"score": 9.0,
"category": "general",
"published_date": "2025-01-01T00:00:00"
}
],
"suggestions": ["related query 1", "related query 2"],
"answers": ["direct answer if available"],
"total_results": 10,
"error": null
}
```
If an error occurs, `results` will be empty and `error` will contain the message.
### As a Python Module
You can also import and use the search function directly:
```python
from scripts.searxng_search import SearXNGSearchTool
tool = SearXNGSearchTool(base_url="http://localhost:8080")
results = tool.search("quantum computing", categories="science,it", max_results=5)
for r in results["results"]:
print(f"[{r['title']}]({r['url']})")
print(f" {r['snippet']}")
```
## Integration with PulseBot
To register this skill in PulseBot, place it under `skills/` and PulseBot
will discover it via SKILL.md frontmatter. The Python script can also be
called as a tool function by wrapping it:
```python
from skills.searxng_web_search.scripts.searxng_search import SearXNGSearchTool
tool = SearXNGSearchTool()
def web_search(query: str, categories: str = "general", max_results: int = 10) -> str:
"""Search the web using SearXNG. Returns JSON results."""
result = tool.search(query, categories=categories, max_results=max_results)
return json.dumps(result, indent=2)
```
## Edge Cases
- If SearXNG is unreachable, the script returns a structured error with `error` field set
- If no results are found, `results` is an empty list (not an error)
- Some engines may be unresponsive; check `unresponsive_engines` in verbose mode
- Rate-limited public instances may return 429; prefer self-hosted instances
- Queries with special characters are URL-encoded automatically
## SearXNG Setup (Quick Start)
For details on deploying SearXNG, see [references/REFERENCE.md](references/REFERENCE.md).
```bash
docker run -d --name searxng -p 8080:8080 \
-v "$(pwd)/searxng:/etc/searxng" \
searxng/searxng:latest
```
Then edit `/etc/searxng/settings.yml` to add `json` to `search.formats`.
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### references/REFERENCE.md
```markdown
# SearXNG Reference Guide
## Quick Deploy with Docker
```bash
# Create config directory
mkdir -p searxng
# Create settings.yml (REQUIRED: enable JSON format)
cat > searxng/settings.yml << 'EOF'
use_default_settings: true
server:
secret_key: "$(openssl rand -hex 32)"
bind_address: "0.0.0.0"
port: 8080
limiter: false
image_proxy: true
search:
safe_search: 0
default_lang: "all"
formats:
- html
- json
EOF
# Run SearXNG
docker run -d --name searxng \
-p 8080:8080 \
-v "$(pwd)/searxng:/etc/searxng" \
--restart unless-stopped \
searxng/searxng:latest
```
## Docker Compose
```yaml
version: "3.8"
services:
searxng:
image: searxng/searxng:latest
container_name: searxng
ports:
- "8080:8080"
volumes:
- ./searxng:/etc/searxng:rw
environment:
- SEARXNG_BASE_URL=http://localhost:8080/
restart: unless-stopped
```
## Verify JSON API
```bash
# Should return JSON results (not HTML or 403)
curl -s 'http://localhost:8080/search?q=test&format=json' | python3 -m json.tool | head -20
```
If you get 403 Forbidden, JSON format is not enabled in `settings.yml`.
## API Endpoint Reference
### `GET /search`
| Parameter | Required | Values | Description |
|---|---|---|---|
| `q` | Yes | string | Search query |
| `format` | No | `json`, `csv`, `rss` | Response format (default: `html`) |
| `categories` | No | comma-separated | `general`, `images`, `videos`, `news`, `map`, `music`, `it`, `science`, `files`, `social media` |
| `engines` | No | comma-separated | Specific engines (e.g. `google,bing`) |
| `language` | No | lang code | `en`, `zh`, `de`, `fr`, `all`, etc. |
| `pageno` | No | integer | Page number (default: 1) |
| `time_range` | No | `day`, `month`, `year` | Time filter |
| `safesearch` | No | `0`, `1`, `2` | 0=off, 1=moderate, 2=strict |
### `GET /config`
Returns instance configuration (engines, categories, supported languages).
## Response Fields
Each result in `results[]` contains:
| Field | Type | Description |
|---|---|---|
| `title` | string | Result title |
| `url` | string | Result URL |
| `content` | string | Text snippet |
| `engine` | string | Primary source engine |
| `engines` | string[] | All engines returning this result |
| `score` | float | Aggregated relevance score |
| `category` | string | Result category |
| `positions` | int[] | Rankings in source engines |
| `publishedDate` | string | ISO date (if available) |
| `thumbnail` | string | Thumbnail URL (if available) |
| `img_src` | string | Image source URL (image results) |
Top-level response also includes:
| Field | Type | Description |
|---|---|---|
| `query` | string | Original query |
| `suggestions` | string[] | Related search suggestions |
| `answers` | string[] | Direct answers |
| `corrections` | string[] | Spelling corrections |
| `infoboxes` | object[] | Knowledge panel data |
| `unresponsive_engines` | array[] | Engines that failed `[[name, reason]]` |
## Common Engine Names
General: `google`, `bing`, `duckduckgo`, `brave`, `startpage`, `qwant`, `mojeek`
News: `google news`, `bing news`, `yahoo news`, `wikinews`
IT: `github`, `stackoverflow`, `npm`, `pypi`, `crates.io`, `dockerhub`
Science: `arxiv`, `google scholar`, `semantic scholar`, `pubmed`
Images: `google images`, `bing images`, `flickr`, `unsplash`
## Troubleshooting
**403 Forbidden**: Add `json` to `search.formats` in `settings.yml`.
**429 Rate Limited**: Disable the limiter for private instances (`limiter: false`),
or whitelist your IP in `limiter.toml` under `pass_ip`.
**Empty results**: Some engines may be rate-limited. Check `unresponsive_engines`
in the response. Try specifying different engines with `--engines`.
**Slow responses**: SearXNG queries multiple engines in parallel. Reduce latency
by limiting categories or specifying fewer engines.
```
### scripts/searxng_search.py
```python
#!/usr/bin/env python3
"""
SearXNG Web Search Tool
A privacy-respecting web search tool powered by SearXNG metasearch engine.
Rewritten from PulseBot's built-in web_search skill for the agentskills.io standard.
Usage:
python searxng_search.py "search query" [options]
Environment Variables:
SEARXNG_BASE_URL - SearXNG instance URL (default: http://localhost:8080)
SEARXNG_MAX_RESULTS - Maximum results to return (default: 10)
SEARXNG_LANGUAGE - Default language (default: all)
SEARXNG_SAFESEARCH - Safe search level 0/1/2 (default: 0)
SEARXNG_TIMEOUT - Request timeout in seconds (default: 15)
SEARXNG_CATEGORIES - Default categories (default: general)
Copyright 2024-2026 Timeplus, Inc. Apache-2.0 License.
"""
import argparse
import json
import logging
import os
import sys
from datetime import datetime
from typing import Any, Optional
from urllib.parse import urljoin
try:
import requests
except ImportError:
print(
json.dumps(
{
"query": "",
"results": [],
"suggestions": [],
"answers": [],
"total_results": 0,
"error": "Missing dependency: pip install requests",
}
)
)
sys.exit(1)
logger = logging.getLogger(__name__)
class SearXNGSearchTool:
"""
SearXNG-based web search tool.
Provides structured web search results from a self-hosted SearXNG
metasearch engine instance. SearXNG aggregates results from 243+
search services (Google, Bing, DuckDuckGo, Brave, etc.) while
preserving user privacy — no tracking, no profiling.
This class is designed to be used both as a standalone CLI tool
and as an importable module for agent frameworks like PulseBot.
"""
def __init__(
self,
base_url: Optional[str] = None,
max_results: int = 0,
language: Optional[str] = None,
safesearch: Optional[int] = None,
timeout: Optional[int] = None,
categories: Optional[str] = None,
):
self.base_url = (
base_url
or os.environ.get("SEARXNG_BASE_URL", "http://localhost:8080")
).rstrip("/")
self.max_results = max_results or int(
os.environ.get("SEARXNG_MAX_RESULTS", "10")
)
self.language = language or os.environ.get("SEARXNG_LANGUAGE", "all")
self.safesearch = (
safesearch
if safesearch is not None
else int(os.environ.get("SEARXNG_SAFESEARCH", "0"))
)
self.timeout = timeout or int(os.environ.get("SEARXNG_TIMEOUT", "15"))
self.categories = categories or os.environ.get(
"SEARXNG_CATEGORIES", "general"
)
self._session = requests.Session()
self._session.headers.update(
{
"Accept": "application/json",
"User-Agent": "PulseBot-SearXNG-Skill/1.0",
}
)
def search(
self,
query: str,
categories: Optional[str] = None,
engines: Optional[str] = None,
language: Optional[str] = None,
pageno: int = 1,
time_range: Optional[str] = None,
safesearch: Optional[int] = None,
max_results: Optional[int] = None,
) -> dict[str, Any]:
"""
Perform a web search via SearXNG.
Args:
query: Search query string.
categories: Comma-separated categories
(general, images, videos, news, map, music, it, science, files, social media).
engines: Comma-separated engine names (e.g. "google,bing,duckduckgo").
language: Language code (e.g. "en", "zh", "de", "all").
pageno: Page number for pagination (default: 1).
time_range: Time filter — "day", "month", or "year".
safesearch: Override safe search level (0, 1, 2).
max_results: Override maximum number of results to return.
Returns:
dict with keys: query, results, suggestions, answers, total_results, error
"""
if not query or not query.strip():
return self._make_response(query="", error="Empty search query")
effective_max = max_results or self.max_results
params = {
"q": query.strip(),
"format": "json",
"categories": categories or self.categories,
"language": language or self.language,
"safesearch": safesearch if safesearch is not None else self.safesearch,
"pageno": pageno,
}
if engines:
params["engines"] = engines
if time_range and time_range in ("day", "month", "year"):
params["time_range"] = time_range
search_url = urljoin(self.base_url + "/", "search")
try:
response = self._session.get(
search_url, params=params, timeout=self.timeout
)
response.raise_for_status()
except requests.exceptions.ConnectionError:
return self._make_response(
query=query,
error=f"Cannot connect to SearXNG at {self.base_url}. "
f"Ensure the instance is running and accessible.",
)
except requests.exceptions.Timeout:
return self._make_response(
query=query,
error=f"Request to SearXNG timed out after {self.timeout}s.",
)
except requests.exceptions.HTTPError as e:
status = e.response.status_code if e.response is not None else "unknown"
if status == 403:
return self._make_response(
query=query,
error="SearXNG returned 403 Forbidden. "
"Ensure JSON format is enabled in settings.yml: "
"search.formats: [html, json]",
)
if status == 429:
return self._make_response(
query=query,
error="Rate limited by SearXNG. Try again later or "
"use a self-hosted instance with limiter disabled.",
)
return self._make_response(
query=query,
error=f"SearXNG HTTP error {status}: {e}",
)
except requests.exceptions.RequestException as e:
return self._make_response(
query=query,
error=f"Request failed: {e}",
)
try:
data = response.json()
except (json.JSONDecodeError, ValueError) as e:
return self._make_response(
query=query,
error=f"Invalid JSON response from SearXNG: {e}",
)
raw_results = data.get("results", [])
results = []
for r in raw_results[:effective_max]:
result = {
"title": r.get("title", ""),
"url": r.get("url", ""),
"snippet": r.get("content", ""),
"engines": r.get("engines", []),
"score": r.get("score", 0),
"category": r.get("category", ""),
}
if r.get("publishedDate"):
result["published_date"] = r["publishedDate"]
if r.get("thumbnail"):
result["thumbnail"] = r["thumbnail"]
if r.get("img_src"):
result["image_url"] = r["img_src"]
results.append(result)
# Log unresponsive engines for debugging
unresponsive = data.get("unresponsive_engines", [])
if unresponsive:
engine_issues = [f"{eng[0]}: {eng[1]}" for eng in unresponsive if len(eng) >= 2]
if engine_issues:
logger.warning("Unresponsive engines: %s", "; ".join(engine_issues))
return self._make_response(
query=query,
results=results,
suggestions=data.get("suggestions", []),
answers=data.get("answers", []),
total_results=len(results),
)
def search_news(
self,
query: str,
language: Optional[str] = None,
time_range: str = "day",
max_results: Optional[int] = None,
) -> dict[str, Any]:
"""Convenience method for news-specific searches."""
return self.search(
query=query,
categories="news",
language=language,
time_range=time_range,
max_results=max_results,
)
def search_images(
self,
query: str,
language: Optional[str] = None,
max_results: Optional[int] = None,
) -> dict[str, Any]:
"""Convenience method for image searches."""
return self.search(
query=query,
categories="images",
language=language,
max_results=max_results,
)
def search_it(
self,
query: str,
language: Optional[str] = None,
max_results: Optional[int] = None,
) -> dict[str, Any]:
"""Convenience method for IT/tech-specific searches."""
return self.search(
query=query,
categories="it",
language=language,
max_results=max_results,
)
def check_health(self) -> dict[str, Any]:
"""
Check if the SearXNG instance is reachable and JSON API is enabled.
Returns:
dict with keys: healthy (bool), base_url, version, error
"""
config_url = urljoin(self.base_url + "/", "config")
try:
response = self._session.get(config_url, timeout=5)
response.raise_for_status()
config = response.json()
return {
"healthy": True,
"base_url": self.base_url,
"version": config.get("version", "unknown"),
"engines_count": len(config.get("engines", [])),
"categories": config.get("categories", []),
"error": None,
}
except requests.exceptions.ConnectionError:
return {
"healthy": False,
"base_url": self.base_url,
"error": f"Cannot connect to {self.base_url}",
}
except Exception as e:
return {
"healthy": False,
"base_url": self.base_url,
"error": str(e),
}
def format_results_as_text(self, response: dict[str, Any]) -> str:
"""
Format search results as readable text for LLM consumption.
Args:
response: Search response dict from self.search()
Returns:
Formatted string with numbered results.
"""
if response.get("error"):
return f"Search error: {response['error']}"
results = response.get("results", [])
if not results:
return f"No results found for: {response.get('query', '')}"
lines = [f"Search results for: {response['query']}\n"]
for i, r in enumerate(results, 1):
lines.append(f"{i}. {r['title']}")
lines.append(f" URL: {r['url']}")
if r.get("snippet"):
lines.append(f" {r['snippet']}")
if r.get("published_date"):
lines.append(f" Published: {r['published_date']}")
engines_str = ", ".join(r.get("engines", []))
if engines_str:
lines.append(f" Sources: {engines_str}")
lines.append("")
suggestions = response.get("suggestions", [])
if suggestions:
lines.append(f"Related searches: {', '.join(suggestions)}")
answers = response.get("answers", [])
if answers:
lines.insert(1, f"Direct answer: {answers[0]}\n")
return "\n".join(lines)
@staticmethod
def _make_response(
query: str = "",
results: Optional[list] = None,
suggestions: Optional[list] = None,
answers: Optional[list] = None,
total_results: int = 0,
error: Optional[str] = None,
) -> dict[str, Any]:
"""Create a standardized response dict."""
return {
"query": query,
"results": results or [],
"suggestions": suggestions or [],
"answers": answers or [],
"total_results": total_results,
"error": error,
}
def main():
"""CLI entry point."""
parser = argparse.ArgumentParser(
description="Search the web using SearXNG metasearch engine.",
epilog="Environment variables: SEARXNG_BASE_URL, SEARXNG_MAX_RESULTS, "
"SEARXNG_LANGUAGE, SEARXNG_SAFESEARCH, SEARXNG_TIMEOUT, SEARXNG_CATEGORIES",
)
parser.add_argument("query", nargs="?", help="Search query")
parser.add_argument(
"--base-url",
default=None,
help="SearXNG instance URL (default: $SEARXNG_BASE_URL or http://localhost:8080)",
)
parser.add_argument(
"--categories",
default=None,
help="Search categories: general, images, videos, news, map, music, it, science, files, social media",
)
parser.add_argument(
"--engines",
default=None,
help="Comma-separated engine names (e.g. google,bing,duckduckgo)",
)
parser.add_argument(
"--language",
default=None,
help="Search language (e.g. en, zh, de, all)",
)
parser.add_argument(
"--time-range",
choices=["day", "month", "year"],
default=None,
help="Time range filter",
)
parser.add_argument(
"--safesearch",
type=int,
choices=[0, 1, 2],
default=None,
help="Safe search level: 0=off, 1=moderate, 2=strict",
)
parser.add_argument(
"--max-results",
type=int,
default=None,
help="Maximum number of results to return",
)
parser.add_argument(
"--page",
type=int,
default=1,
help="Page number for pagination (default: 1)",
)
parser.add_argument(
"--text",
action="store_true",
help="Output as human-readable text instead of JSON",
)
parser.add_argument(
"--health",
action="store_true",
help="Check SearXNG instance health and exit",
)
parser.add_argument(
"--verbose",
action="store_true",
help="Enable verbose logging",
)
args = parser.parse_args()
if args.verbose:
logging.basicConfig(level=logging.DEBUG, format="%(levelname)s: %(message)s")
else:
logging.basicConfig(level=logging.WARNING)
tool = SearXNGSearchTool(
base_url=args.base_url,
categories=args.categories,
language=args.language,
safesearch=args.safesearch,
)
# Health check mode
if args.health:
health = tool.check_health()
print(json.dumps(health, indent=2))
sys.exit(0 if health["healthy"] else 1)
# Search mode requires a query
if not args.query:
parser.error("Search query is required (or use --health)")
result = tool.search(
query=args.query,
categories=args.categories,
engines=args.engines,
language=args.language,
pageno=args.page,
time_range=args.time_range,
safesearch=args.safesearch,
max_results=args.max_results,
)
if args.text:
print(tool.format_results_as_text(result))
else:
print(json.dumps(result, indent=2, ensure_ascii=False))
# Exit with non-zero if there was an error
if result.get("error"):
sys.exit(1)
if __name__ == "__main__":
main()
```
---
## Skill Companion Files
> Additional files collected from the skill directory layout.
### README.md
```markdown
# searxng-web-search
[](https://agentskills.io)
[](LICENSE)
[](https://clawhub.ai)
A privacy-respecting web search skill for AI agents, powered by [SearXNG](https://docs.searxng.org/) metasearch engine. Follows the [agentskills.io](https://agentskills.io) standard.
Rewritten from [PulseBot](https://github.com/timeplus-io/PulseBot)'s built-in `web_search` skill to use SearXNG as a self-hosted, privacy-first search backend.
## Features
- **Privacy-first**: SearXNG aggregates 243+ search engines without tracking users
- **Self-hosted**: Full control over your search infrastructure
- **Multi-category**: Search general web, news, images, IT, science, and more
- **Structured output**: JSON results with title, URL, snippet, score, and engine attribution
- **Agent-ready**: Works with PulseBot, Claude Code, OpenClaw, GitHub Copilot, and any agentskills.io-compatible agent
- **Robust error handling**: Graceful failures with structured error responses
## Quick Start
### 1. Install the Skill
**Via ClawHub CLI:**
```bash
npm i -g clawhub
clawhub install searxng-web-search
```
**Via Git:**
```bash
git clone https://github.com/timeplus-io/searxng-web-search.git
cp -r searxng-web-search ~/.openclaw/workspace/skills/
# or for Claude Code:
cp -r searxng-web-search .claude/skills/
```
### 2. Start SearXNG
```bash
docker run -d --name searxng -p 8080:8080 \
-v "$(pwd)/searxng:/etc/searxng" searxng/searxng:latest
```
> **Important**: Enable JSON API in your SearXNG `settings.yml` — see [references/REFERENCE.md](references/REFERENCE.md).
### 3. Install Python Dependency
```bash
pip install requests
```
### 4. Search
```bash
# CLI
python scripts/searxng_search.py "latest streaming SQL developments"
# Human-readable output
python scripts/searxng_search.py "SearXNG setup guide" --text
# News search, last 24 hours
python scripts/searxng_search.py "AI agents" --categories news --time-range day
# Health check
python scripts/searxng_search.py --health
```
## Directory Structure
```
searxng-web-search/
├── SKILL.md # Skill metadata + instructions (agentskills.io)
├── README.md # This file
├── LICENSE # Apache 2.0
├── scripts/
│ └── searxng_search.py # Main search tool (CLI + importable module)
├── references/
│ └── REFERENCE.md # SearXNG API reference & setup guide
└── assets/
└── settings.example.yml # Example SearXNG configuration
```
## Configuration
| Environment Variable | Default | Description |
|---|---|---|
| `SEARXNG_BASE_URL` | `http://localhost:8080` | SearXNG instance URL |
| `SEARXNG_MAX_RESULTS` | `10` | Max results per query |
| `SEARXNG_LANGUAGE` | `all` | Default language |
| `SEARXNG_SAFESEARCH` | `0` | Safe search (0/1/2) |
| `SEARXNG_TIMEOUT` | `15` | Request timeout (seconds) |
| `SEARXNG_CATEGORIES` | `general` | Default categories |
## Compatibility
This skill follows the [agentskills.io specification](https://agentskills.io/specification) and works with:
- [PulseBot](https://github.com/timeplus-io/PulseBot) — Stream-native AI agent framework
- [Claude Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude-code) — Anthropic's coding agent
- [OpenClaw / ClawHub](https://clawhub.ai) — Open-source AI agent platform
- [GitHub Copilot](https://code.visualstudio.com/docs/copilot/customization/agent-skills) — VS Code agent skills
- [OpenAI Codex](https://developers.openai.com/codex/skills/) — OpenAI's coding agent
- Any agent that supports the agentskills.io SKILL.md standard
## Publishing to ClawHub
```bash
npm i -g clawhub
clawhub login
clawhub publish ./searxng-web-search \
--slug searxng-web-search \
--name "SearXNG Web Search" \
--version 1.0.0 \
--changelog "Initial release: SearXNG-powered web search skill" \
--tags "search,web,searxng,privacy,metasearch"
```
## License
Apache 2.0 — see [LICENSE](LICENSE).
## Credits
- Originally based on [PulseBot](https://github.com/timeplus-io/PulseBot) by [Timeplus](https://www.timeplus.com)
- Powered by [SearXNG](https://github.com/searxng/searxng)
- Follows the [Agent Skills](https://agentskills.io) open standard
```
### _meta.json
```json
{
"owner": "gangtao",
"slug": "searxng-web-search",
"displayName": "searxng-web-search",
"latest": {
"version": "1.0.0",
"publishedAt": 1772818244196,
"commit": "https://github.com/openclaw/skills/commit/1e7bc4b0bcc9381a0a59f230e8f2f98187a30604"
},
"history": []
}
```
### assets/settings.example.yml
```yaml
# SearXNG Settings for PulseBot / Agent Skill Usage
# Copy this file to your SearXNG config directory:
# cp settings.example.yml /etc/searxng/settings.yml
#
# IMPORTANT: The "json" format MUST be listed under search.formats
# for the API to work. Without it, requests with format=json return 403.
use_default_settings: true
server:
# Generate a unique secret key for your instance:
# openssl rand -hex 32
secret_key: "CHANGE_ME_TO_A_RANDOM_STRING"
# For private/agent use, bind to localhost and reverse-proxy
bind_address: "0.0.0.0"
port: 8080
# Disable rate limiter for private instances used by agents.
# For public instances, set to true and configure limiter.toml.
limiter: false
# Proxy images through SearXNG (recommended for privacy)
image_proxy: true
search:
# Safe search: 0=off, 1=moderate, 2=strict
safe_search: 0
# Default language for searches
default_lang: "all"
# CRITICAL: Enable JSON format for API access
formats:
- html
- json
# Default search engines (optional, uncomment to customize)
# default_engines:
# - google
# - bing
# - duckduckgo
# - brave
# Autocomplete provider (optional)
# autocomplete: "google"
# Optional: Configure specific engines
# engines:
# - name: google
# engine: google
# shortcut: g
# disabled: false
#
# - name: bing
# engine: bing
# shortcut: b
# disabled: false
#
# - name: duckduckgo
# engine: duckduckgo
# shortcut: ddg
# disabled: false
# Optional: UI settings (only relevant for browser access)
# ui:
# default_theme: simple
# default_locale: ""
# results_on_new_tab: false
```