Files
iris_x_hermes/README.md
T
ARIA 7faaf2aa1c
CI / Gateway plugin tests (push) Successful in 5m5s
CI / Kotlin tests (android host + desktop) (push) Successful in 6m50s
Per-device tokens with revocation (issue #11)
Auth previously used the shared IRIS_TOKEN as the security principal:
a leaked token meant access to all devices, and a compromised device
could not be isolated.

Gateway:
- pairing.py: devices.token column (in-place migration) + revoked
  denylist table; issue_token (idempotent, 64 hex), token_for,
  reissue_token, revoke/unrevoke/is_revoked/list_revoked. The token
  never leaks into device dicts (push fan-out / listings).
- http_server.py: auth accepts the shared token (bootstrap/legacy) OR
  the device's own token (both constant-time); a revoked device_id is
  rejected with 401 before either comparison. On SSE open (pairing)
  the per-device token is minted and returned in hello.ack.
- protocol.py: hello_ack(..., device_token).
- adapter.py: setup flow (hermes gateway setup -> Iris) now offers
  'Remove a paired device?' on an existing setup: numbered select
  menu (last option = exit the removal loop), confirmation, back to
  the menu for further removals.
- tools/iris_devices.py: operator CLI (list / revoke / unrevoke /
  reissue), stdlib only.

App:
- SecureStore.deviceToken (Android: EncryptedSharedPreferences;
  Desktop: second keyring slot iris-device-token / device_token.enc).
- HelloAckPayload.deviceToken; GatewayClient stores it on hello and
  presents it instead of the shared token from then on (live provider
  in HttpGateway); savePairing/clear wipe it for re-pairing.

Docs: 09 §9.3 stretch -> implemented (revocation semantics, both
control surfaces), 04 hello.ack example, frames.schema.json, M7 row 13.

Tests: 8 new Python tests (issuance, acceptance, revocation,
isolation, unrevoke, registry unit x2, setup-flow menu) - 94/94 pass;
2 new Kotlin wire tests - green. Live-verified against a running
gateway (hello.ack token matches devices.db; revoke -> 401 even with
shared token; unrevoke -> 200; setup TUI both paths).
2026-08-24 19:37:44 +02:00

156 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 WebSocket 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!** — everything stays on your own infrastructure
(push: ntfy by default; FCM is opt-in and routes push metadata via Google —
see [Push notifications](#push-notifications))
- **No file limit**
- **No character limit**
- **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 ──(WebSocket :8790)──> Iris app (Android / Desktop)
```
- `gateway-plugin/` is a hermes **platform plugin** (`android`). It runs inside the
`hermes gateway` process and opens a WebSocket 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)** — push metadata stays on your own infrastructure
(self-hosted ntfy recommended). This is the backend for truly private
communication.
- **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/setup.md`](docs/setup.md) §4; details:
[`docs/08-push.md`](docs/08-push.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)
```bash
# hermes-agent is a separate project (not part of this repo)
cd hermes-agent && uv sync
# install the Iris plugin into the live hermes home
mkdir -p ~/.hermes/plugins
ln -s "$PWD/gateway-plugin" ~/.hermes/plugins/android
hermes gateway status # should list "android"
hermes gateway setup # generates ANDROID_TOKEN, prints the server URL
hermes gateway # run the gateway
```
### 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** — `ws://<gateway-ip>:8790/ws` (printed by `hermes gateway setup`).
2. **Pairing token** — from the setup output, or `ANDROID_TOKEN` in `~/.hermes/.env`
on the gateway host.
3. **Test & Connect.**
Notes:
- The app has **no QR scanner** — pairing is manual URL + token entry.
- The default bind is `127.0.0.1` (desktop on the same machine only). For a phone
on the LAN, set `ANDROID_WS_HOST` to the gateway's LAN IP.
- Remote access: Tailscale/WireGuard, or a reverse proxy with CA-signed WSS
(`ANDROID_WS_CERT` / `ANDROID_WS_KEY`).
Full walkthrough, push setup (ntfy/FCM), and troubleshooting:
[`docs/setup.md`](docs/setup.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): `gateway-plugin/tests/ws_probe.py` and
`gateway-plugin/tests/e2e.py` — see [`gateway-plugin/tests/README.md`](gateway-plugin/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).