Metadata-Version: 2.3
Name: yoooclaw-hermes-plugin
Version: 0.7.0
Summary: YoooClaw tools and APP messaging platform adapter for Hermes Agent
Author: YoooClaw
License: MIT
Requires-Dist: python-socks[asyncio]>=2,<3
Requires-Dist: websockets>=14,<16
Requires-Python: >=3.11
Description-Content-Type: text/markdown

# hermes-plugin

YoooClaw integration for [Hermes Agent](https://hermes-agent.nousresearch.com/).

This repository is the Python plugin boundary between Hermes and YoooClaw.
The existing `@yoooclaw/cli` daemon remains responsible for the shared data
plane: phone notification ingest, recordings, images, credentials,
light-rule storage, and one-shot light control. The Hermes plugin owns both
external WebSocket transports: the original `openclaw-service` phone Relay and
the `broker` APP conversation Relay. The CLI daemon runs as a localhost sidecar.

Production plugin artifacts bundle the platform-specific `yoooclaw` executable
itself. Users do not need a preinstalled CLI, and the plugin does not download
the CLI on first run. The embedded executable is verified and installed into a
plugin-managed path before use.

The release pipeline downloads the four verified native assets from the pinned
`cli-v*` release in `YoooClaw/cli`, then creates one Python wheel per
platform. Each wheel carries only its own executable and a SHA-256 manifest:

```text
darwin-arm64
darwin-x64
linux-arm64
linux-x64
win32-x64
```

The build job emits the wheels, `cli-wheel-index.json`, and `SHA256SUMS` once,
then promotes that exact `dist/` bundle through the release flow: test OSS
first, production OSS after approval, and finally PyPI / GitHub Release for
stable versions. Installers and channel markers remain environment-specific and
are regenerated at deploy time. This keeps the Git repository small while
guaranteeing that QA verifies the same bytes we later ship.

## One-command install

Install the plugin, store the YoooClaw API key, enable the Hermes tool plugin
and APP adapter, and restart the gateway:

```bash
curl -fsSL https://artifact.yoooclaw.com/hermes-plugin/install.sh | bash -s -- --api-key "ock_..."
```

Windows (PowerShell):

```powershell
& ([scriptblock]::Create((irm https://artifact.yoooclaw.com/hermes-plugin/install.ps1))) --api-key ock_...
```

Re-run the same command to update. The PowerShell installer resolves the
matching `win_amd64` wheel from OSS, locates the Hermes Agent virtualenv under
the standard Windows install locations, bootstraps `pip` with `ensurepip` when
needed, and installs with `pip install --upgrade`.

The installer requires Hermes Agent `>= 0.14.0`. Older Hermes builds do not
provide the plugin platform API (`PluginContext.register_platform`) used by the
APP adapter, so the installer stops with an upgrade message instead of leaving a
half-enabled plugin behind.

The installer writes the shared key to `~/.yoooclaw/credentials.json`, removes
stale API-key and legacy local APP authorization environment overrides from
`~/.hermes/.env`, and ensures both `yoooclaw` and `yoooclaw_app` are present in
`plugins.enabled` in `~/.hermes/config.yaml`. Pass `--no-enable-app` or set
`YOOOCLAW_HERMES_ENABLE_APP=0` for a tools-only install. It also installs the
bundled `yoooclaw`/`yc` CLI
artifact into `~/.yoooclaw/hermes-plugin/bin`, maintains managed shims, and links
those commands next to the detected `hermes` executable when that directory is
writable. Set `YOOOCLAW_HERMES_CLI_LINK_DIR` to choose a different link
directory, or `YOOOCLAW_HERMES_INSTALL_CLI=0` to skip shell command links. After
the gateway restart, the installer ensures the YoooClaw daemon is running; pass
`--no-start-daemon` or set `YOOOCLAW_HERMES_START_DAEMON=0` to skip that step.

The installer keeps the daemon, openclaw-service Relay tunnel, and APP adapter
lifecycle aligned so the APP can connect back to Hermes immediately after
install.

Run `scripts/install.sh --help` or `powershell -NoProfile -File
scripts/install.ps1 --help` for local development options such as
`--skip-install`, `--package`, `--oss-base-url`, `--version`, `--python`,
`--no-restart`, and `--no-start-daemon`. The source-tree installer defaults to
PyPI unless a local Windows wheel sits next to `install.ps1`; the installers
uploaded by CI are rendered with the OSS base URL from `.env` / GitHub Secrets
and install the matching platform wheel directly from OSS.

## Manual install

Stable plugin releases also publish the platform wheels to PyPI. Hermes
discovers both entry-point plugins after a normal package install into the same
Python environment Hermes uses:

```bash
pip install yoooclaw-hermes-plugin
```

> **Enabling pip-installed plugins on Hermes 0.15.1 (and earlier).** The
> `hermes plugins enable <name>` / `disable` / `list` commands only recognize
> directory-based (`~/.hermes/plugins/`) and bundled plugins — they do not scan
> Python entry points, so `hermes plugins enable yoooclaw` fails with
> "Plugin 'yoooclaw' is not installed or bundled." The runtime loader *does*
> load entry-point plugins, but only when their names appear in the
> `plugins.enabled` allow-list. For a pip install, enable them by editing
> `~/.hermes/config.yaml` directly:
>
> ```yaml
> plugins:
>   enabled: [yoooclaw, yoooclaw_app]
> ```
>
> Then restart the gateway (`hermes gateway restart`). If a future Hermes
> release teaches `hermes plugins enable` to recognize entry-point plugins,
> the CLI commands below will work as written. The one-command installer above
> applies this config-file enablement automatically.

## Release flow

This repository now uses artifact promotion instead of rebuilding at each
environment boundary:

1. Push to `release/**` runs `test-oss.yml` and uploads a cache-busted build to
   the test OSS bucket. A second upload refreshes the stable test update
   channel without replacing the unique install URL. Pure `pyproject.toml`
   version bumps are ignored so a release commit does not double-publish.
2. Push tag `v*` runs `release.yml`: `build` creates the wheels once, writes
   `SHA256SUMS`, and stores the bundle as a GitHub Actions artifact.
   `deploy-test` promotes that bundle to the stable test OSS prefix and sends
   an "RC pending manual promotion" notification with the exact version number
   to pass into the next step.
3. After QA signs off, run `promote-prod.yml` manually (`workflow_dispatch`)
   from GitHub Actions. It auto-discovers the successful `release.yml` run for
   `v{version}`, downloads that exact artifact, re-verifies `SHA256SUMS`, then
   promotes it to production OSS. Stable releases continue on to PyPI through
   the existing `pypi` environment and then publish the GitHub Release.
4. `rollback.yml` is a manual `workflow_dispatch` rollback. It repoints the
   `latest` or `beta` marker to a previous version and restores the archived
   installers from `v{version}/installer/install.sh` and
   `v{version}/installer/install.ps1` when those archives exist.

Repository settings still matter:

- `test` / `production` environment secrets hold the OSS credentials used by
  the deploy and rollback workflows; `OPENCLAW_RELEASE_TOKEN` stays build-only.
- The manual approval fallback is the `promote-prod.yml` dispatch itself, so it
  works even when private-repository required reviewers are unavailable on the
  current GitHub plan.
- The `pypi` environment remains the OIDC boundary for trusted publishing.

## Features

- General Hermes plugin: tools, hooks, slash commands, CLI subcommands, skills.
- `yoooclaw_app` platform adapter: APP messages into Hermes and Hermes replies
  back to the APP.
- Tool bridge: call `yc --format json` and the daemon localhost HTTP API.
- Embedded CLI: ship the target platform's `yoooclaw` executable in the plugin
  artifact and use PATH lookup only as a local development fallback.
- APP transport: keep a single hosted WebSocket family open in the plugin
  process — the `openclaw-service*.yoooclaw.com` tunnels (one per apiKey). They
  carry Hermes APP chat RPC frames and phone notification / recording / image
  relay frames on the same connection; the APP protocol layer claims its
  whitelisted frames first and everything else is bridged into the local CLI
  sidecar.
- Local relay server and browser chat UI for end-to-end APP conversation tests.
- Notification watcher: use OS file notifications on the active CLI profile's
  `notifications/` directory to feed newly persisted items into the host-side
  light-rule runtime without adding a daemon event API.

The tool plugin exposes notification, recording, image, Relay, light-control,
light-rule CRUD, daemon status, and doctor tools. The APP platform adapter
connects directly to Relay and translates the existing OpenClaw-compatible
`chat.send`, `chat.history`, `chat.abort`, and `sessions.*` RPC frames into
Hermes messages and APP-owned session state.

## APP Authentication

APP user authentication is enforced by the Relay service. The Hermes plugin no
longer keeps a separate local APP user allowlist.

The APP adapter resolves its Relay key from `~/.yoooclaw/credentials.json` by
default — the same source the `yoooclaw` cli / base daemon reads, so the plugin
and the cli stay on the same key. Stale `YOOOCLAW_APP_API_KEY` /
`YOOOCLAW_API_KEY` values in the Hermes environment are ignored and stripped by
the installer.

APP chat rides the openclaw-service Relay tunnel, so the endpoint follows
`PHONE_NOTIFICATIONS_ENV` / the active profile exactly like the CLI:

| `PHONE_NOTIFICATIONS_ENV` | Relay host |
| --- | --- |
| `development` | `openclaw-service-dev.yoooclaw.com` |
| `test` | `openclaw-service-test.yoooclaw.com` |
| `production` (default) | `openclaw-service.yoooclaw.com` |

Unset or unknown values fall back to the active profile's environment, then
`production`. `YOOOCLAW_OPENCLAW_RELAY_URL` overrides the tunnel URL for local
or staging verification. The retired `YOOOCLAW_APP_RELAY_URL` variable is dead
and is stripped from `.env` by the installer.

## Switching environments

The base daemon's Relay endpoint is per `yoooclaw` profile (`relay.url` in each
profile's `config.json`). Use `env` to switch the active profile and restart the
managed daemon/websocket service group in one step:

CLI profiles use `openclaw-service*.yoooclaw.com/message/messages/ws/plugin` for
the Relay tunnel; APP chat shares the same connection. The installer still
repairs stale legacy `broker*.yoooclaw.com` URLs if an old plugin version
persisted them into CLI profiles.

```bash
hermes yoooclaw env            # show the active environment + Relay status
hermes yoooclaw env test       # switch to the `test` profile and restart the daemon
hermes yc env test             # equivalent short alias
hermes yoooclaw env default    # switch back to the `default` (prod) profile
```

`/yoooclaw env <profile>` and `/yc env <profile>` do the same from a chat
session. Built-in `default`, `test`, and `development` profiles are initialized
on first use with the matching Relay endpoint. The switch stops the previous
profile's daemon before starting the target's, so exactly one daemon runs; an
unknown profile name fails without disturbing the running daemon. The Relay
apiKey stays account-global (`~/.yoooclaw/credentials.json`); switch it with
`yoooclaw auth set-default-api-key <label>` when an environment needs a different
key.

## Lifecycle

Hermes manages the YoooClaw CLI daemon plus the openclaw-service Relay tunnels
as one service group. The CLI daemon runs as a local sidecar in
`--ingress proxied` mode, so it exposes ingest APIs without opening its own
Relay tunnel. The plugin owns
`openclaw-service*.yoooclaw.com/message/messages/ws/plugin` (one tunnel per
apiKey), carrying Hermes APP chat / session RPC, phone notifications,
recordings, images, and daemon egress events.

On startup the plugin computes a lifecycle generation from the plugin version,
embedded CLI binary, active profile, Relay environment, openclaw-service URL,
API-key fingerprint, proxied ingress mode, and the local egress callback. If a
daemon is already running but does not advertise the same
`owner=hermes-plugin` generation and `ingressMode=proxied` contract, the plugin
stops it, starts the current embedded CLI daemon, waits for readiness, and then
connects the openclaw-service tunnels. On shutdown it disconnects the tunnels
first and then stops the owned daemon.

In proxied mode, openclaw-service Relay frames are forwarded to the daemon's
localhost ingest/gateway endpoints with the same internal relay headers the CLI
dispatcher used (`x-openclaw-relay-internal` and the api-key label). Daemon
egress events such as `recording.status` POST back to a token-protected
`127.0.0.1` callback owned by the plugin, then the plugin broadcasts them over
the openclaw-service Relay websocket.

Relay-provided image and recording URLs are downloaded only over HTTP(S), with
redirect validation, public-address checks, size limits, and atomic file
replacement. Local/private-network integration tests must opt in explicitly
with `YOOOCLAW_HERMES_ALLOW_PRIVATE_DOWNLOADS=1`; do not enable that override in
normal deployments.

Use the lifecycle diagnostics when debugging upgrade or connection problems:

```bash
hermes yoooclaw lifecycle status
hermes yoooclaw lifecycle restart
hermes yc lifecycle status
```

The APP adapter also runs a watchdog while connected. If the daemon disappears
or its generation changes, the adapter restarts the whole service group. If
the Relay websocket remains disconnected past
`YOOOCLAW_HERMES_WS_RESTART_AFTER` (default 30 seconds), it also restarts the group. Set
`YOOOCLAW_HERMES_LIFECYCLE_WATCH_INTERVAL` to tune the watchdog interval
(default 5 seconds). Failed recoveries use exponential backoff capped by
`YOOOCLAW_HERMES_LIFECYCLE_MAX_BACKOFF` (default 60 seconds). If a Relay
connection still fails, the watchdog restores the local daemon independently
and keeps retrying the remote connections, so a transient network outage cannot
leave the daemon stopped.

## Logging

The plugin uses an OpenClaw-style file logger in addition to Hermes' normal
logging pipeline. Logs are written daily under:

```text
~/.yoooclaw/plugins/yoooclaw-hermes/logs/YYYY-MM-DD.log
```

Set `YOOOCLAW_HERMES_LOG_DIR` to override the log directory. Stable releases
redact common secrets, user text, user URLs, emails, phone numbers, bearer
tokens, JWTs, and long hex tokens before writing files or passing records to
Hermes' upstream logging handlers. Versions containing `beta` keep raw logs for
debugging, matching the OpenClaw plugin behavior. Log files older than 30 days
are pruned automatically; override with
`YOOOCLAW_HERMES_LOG_RETENTION_DAYS`.

Search recent plugin logs through the Hermes plugin CLI:

```bash
hermes yoooclaw logs --keyword relay --from 2026-06-01 --to 2026-06-04 --limit 50
```

The Hermes-owned foreground daemon still writes its child-process stdout/stderr
to `~/.yoooclaw/hermes-plugin/daemon-fg.log`.

## Local Relay And Web UI

For local end-to-end testing, run the Bun/Hono relay server instead of the
production Relay, then start the web test client. Both now live in their own
repository: [`YoooClaw/relay-server`](https://github.com/YoooClaw/relay-server).

```bash
git clone git@github.com:YoooClaw/relay-server.git
cd relay-server
bun install
bun run dev        # relay server on 127.0.0.1:8799
bun run web:dev    # web test client on 127.0.0.1:5173
```

Then point the plugin's Relay tunnel at it (the key comes from
`~/.yoooclaw/credentials.json`, same as production):

```bash
export YOOOCLAW_OPENCLAW_RELAY_URL="ws://127.0.0.1:8799/message/messages/ws/plugin"
hermes gateway restart
```

Ensure `yoooclaw_app` is in the `plugins.enabled` list in
`~/.hermes/config.yaml` (see the install note above; for a linked development
checkout under `~/.hermes/plugins/` you can instead run
`hermes plugins enable yoooclaw_app`).

See the `relay-server` repository README for the full list of endpoints,
configuration, and the web test client.

## Layout

```text
yoooclaw_hermes/   # General Hermes plugin
yoooclaw_app/      # Hermes platform adapter for YoooClaw APP chat
tests/
```

> The local Relay server and its web test client now live in the separate
> [`YoooClaw/relay-server`](https://github.com/YoooClaw/relay-server) repository.

## Development

Install [uv](https://docs.astral.sh/uv/getting-started/installation/) first, then:

```bash
uv sync --dev
uv run pytest
uv build
```

For local Hermes discovery, link the plugin directories into `~/.hermes/plugins/`
and enable them:

```bash
ln -s "$PWD/yoooclaw_hermes" ~/.hermes/plugins/yoooclaw
ln -s "$PWD/yoooclaw_app" ~/.hermes/plugins/yoooclaw_app
hermes plugins enable yoooclaw
hermes plugins enable yoooclaw_app
hermes plugins list
```

## References

- [Build a Hermes Plugin](https://hermes-agent.nousresearch.com/docs/guides/build-a-hermes-plugin)
- [Adding a Platform Adapter](https://hermes-agent.nousresearch.com/docs/developer-guide/adding-platform-adapters)
