* build: compileSdk 36 -> 37 + okhttp 5.4.0 -> 5.5.0 Take okhttp 5.5.0 (folds in dependabot PR #940) which hard-requires compileSdk 37 via AAR metadata. targetSdk stays 36 for now. * build: targetSdk 36 -> 37 (Android 17) + ACCESS_LOCAL_NETWORK gating Opt into Android 17 runtime behavior. The app connects to a gateway (default 127.0.0.1:9119, plus LAN + remote), so targeting 37 forces the new ACCESS_LOCAL_NETWORK permission for LAN hosts. - AndroidManifest: declare ACCESS_LOCAL_NETWORK (harmless below API 37) - LocalNetwork.kt: pure detector + needsLocalNetworkPermission(baseUrl) (loopback + public remote exempt; RFC1918/link-local/CGNAT gated) - ConnectViewModel: requestConnect() gates on the permission; screen launches the prompt; onLanPermissionResult proceeds or shows a clear error - flake.nix: android platform 36 -> 37 - Tests: LocalNetworkTest + ConnectPermissionGateTest (SDK_INT / permission branching mocked, no device needed) - Task 8 finding: assistant services (VoiceInteraction*/Recognition) and the remoteMessaging ChatNotificationService do NOT hit the Android 17 mic/ while-in-use foreground hardening, so no change required. * fix: ktlint + @Composable LocalContext hoist (was blocking CI) - ConnectScreen: hoist LocalContext.current out of onClick lambda (calling a @Composable inside a non-composable lambda broke compileDebugKotlin) - ktlint --format: multiline-expression-wrapping, function-signature, no-empty-first-line-in-class-body, no-unused-imports - LocalNetwork.kt: merge consecutive KDocs (no-consecutive-comments) flake.nix buildToolsVersion/platformToolsVersion intentionally stay 36.0.0 — they are decoupled from compileSdk; only platformVersions needs 37. * fix: suspend mock (coEvery) + flake toolchain pinned to 37.0.0 - ConnectPermissionGateTest: getStatus() is suspend → use coEvery (was breaking compileDebugUnitTestKotlin) - flake.nix: buildToolsVersion/platformToolsVersion/cmdLineToolsVersion pinned to 37.0.0 (verified against pinned nixpkgs 241313f — resolves to android-sdk-*-37.0.0 derivations; platformVersions already 37) * fix(flake): revert cmdLineToolsVersion to 11.0 (not API-aligned) cmdline-tools uses its own versioning (latest = 21.0, never 37.0.0) — nixpkgs rejects 37.0.0 with 'version missing'. build-tools + platform-tools stay 37.0.0 (verified resolve to android-sdk-*-37.0.0). flake now evals clean against pinned nixpkgs 241313f. * flake: track nixos-unstable + pin toolchain to latest (cmdline 22.0, platform-tools 37.0.1, build-tools 37.0.0, platform 37.1) - inputs.nixpkgs -> nixos-unstable, flake.lock bumped to rev ffb3c9b7 (2026-08-19) - cmdLineToolsVersion 11.0 -> 22.0 (own versioning, not API-aligned; v22_0 confirms) - platformToolsVersion 37.0.0 -> 37.0.1 - buildToolsVersion stays 37.0.0 - platformVersions 37 -> 37.1 - verified: nix flake check --no-build -> all checks passed on hyari * fix: unit tests — Uri stub + Build.SDK_INT final-field mock - LocalNetwork.kt: needsLocalNetworkPermission uses java.net.URI instead of android.net.Uri (latter is a non-functional stub in JVM unit tests, its host returns null -> gate always false). Added pure requiresLocalNetworkPermission(sdkInt, baseUrl). - ConnectViewModel: inject sdkVersion (package-private) instead of reading the final static Build.VERSION.SDK_INT directly; gate via requiresLocalNetworkPermission. - ConnectPermissionGateTest: drop mockkStatic(Build.VERSION) (MockKException on final static field, and against repo convention); inject sdkVersion; add pure gate-decision matrix. Fixes 7 red tests. * fix(test): remove illegal ':' from test function names (compile error) * fix(test): order getString mocks so specific resources win over wildcard * fix(flake): include android-37.0 system image so the emulator can boot Google only publishes the android-37.0 system image (no 37.1 image exists yet). With platformVersions=["37.1"] composeAndroidPackages found no matching system image, leaving system-images/ empty and every AVD FATALing with 'Broken AVD system path'. Adding 37.0 pulls the published image so an AVD boots (verified: hermes37 AVD boots Android 17 / API 37 on hyari). compileSdk/targetSdk stay 37. Also bump AGENTS.md SDK note 36 -> 37 (was stale). * fix(flake): platformVersions 37.1 -> 37.0 Drop 37.1; 37.0 provides both the SDK platform for compileSdk 37 and the published android-37.0 system image the emulator needs. * fix(flake): remove stray tool-scratch line leaked into flake.nix (line 116) A terminal tool's /tmp scratch rename line got appended to flake.nix, making it invalid Nix. Removed; flake now parses + 'nix flake check' passes on hyari. * fix(build): pin buildToolsVersion 37.0.0 to match nix SDK AGP defaulted build-tools to 36.0.0, which isn't in the nix-managed SDK (only 37.0.0 is provisioned). Local ./gradlew builds then tried to *install* 36.0.0 into the immutable /nix/store and failed. Pin 37.0.0 so the local nix devShell builds. CI uses its own SDK so unaffected. --------- Co-authored-by: Hy4ri <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.
Screenshots
Chat, automation, productivity, and agent configuration — from your phone.
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.



