Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .github/workflows/hermes-plugin-sync.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Hermes plugin sync

# The Hermes plugin installs straight from this repo's subdirectory
# (plugins/zero-hermes/hermes/), and a subdirectory install ships only what is
# checked in there — it cannot reference plugins/zero/ at runtime. The shared
# hook script and skill are therefore vendored into the plugin directory by
# scripts/sync-hermes.sh. This check fails any PR where the vendored copies
# drift from their sources.
on:
pull_request:
paths:
- 'plugins/zero/hooks/ensure-runner.sh'
- 'plugins/zero/skills/zero/SKILL.md'
- 'plugins/zero-hermes/**'
- 'scripts/sync-hermes.sh'
push:
branches: [main]
paths:
- 'plugins/zero/hooks/ensure-runner.sh'
- 'plugins/zero/skills/zero/SKILL.md'
- 'plugins/zero-hermes/**'
- 'scripts/sync-hermes.sh'

permissions:
contents: read

jobs:
hermes-plugin-sync:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Verify vendored Hermes plugin files are in sync
run: scripts/sync-hermes.sh --check
9 changes: 6 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ 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 \
plugins/zero-hermes/hermes/plugin.yaml

# The manifest read for the current version (all of MANIFESTS stay in lockstep).
VERSION_SOURCE := plugins/zero/.claude-plugin/plugin.json
Expand Down Expand Up @@ -58,9 +60,10 @@ bump:
next="$$major.$$minor.$$patch"; \
for m in $(MANIFESTS); do \
[ -f "$$m" ] || { echo "make: missing manifest: $$m" >&2; exit 1; }; \
grep -q '"version"' "$$m" || { echo "make: no \"version\" key in $$m" >&2; exit 1; }; \
grep -Eq '"version"|^version:' "$$m" || { echo "make: no version key in $$m" >&2; exit 1; }; \
tmp=$$(mktemp); \
sed 's/\("version"[[:space:]]*:[[:space:]]*"\)[^"]*"/\1'"$$next"'"/' "$$m" > "$$tmp" && mv "$$tmp" "$$m"; \
sed -e 's/\("version"[[:space:]]*:[[:space:]]*"\)[^"]*"/\1'"$$next"'"/' \
-e 's/^version:[[:space:]]*.*/version: '"$$next"'/' "$$m" > "$$tmp" && mv "$$tmp" "$$m"; \
echo " $$m -> $$next"; \
done; \
echo "Bumped $$current -> $$next"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ installation, use the guides below. Pick your agent:
- **[Gemini CLI](guides/gemini-cli.md)**
- **[GitHub Copilot CLI](guides/generic.md)**
- **[Goose](guides/generic.md)**
- **[Hermes Agent](guides/generic.md)**
- **[Hermes Agent](guides/hermes-agent.md)**
- **[Kilo Code](guides/generic.md)**
- **[Kiro](guides/generic.md)**
- **[OpenCode](guides/generic.md)**
Expand Down
22 changes: 14 additions & 8 deletions guides/agent-install.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,30 @@ The default path is npm — it works in any harness with Node.js:
| Claude Code | `claude plugin marketplace add officialzeroxyz/zero-plugins && claude plugin install zero@zero-plugins` |
| Codex | `codex plugin marketplace add officialzeroxyz/zero-plugins && codex plugin add zero@zero-plugins` |
| Gemini CLI | `gemini extensions install https://github.com/officialzeroxyz/zero-plugins` |
| Hermes Agent | `hermes plugins install officialzeroxyz/zero-plugins/plugins/zero-hermes/hermes --force --enable` |
| Claude Chat (claude.ai) / Claude Cowork | no shell — follow [zero.xyz/install/claude.md](https://www.zero.xyz/install/claude.md) instead |
| None of the above | see [Fallback installer](#fallback-installer) |

The npm row installs the CLI from the public
[`@zeroxyz/cli`](https://www.npmjs.com/package/@zeroxyz/cli) package (inspect
it with `npm view @zeroxyz/cli`), then `zero init` integrates the skill and
hooks with every harness it detects. The Claude Code, Codex, and Gemini CLI
rows are per-harness alternatives that install through that harness's own
plugin manager: they add the zero-plugins repo as a source, then install the
Zero plugin from it via the manager's normal review-and-consent flow. The
plugin's full contents are listed below. The npm and fallback rows end by
running `zero init`.
hooks with every harness it detects. The Claude Code, Codex, Gemini CLI, and
Hermes Agent rows are per-harness alternatives that install through that
harness's own plugin manager: they add the zero-plugins repo as a source (or,
for Hermes, install straight from a subdirectory of it), then install the
Zero plugin via the manager's normal review-and-consent flow. The plugin's
full contents are listed below. The npm and fallback rows end by running
`zero init`.

## What the plugin installs

All three plugin-manager rows install the same contents, from
The plugin-manager rows install the same contents, from
[`plugins/zero/`](https://github.com/officialzeroxyz/zero-plugins/tree/main/plugins/zero)
in the repo:
in the repo (Hermes Agent installs the same skill and session-start hook from
[`plugins/zero-hermes/hermes/`](https://github.com/officialzeroxyz/zero-plugins/tree/main/plugins/zero-hermes/hermes),
where they are vendored for its plugin format; instead of an auto-approve
hook — Hermes hooks can't approve — it adds a guard that blocks `zero fetch`
commands missing an explicit `--max-pay` cap, plus a `/zero` command):

- **A skill** — the usage guide for the `zero` CLI
([source](https://github.com/officialzeroxyz/zero-plugins/blob/main/plugins/zero/skills/zero/SKILL.md))
Expand Down
42 changes: 42 additions & 0 deletions guides/hermes-agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Zero for Hermes Agent

How to install Zero in Hermes Agent and keep it up to date.

Requires Hermes Agent v2026.6.19 or newer (`hermes --version`).

## Install

### From the terminal

```bash
hermes plugins install officialzeroxyz/zero-plugins/plugins/zero-hermes/hermes --force --enable
```

This installs and enables the Zero plugin (hooks, the `/zero` command, and
the Zero skill) from this repository.

Start a new Hermes session, then use `/zero` or ask Hermes:
*"Help me set up and test Zero."* It walks you through signing in with
`zero auth login` — a device-code login persisted to `~/.zero`, shared with
every other Zero install on the machine.

If you run Hermes in an ephemeral environment where a session can't persist
to disk, add the Zero MCP server instead and authorize through it:

```bash
hermes mcp add zero --url https://mcp.zero.xyz --auth oauth
```

## Staying up to date

- The Zero runner (the `zero` CLI) updates at the start of each session.
- To update the plugin itself, re-run the install command — with `--force`
it replaces the installed copy with the latest:

```bash
hermes plugins install officialzeroxyz/zero-plugins/plugins/zero-hermes/hermes --force --enable
```

Note: `hermes plugins update zero` does not work for this plugin — Hermes
installs it from a repo subdirectory, which keeps no `.git` folder to pull
from. The `--force` reinstall above is the update path.
2 changes: 1 addition & 1 deletion plugins/zero-gemini/gemini-extension.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "zero",
"version": "1.10.0",
"version": "1.11.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": {
Expand Down
44 changes: 44 additions & 0 deletions plugins/zero-hermes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# zero-hermes

Hermes Agent adapter for Zero. The installable plugin lives at
[`hermes/`](hermes/) — Hermes installs it straight from this repo's
subdirectory:

```bash
hermes plugins install officialzeroxyz/zero-plugins/plugins/zero-hermes/hermes --force --enable
```

Requires Hermes **v2026.6.19 or newer** (the first release with subdirectory
installs). `--enable` skips the only prompt; `--force` makes the command
idempotent, so re-running it is also how the plugin is updated (a subdirectory
install keeps no `.git`, so `hermes plugins update zero` does not apply — see
the guide).

## Layout

- `manifest.json` — versioned release marker for this adapter (kept in
lockstep by the Makefile)
- `hermes/plugin.yaml` — Hermes plugin manifest (`name: zero`, so the plugin
installs as `~/.hermes/plugins/zero`)
- `hermes/__init__.py` — plugin registration: hooks, the `/zero` slash
command, and the plugin-namespaced `zero:zero` skill
- `hermes/mcp-zero.yaml` — config fragment for the Zero MCP server
- `hermes/hooks/ensure-runner.sh`, `hermes/skills/zero/SKILL.md` — **vendored
copies** of the shared files in [`../zero/`](../zero/), generated by
[`scripts/sync-hermes.sh`](../../scripts/sync-hermes.sh). A subdirectory
install ships only this directory, so the plugin cannot reference
`plugins/zero/` at runtime. Do not edit the vendored files directly — edit
the sources and re-run the sync script; CI
(`.github/workflows/hermes-plugin-sync.yml`) fails on drift.

## What the plugin registers

- `on_session_start` — runs `ensure-runner.sh` to provision the shared Zero
runner (`~/.zero/runtime/`)
- `pre_llm_call` — injects a one-line reminder that Zero is available
- `pre_tool_call` — blocks `zero fetch` terminal commands that omit an
explicit `--max-pay` cap (Hermes hooks can block but not auto-approve)
- `/zero` — slash command that routes a request through the zero skill
- `zero:zero` — the Zero skill, plugin-namespaced (explicit loads only; the
flat `~/.hermes/skills` copy that `zero init` writes remains the
discoverable one)
103 changes: 103 additions & 0 deletions plugins/zero-hermes/hermes/__init__.py
Original file line number Diff line number Diff line change
@@ -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="<request>",
)
_register_skills(ctx)
Loading
Loading