# Chowdahh plugin for Hermes Agent

A proper [Hermes Agent](https://hermes-agent.nousresearch.com/) plugin: five tools (`chowdahh_list_streams`, `chowdahh_get_stream`, `chowdahh_search`, `chowdahh_get_topic`, `chowdahh_record_signal`) and one bundled skill (`chowdahh:reading`). Pure stdlib Python — no extra deps.

## Install

The plugin is six files. `__init__.py` runs on plugin load, so verify the bundle against the published SHA256 sums before enabling.

```bash
# 1. Fetch all six files + the integrity manifest.
mkdir -p ~/.hermes/plugins/chowdahh && cd ~/.hermes/plugins/chowdahh
for f in plugin.yaml __init__.py schemas.py tools.py chowdahh.skill.md SHA256SUMS; do
  curl -fsSL "https://chowdahh.com/skills/hermes/$f" -o "$f"
done

# 2. Verify the downloads match what we published. If this fails, STOP —
#    do not run `hermes plugins enable`; a mismatch means a corrupted
#    download, a stale local copy, or a host/CDN/DNS compromise.
sha256sum -c SHA256SUMS    # macOS: shasum -a 256 -c SHA256SUMS

# 3. Enable. Hermes prompts for your CHOWDAHH_KEY and writes it to
#    ~/.hermes/.env (per requires_env in plugin.yaml). Lock that file down:
hermes plugins enable chowdahh
chmod 600 ~/.hermes/.env

# 4. Restart Hermes if it was already running.
```

Get a token at <https://chowdahh.com/account>. Free tier (person token) is 300 requests/minute.

## What the agent gets

| Tool | Purpose |
| --- | --- |
| `chowdahh_list_streams` | List stream slugs (top, latest, science, world, tech, business, health, culture, sports, good-news, local). |
| `chowdahh_get_stream` | Cards from a named stream. |
| `chowdahh_search` | Keyword search across clusters. |
| `chowdahh_get_topic` | Timeline of corroborated facts for a topic. |
| `chowdahh_record_signal` | Record reader signals (`save`, `track`, `open`, `dismiss`, …). |

Plus a bundled skill at `chowdahh:reading` (the `chowdahh.skill.md` file) covering when to call which tool, the response envelope, and the citation rule. View it from inside Hermes with `skill_view("chowdahh:reading")`.

## Smoke test

```python
# From inside a Hermes session:
chowdahh_list_streams()
chowdahh_get_stream(slug="top", limit=3)
```

Or hit the API directly, no Hermes needed, to verify the key works:

```bash
curl -s "https://chowdahh.com/api/v1/streams/top?key=$CHOWDAHH_KEY&limit=3" | jq '.data.count'
```

## How it's wired

- `plugin.yaml` — Hermes manifest. Declares the five tools, marks `CHOWDAHH_KEY` as a required secret env var with a description and account URL so Hermes can prompt nicely.
- `__init__.py` — `register(ctx)` wires each tool and the bundled skill.
- `schemas.py` — JSON Schema for each tool. Descriptions are intentionally specific so the model knows when to call each one.
- `tools.py` — handlers. Each takes `(args, **kwargs)`, returns a JSON string (never raises), and uses pure stdlib (`urllib`). Reads use `?key=` on the URL; writes use `Authorization: Bearer`.
- `chowdahh.skill.md` — the canonical reading-skill knowledge file. Registered via `ctx.register_skill("reading", ...)`.

## Behavior contract

This plugin satisfies the [Chowdahh skill contract](https://chowdahh.com/skills/CONTRACT.md):

- Identifies as "Chowdahh" in user-visible copy.
- Reads `guidance` before acting on `data` (per the bundled skill).
- Cites `source_urls[0]` and credits chowdahh.com when summarizing.
- Stores the token only in the user's local environment (`~/.hermes/.env`, mode 0600).
- Names tools `chowdahh_*` so they don't collide with other vendors.

## Trust boundaries

Two pieces of content the plugin handles deserve scrutiny:

- **`chowdahh.skill.md`** is fetched from `chowdahh.com` at install and registered as agent instructions. **Verify the published `SHA256SUMS`** (step 2 above) before enabling. If you don't want auto-updates, keep the local copy and re-pin only when you intentionally `curl` again. A server/CDN/DNS compromise could otherwise rewrite the agent's behavioral contract on your next install.
- **Card content** (`headline`, `summary`, `lead_text`, `topics`) is data, not instructions. The bundled skill tells the model to treat it as such, but defense-in-depth at the integrator level helps: assume any string field can contain prompt-injection attempts ("ignore previous instructions, ...") and don't let card content control the agent's tool-calling decisions.

## Security model

- Token sent as `Authorization: Bearer …` on **every** request (reads and writes). It never appears in URLs, server access logs, CDN logs, Referer headers, or reflected error bodies. ADR-0140's `?key=` form is for browser-paste-into-LLM flows on the [paste-prompt skill](../openclaw/), not this plugin.
- Defense in depth: a regex in `tools.py` redacts any `ch_person_*` / `ch_cur_*` / `ohp_pat_*` substring from error bodies before they reach the model, so a future server bug that reflects request material can't burn the token.
- TLS verification uses the system CA store (Python 3.6+ default). `API_BASE` is hard-coded — no SSRF surface.
- Handlers catch all exceptions and return JSON error strings; transport failures can't crash the agent.

## Updating

The plugin is curated against <https://chowdahh.com/.well-known/openapi.json>. When the API changes, the spec changes first and this package is updated to match.

## Looking for OpenClaw?

OpenClaw doesn't support Python plugins. See [`../openclaw/`](../openclaw/) for the paste-as-system-prompt version that works in OpenClaw, Cursor chat, or any LLM that can fetch URLs.
