* fix(#724): inline host-path MEDIA: images on mobile (client-side) The previous approach re-fetched the REST session-messages endpoint, but that endpoint returns the raw MEDIA:<path> text (api_server._message_response does not inline, and the transcript stores raw content). The desktop app instead reads the file locally via readFileDataUrl because it is co-located with the host. Mirror that on mobile: on assistant message.complete/done, inline any MEDIA:<path> image into a base64 data:image/... markdown tag the renderer can show. Mobile-only, backend untouched. Unreachable tags are stripped (graceful degrade). Pure inlining logic extracted to MediaInline for unit testing. * fix: correct RegexOption name (IGNORE_CASE) in MediaInline * test: fix byte literal types in MediaInlineTest * fix(#724): rewrite host-path MEDIA: to gateway /api/files/download URL Pivot from the earlier base64-inline approach to the desktop-proven, remote-phone-working fix: rewrite MEDIA:<path> directives (delivered raw over the WS stream) into authenticated  markdown tags, mirroring desktop media.ts mediaExternalUrl. The mobile renderer (MarkdownText) shows http(s) ![]() via Coil, so agent-sent images now render on mobile too — including on a remote phone (real HTTP, unlike a host-local file read). Mobile-only, backend untouched. Pure rewrite logic in MediaUrlRewriter (replaces MediaInline) with unit tests. * feat(#724): full gateway-file support — attach MEDIA: files (not just images) Pivot the MEDIA: fix from image-only URL rewriting to real attachments: - Attachment: add gatewayUrl + AttachmentSource (LOCAL/GATEWAY) so gateway-hosted files are first-class and render via existing InlineAttachment. - HostMediaExtractor (replaces MediaUrlRewriter): parse ANY absolute MEDIA: directive (any extension) and strip them from message text. - ChatViewModel.attachHostMedia: build the authed /api/files/download URL via GatewayFileClient, classify via MediaTypes, attach as Attachment (images inline, every other file tappable). Idempotent. - InlineAttachment: tappable. Gateway files fetch via GatewayFileClient and share via a chooser Intent (FileProvider); images load the URL in Coil. - Hence agent-delivered images, audio, video, CSV, PDF and arbitrary files all work on mobile — including a remote phone. Backend untouched. Builds on #726 (GatewayFileClient). Rebased onto main after #726 merged. * fix(#724): resolve imports, bubble callback scope, and quoted media path regex * feat(chat): open attachment in-place on tap (#724) Tap opens LOCAL (content://) and GATEWAY (fetch->FileProvider) files via ACTION_VIEW; failures surface as a non-blocking snackbar via openError. * fix: remove redundant out-of-order io.mockk.* wildcard import (ktlint CI) ktlint import-ordering requires lexicographic order; the trailing wildcard 'import io.mockk.*' sorts before 'io.mockk.coEvery' and was redundant — every mockk symbol is already explicitly imported, and the single mockkConstructor call uses a fully-qualified io.mockk. prefix (needs no import). Fixes ktlint failure on PR #725. * fix(ci): mock android.jar Intent stub so openAttachment GATEWAY test passes android.jar unit-test stubs Intent.setDataAndType/addFlags to throw '... not mocked'. openWithView builds an Intent and calls them, so openBytes' runCatching swallowed the exception and startActivity never fired, failing the test. Mock the Intent constructor + setters and capture the constructed instance to assert the ACTION_VIEW contract. * fix(#724): resolve host media download URLs and deduplicate REST synced chat bubbles * fix(#724): add FLAG_ACTIVITY_NEW_TASK for file intents and update GatewayFileClientTest --------- Co-authored-by: M57 <hy4ri@users.noreply.github.com>
Hermes Mobile
Native Android companion app for your Hermes AI agent.
Overview
Hermes Mobile is the native Android client for Hermes Agent. It connects securely to your local Hermes gateway (REST API and WebSocket TUI Gateway) over LAN, giving you pocket control over your AI assistant.
Features
- Real-Time Chat: Message your agent with Room-backed local database history and inline reply notifications.
- System Config: Manage active profiles, installed skills, plugins, toolsets, and LLM model/provider selections.
- Operations: Stream and filter live logs, manage cron jobs, edit environment keys, test webhooks, and monitor processes.
- Gateway Status: Monitor WebSocket connection, MCP servers, messaging channels, and OAuth providers.
- Productivity: View and manage tasks via integrated Kanban boards, track agent milestones, and browse session history.
- Analytics & Billing: Usage analytics dashboard and billing/subscription management.
- Theming: 6 built-in color presets (Default, Monochrome, Gruvbox, Catppuccin, AMOLED, Neon Noir) plus Material You dynamic colors on supported devices.
- Modern UX: Native Material 3 design with pull-to-refresh, scroll-aware TopBar, and customizable bottom navigation.
Quick Start
Prerequisites
- JDK 21+ (required for Kotlin compilation and the Gradle toolchain)
- Android Studio (Ladybug+) or a Nix development environment
Build & Deploy
- Clone the repository:
git clone https://github.com/Hy4ri/hermes-mobile.git cd hermes-mobile - Build the debug APK:
./gradlew assembleDebug - Install on your emulator/device:
adb install app/build/outputs/apk/debug/app-debug.apk
Note: For release builds, ensure keystore environment variables (KEYSTORE_PATH, KEYSTORE_PASSWORD, KEY_ALIAS, KEY_PASSWORD) are configured, or let the GitHub Actions release workflow handle it on tag push (v*).
Authentication
Once the app is installed, you need to point it at your Hermes gateway. The app auto-detects which auth mode the dashboard is using — just fill in the fields it shows.
1. Start the dashboard
On your host machine, start the dashboard:
hermes dashboard # loopback (127.0.0.1:9119) — no auth needed
hermes dashboard --host 0.0.0.0 # LAN — requires auth
For LAN access, configure credentials in ~/.hermes/config.yaml:
dashboard:
basic_auth:
username: admin # pick your own
password: hermes # pick your own
2. Connect the app
Tap Sign in on the landing screen and enter the dashboard host and port. The app probes the dashboard and reveals the fields you need:
| Auth mode | When | What you fill |
|---|---|---|
| Token only | Dashboard on same machine (loopback) | Token — grab from ~/.hermes/dashboard-token.txt or ~/.hermes/.env (HERMES_DASHBOARD_SESSION_TOKEN). The app can also auto-extract it from the dashboard page |
| Basic auth | Dashboard on LAN with password gate | Username + Password (default admin / hermes). The app logs in, gets a session cookie, and mints a WebSocket ticket automatically |
The app communicates over plain HTTP — it's designed for trusted local networks only. Do not expose your Hermes gateway to untrusted networks.
Connection profiles
Have multiple gateways? Switch between them in Settings → Connection profiles. Each profile stores its own host, port, and token — just tap to swap.
Pairing (admin)
The Pairing screen lets you approve or revoke agents and services that are trying to connect to your gateway, such as Telegram or Discord sessions.
Project Structure
app/src/main/java/com/m57/hermescontrol/
├── data/ # Local (Room, AuthManager), Remote (Retrofit, OkHttp), WS (WebSocket), Models
├── notification/ # Foreground service + inline reply for chat notifications
├── theme/ # Preset-based design system (6 themes), status colors, spacing, typography
├── ui/ # 28 Compose feature screens + common components (HermesScaffold, StateViews)
├── util/ # CronExpressionFormatter, LocaleContextWrapper
└── Navigation*.kt # Navigation3 wiring, keys, screen registry, controller
Tech Stack
- Language: Kotlin 2.4.10 with KSP 2.3.10 compiler plugin
- UI & Layout: Jetpack Compose (BOM 2026.03.01) & Material 3 / Material You
- Navigation: Navigation3 (Compose-first Routing)
- Networking: Retrofit 3.0.0, OkHttp 5.4.0, Kotlinx Serialization 1.11.0
- Database: Room 2.7.1 with SQLCipher 4.17.0 encryption
- Security:
EncryptedSharedPreferences(AES256-GCM), DataStore - Theming: 6 built-in presets (Default, Monochrome, Gruvbox, Catppuccin, AMOLED, Neon Noir) + Material You dynamic colors
- Image Loading: Coil 2.7.0
- Testing: JUnit 5, MockK, Turbine, Espresso, Compose UI testing
- Formatting:
ktlint1.2.1 style rules (checked automatically in CI)
Contributing
Contributions are welcome! Please read CONTRIBUTING.md for our branch workflow, code style guidelines, and PR checklist.
For developer-specific details, code conventions, and project architecture notes, refer to AGENTS.md.
License
Copyright © 2026 M57 (Hy4ri).
This project is licensed under the Apache License, Version 2.0. See the LICENSE file for details.