[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6DFX020YXVD65XRPTBZKC18":3,"skill-same:01M6DFX020YXVD65XRPTBZKC18":86},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":36,"linkOnly":37,"markdownContent":38,"markdownBody":39,"frontMatter":40,"refName":58,"commitSha":59,"skillDirectory":60,"forksCount":61,"repositoryHtmlUrl":62,"repositoryHomepageUrl":6,"ownerHtmlUrl":63,"ownerBlogUrl":6,"ownerDescription":64,"updatedAt":65,"install":66,"resources":79},"01M6DFX020YXVD65XRPTBZKC18","rustxng-web-search-plugin",null,"Add RustXNG as Hermes web search provider via git submodule.","zapabob\u002FSkills","zapabob","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F130830376?v=4","01M6DBMYZAGRTFQDSQYW3W764P","峯岸 亮","01M6DBMZ3GKDSJ01NZ9XTZD7JD",2,"AGPL-3.0","https:\u002F\u002Fgithub.com\u002Fzapabob\u002FSkills\u002Fblob\u002Fmain\u002Farchive\u002Fskill-md-reviewed\u002Fsha256\u002F54\u002F540fa39043ab573938cbc67cf00bf7c07bd8adb2f9e7b5335dd007ba31e8b1f1\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35],"search","rustxng web search plugin","web search","plugin","web","http","windows","development","git","macos","hermes","hermes agent","provider","searxng","as","software","linux","2026-11-10 10:12:30",false,"---\r\nname: rustxng-web-search-plugin\r\ndescription: Add RustXNG as Hermes web search provider via git submodule.\r\nversion: 0.1.0\r\nauthor: Hermes Agent\r\nlicense: AGPL-3.0-or-later\r\nplatforms: [windows, linux, macos]\r\nmetadata:\r\n  hermes:\r\n    tags: [Plugin, WebSearch, RustXNG, SearXNG, GitSubmodule]\r\n    category: software-development\r\n    related_skills: [hermes-plugin-development]\r\n    config:\r\n      RUSTXNG_URL: \"http:\u002F\u002Flocalhost:8888\"\r\n---\r\n\r\n# RustXNG Web Search Provider Plugin\r\n\r\n## Overview\r\n\r\nThis skill captures the end-to-end workflow for adding **RustXNG** (a SearXNG-compatible metasearch engine written in Rust) as a Hermes web search provider plugin. The pattern applies to any SearXNG-compatible engine.\r\n\r\n**What it does:** Integrates a self-hosted RustXNG instance as a `web.search_backend` via git submodule + plugin.\r\n**What it does NOT do:** Does not host RustXNG itself (user runs Docker\u002Fbinary separately). Does not implement `web_extract` — pair with firecrawl\u002Ftavily\u002Fexa for extraction.\r\n\r\n## When to Use\r\n\r\n- Adding a new SearXNG-compatible metasearch backend to Hermes\r\n- Integrating any engine exposing `\u002Fsearch?q=...&format=json` with SearXNG response shape\r\n- Need privacy-respecting, self-hosted web search with 200+ upstream engines\r\n- Want to avoid API keys and use a local\u002Fprivate search instance\r\n\r\n## Prerequisites\r\n\r\n- Hermes Agent repository cloned locally\r\n- Git available (for submodule)\r\n- Docker (recommended) OR Rust toolchain (for binary build)\r\n- `~\u002F.hermes\u002F.env` writable for `RUSTXNG_URL`\r\n- `~\u002F.hermes\u002Fconfig.yaml` writable for `web.search_backend`\r\n\r\n## How to Run\r\n\r\n```bash\r\n# 1. Add RustXNG as git submodule (from repo root)\r\nterminal(command=\"git submodule add https:\u002F\u002Fgithub.com\u002Fzapabob\u002FRustXNG.git plugins\u002Frustxng\")\r\n\r\n# 2. Create web provider plugin structure\r\nmkdir -p plugins\u002Fweb\u002Frustxng\r\n\r\n# 3. Write plugin files (see Procedure for exact content)\r\nwrite_file(path=\"plugins\u002Fweb\u002Frustxng\u002F__init__.py\", content=...)\r\nwrite_file(path=\"plugins\u002Fweb\u002Frustxng\u002Fprovider.py\", content=...)\r\nwrite_file(path=\"plugins\u002Fweb\u002Frustxng\u002Fplugin.yaml\", content=...)\r\nwrite_file(path=\"plugins\u002Fweb\u002Frustxng\u002Frustxng_manager.py\", content=...)\r\nwrite_file(path=\"plugins\u002Fweb\u002Frustxng\u002FREADME.md\", content=...)\r\n\r\n# 4. Build & start RustXNG instance\r\npython plugins\u002Fweb\u002Frustxng\u002Frustxng_manager.py docker-build\r\npython plugins\u002Fweb\u002Frustxng\u002Frustxng_manager.py start\r\n\r\n# 5. Configure Hermes\r\necho \"RUSTXNG_URL=http:\u002F\u002Flocalhost:8888\" >> ~\u002F.hermes\u002F.env\r\n# Add to ~\u002F.hermes\u002Fconfig.yaml:\r\n# web:\r\n#   search_backend: \"rustxng\"\r\n\r\n# 6. Verify\r\nhermes tools  # Look for \"RustXNG\" in web search providers\r\n```\r\n\r\n## Quick Reference\r\n\r\n| File | Purpose |\r\n|------|---------|\r\n| `plugins\u002Frustxng\u002F` | Git submodule (upstream RustXNG source) |\r\n| `plugins\u002Fweb\u002Frustxng\u002F__init__.py` | Plugin entrypoint, registers provider |\r\n| `plugins\u002Fweb\u002Frustxng\u002Fprovider.py` | `RustXNGWebSearchProvider` class |\r\n| `plugins\u002Fweb\u002Frustxng\u002Fplugin.yaml` | Manifest: `web-rustxng`, kind: `backend` |\r\n| `plugins\u002Fweb\u002Frustxng\u002Frustxng_manager.py` | Docker\u002Fbinary lifecycle manager |\r\n| `plugins\u002Fweb\u002Frustxng\u002FREADME.md` | User documentation |\r\n\r\n| Config | Value |\r\n|--------|-------|\r\n| `RUSTXNG_URL` | `http:\u002F\u002Flocalhost:8888` (default) |\r\n| `web.search_backend` | `rustxng` |\r\n| `web.backend` | `rustxng` (fallback) |\r\n\r\n| Manager Commands |\r\n|------------------|\r\n| `python rustxng_manager.py docker-build` | Build Docker image |\r\n| `python rustxng_manager.py start` | Start via Docker (default) |\r\n| `python rustxng_manager.py start --mode binary` | Start from binary |\r\n| `python rustxng_manager.py stop` | Stop Docker container |\r\n| `python rustxng_manager.py status` | Check if running |\r\n| `python rustxng_manager.py build` | Build binary from source |\r\n\r\n## Procedure\r\n\r\n### 1. Add Git Submodule\r\n\r\nFrom the Hermes repository root:\r\n\r\n```bash\r\nterminal(command=\"git submodule add https:\u002F\u002Fgithub.com\u002Fzapabob\u002FRustXNG.git plugins\u002Frustxng\")\r\n```\r\n\r\nVerify:\r\n```bash\r\nterminal(command=\"git submodule status plugins\u002Frustxng\")\r\n# Should show mode 160000 (gitlink)\r\n```\r\n\r\n### 2. Create Plugin Directory\r\n\r\n```bash\r\nterminal(command=\"mkdir -p plugins\u002Fweb\u002Frustxng\")\r\n```\r\n\r\n### 3. Write `__init__.py`\r\n\r\n```python\r\nwrite_file(\r\n    path=\"plugins\u002Fweb\u002Frustxng\u002F__init__.py\",\r\n    content='\"\"\"RustXNG search plugin — bundled, auto-loaded.\\n\\nBacked by a user-hosted RustXNG instance (URL configured via ``RUSTXNG_URL``).\\nSearch-only — pair with an extract provider (firecrawl\u002Ftavily\u002Fexa) for\\n``web_extract`` calls.\\n\\nRustXNG is a SearXNG-compatible metasearch engine written in Rust.\\nAPI-compatible with SearXNG: same \u002Fsearch?format=json endpoint and response shape.\\n\"\"\"\\n\\nfrom __future__ import annotations\\n\\nfrom plugins.web.rustxng.provider import RustXNGWebSearchProvider\\n\\n\\ndef register(ctx) -> None:\\n    \"\"\"Register the RustXNG provider with the plugin context.\"\"\"\\n    ctx.register_web_search_provider(RustXNGWebSearchProvider())\\n'\r\n)\r\n```\r\n\r\n### 4. Write `provider.py`\r\n\r\nImplements `agent.web_search_provider.WebSearchProvider` ABC:\r\n\r\n```python\r\nwrite_file(\r\n    path=\"plugins\u002Fweb\u002Frustxng\u002Fprovider.py\",\r\n    content='\"\"\"RustXNG search — plugin form.\\n\\nSubclasses :class:`agent.web_search_provider.WebSearchProvider`. Same JSON\\nAPI call (``\u002Fsearch?format=json``), same result normalization as SearXNG.\\nRustXNG is a SearXNG-compatible metasearch engine written in Rust with\\nthe same API surface.\\n\\nSearch-only — RustXNG aggregates results from upstream engines but does not\\nfetch\u002Fextract arbitrary URLs. ``supports_extract()`` returns False.\\n\\nConfig keys this provider responds to::\\n\\n    web:\\n      search_backend: \"rustxng\"     # explicit per-capability\\n      backend: \"rustxng\"            # shared fallback\\n\\nEnv var::\\n\\n    RUSTXNG_URL=http:\u002F\u002Flocalhost:8888\\n\"\"\"\\n\\nfrom __future__ import annotations\\n\\nimport logging\\nimport os\\nfrom typing import Any, Dict\\n\\nfrom agent.web_search_provider import WebSearchProvider\\n\\nlogger = logging.getLogger(__name__)\\n\\n\\ndef _rustxng_url() -> str:\\n    \"\"\"Return RUSTXNG_URL from Hermes config-aware env, falling back to process env.\"\"\"\\n    try:\\n        from hermes_cli.config import get_env_value\\n        val = get_env_value(\"RUSTXNG_URL\")\\n    except Exception:\\n        val = None\\n    if val is None:\\n        val = os.getenv(\"RUSTXNG_URL\", \"\")\\n    return (val or \"\").strip()\\n\\n\\nclass RustXNGWebSearchProvider(WebSearchProvider):\\n    \"\"\"Search via a user-hosted RustXNG instance.\"\"\"\\n\\n    @property\\n    def name(self) -> str:\\n        return \"rustxng\"\\n\\n    @property\\n    def display_name(self) -> str:\\n        return \"RustXNG\"\\n\\n    def is_available(self) -> bool:\\n        \"\"\"Return True when ``RUSTXNG_URL`` is set.\"\"\"\\n        return bool(_rustxng_url())\\n\\n    def supports_search(self) -> bool:\\n        return True\\n\\n    def supports_extract(self) -> bool:\\n        return False\\n\\n    def search(self, query: str, limit: int = 5) -> Dict[str, Any]:\\n        \"\"\"Execute a search against the configured RustXNG instance.\"\"\"\\n        import httpx\\n\\n        base_url = _rustxng_url().rstrip(\"\u002F\")\\n        if not base_url:\\n            return {\"success\": False, \"error\": \"RUSTXNG_URL is not set\"}\\n\\n        params: Dict[str, Any] = {\\n            \"q\": query,\\n            \"format\": \"json\",\\n            \"pageno\": 1,\\n        }\\n\\n        try:\\n            resp = httpx.get(\\n                f\"{base_url}\u002Fsearch\",\\n                params=params,\\n                timeout=15,\\n                headers={\"Accept\": \"application\u002Fjson\"},\\n            )\\n            resp.raise_for_status()\\n        except httpx.HTTPStatusError as exc:\\n            logger.warning(\"RustXNG HTTP error: %s\", exc)\\n            status_code = getattr(exc.response, \"status_code\", \"unknown\")\\n            return {\"success\": False, \"error\": f\"RustXNG returned HTTP {status_code}\"}\\n        except httpx.RequestError as exc:\\n            logger.warning(\"RustXNG request error: %s\", exc)\\n            return {\"success\": False, \"error\": f\"Could not reach RustXNG at {base_url}: {exc}\"}\\n\\n        try:\\n            data = resp.json()\\n        except Exception as exc:\\n            logger.warning(\"RustXNG response parse error: %s\", exc)\\n            return {\"success\": False, \"error\": \"Could not parse RustXNG response as JSON\"}\\n\\n        raw_results = data.get(\"results\", [])\\n\\n        sorted_results = sorted(\\n            raw_results,\\n            key=lambda r: float(r.get(\"score\", 0)),\\n            reverse=True,\\n        )[:limit]\\n\\n        web_results = [\\n            {\\n                \"title\": str(r.get(\"title\", \"\")),\\n                \"url\": str(r.get(\"url\", \"\")),\\n                \"description\": str(r.get(\"content\", \"\")),\\n                \"position\": i + 1,\\n            }\\n            for i, r in enumerate(sorted_results)\\n        ]\\n\\n        logger.info(\\n            \"RustXNG search '%s': %d results (from %d raw, limit %d)\",\\n            query, len(web_results), len(raw_results), limit,\\n        )\\n\\n        return {\"success\": True, \"data\": {\"web\": web_results}}\\n\\n    def get_setup_schema(self) -> Dict[str, Any]:\\n        return {\\n            \"name\": \"RustXNG\",\\n            \"badge\": \"free · self-hosted\",\\n            \"tag\": \"SearXNG-compatible metasearch in Rust. Point RUSTXNG_URL at your instance.\",\\n            \"env_vars\": [\\n                {\\n                    \"key\": \"RUSTXNG_URL\",\\n                    \"prompt\": \"RustXNG instance URL (e.g. http:\u002F\u002Flocalhost:8888)\",\\n                    \"url\": \"https:\u002F\u002Fgithub.com\u002Fzapabob\u002FRustXNG\",\\n                },\\n            ],\\n        }\\n'\r\n)\r\n```\r\n\r\n### 5. Write `plugin.yaml`\r\n\r\n```yaml\r\nwrite_file(\r\n    path=\"plugins\u002Fweb\u002Frustxng\u002Fplugin.yaml\",\r\n    content='name: web-rustxng\\nversion: 1.0.0\\ndescription: \"RustXNG — SearXNG-compatible metasearch engine in Rust. Self-hosted, privacy-respecting, 200+ search engines. Requires RUSTXNG_URL pointing to your instance.\"\\nauthor: NousResearch\\nkind: backend\\nprovides_web_providers:\\n  - rustxng\\n'\r\n)\r\n```\r\n\r\n### 6. Write `rustxng_manager.py`\r\n\r\nHelper script to manage local RustXNG instance (Docker or binary). See the full script in the skill's `scripts\u002Frustxng_manager.py` — key functions:\r\n\r\n- `docker-build` — builds `rustxng:local` image from submodule\r\n- `start [--mode docker|binary]` — starts RustXNG on port 8888\r\n- `stop` — stops Docker container\r\n- `status` — checks if responding on port\r\n- `build` — builds binary via `cargo build --release`\r\n\r\n### 7. Write `README.md`\r\n\r\nUser-facing documentation with quick start, config, search syntax, troubleshooting.\r\n\r\n### 8. Build & Start RustXNG Instance\r\n\r\n```bash\r\n# Docker (recommended)\r\npython plugins\u002Fweb\u002Frustxng\u002Frustxng_manager.py docker-build\r\npython plugins\u002Fweb\u002Frustxng\u002Frustxng_manager.py start\r\n```\r\n\r\n### 9. Configure Hermes\r\n\r\n```bash\r\n# Environment variable\r\necho \"RUSTXNG_URL=http:\u002F\u002Flocalhost:8888\" >> ~\u002F.hermes\u002F.env\r\n\r\n# Config.yaml\r\ncat >> ~\u002F.hermes\u002Fconfig.yaml \u003C\u003C 'EOF'\r\nweb:\r\n  search_backend: \"rustxng\"\r\nEOF\r\n```\r\n\r\n### 10. Verify Integration\r\n\r\n```bash\r\nhermes tools\r\n# Should show \"RustXNG\" as available web search provider\r\n```\r\n\r\n## Pitfalls\r\n\r\n1. **RUSTXNG_URL not set** — Provider shows unavailable. Check `~\u002F.hermes\u002F.env` and restart Hermes.\r\n2. **Docker not available** — Use binary mode: `python rustxng_manager.py build && python rustxng_manager.py start --mode binary`\r\n3. **Path with spaces** — Rust build fails on Windows if path contains spaces. Manager script copies to spaceless path.\r\n4. **Port conflict** — Default 8888. Change via `RUSTXNG_URL` and `--port` flag.\r\n5. **Plugin not discovered** — Ensure `plugin.yaml` exists and `__init__.py` has no import errors. Run `hermes plugins list` to check.\r\n6. **SEARXNG_URL takes precedence** — If both `SEARXNG_URL` and `RUSTXNG_URL` are set, legacy ladder may pick searxng. Explicitly set `web.search_backend: \"rustxng\"` in config.yaml.\r\n\r\n## Verification\r\n\r\n```bash\r\n# 1. Provider loads and is available\r\npython -c \"\r\nimport os; os.environ['RUSTXNG_URL'] = 'http:\u002F\u002Flocalhost:8888'\r\nfrom plugins.web.rustxng.provider import RustXNGWebSearchProvider\r\nfrom agent.web_search_registry import register_provider, get_active_search_provider\r\nregister_provider(RustXNGWebSearchProvider())\r\np = get_active_search_provider()\r\nassert p.name == 'rustxng', f'Expected rustxng, got {p.name}'\r\nprint('✓ Provider resolves as active search backend')\r\n\"\r\n\r\n# 2. Web tools picks it up\r\npython -c \"\r\nimport os; os.environ['RUSTXNG_URL'] = 'http:\u002F\u002Flocalhost:8888'\r\n# clear other keys that take precedence\r\nfor k in ['TAVILY_API_KEY','EXA_API_KEY','PARALLEL_API_KEY','FIRECRAWL_API_KEY','BRAVE_SEARCH_API_KEY','SEARXNG_URL']:\r\n    os.environ.pop(k, None)\r\nfrom agent.web_search_registry import _reset_for_tests\r\nfrom plugins.web.rustxng.provider import RustXNGWebSearchProvider\r\nfrom agent.web_search_registry import register_provider\r\n_reset_for_tests()\r\nregister_provider(RustXNGWebSearchProvider())\r\nimport importlib, tools.web_tools as wt\r\nimportlib.reload(wt)\r\nassert wt._get_backend('search') == 'rustxng'\r\nprint('✓ web_tools fallback resolves to rustxng')\r\n\"\r\n\r\n# 3. End-to-end (requires running RustXNG instance)\r\ncurl \"http:\u002F\u002Flocalhost:8888\u002Fsearch?q=rust&format=json\"\r\n# Should return JSON with results array\r\n```\r\n\r\n---\r\n\r\n**Reference Implementation:** See `_docs\u002F2026-10-24_rustxng-plugin-integration_hakua.md` for the full integration record.","# RustXNG Web Search Provider Plugin\r\n\r\n## Overview\r\n\r\nThis skill captures the end-to-end workflow for adding **RustXNG** (a SearXNG-compatible metasearch engine written in Rust) as a Hermes web search provider plugin. The pattern applies to any SearXNG-compatible engine.\r\n\r\n**What it does:** Integrates a self-hosted RustXNG instance as a `web.search_backend` via git submodule + plugin.\r\n**What it does NOT do:** Does not host RustXNG itself (user runs Docker\u002Fbinary separately). Does not implement `web_extract` — pair with firecrawl\u002Ftavily\u002Fexa for extraction.\r\n\r\n## When to Use\r\n\r\n- Adding a new SearXNG-compatible metasearch backend to Hermes\r\n- Integrating any engine exposing `\u002Fsearch?q=...&format=json` with SearXNG response shape\r\n- Need privacy-respecting, self-hosted web search with 200+ upstream engines\r\n- Want to avoid API keys and use a local\u002Fprivate search instance\r\n\r\n## Prerequisites\r\n\r\n- Hermes Agent repository cloned locally\r\n- Git available (for submodule)\r\n- Docker (recommended) OR Rust toolchain (for binary build)\r\n- `~\u002F.hermes\u002F.env` writable for `RUSTXNG_URL`\r\n- `~\u002F.hermes\u002Fconfig.yaml` writable for `web.search_backend`\r\n\r\n## How to Run\r\n\r\n```bash\r\n# 1. Add RustXNG as git submodule (from repo root)\r\nterminal(command=\"git submodule add https:\u002F\u002Fgithub.com\u002Fzapabob\u002FRustXNG.git plugins\u002Frustxng\")\r\n\r\n# 2. Create web provider plugin structure\r\nmkdir -p plugins\u002Fweb\u002Frustxng\r\n\r\n# 3. Write plugin files (see Procedure for exact content)\r\nwrite_file(path=\"plugins\u002Fweb\u002Frustxng\u002F__init__.py\", content=...)\r\nwrite_file(path=\"plugins\u002Fweb\u002Frustxng\u002Fprovider.py\", content=...)\r\nwrite_file(path=\"plugins\u002Fweb\u002Frustxng\u002Fplugin.yaml\", content=...)\r\nwrite_file(path=\"plugins\u002Fweb\u002Frustxng\u002Frustxng_manager.py\", content=...)\r\nwrite_file(path=\"plugins\u002Fweb\u002Frustxng\u002FREADME.md\", content=...)\r\n\r\n# 4. Build & start RustXNG instance\r\npython plugins\u002Fweb\u002Frustxng\u002Frustxng_manager.py docker-build\r\npython plugins\u002Fweb\u002Frustxng\u002Frustxng_manager.py start\r\n\r\n# 5. Configure Hermes\r\necho \"RUSTXNG_URL=http:\u002F\u002Flocalhost:8888\" >> ~\u002F.hermes\u002F.env\r\n# Add to ~\u002F.hermes\u002Fconfig.yaml:\r\n# web:\r\n#   search_backend: \"rustxng\"\r\n\r\n# 6. Verify\r\nhermes tools  # Look for \"RustXNG\" in web search providers\r\n```\r\n\r\n## Quick Reference\r\n\r\n| File | Purpose |\r\n|------|---------|\r\n| `plugins\u002Frustxng\u002F` | Git submodule (upstream RustXNG source) |\r\n| `plugins\u002Fweb\u002Frustxng\u002F__init__.py` | Plugin entrypoint, registers provider |\r\n| `plugins\u002Fweb\u002Frustxng\u002Fprovider.py` | `RustXNGWebSearchProvider` class |\r\n| `plugins\u002Fweb\u002Frustxng\u002Fplugin.yaml` | Manifest: `web-rustxng`, kind: `backend` |\r\n| `plugins\u002Fweb\u002Frustxng\u002Frustxng_manager.py` | Docker\u002Fbinary lifecycle manager |\r\n| `plugins\u002Fweb\u002Frustxng\u002FREADME.md` | User documentation |\r\n\r\n| Config | Value |\r\n|--------|-------|\r\n| `RUSTXNG_URL` | `http:\u002F\u002Flocalhost:8888` (default) |\r\n| `web.search_backend` | `rustxng` |\r\n| `web.backend` | `rustxng` (fallback) |\r\n\r\n| Manager Commands |\r\n|------------------|\r\n| `python rustxng_manager.py docker-build` | Build Docker image |\r\n| `python rustxng_manager.py start` | Start via Docker (default) |\r\n| `python rustxng_manager.py start --mode binary` | Start from binary |\r\n| `python rustxng_manager.py stop` | Stop Docker container |\r\n| `python rustxng_manager.py status` | Check if running |\r\n| `python rustxng_manager.py build` | Build binary from source |\r\n\r\n## Procedure\r\n\r\n### 1. Add Git Submodule\r\n\r\nFrom the Hermes repository root:\r\n\r\n```bash\r\nterminal(command=\"git submodule add https:\u002F\u002Fgithub.com\u002Fzapabob\u002FRustXNG.git plugins\u002Frustxng\")\r\n```\r\n\r\nVerify:\r\n```bash\r\nterminal(command=\"git submodule status plugins\u002Frustxng\")\r\n# Should show mode 160000 (gitlink)\r\n```\r\n\r\n### 2. Create Plugin Directory\r\n\r\n```bash\r\nterminal(command=\"mkdir -p plugins\u002Fweb\u002Frustxng\")\r\n```\r\n\r\n### 3. Write `__init__.py`\r\n\r\n```python\r\nwrite_file(\r\n    path=\"plugins\u002Fweb\u002Frustxng\u002F__init__.py\",\r\n    content='\"\"\"RustXNG search plugin — bundled, auto-loaded.\\n\\nBacked by a user-hosted RustXNG instance (URL configured via ``RUSTXNG_URL``).\\nSearch-only — pair with an extract provider (firecrawl\u002Ftavily\u002Fexa) for\\n``web_extract`` calls.\\n\\nRustXNG is a SearXNG-compatible metasearch engine written in Rust.\\nAPI-compatible with SearXNG: same \u002Fsearch?format=json endpoint and response shape.\\n\"\"\"\\n\\nfrom __future__ import annotations\\n\\nfrom plugins.web.rustxng.provider import RustXNGWebSearchProvider\\n\\n\\ndef register(ctx) -> None:\\n    \"\"\"Register the RustXNG provider with the plugin context.\"\"\"\\n    ctx.register_web_search_provider(RustXNGWebSearchProvider())\\n'\r\n)\r\n```\r\n\r\n### 4. Write `provider.py`\r\n\r\nImplements `agent.web_search_provider.WebSearchProvider` ABC:\r\n\r\n```python\r\nwrite_file(\r\n    path=\"plugins\u002Fweb\u002Frustxng\u002Fprovider.py\",\r\n    content='\"\"\"RustXNG search — plugin form.\\n\\nSubclasses :class:`agent.web_search_provider.WebSearchProvider`. Same JSON\\nAPI call (``\u002Fsearch?format=json``), same result normalization as SearXNG.\\nRustXNG is a SearXNG-compatible metasearch engine written in Rust with\\nthe same API surface.\\n\\nSearch-only — RustXNG aggregates results from upstream engines but does not\\nfetch\u002Fextract arbitrary URLs. ``supports_extract()`` returns False.\\n\\nConfig keys this provider responds to::\\n\\n    web:\\n      search_backend: \"rustxng\"     # explicit per-capability\\n      backend: \"rustxng\"            # shared fallback\\n\\nEnv var::\\n\\n    RUSTXNG_URL=http:\u002F\u002Flocalhost:8888\\n\"\"\"\\n\\nfrom __future__ import annotations\\n\\nimport logging\\nimport os\\nfrom typing import Any, Dict\\n\\nfrom agent.web_search_provider import WebSearchProvider\\n\\nlogger = logging.getLogger(__name__)\\n\\n\\ndef _rustxng_url() -> str:\\n    \"\"\"Return RUSTXNG_URL from Hermes config-aware env, falling back to process env.\"\"\"\\n    try:\\n        from hermes_cli.config import get_env_value\\n        val = get_env_value(\"RUSTXNG_URL\")\\n    except Exception:\\n        val = None\\n    if val is None:\\n        val = os.getenv(\"RUSTXNG_URL\", \"\")\\n    return (val or \"\").strip()\\n\\n\\nclass RustXNGWebSearchProvider(WebSearchProvider):\\n    \"\"\"Search via a user-hosted RustXNG instance.\"\"\"\\n\\n    @property\\n    def name(self) -> str:\\n        return \"rustxng\"\\n\\n    @property\\n    def display_name(self) -> str:\\n        return \"RustXNG\"\\n\\n    def is_available(self) -> bool:\\n        \"\"\"Return True when ``RUSTXNG_URL`` is set.\"\"\"\\n        return bool(_rustxng_url())\\n\\n    def supports_search(self) -> bool:\\n        return True\\n\\n    def supports_extract(self) -> bool:\\n        return False\\n\\n    def search(self, query: str, limit: int = 5) -> Dict[str, Any]:\\n        \"\"\"Execute a search against the configured RustXNG instance.\"\"\"\\n        import httpx\\n\\n        base_url = _rustxng_url().rstrip(\"\u002F\")\\n        if not base_url:\\n            return {\"success\": False, \"error\": \"RUSTXNG_URL is not set\"}\\n\\n        params: Dict[str, Any] = {\\n            \"q\": query,\\n            \"format\": \"json\",\\n            \"pageno\": 1,\\n        }\\n\\n        try:\\n            resp = httpx.get(\\n                f\"{base_url}\u002Fsearch\",\\n                params=params,\\n                timeout=15,\\n                headers={\"Accept\": \"application\u002Fjson\"},\\n            )\\n            resp.raise_for_status()\\n        except httpx.HTTPStatusError as exc:\\n            logger.warning(\"RustXNG HTTP error: %s\", exc)\\n            status_code = getattr(exc.response, \"status_code\", \"unknown\")\\n            return {\"success\": False, \"error\": f\"RustXNG returned HTTP {status_code}\"}\\n        except httpx.RequestError as exc:\\n            logger.warning(\"RustXNG request error: %s\", exc)\\n            return {\"success\": False, \"error\": f\"Could not reach RustXNG at {base_url}: {exc}\"}\\n\\n        try:\\n            data = resp.json()\\n        except Exception as exc:\\n            logger.warning(\"RustXNG response parse error: %s\", exc)\\n            return {\"success\": False, \"error\": \"Could not parse RustXNG response as JSON\"}\\n\\n        raw_results = data.get(\"results\", [])\\n\\n        sorted_results = sorted(\\n            raw_results,\\n            key=lambda r: float(r.get(\"score\", 0)),\\n            reverse=True,\\n        )[:limit]\\n\\n        web_results = [\\n            {\\n                \"title\": str(r.get(\"title\", \"\")),\\n                \"url\": str(r.get(\"url\", \"\")),\\n                \"description\": str(r.get(\"content\", \"\")),\\n                \"position\": i + 1,\\n            }\\n            for i, r in enumerate(sorted_results)\\n        ]\\n\\n        logger.info(\\n            \"RustXNG search '%s': %d results (from %d raw, limit %d)\",\\n            query, len(web_results), len(raw_results), limit,\\n        )\\n\\n        return {\"success\": True, \"data\": {\"web\": web_results}}\\n\\n    def get_setup_schema(self) -> Dict[str, Any]:\\n        return {\\n            \"name\": \"RustXNG\",\\n            \"badge\": \"free · self-hosted\",\\n            \"tag\": \"SearXNG-compatible metasearch in Rust. Point RUSTXNG_URL at your instance.\",\\n            \"env_vars\": [\\n                {\\n                    \"key\": \"RUSTXNG_URL\",\\n                    \"prompt\": \"RustXNG instance URL (e.g. http:\u002F\u002Flocalhost:8888)\",\\n                    \"url\": \"https:\u002F\u002Fgithub.com\u002Fzapabob\u002FRustXNG\",\\n                },\\n            ],\\n        }\\n'\r\n)\r\n```\r\n\r\n### 5. Write `plugin.yaml`\r\n\r\n```yaml\r\nwrite_file(\r\n    path=\"plugins\u002Fweb\u002Frustxng\u002Fplugin.yaml\",\r\n    content='name: web-rustxng\\nversion: 1.0.0\\ndescription: \"RustXNG — SearXNG-compatible metasearch engine in Rust. Self-hosted, privacy-respecting, 200+ search engines. Requires RUSTXNG_URL pointing to your instance.\"\\nauthor: NousResearch\\nkind: backend\\nprovides_web_providers:\\n  - rustxng\\n'\r\n)\r\n```\r\n\r\n### 6. Write `rustxng_manager.py`\r\n\r\nHelper script to manage local RustXNG instance (Docker or binary). See the full script in the skill's `scripts\u002Frustxng_manager.py` — key functions:\r\n\r\n- `docker-build` — builds `rustxng:local` image from submodule\r\n- `start [--mode docker|binary]` — starts RustXNG on port 8888\r\n- `stop` — stops Docker container\r\n- `status` — checks if responding on port\r\n- `build` — builds binary via `cargo build --release`\r\n\r\n### 7. Write `README.md`\r\n\r\nUser-facing documentation with quick start, config, search syntax, troubleshooting.\r\n\r\n### 8. Build & Start RustXNG Instance\r\n\r\n```bash\r\n# Docker (recommended)\r\npython plugins\u002Fweb\u002Frustxng\u002Frustxng_manager.py docker-build\r\npython plugins\u002Fweb\u002Frustxng\u002Frustxng_manager.py start\r\n```\r\n\r\n### 9. Configure Hermes\r\n\r\n```bash\r\n# Environment variable\r\necho \"RUSTXNG_URL=http:\u002F\u002Flocalhost:8888\" >> ~\u002F.hermes\u002F.env\r\n\r\n# Config.yaml\r\ncat >> ~\u002F.hermes\u002Fconfig.yaml \u003C\u003C 'EOF'\r\nweb:\r\n  search_backend: \"rustxng\"\r\nEOF\r\n```\r\n\r\n### 10. Verify Integration\r\n\r\n```bash\r\nhermes tools\r\n# Should show \"RustXNG\" as available web search provider\r\n```\r\n\r\n## Pitfalls\r\n\r\n1. **RUSTXNG_URL not set** — Provider shows unavailable. Check `~\u002F.hermes\u002F.env` and restart Hermes.\r\n2. **Docker not available** — Use binary mode: `python rustxng_manager.py build && python rustxng_manager.py start --mode binary`\r\n3. **Path with spaces** — Rust build fails on Windows if path contains spaces. Manager script copies to spaceless path.\r\n4. **Port conflict** — Default 8888. Change via `RUSTXNG_URL` and `--port` flag.\r\n5. **Plugin not discovered** — Ensure `plugin.yaml` exists and `__init__.py` has no import errors. Run `hermes plugins list` to check.\r\n6. **SEARXNG_URL takes precedence** — If both `SEARXNG_URL` and `RUSTXNG_URL` are set, legacy ladder may pick searxng. Explicitly set `web.search_backend: \"rustxng\"` in config.yaml.\r\n\r\n## Verification\r\n\r\n```bash\r\n# 1. Provider loads and is available\r\npython -c \"\r\nimport os; os.environ['RUSTXNG_URL'] = 'http:\u002F\u002Flocalhost:8888'\r\nfrom plugins.web.rustxng.provider import RustXNGWebSearchProvider\r\nfrom agent.web_search_registry import register_provider, get_active_search_provider\r\nregister_provider(RustXNGWebSearchProvider())\r\np = get_active_search_provider()\r\nassert p.name == 'rustxng', f'Expected rustxng, got {p.name}'\r\nprint('✓ Provider resolves as active search backend')\r\n\"\r\n\r\n# 2. Web tools picks it up\r\npython -c \"\r\nimport os; os.environ['RUSTXNG_URL'] = 'http:\u002F\u002Flocalhost:8888'\r\n# clear other keys that take precedence\r\nfor k in ['TAVILY_API_KEY','EXA_API_KEY','PARALLEL_API_KEY','FIRECRAWL_API_KEY','BRAVE_SEARCH_API_KEY','SEARXNG_URL']:\r\n    os.environ.pop(k, None)\r\nfrom agent.web_search_registry import _reset_for_tests\r\nfrom plugins.web.rustxng.provider import RustXNGWebSearchProvider\r\nfrom agent.web_search_registry import register_provider\r\n_reset_for_tests()\r\nregister_provider(RustXNGWebSearchProvider())\r\nimport importlib, tools.web_tools as wt\r\nimportlib.reload(wt)\r\nassert wt._get_backend('search') == 'rustxng'\r\nprint('✓ web_tools fallback resolves to rustxng')\r\n\"\r\n\r\n# 3. End-to-end (requires running RustXNG instance)\r\ncurl \"http:\u002F\u002Flocalhost:8888\u002Fsearch?q=rust&format=json\"\r\n# Should return JSON with results array\r\n```\r\n\r\n---\r\n\r\n**Reference Implementation:** See `_docs\u002F2026-10-24_rustxng-plugin-integration_hakua.md` for the full integration record.",{"name":5,"author":41,"license":42,"version":43,"metadata":44,"platforms":57,"description":7},"Hermes Agent","AGPL-3.0-or-later","0.1.0",{"hermes":45},{"tags":46,"config":52,"category":54,"related_skills":55},[47,48,49,50,51],"Plugin","WebSearch","RustXNG","SearXNG","GitSubmodule",{"RUSTXNG_URL":53},"http:\u002F\u002Flocalhost:8888","software-development",[56],"hermes-plugin-development",[25,35,28],"main","0d1434f89f4bc6b07e9d272accd8c1013768820f","archive\u002Fskill-md-reviewed\u002Fsha256\u002F54\u002F540fa39043ab573938cbc67cf00bf7c07bd8adb2f9e7b5335dd007ba31e8b1f1",0,"https:\u002F\u002Fgithub.com\u002Fzapabob\u002FSkills","https:\u002F\u002Fgithub.com\u002Fzapabob","The Open University of JAPAN　2019-now\r\n\r\nTokyo University of Science　2007-2010","2026-10-26 16:29:01",{"source":8,"skillName":5,"commands":67,"prompt":77,"zipAvailable":78},[68,71,74],{"tool":69,"command":70},"npx","npx skills add zapabob\u002FSkills --skill rustxng-web-search-plugin",{"tool":72,"command":73},"bunx","bunx skills add zapabob\u002FSkills --skill rustxng-web-search-plugin",{"tool":75,"command":76},"pnpm","pnpm dlx skills add zapabob\u002FSkills --skill rustxng-web-search-plugin","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fzapabob\u002FSkills\u002Fblob\u002Fmain\u002Farchive\u002Fskill-md-reviewed\u002Fsha256\u002F54\u002F540fa39043ab573938cbc67cf00bf7c07bd8adb2f9e7b5335dd007ba31e8b1f1\u002FSKILL.md",true,[80],{"path":81,"resourceType":82,"resourceTypeDesc":83,"sizeBytes":84,"sourceHtmlUrl":85},"reference\u002Fscripts\u002Frun_actor.js","SCRIPT","脚本",11745,"https:\u002F\u002Fgithub.com\u002Fagency-black\u002FSkills\u002Fblob\u002Fmain\u002Fapify-market-research\u002Freference\u002Fscripts\u002Frun_actor.js",[87,120,154,181,208,224],{"skillNo":88,"name":89,"nameZh":6,"description":90,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":91,"licenseFileUrl":6,"sourceHtmlUrl":92,"categories":93,"keywords":107,"publishedAt":36},"01M6GJTE1GESGGF9K028Y7753E","xurl","X\u002FTwitter via xurl CLI: post, search, DM, media, v2 API.","MIT","https:\u002F\u002Fgithub.com\u002Fzapabob\u002FSkills\u002Fblob\u002Fmain\u002Fregistry\u002Fskills\u002Fxurl\u002Fvariants\u002Fhermes-agent\u002FSKILL.md",[94,99,102],{"categoryNo":95,"categoryName":96,"taxonomyType":97,"taxonomyTypeDesc":98},"01JBSEEDCAT000000000000010","后端与接口","SCENARIO","使用场景",{"categoryNo":100,"categoryName":101,"taxonomyType":97,"taxonomyTypeDesc":98},"01JBSEEDCAT000000000000023","Skill与智能体开发",{"categoryNo":103,"categoryName":104,"taxonomyType":105,"taxonomyTypeDesc":106},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[89,108,28,109,110,35,25,111,19,112,113,114,115,29,30,116,58,117,118,119],"openclaw","cli","github","official","https","social-media","api","Media","twitter","social","via","post",{"skillNo":121,"name":122,"nameZh":6,"description":123,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":91,"licenseFileUrl":6,"sourceHtmlUrl":124,"categories":125,"keywords":137,"publishedAt":36},"01M6GJT8S1PXQ8WPXT1HSNBWH2","writing-plans","Write implementation plans: bite-sized tasks, paths, code.","https:\u002F\u002Fgithub.com\u002Fzapabob\u002FSkills\u002Fblob\u002Fmain\u002Fregistry\u002Fskills\u002Fwriting-plans\u002Fvariants\u002Fhermes-agent\u002FSKILL.md",[126,129,132,133,134],{"categoryNo":127,"categoryName":128,"taxonomyType":97,"taxonomyTypeDesc":98},"01JBSEEDCAT000000000000015","视觉设计",{"categoryNo":130,"categoryName":131,"taxonomyType":97,"taxonomyTypeDesc":98},"01JBSEEDCAT000000000000018","写作与文案",{"categoryNo":100,"categoryName":101,"taxonomyType":97,"taxonomyTypeDesc":98},{"categoryNo":103,"categoryName":104,"taxonomyType":105,"taxonomyTypeDesc":106},{"categoryNo":135,"categoryName":136,"taxonomyType":105,"taxonomyTypeDesc":106},"01JBSEEDCAT000000000000036","内容创作者",[138,139,140,141,28,142,35,143,25,144,145,26,146,147,148,149,150,151,152,153],"writing","writing plans","plans","design","review","workflow","documentation","tasks","code","planning","code-review","subagent","write","superpowers","paths","test-driven-development",{"skillNo":155,"name":156,"nameZh":6,"description":157,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":91,"licenseFileUrl":6,"sourceHtmlUrl":158,"categories":159,"keywords":163,"publishedAt":36},"01M6GJST0H5X7WHBJ3BZK0DVKH","whisper","OpenAI's general-purpose speech recognition model. Supports 99 languages, transcription, translation to English, and language identification. Six model sizes from tiny (39M params) to large (1550M params). Use for speech-to-text, podcast transcription, or multilingual audio processing. Best for robust, multilingual ASR.","https:\u002F\u002Fgithub.com\u002Fzapabob\u002FSkills\u002Fblob\u002Fmain\u002Fregistry\u002Fskills\u002Fwhisper\u002Fvariants\u002Fhermes-agent-optional\u002FSKILL.md",[160],{"categoryNo":161,"categoryName":162,"taxonomyType":97,"taxonomyTypeDesc":98},"01JBSEEDCAT000000000000016","图像与多媒体",[156,28,35,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180],"openai","speech","multimodal","Transformers","ASR","Audio","text","model","speech recognition","use","general","multilingual","translation","english","identification","processing","Audio Processing",{"skillNo":182,"name":183,"nameZh":6,"description":184,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":91,"licenseFileUrl":6,"sourceHtmlUrl":185,"categories":186,"keywords":190,"publishedAt":36},"01M6GJSMRQ8EETZTYMG72P70EA","weights-and-biases","W&B: log ML experiments, sweeps, model registry, dashboards.","https:\u002F\u002Fgithub.com\u002Fzapabob\u002FSkills\u002Fblob\u002Fmain\u002Fregistry\u002Fskills\u002Fweights-and-biases\u002Fvariants\u002Fhermes-agent\u002FSKILL.md",[187],{"categoryNo":188,"categoryName":189,"taxonomyType":97,"taxonomyTypeDesc":98},"01JBSEEDCAT000000000000013","分析与可视化",[191,28,192,193,25,194,195,196,197,198,199,171,200,201,202,203,204,205,206,207],"weights and biases","tensorflow","experiments","pytorch","visualization","registry","real-time","tuning","dashboards","experiment","log","time","Experiment Tracking","Model Registry","Hyperparameter Tuning","WandB","Real-Time Visualization",{"skillNo":209,"name":210,"nameZh":6,"description":211,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":91,"licenseFileUrl":6,"sourceHtmlUrl":212,"categories":213,"keywords":215,"publishedAt":36},"01M6GJS19KDPRY112JTR326S06","watchers","Poll RSS, JSON APIs, and GitHub with watermark dedup.","https:\u002F\u002Fgithub.com\u002Fzapabob\u002FSkills\u002Fblob\u002Fmain\u002Fregistry\u002Fskills\u002Fwatchers\u002Fvariants\u002Fhermes-agent-optional\u002FSKILL.md",[214],{"categoryNo":100,"categoryName":101,"taxonomyType":97,"taxonomyTypeDesc":98},[210,28,216,110,35,217,24,218,219,220,221,29,30,222,223],"terminal","automation","monitoring","cron","json","rss","devops","with",{"skillNo":225,"name":226,"nameZh":6,"description":227,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":91,"licenseFileUrl":6,"sourceHtmlUrl":228,"categories":229,"keywords":232,"publishedAt":36},"01M6GJRHJD2A6Q830SWF9W48BC","serving-llms-vllm","vLLM: high-throughput LLM serving, OpenAI API, quantization.","https:\u002F\u002Fgithub.com\u002Fzapabob\u002FSkills\u002Fblob\u002Fmain\u002Fregistry\u002Fskills\u002Fvllm\u002Fvariants\u002Fhermes-agent\u002FSKILL.md",[230,231],{"categoryNo":95,"categoryName":96,"taxonomyType":97,"taxonomyTypeDesc":98},{"categoryNo":103,"categoryName":104,"taxonomyType":105,"taxonomyTypeDesc":106},[233,234,235,35,236,164,237,238,239,167,240,114,241,242,243,244,245,246,247,248],"llms","vLLM","serving llms vllm","research","openai api","tensor","Inference Serving","Production","llm serving","High Throughput","PagedAttention","Continuous Batching","Quantization","Tensor Parallelism","high-throughput","throughput"]