The docs claimed ntfy keeps push metadata on your own infrastructure, but the default NTFY_SERVER_URL is the public https://ntfy.sh cloud service. Make explicit that push metadata (topic, notification title) passes through ntfy.sh unless you self-host ntfy.
188 lines
7.9 KiB
Markdown
188 lines
7.9 KiB
Markdown
# Iris × Hermes
|
||
|
||
A chat app for [hermes-agent](https://github.com/NousResearch/hermes-agent): a **native Android app** and a **desktop app** (Linux, macOS, Windows) — both built from one shared Kotlin codebase (Compose Multiplatform).
|
||
|
||
Iris pairs with your running `hermes gateway` over a private, token-authenticated connection and gives you a Telegram-quality chat experience with your personal agent: streaming replies, visible reasoning, structured tool activity, channels, threads, media, search, and push notifications.
|
||
|
||
## Features
|
||
|
||
- **Native Hermes-Gateway integration** — your hermes → gateway → Iris app
|
||
- **Absolute Privacy!** — chat stays on your own infrastructure
|
||
(push: ntfy by default, **but the default ntfy server is the public
|
||
`ntfy.sh`** — self-host ntfy to keep push metadata on your own machine;
|
||
FCM is opt-in and routes push metadata via Google — see
|
||
[Push notifications](#push-notifications))
|
||
- **100 MB file uploads by default** — configurable on the gateway via
|
||
`max_upload_bytes` (see [Media](docs/07-media.md) §7.7); all limits are set
|
||
on the gateway side (hermes), not in the app
|
||
- **No 4,096-character message limit like Telegram** — messages travel over
|
||
your own gateway (hard frame-body cap: 1 MiB)
|
||
- **Full markdown support** — tables, checkmarks, bold, inline code, code blocks + syntax highlighting…
|
||
- **HTML Artifact Preview** — agent-sent HTML/CSS/JS rendered in an in-app WebView
|
||
- **All settings live in the app**, not in hermes `config.yml`! Change everything on the fly.
|
||
- **Channels** — create channels to keep track of your reports, cronjobs, webhook calls
|
||
- **Pin channels, rename them, change the color/icon**
|
||
- **Customize how your chat should look like** — color? Check! Images? Check!
|
||
- **Reasoning collapse/expandable** in the chat bubble
|
||
- **Change the default behavior** of tool & reasoning verbosity
|
||
- **Threads** — create your own, or let the AI create them with a title
|
||
- **Search messages from everywhere**
|
||
|
||
## How it works
|
||
|
||
```
|
||
hermes-agent ──> hermes gateway ──(HTTP :8791)──> Iris app (Android / Desktop)
|
||
```
|
||
|
||
- `gateway-plugin/` is a hermes **platform plugin** (`android`). It runs inside the
|
||
`hermes gateway` process and opens an HTTP server the apps connect to.
|
||
Zero new Python dependencies, zero hermes-core changes.
|
||
- `app/` is one Compose Multiplatform Gradle project: `:shared` (KMP, most of the
|
||
code), `:androidApp` (native Kotlin + Jetpack Compose client), `:desktopApp`
|
||
(the same app, tweaked for a big screen).
|
||
- The app is a first-class hermes *messaging platform*, so everything the gateway
|
||
already does just works: slash commands, cron delivery, `send_message` routing,
|
||
coexistence with Telegram/Discord/etc.
|
||
- Push notifications: ntfy (default) or FCM (opt-in).
|
||
|
||
## Push notifications
|
||
|
||
Push wakes a backgrounded/offline device; on reconnect the app syncs the
|
||
outbox, so nothing is lost.
|
||
|
||
- **ntfy (default)** — the backend for truly private communication.
|
||
⚠️ **By default it uses the public `https://ntfy.sh` cloud service** — push
|
||
metadata (topic, notification title) passes through ntfy.sh's servers.
|
||
Set `NTFY_SERVER_URL` to a **self-hosted ntfy** to keep push metadata on
|
||
your own infrastructure (recommended; public ntfy.sh SSE is also flaky).
|
||
- **FCM (opt-in, `IRIS_PUSH_BACKEND=fcm`)** — standard/reliable, but FCM push
|
||
metadata (notification title, device token) is routed through **Google's
|
||
servers**. If you want truly private communication, use ntfy instead.
|
||
|
||
Setup: [`docs/install.md`](docs/install.md); details:
|
||
[`docs/08-push.md`](docs/08-push.md).
|
||
|
||
## Install the gateway
|
||
|
||
Three commands on the machine where hermes runs:
|
||
|
||
```bash
|
||
cd hermes-agent && uv sync # 1. hermes with its venv (separate project, not this repo)
|
||
|
||
# 2. install the Iris plugin — the #gateway-plugin suffix points the
|
||
# installer at the plugin subfolder of this monorepo
|
||
hermes plugins install git@gitea.zephyre.one:ARIA/iris_x_hermes.git#gateway-plugin
|
||
|
||
# 3. generate the pairing token + server URL, then run the gateway
|
||
hermes gateway setup
|
||
hermes gateway
|
||
```
|
||
|
||
`hermes gateway setup` prints the **server URL** and **pairing token / QR**
|
||
the app needs on its Connect screen.
|
||
|
||
All options (LAN binding, TLS, push, device allowlist) and the full app
|
||
pairing walkthrough: [`docs/install.md`](docs/install.md).
|
||
|
||
## Build from source
|
||
|
||
### Prerequisites
|
||
|
||
| Where | You need |
|
||
| --- | --- |
|
||
| Gateway host | [hermes-agent](https://github.com/NousResearch/hermes-agent) with its venv (`uv sync`) |
|
||
| Android build machine | JDK 17, Android SDK (`sdk.dir` in `app/local.properties` or `ANDROID_HOME`), ADB with a connected device |
|
||
| Desktop build machine | JDK 17 only |
|
||
|
||
No system Gradle needed — both apps use the project wrapper (`./gradlew`).
|
||
|
||
### 1. Gateway (on the gateway host)
|
||
|
||
If you're developing from a checkout, skip `hermes plugins install` and
|
||
symlink the plugin so it always tracks your working tree:
|
||
|
||
```bash
|
||
cd hermes-agent && uv sync
|
||
mkdir -p ~/.hermes/plugins
|
||
ln -s "$PWD/gateway-plugin" ~/.hermes/plugins/iris
|
||
hermes gateway status # should list the Iris platform
|
||
|
||
hermes gateway setup # generates IRIS_TOKEN, prints server URL + pairing QR
|
||
hermes gateway # run the gateway
|
||
```
|
||
|
||
(Otherwise see [Install the gateway](#install-the-gateway) above.)
|
||
|
||
### 2. Android app
|
||
|
||
```bash
|
||
cd app
|
||
./gradlew :androidApp:installDebug # build + install on the connected ADB device
|
||
# or just build the APK:
|
||
./gradlew :androidApp:assembleDebug # → app/androidApp/build/outputs/apk/debug/
|
||
```
|
||
|
||
### 3. Desktop app
|
||
|
||
```bash
|
||
cd app
|
||
./gradlew :desktopApp:run # dev run
|
||
./gradlew :desktopApp:jpackage # native app-image (bundles the JRE)
|
||
```
|
||
|
||
### 4. Pair
|
||
|
||
On the app's **Connect** screen:
|
||
|
||
1. **Server URL** — `http://<gateway-ip>:8791` (printed by `hermes gateway setup`).
|
||
2. **Pairing token** — from the setup output, or `IRIS_TOKEN` in `~/.hermes/.env`
|
||
on the gateway host.
|
||
3. **Test & Connect.**
|
||
|
||
Notes:
|
||
|
||
- **Android** has a **Scan QR** button that reads the QR printed by
|
||
`hermes gateway setup` and pre-fills URL + token; desktop uses manual entry.
|
||
- The default bind is `127.0.0.1` (desktop on the same machine only). For a
|
||
phone on the LAN, set `IRIS_HTTP_HOST` to the gateway's LAN IP.
|
||
- Remote access: Tailscale/WireGuard, or a reverse proxy/tunnel with TLS
|
||
(`IRIS_HTTP_CERT` / `IRIS_HTTP_KEY`).
|
||
|
||
Full walkthrough, push setup (ntfy/FCM), TLS, and troubleshooting:
|
||
[`docs/install.md`](docs/install.md).
|
||
|
||
## Contributing
|
||
|
||
Contributions are welcome! Before you start:
|
||
|
||
1. **Read the docs.** The full reference library is in [`docs/`](docs/README.md) —
|
||
start with [`docs/00-overview.md`](docs/00-overview.md), then follow the
|
||
numbered docs (architecture, wire protocol, plugin design, testing, …).
|
||
2. **Know the layout.**
|
||
|
||
| Path | What |
|
||
| --- | --- |
|
||
| `gateway-plugin/` | Python hermes platform plugin (`android`); `protocol.py` is the frame source of truth |
|
||
| `app/shared` | KMP module with most of the client code (shared by Android + Desktop) |
|
||
| `app/androidApp` | Thin Android shell (package `dev.iris.app`) |
|
||
| `app/desktopApp` | Thin desktop shell |
|
||
| `docs/` | Numbered reference library |
|
||
|
||
3. **Run the tests.**
|
||
- Python (gateway plugin): `cd hermes-agent && scripts/run_tests.sh tests/gateway/test_android.py`
|
||
(never bare `pytest` — hermes's runner sandboxes `HERMES_HOME`).
|
||
- Kotlin: `cd app && ./gradlew :shared:testDebugUnitTest`
|
||
- Live check (gateway must be running): `tests/ws_probe.py` and
|
||
`tests/e2e.py` — see [`tests/README.md`](tests/README.md).
|
||
4. **Keep the protocol in sync.** `gateway-plugin/protocol.py`,
|
||
`app/shared/.../protocol/Protocol.kt`, and `docs/protocol/frames.schema.json`
|
||
must always agree.
|
||
5. **Pre-commit hooks** are configured (`.pre-commit-config.yaml`); run
|
||
`pre-commit install` once after cloning.
|
||
|
||
Open an issue first for anything big, then send a pull request.
|
||
|
||
## License
|
||
|
||
Apache License 2.0 — see [LICENSE](LICENSE).
|