* feat(#195): add chat attachment support for files and images - Add Attachment data model with computed properties (isImage, formattedSize, fileExtension) - Extend ChatMessage with optional attachments list - Add Coil 2.7.0 for async image loading in Compose - Extend ChatViewModel with pending attachment state, addAttachment/removeAttachment/clearAttachments - Build attachment-prefixed message text (data URIs for images, file refs for others) - Add paperclip button and file picker launcher (ActivityResultContracts.GetContent) to ChatScreen - Add animated attachment preview chips with thumbnails before sending - Render attachments inline in UserBubble (AsyncImage for images, file cards for others) - Add string resources for attachment UI features * fix(#195): rewrite attachment upload protocol to match Hermes backend RPC methods BREAKS: removes old buildAttachmentText() that embedded base64 data URIs in prompt text. Changes: - Replace inline data-URI encoding with proper backend RPC flow: - image.attach_bytes for images (fire-and-forget, auto-queues to session) - file.attach for non-image files (awaited, returns @file: ref text) - Add CompletableDeferred to PendingRequest so sendMessage can await RPC responses - Add readContentUriBytes() to read file content from content:// URIs - Add sendRpcAndAwait() suspend function for sequential RPC calls - Add RpcCallException for awaited RPC error handling - Suppress error UI for awaited RPCs (caller handles them directly) - Keep ChatScreen/ChatBubble UI layer unchanged (file picker, previews, inline images) * feat(#195): add camera capture support via FileProvider + TakePicture contract - Creates temporary photo files in cacheDir via FileProvider - Launches system camera with ActivityResultContracts.TakePicture - Adds camera icon button (📷) next to the paperclip in ChatInputBar - Captured photos added as image/jpeg attachments via same addAttachment flow - FileProvider configured with cache-root path in res/xml/file_paths.xml - Error handling for launch failures and capture failures with snackbar * refactor(#195): group camera/image/file into single attach dropdown - Replaces separate 📷 and 📎 buttons with a single 📎 button - DropdownMenu opens with three options: - 📷 Camera → TakePicture via FileProvider temp URI - 🖼️ Image → GetContent("image/*") - 📄 File → GetContent("*/*") - Removes onAttachClick, adds onImageTap/onFileTap callbacks - Input bar stays clean with one attachment affordance * fix(#195): add missing imports for CI compilation ChatScreen.kt: - android.util.Log - androidx.compose.animation.core.* (infiniteRepeatable, tween, LinearEasing, RepeatMode, rememberInfiniteTransition) - androidx.compose.foundation.layout.widthIn - androidx.compose.material.icons.filled.AttachFile - androidx.compose.material.icons.filled.InsertDriveFile - androidx.compose.ui.layout.ContentScale - coil.compose.AsyncImage ChatBubble.kt: - androidx.compose.material.icons.filled.InsertDriveFile * fix(#195): resolve android-lint LocalContextGetResourceValueCall error Replace context.getString(R.string.chat_camera_error) with a composable-level cameraErrorMsg captured via stringResource() to avoid querying resources via LocalContext.current in a callback. * fix(#195): migrate InsertDriveFile to AutoMirrored, fix chip double-remove - Replace deprecated Icons.Filled.InsertDriveFile with Icons.AutoMirrored.Filled.InsertDriveFile in ChatBubble.kt and ChatScreen.kt - Remove onClick from AttachmentChip Surface so only the dedicated close button triggers removal, not accidental taps on the chip body --------- Co-authored-by: Hermes Agent <hermes@agent.local>
Hermes Control
Remote control for your Hermes AI agent — from your pocket.
Features • Screenshots • Quick Start • Project Structure • Tech Stack • Contributing • Releases
Overview
Hermes Control is a native Android companion app for Hermes Agent — the open-source, multi-platform AI assistant by Nous Research.
Instead of pulling out a laptop or switching to Telegram/Discord, you can manage your Hermes agent directly from your phone:
- 💬 Chat with your agent in real time
- 🔧 Inspect and reconfigure system settings, profiles, and plugins
- 📊 Monitor gateway status, MCP servers, and webhook health
- 📋 Manage cron jobs, environment keys, and toolsets
- 🗂️ Track kanban boards and agent achievements
- 🔄 Pull-to-refresh every data screen
All this with a Material You design that adapts to your device's dynamic color theme.
Features
🤖 Agent Chat
- Real-time message exchange with your Hermes agent
- Session management with profile switching
- Message history with persistent storage (Room)
⚙️ System Management
- Profiles — View and switch between Hermes agent profiles
- Skills — Browse installed skills with inline descriptions
- Plugins — Manage active plugins and their configurations
- Toolsets — See which toolsets are enabled per profile
🛠️ Operations
- Cron Jobs — View scheduled tasks, their schedules, and last-run status
- Webhooks — Configure and test webhook endpoints
- Keys & Credentials — Securely manage environment variables
- Logs — Stream and filter agent logs
📡 Connectivity
- Gateway Status — Monitor WebSocket and API gateway health
- MCP Servers — Manage Model Context Protocol server connections
- Channels — View connected messaging platform status
📊 Productivity
- Kanban Boards — Track tasks across boards, columns, and cards
- System Info — Runtime environment and configuration overview
- Achievements — Agent milestone tracker
🎨 UX
- Material You — Dynamic color theming from your wallpaper
- Pull-to-Refresh — Swipe down on any data screen to reload
- Shared Scaffold — Consistent navigation and layout across all screens
- Scroll-aware TopBar — Collapsing top bar on scroll
Screenshots
📸 Screenshots coming soon. Build the app and see it in action!
| Chat | System | Profile | Kanban |
|---|---|---|---|
| 🔲 | 🔲 | 🔲 | 🔲 |
Quick Start
Prerequisites
| Tool | Version | Reason |
|---|---|---|
| JDK | 17+ | Kotlin/JVM compilation |
| Android Studio | Hedgehog+ | IDE + SDK management |
| Android SDK | 36 (compile), 26 (min) | Via Android Studio SDK Manager |
| Git | any | Clone the repo |
Clone & Build
git clone https://github.com/Hy4ri/hermes-mobile.git
cd hermes-mobile
./gradlew assembleDebug
The debug APK will be at app/build/outputs/apk/debug/app-debug.apk.
Build with custom version
./gradlew assembleRelease -PversionName="1.2.0" -PversionCode="120"
Note: Release builds require proper signing configuration. Ensure the environment variables
KEYSTORE_PATH,KEYSTORE_PASSWORD,KEY_ALIAS, andKEY_PASSWORDare set before building.
Install
adb install app/build/outputs/apk/debug/app-debug.apk
Or open the project in Android Studio and hit Run ▶.
Project Structure
hermes-mobile/
├── app/
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/com/m57/hermescontrol/
│ │ │ │ ├── Navigation.kt # Drawer + NavDisplay + entry wiring
│ │ │ │ ├── NavigationController.kt # Central navigation guard (dedup)
│ │ │ │ ├── NavigationKeys.kt # Screen key definitions
│ │ │ │ ├── MainActivity.kt # Single activity entry point
│ │ │ │ ├── data/
│ │ │ │ │ ├── model/ # API models / DTOs (20+ files)
│ │ │ │ │ ├── remote/ # Retrofit + OkHttp API client
│ │ │ │ │ ├── local/ # Room DB (Dao, Entity, Mapper)
│ │ │ │ │ └── ws/ # WebSocket TUI Gateway (JSON-RPC 2.0)
│ │ │ │ ├── notification/
│ │ │ │ │ └── ChatNotificationService.kt # Foreground service for reply alerts
│ │ │ │ ├── theme/
│ │ │ │ │ ├── Theme.kt # Material You dynamic theming
│ │ │ │ │ ├── Color.kt # Color palette definitions
│ │ │ │ │ ├── HermesStatusColors.kt # Semantic status colours (CompositionLocal)
│ │ │ │ │ ├── Type.kt # Typography scale
│ │ │ │ │ ├── Motion.kt # Shared transition animations
│ │ │ │ │ ├── Spacing.kt # Spacing tokens (CompositionLocal)
│ │ │ │ │ └── Shapes.kt # Shape tokens
│ │ │ │ ├── ui/
│ │ │ │ │ ├── common/ # Shared components
│ │ │ │ │ │ ├── HermesScaffold.kt # Standardised screen layout
│ │ │ │ │ │ ├── StateViews.kt # Loading, error, empty placeholders
│ │ │ │ │ │ └── SharedComponents.kt # SearchBar, FilterChips, ToggleRow, etc.
│ │ │ │ │ ├── chat/ # Chat screen + view model
│ │ │ │ │ ├── settings/ # Settings screen + view model
│ │ │ │ │ ├── connect/ # Connect / pairing screen
│ │ │ │ │ ├── profiles/ # Profile management
│ │ │ │ │ ├── skills/ # Skills browser
│ │ │ │ │ ├── plugins/ # Plugin management
│ │ │ │ │ ├── toolsets/ # Toolset viewer
│ │ │ │ │ ├── cron/ # Cron job viewer
│ │ │ │ │ ├── webhooks/ # Webhook management
│ │ │ │ │ ├── logs/ # Log stream viewer
│ │ │ │ │ ├── keys/ # Environment variable manager
│ │ │ │ │ ├── gateway/ # Gateway health monitor
│ │ │ │ │ ├── mcp/ # MCP server manager
│ │ │ │ │ ├── model/ # LLM model selector
│ │ │ │ │ ├── channels/ # Messaging channels
│ │ │ │ │ ├── pairing/ # Device pairing
│ │ │ │ │ ├── config/ # Raw config viewer
│ │ │ │ │ ├── system/ # System info screen
│ │ │ │ │ ├── kanban/ # Kanban board viewer
│ │ │ │ │ └── achievements/ # Agent achievements
│ │ │ │ └── util/ # Utility classes
│ │ │ │ └── CronExpressionFormatter.kt
│ │ │ ├── res/
│ │ │ │ ├── values/
│ │ │ │ │ └── strings.xml # String resources
│ │ │ │ ├── drawable/ # Vector icons & drawables
│ │ │ │ └── mipmap-*/ # Launcher icons
│ │ │ └── AndroidManifest.xml
│ │ └── test/ # Unit tests
│ │ └── kotlin/com/m57/hermescontrol/
│ └── build.gradle.kts # App-level build config
├── gradle/
│ └── libs.versions.toml # Version catalog
├── .github/
│ └── workflows/
│ ├── android.yml # CI (ktlint + unit tests)
│ └── release.yml # Release APK builder
├── build.gradle.kts # Root build config
├── settings.gradle.kts # Project settings
└── gradlew # Gradle wrapper
\n---
Tech Stack
| Category | Library | Purpose |
|---|---|---|
| Language | Kotlin 2.3+ | Primary language |
| UI | Jetpack Compose (BOM 2026.03.01) | Declarative UI |
| Design | Material3 (Compose) + Material You | Dynamic theming |
| Navigation | Navigation3 | Screen routing |
| Networking | Retrofit 2 + OkHttp 4 | REST API calls |
| Persistence | Room (SQLite) | Local message history |
| Encryption | AndroidX Security Crypto | Secure key storage |
| Background | Foreground Service | Notification polling (ChatNotificationService) |
| Serialization | Gson | JSON parsing |
| Testing | jUnit 5 + MockK + OkHttp MockWebServer | Unit testing |
| Linting | ktlint | Kotlin code style |
Architecture
The app follows a single-activity, Compose-first architecture with MVVM:
View (Composable) → ViewModel → ApiClient (Retrofit) → Hermes Agent (HTTP)
- Screens are pure Compose functions that observe
StateFlowfrom their ViewModel - ViewModels manage UI state and call the API client
- ApiClient is a Retrofit-based HTTP client that talks to the Hermes Agent REST API
- Navigation uses a custom NavHost with drawer-based routing
Contributing
PRs are welcome! Here's the workflow:
- Pick an issue — or create one first to discuss the change
- Branch from
main— usefeat/issue-N-descriptionorfix/issue-N-description - Implement — one PR per logical change
- CI must pass — ktlint + lintDebug + unit tests run on every PR
- Squash merge into
main
Code style
- ktlint enforces consistent formatting (
./gradlew ktlintCheck) - Use
HermesScaffoldfor new screens (never raw Material3Scaffold) - Compose parameters follow the standard order:
modifier, callbacks, content
PR checklist
- Branch from
mainand targetmain - CI is green (ktlint, lint, tests)
- No unused imports or dead code
contentDescriptionon allImage/Iconelements (accessibility)- Follows the existing pattern for similar screens
Releases
Pre-built APKs are available on the Releases page.
Each release is built automatically by CI when a maintainer pushes a v* tag:
git tag v1.2.0
git push origin v1.2.0
The release.yml workflow will:
- Run full CI (ktlint, lint, tests)
- Build a release APK with the correct version
- Create a GitHub Release with auto-generated notes
- Attach the APK as a downloadable asset
License
Copyright 2026 Hy4ri
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.