From dbe881b7b0d1827080aabf4f8c2e51447167037e Mon Sep 17 00:00:00 2001 From: Spencer Charest Date: Sun, 14 Jun 2026 18:07:58 -0700 Subject: [PATCH] feat(hermes): add ZERO-294 Hermes Agent adapter --- Makefile | 3 +- README.md | 19 ++-- guides/hermes-agent.md | 58 ++++++++++++ plugins/zero-gemini/gemini-extension.json | 2 +- plugins/zero-hermes/README.md | 38 ++++++++ plugins/zero-hermes/hermes/__init__.py | 103 ++++++++++++++++++++++ plugins/zero-hermes/hermes/mcp-zero.yaml | 7 ++ plugins/zero-hermes/hermes/plugin.yaml | 11 +++ plugins/zero-hermes/manifest.json | 8 ++ plugins/zero/.claude-plugin/plugin.json | 2 +- plugins/zero/.codex-plugin/plugin.json | 2 +- plugins/zero/.factory-plugin/plugin.json | 2 +- scripts/build-hermes.sh | 77 ++++++++++++++++ 13 files changed, 319 insertions(+), 13 deletions(-) create mode 100644 guides/hermes-agent.md create mode 100644 plugins/zero-hermes/README.md create mode 100644 plugins/zero-hermes/hermes/__init__.py create mode 100644 plugins/zero-hermes/hermes/mcp-zero.yaml create mode 100644 plugins/zero-hermes/hermes/plugin.yaml create mode 100644 plugins/zero-hermes/manifest.json create mode 100755 scripts/build-hermes.sh diff --git a/Makefile b/Makefile index 8c75263..bc4d0f5 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,8 @@ MANIFESTS := \ plugins/zero/.claude-plugin/plugin.json \ plugins/zero/.codex-plugin/plugin.json \ plugins/zero/.factory-plugin/plugin.json \ - plugins/zero-gemini/gemini-extension.json + plugins/zero-gemini/gemini-extension.json \ + plugins/zero-hermes/manifest.json # The manifest read for the current version (all of MANIFESTS stay in lockstep). VERSION_SOURCE := plugins/zero/.claude-plugin/plugin.json diff --git a/README.md b/README.md index ce4d9fb..3420e2e 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ installation, use the guides below. Pick your agent: - **[Codex app](guides/codex-app.md)** - **[Droid](guides/droid.md)** - **[Gemini CLI](guides/gemini-cli.md)** +- **[Hermes Agent](guides/hermes-agent.md)** - **[Anything else (standalone installer)](guides/generic.md)** — works in any agent with a shell (and for humans at a terminal) @@ -37,18 +38,19 @@ you in and takes it from there. ## What the plugin does -Every install ships the same three ingredients: +Every install ships the same core skill and then the host-specific integration +points that host supports: - **The `zero` skill** — teaches the agent how to search Zero, call a capability, and review the result. -- **Hooks** — keep the Zero CLI runner provisioned and up to date, and remind - the agent that Zero is available. +- **Hooks, when supported** — keep the Zero CLI runner provisioned and up to + date, and remind the agent that Zero is available. - **The Zero MCP connector** — capability search and account status over MCP, on hosts that load it. All hosts share one login (`~/.zero/config.json`) and one runtime -(`~/.zero/runtime`) — sign in once per machine. Updates are automatic; the -per-agent guides have the details. +(`~/.zero/runtime`) — sign in once per machine. Hosts with hooks can update +automatically; the per-agent guides have the details. ## How this repo is organized @@ -63,7 +65,8 @@ plugins/zero/ # the shared plugin: skill + hooks (+ Claude's ├── .codex-plugin/ # Codex manifest └── .factory-plugin/ # Droid manifest plugins/zero-gemini/ # thin Gemini-only overlay (manifest + hook wiring) -scripts/build-gemini.sh # assembles the installable Gemini extension into dist/ +plugins/zero-hermes/ # thin Hermes Agent overlay (plugin + skill/config templates) +scripts/build-*.sh # assemble host-specific install bundles into dist/ guides/ # per-host install guides + the agent install runbook ``` @@ -73,5 +76,5 @@ mechanics — live in the per-host guides. ## Status This repo is built up iteratively, one carefully reviewed PR at a time. Today -it ships the **Claude Code**, **Codex**, **Droid**, and **Gemini CLI** plugins; -additional hosts (Cursor) will land in subsequent PRs. +it ships the **Claude Code**, **Codex**, **Droid**, **Gemini CLI**, and +**Hermes Agent** plugins; additional hosts will land in subsequent PRs. diff --git a/guides/hermes-agent.md b/guides/hermes-agent.md new file mode 100644 index 0000000..80328f4 --- /dev/null +++ b/guides/hermes-agent.md @@ -0,0 +1,58 @@ +# Zero for Hermes Agent + +How to install Zero in Hermes Agent and keep it up to date. + +## Install + +### Inside Hermes Agent + +Install the Zero runtime once: + +```bash +curl -fsSL https://zero.xyz/install.sh | bash +``` + +Build the Hermes adapter from this repository, then copy it into your Hermes +home: + +```bash +scripts/build-hermes.sh +mkdir -p ~/.hermes/plugins ~/.hermes/skills +cp -R dist/zero-hermes/.hermes/plugins/zero ~/.hermes/plugins/ +cp -R dist/zero-hermes/.hermes/skills/zero ~/.hermes/skills/ +``` + +Enable the plugin and add the Zero MCP server: + +```bash +hermes plugins enable zero +hermes mcp add zero --url https://mcp.zero.xyz --auth oauth +``` + +Start a new Hermes session, then use `/zero` or ask Hermes: +*"Help me set up and test Zero."* + +### From the terminal + +```bash +curl -fsSL https://zero.xyz/install.sh | bash +scripts/build-hermes.sh +mkdir -p ~/.hermes/plugins ~/.hermes/skills +cp -R dist/zero-hermes/.hermes/plugins/zero ~/.hermes/plugins/ +cp -R dist/zero-hermes/.hermes/skills/zero ~/.hermes/skills/ +hermes plugins enable zero +hermes mcp add zero --url https://mcp.zero.xyz --auth oauth +``` + +Restart Hermes, then ask: *"Help me set up and test Zero."* + +## Staying up to date + +The Zero runner updates when the Hermes plugin's session-start hook runs. To +refresh the Hermes adapter files, re-run: + +```bash +scripts/build-hermes.sh +cp -R dist/zero-hermes/.hermes/plugins/zero ~/.hermes/plugins/ +cp -R dist/zero-hermes/.hermes/skills/zero ~/.hermes/skills/ +``` diff --git a/plugins/zero-gemini/gemini-extension.json b/plugins/zero-gemini/gemini-extension.json index 69ea920..ac48f52 100644 --- a/plugins/zero-gemini/gemini-extension.json +++ b/plugins/zero-gemini/gemini-extension.json @@ -1,6 +1,6 @@ { "name": "zero", - "version": "1.3.3", + "version": "1.4.0", "description": "Discover and call paid AI capabilities (x402 / MPP) with automatic, wallet-backed payment. Search a live index of external services — image/video/audio generation, web scraping, data enrichment, real-time data, messaging — then call them and pay per use. Bundles the 'zero' skill and the Zero MCP connector.", "mcpServers": { "zero": { diff --git a/plugins/zero-hermes/README.md b/plugins/zero-hermes/README.md new file mode 100644 index 0000000..cfe361a --- /dev/null +++ b/plugins/zero-hermes/README.md @@ -0,0 +1,38 @@ +# zero-hermes (overlay - not directly installable) + +This directory is **not** a complete Hermes Agent install on its own. It holds +the Hermes-specific files: + +- `manifest.json` - versioned release marker for this adapter +- `hermes/plugin.yaml` - Hermes plugin manifest +- `hermes/__init__.py` - plugin registration for hooks, `/zero`, and the + plugin-namespaced Zero skill +- `hermes/mcp-zero.yaml` - config fragment for the Zero MCP server + +The `zero` skill and `ensure-runner.sh` hook script are copied from +[`../zero/`](../zero/) during packaging. `scripts/build-hermes.sh` rewrites the +skill frontmatter for Hermes while keeping the shared Zero instructions body. + +## How the adapter is built + +Hermes reads plugins from `~/.hermes/plugins//` and normal skills from +`~/.hermes/skills///`. Plugins are opt-in, so users enable the +Zero plugin with `hermes plugins enable zero` after copying it into place. + +`scripts/build-hermes.sh` assembles those pieces into `dist/zero-hermes/`: + +``` +dist/zero-hermes/ + ├── .hermes/plugins/zero/__init__.py + ├── .hermes/plugins/zero/hooks/ensure-runner.sh + ├── .hermes/plugins/zero/plugin.yaml + ├── .hermes/plugins/zero/skills/zero/SKILL.md + ├── .hermes/skills/zero/zero/SKILL.md + ├── hermes/mcp-zero.yaml + └── manifest.json +``` + +Hermes has plugin hooks, so this adapter provisions the Zero runner on session +start and injects Zero context through `pre_llm_call`. Hermes does not use hook +output for auto-approve decisions; the `pre_tool_call` hook only blocks +`zero fetch` commands that omit an explicit `--max-pay` cap. diff --git a/plugins/zero-hermes/hermes/__init__.py b/plugins/zero-hermes/hermes/__init__.py new file mode 100644 index 0000000..b89e725 --- /dev/null +++ b/plugins/zero-hermes/hermes/__init__.py @@ -0,0 +1,103 @@ +"""Zero plugin for Hermes Agent.""" + +from __future__ import annotations + +import logging +import re +import subprocess +from pathlib import Path + +LOGGER = logging.getLogger(__name__) +PLUGIN_DIR = Path(__file__).resolve().parent + +ZERO_CONTEXT = ( + "Zero is available: a search engine and payment layer for capabilities " + "beyond Hermes' native tools. Before saying you cannot do something, or " + "telling the user to sign up for an external service, use the zero skill " + "and runner flow: search, inspect, call with an explicit max-pay cap, and " + "review successful paid runs when appropriate." +) + +ZERO_FETCH_RE = re.compile( + r"(^|[;&|]\s*|\s)(?:[A-Za-z_][A-Za-z0-9_]*=\S+\s+)*" + r"(?:zero|zerocli|(?:\$HOME|~)?/[^;&|'\"]*/zero)\s+fetch\b" +) + + +def _ensure_runner(**kwargs): + """Provision the shared Zero runner without breaking Hermes startup.""" + del kwargs + script = PLUGIN_DIR / "hooks" / "ensure-runner.sh" + if not script.exists(): + LOGGER.warning("Zero ensure-runner hook missing: %s", script) + return None + + try: + subprocess.run( + ["bash", str(script)], + check=False, + timeout=120, + stdout=subprocess.DEVNULL, + stderr=subprocess.PIPE, + text=True, + ) + except Exception as exc: # pragma: no cover - defensive hook boundary + LOGGER.warning("Zero ensure-runner hook failed: %s", exc) + return None + + +def _inject_zero_context(**kwargs): + del kwargs + return {"context": ZERO_CONTEXT} + + +def _zero_guardrail(tool_name: str, args: dict, **kwargs): + """Hermes can block tools here, but it cannot auto-approve them.""" + del kwargs + if tool_name != "terminal" or not isinstance(args, dict): + return None + + command = str(args.get("command") or args.get("cmd") or "") + if ZERO_FETCH_RE.search(command) and "--max-pay" not in command: + return { + "action": "block", + "message": "Zero fetch commands must include an explicit --max-pay cap.", + } + return None + + +def _handle_zero(raw_args: str) -> str: + request = raw_args.strip() + if request: + return ( + "Use the zero skill for this request. Resolve the zero runner, run " + "`zero search`, inspect with `zero get`, and only use `zero fetch` " + f"with an explicit `--max-pay` cap. User request: {request}" + ) + return ( + "Use the zero skill. Resolve the zero runner, run `zero search`, inspect " + "with `zero get`, and only use `zero fetch` with an explicit `--max-pay` cap." + ) + + +def _register_skills(ctx): + skills_dir = PLUGIN_DIR / "skills" + if not skills_dir.exists(): + return + for child in sorted(skills_dir.iterdir()): + skill_md = child / "SKILL.md" + if child.is_dir() and skill_md.exists(): + ctx.register_skill(child.name, skill_md) + + +def register(ctx): + ctx.register_hook("on_session_start", _ensure_runner) + ctx.register_hook("pre_llm_call", _inject_zero_context) + ctx.register_hook("pre_tool_call", _zero_guardrail) + ctx.register_command( + "zero", + handler=_handle_zero, + description="Use Zero for external capabilities", + args_hint="", + ) + _register_skills(ctx) diff --git a/plugins/zero-hermes/hermes/mcp-zero.yaml b/plugins/zero-hermes/hermes/mcp-zero.yaml new file mode 100644 index 0000000..aa48727 --- /dev/null +++ b/plugins/zero-hermes/hermes/mcp-zero.yaml @@ -0,0 +1,7 @@ +mcp_servers: + zero: + url: "https://mcp.zero.xyz" + auth: oauth + enabled: true + timeout: 120 + connect_timeout: 60 diff --git a/plugins/zero-hermes/hermes/plugin.yaml b/plugins/zero-hermes/hermes/plugin.yaml new file mode 100644 index 0000000..1af3676 --- /dev/null +++ b/plugins/zero-hermes/hermes/plugin.yaml @@ -0,0 +1,11 @@ +name: zero +version: 1.0.0 +description: Zero capability search and payment layer for Hermes Agent +provides_hooks: + - on_session_start + - pre_llm_call + - pre_tool_call +provides_commands: + - zero +provides_skills: + - zero diff --git a/plugins/zero-hermes/manifest.json b/plugins/zero-hermes/manifest.json new file mode 100644 index 0000000..bf5620f --- /dev/null +++ b/plugins/zero-hermes/manifest.json @@ -0,0 +1,8 @@ +{ + "name": "zero-hermes", + "version": "1.4.0", + "description": "Hermes Agent adapter for Zero: plugin hooks, slash command, Hermes skill package, and MCP config fragment.", + "homepage": "https://zero.xyz", + "repository": "https://github.com/officialzeroxyz/zero-plugins", + "license": "MIT" +} diff --git a/plugins/zero/.claude-plugin/plugin.json b/plugins/zero/.claude-plugin/plugin.json index 6b83198..59e52a7 100644 --- a/plugins/zero/.claude-plugin/plugin.json +++ b/plugins/zero/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "zero", - "version": "1.3.3", + "version": "1.4.0", "description": "Discover and call paid AI capabilities (x402 / MPP) with automatic, wallet-backed payment. Search a live index of external services — image/video/audio generation, web scraping, data enrichment, real-time data, messaging — then call them and pay per use. Bundles the 'zero' skill and the Zero MCP connector.", "author": { "name": "Zero", "url": "https://zero.xyz" }, "homepage": "https://zero.xyz", diff --git a/plugins/zero/.codex-plugin/plugin.json b/plugins/zero/.codex-plugin/plugin.json index e90630a..3a8d304 100644 --- a/plugins/zero/.codex-plugin/plugin.json +++ b/plugins/zero/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "zero", - "version": "1.3.3", + "version": "1.4.0", "description": "Discover and call paid AI capabilities (x402 / MPP) with automatic, wallet-backed payment. Search a live index of external services — image/video/audio generation, web scraping, data enrichment, real-time data, messaging — then call them and pay per use. Bundles the 'zero' skill.", "author": { "name": "Zero", "url": "https://zero.xyz" }, "homepage": "https://zero.xyz", diff --git a/plugins/zero/.factory-plugin/plugin.json b/plugins/zero/.factory-plugin/plugin.json index 6b83198..59e52a7 100644 --- a/plugins/zero/.factory-plugin/plugin.json +++ b/plugins/zero/.factory-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "zero", - "version": "1.3.3", + "version": "1.4.0", "description": "Discover and call paid AI capabilities (x402 / MPP) with automatic, wallet-backed payment. Search a live index of external services — image/video/audio generation, web scraping, data enrichment, real-time data, messaging — then call them and pay per use. Bundles the 'zero' skill and the Zero MCP connector.", "author": { "name": "Zero", "url": "https://zero.xyz" }, "homepage": "https://zero.xyz", diff --git a/scripts/build-hermes.sh b/scripts/build-hermes.sh new file mode 100755 index 0000000..daad2a4 --- /dev/null +++ b/scripts/build-hermes.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# +# Assemble the Hermes Agent adapter into dist/zero-hermes/. +# +# Usage: +# scripts/build-hermes.sh # assemble dist/zero-hermes/ +# scripts/build-hermes.sh --tar OUT.tgz # also write a tarball + +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +SHARED="$ROOT/plugins/zero" +OVERLAY="$ROOT/plugins/zero-hermes" +OUT="$ROOT/dist/zero-hermes" + +TARBALL="" +if [ "${1:-}" = "--tar" ]; then + TARBALL="${2:?--tar requires an output path}" +fi + +for f in \ + "$OVERLAY/manifest.json" \ + "$OVERLAY/hermes/plugin.yaml" \ + "$OVERLAY/hermes/__init__.py" \ + "$OVERLAY/hermes/mcp-zero.yaml" \ + "$SHARED/skills/zero/SKILL.md" \ + "$SHARED/hooks/ensure-runner.sh"; do + [ -f "$f" ] || { echo "build-hermes: missing required input: $f" >&2; exit 1; } +done + +rm -rf "$OUT" +mkdir -p \ + "$OUT/.hermes/plugins/zero/hooks" \ + "$OUT/.hermes/plugins/zero/skills/zero" \ + "$OUT/.hermes/skills/zero/zero" \ + "$OUT/hermes" + +write_hermes_skill() { + local dest="$1" + cat > "$dest" <<'HEADER' +--- +name: zero +description: Use Zero for capabilities beyond Hermes. +version: 1.0.0 +author: Zero +license: MIT +platforms: [linux, macos, windows] +metadata: + hermes: + tags: [zero, capability-search, mcp, x402] + category: automation +--- +HEADER + awk ' + NR == 1 && $0 == "---" { in_frontmatter = 1; next } + in_frontmatter && $0 == "---" { in_frontmatter = 0; next } + !in_frontmatter { print } + ' "$SHARED/skills/zero/SKILL.md" >> "$dest" +} + +cp "$OVERLAY/manifest.json" "$OUT/manifest.json" +cp "$OVERLAY/hermes/plugin.yaml" "$OUT/.hermes/plugins/zero/plugin.yaml" +cp "$OVERLAY/hermes/__init__.py" "$OUT/.hermes/plugins/zero/__init__.py" +cp "$OVERLAY/hermes/mcp-zero.yaml" "$OUT/hermes/mcp-zero.yaml" +cp "$SHARED/hooks/ensure-runner.sh" "$OUT/.hermes/plugins/zero/hooks/ensure-runner.sh" +write_hermes_skill "$OUT/.hermes/plugins/zero/skills/zero/SKILL.md" +write_hermes_skill "$OUT/.hermes/skills/zero/zero/SKILL.md" +chmod +x "$OUT/.hermes/plugins/zero/hooks/ensure-runner.sh" + +echo "build-hermes: assembled $OUT" +find "$OUT" -type f | sed "s#^$ROOT/##" | sort + +if [ -n "$TARBALL" ]; then + mkdir -p "$(dirname "$TARBALL")" + tar -czf "$TARBALL" -C "$OUT" . + echo "build-hermes: wrote $TARBALL" +fi