M57andM57 c1af989220 feat(#349): Smart auth login flow — probe dashboard and show correct fields (#351)
* feat(#349): smart auth login flow — probe dashboard, show correct fields

Landing screen with two buttons (Auth Login / Pairing Login) replaces
ConnectScreen as the start screen. Auth login probes the dashboard to
detect what auth it needs (token-only, basic-auth, or both) and shows
only the relevant input fields.

Changes:
- NavigationKeys.kt: add LandingScreen, AuthLoginScreen NavKeys
- LandingScreen.kt: new screen with hero icon + two login buttons
- AuthLoginScreen.kt: IP/Port input, probe logic, dynamic field rendering
- AuthLoginViewModel.kt: probe API, dashboard auth detection, connect flow
- Navigation.kt: wire new screens, start on LandingScreen, hide bottom bar
- strings.xml: add string resources for both screens

* fix(#351): align AuthLoginScreen field names with ViewModel API, fix TAG refs

- state.isProbing → state.probing
- DashboardAuthMode.BOTH → DashboardAuthMode.ALL
- viewModel.probe() → viewModel.probeDashboard()
- state.isConnecting → state.isLoading
- Removed unused TAG constant, replaced with inline tag string
- Made connectTokenOnly suspend to fix safeApiCall compilation

This fixes the CI compilation errors.

* fix(#351): revert probeDashboard() call to probe() to match VM

* fix(#351): rename isProbing to probing in ViewModel

* fix(#351): follow redirects in basic auth login — / redirects to /login

* fix(#351): show all fields when basic auth detected — still needs session token for WS

* refactor(#349): remove old ConnectScreen, clean up NavKey + navigation

* fix(#351): validate token against /api/sessions (auth-protected) instead of /api/status (public)

/api/status is a public endpoint that returns 200 even with wrong tokens,
causing invalid tokens to be saved and the WebSocket to spin forever on
the chat screen. /api/sessions requires auth and returns 401 on bad tokens.

* fix(#351): rewrite basic auth login — POST to /auth/password-login, then fetch SPA with cookie to extract token

* fix(#351): use ticket-based WS auth when basic auth is active

The updated dashboard on 0.0.0.0 always has auth_required=True, which
rejects legacy ?token= in favor of ?ticket= (single-use, 30s TTL).
Added AuthManager.setWsAuthParam() to support both ?token= (loopback)
and ?ticket= (gated mode) in wsUrl(). connectBasicAuth now mints a
ticket via POST /api/auth/ws-ticket after login instead of extracting
the token from the SPA.

* fix(#351): use requirePrefs() instead of direct prefs reference in wsUrl()

* fix(#351): handle relaxed-mock empty string in wsUrl() authParam fallback

* feat(#349): auto-extract session token from SPA HTML during probe

The probe now extracts the __HERMES_SESSION_TOKEN__ value from the
dashboard SPA and auto-populates the token field in TOKEN_ONLY mode,
eliminating the need for manual token pasting.

- Introduced ProbeResult data class to carry authMode + extractedToken
- Regex extraction of __HERMES_SESSION_TOKEN__ from dashboard HTML
- Token field auto-filled on probe completion for TOKEN_ONLY mode

* fix(#349): return BASIC_AUTH from probe when basic auth detected

Bug: probe returned ALL instead of BASIC_AUTH for basic-auth-only
dashboards, causing token+credential fields to show and the connect
flow to fall through to connectTokenOnly() on basic auth failure,
producing a confusing 'Unauthorized (HTTP 401)' NetworkResult error.

Fix: probe now returns DashboardAuthMode.BASIC_AUTH when a redirect
to /login is detected. BASIC_AUTH in connect() calls
connectBasicAuth() directly with no fallback. ALL mode also no longer
falls through to connectTokenOnly().

* fix(#349): use session cookie for REST API in gated mode

The dashboard's gated mode authenticates REST API requests via
session cookies (hermes_session_at), not Bearer tokens. The app
was storing the WS ticket as the token and sending it as
Authorization: Bearer <ticket> for all REST calls, which the
server rejected with 401.

- Extract hermes_session_at cookie from Set-Cookie header after
  POST /auth/password-login and store it in AuthManager
- ApiClient authInterceptor now sends Cookie header when session
  cookie is present, falls back to Bearer token for loopback mode
- ConnectResult carries both WS credential (ticket) and session
  cookie separately
- WS ticket used only for WebSocket (?ticket=), session cookie
  used for REST API

* feat(#349): add logout button to Settings > About

Red error-style button that clears all auth credentials (token,
session cookie, WS auth param) and navigates back to LandingScreen.
Wired through SettingsScreen onLogout callback to Navigation.kt.

* fix(#349): improve logout navigation flow

* fix(#349): keep backstack stable across logout

Use remember without keys for NavBackStack so the same backstack
object persists across startScreen changes. Use LaunchedEffect keyed
on startScreen to reset stack content atomically. This avoids
NavDisplay losing snapshot observation when a new NavBackStack
object is created.

* fix(#349): mint fresh WS ticket on every connect

The WS ticket is a single-use 30-second credential. On app restart
(or reconnect), the stored ticket is expired. Now refreshWsTicketIfNeeded()
mints a new ticket using the session cookie before every WS connection
attempt. Loopback mode (no session cookie) is unaffected.

* fix(#349): refresh ticket in openSocket for reconnect paths

scheduleReconnect() bypasses connect() and calls openSocket()
directly. Moving the ticket refresh into openSocket() ensures
every WS connection attempt — first launch and reconnect —
uses a fresh ticket.

* fix(#349): stub getSessionCookie() in ApiClientTest

mockkObject(AuthManager) is strict, so unstubbed getSessionCookie()
calls throw MockKException which manifests as IOException.

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-24 16:37:09 +03:00
2026-06-22 21:35:40 +03:00
2026-06-22 21:35:40 +03:00
2026-06-18 22:28:27 +03:00


Android Jetpack Compose Kotlin Material You

Hermes Control

Remote control for your Hermes AI agent — from your pocket.

Features • Screenshots • Quick Start • Project Structure • Tech Stack • Contributing • Releases

Latest Release CI Last Commit minSdk 26 targetSdk 36


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, and KEY_PASSWORD are 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 StateFlow from 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:

  1. Pick an issue — or create one first to discuss the change
  2. Branch from main — use feat/issue-N-description or fix/issue-N-description
  3. Implement — one PR per logical change
  4. CI must pass — ktlint + lintDebug + unit tests run on every PR
  5. Squash merge into main

Code style

  • ktlint enforces consistent formatting (./gradlew ktlintCheck)
  • Use HermesScaffold for new screens (never raw Material3 Scaffold)
  • Compose parameters follow the standard order: modifier, callbacks, content

PR checklist

  • Branch from main and target main
  • CI is green (ktlint, lint, tests)
  • No unused imports or dead code
  • contentDescription on all Image/Icon elements (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:

  1. Run full CI (ktlint, lint, tests)
  2. Build a release APK with the correct version
  3. Create a GitHub Release with auto-generated notes
  4. 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.
S
Description
AITURK Mobil — Android companion for AITURK IDE (fork of Hy4ri/hermes-mobile, Apache-2.0)
Readme Apache-2.0
10 MiB
0 Stars 1 Watchers 0 Forks
Languages
Kotlin 99.9%