Skip to main content

hermes-client-web-ui

Web-based chat interface for Hermes Agent with multi-profile management, streaming chat, and interactive terminal integration

Zur Installation springen

Quellinformationen

Repository
reason-machines/devtools-skills
Letzte Quellaktivität
21. Mai 2026 um 01:49
Erkannte Sprache von SKILL.md
Englisch
Sterne
4
Forks
0

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
hermes-client-web-ui
description
Web-based chat interface for Hermes Agent with multi-profile management, streaming chat, and interactive terminal integration
triggers
["how do I use Hermes Client web interface","set up Hermes Client dashboard","manage multiple Hermes agent profiles","configure Hermes Client chat UI","integrate with Hermes Agent CLI","stream Hermes conversations in browser","use Hermes Client API endpoints","troubleshoot Hermes Client connection issues"]
# Hermes Client Web UI > Skill by [ara.so](https://ara.so) — Devtools Skills collection. A web-based chat interface for the Hermes Agent by Nous Research. Manages multiple Hermes profiles as separate "agents", runs conversations with full streaming via SSE, and provides an interactive terminal for setup commands. Each UI agent maps 1:1 to a Hermes profile with its own home directory, config, and sessions. ## Installation **Prerequisites:** - Node.js 18+ - Hermes Agent installed with `hermes` on your `PATH` - Git for Windows (Windows only, for auto-update) - Visual Studio Build Tools (Windows only, for native modules) ```bash # Verify Hermes is installed hermes --version hermes status # Clone and start git clone https://github.com/lotsoftick/hermes_client.git cd hermes_client npm start ``` `npm start` builds, deploys to `~/.hermes_client`, installs auto-start (LaunchAgent/Startup), and creates the global `hermes_client` command. Default URLs: - Client: http://localhost:18888 - API: http://localhost:18889 Default credentials: - Email: `admin@admin.com` - Password: `123456` ## Service Management After `npm start`, use the global command from any directory: ```bash # Start/stop/restart hermes_client start hermes_client stop hermes_client restart hermes_client status # Uninstall hermes_client uninstall # Keeps database hermes_client uninstall --purge # Deletes database (confirms) ``` ## Development Mode ```bash # Hot reload (API + Client) npm run dev # Generate .env only npm run setup # Stop services npm run stop ``` ## Configuration ### Port Configuration (`~/.hermes_client/.env`) Created automatically on first run: ```env API_PORT=18889 CLIENT_PORT=18888 ``` Apply changes: ```bash hermes_client restart # Production npm run dev # Development ``` ### API Configuration (`api/.env`) Auto-generated from `api/.env.example`: ```env NODE_ENV=development JWT_SECRET=<random-generated> DB_PATH=./data/hermes.sqlite PORT=18889 ALLOWED_DOMAIN= HERMES_STRICT_CORS=0 API_PUBLIC_URL= HERMES_BIN= HERMES_HOME=~/.hermes HERMES_CLIENT_UPLOADS_DIR=~/.hermes_client/uploads HERMES_SINGLE_USER_MODE=1 ``` **Key variables:** - `HERMES_BIN`: Override Hermes binary path if not on `PATH` - `HERMES_STRICT_CORS=1`: Enable strict CORS with `ALLOWED_DOMAIN` allowlist - `HERMES_SINGLE_USER_MODE`: Lock UI to single-user account page (`1/true/yes/on` or `0/false/no/off`) ## Architecture ### CLI-Driven Streaming Every chat turn spawns `hermes -p <profile> chat -Q -q "<message>"` and streams stdout over Server-Sent Events: ```typescript // Example API streaming endpoint structure app.post('/api/conversations/:conversationId/messages', async (req, res) => { const { profileName, message } = req.body; res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); const hermes = spawn('hermes', [ '-p', profileName, 'chat', '-Q', // Quiet mode '-q', message ]); hermes.stdout.on('data', (chunk) => { res.write(`data: ${JSON.stringify({ content: chunk.toString() })}\n\n`); }); hermes.on('close', () => { res.write('data: [DONE]\n\n'); res.end(); }); }); ``` ### Profile Management Each UI agent maps to a Hermes profile: ```bash # Backend runs these commands hermes profile add <name> hermes profile delete <name> hermes profile list hermes -p <name> model # Via interactive terminal ``` ### Session Sync Sessions started in standalone `hermes` REPL auto-appear in sidebar. Backend watches `~/.hermes/profiles/<profile>/sessions/*.json`: ```typescript // Example session sync pattern import { watch } from 'fs'; import { readdir, readFile } from 'fs/promises'; async function syncSessions(profileName: string) { const sessionsDir = `${process.env.HERMES_HOME}/profiles/${profileName}/sessions`; // Initial load const files = await readdir(sessionsDir); for (const file of files.filter(f => f.endsWith('.json'))) { const session = JSON.parse(await readFile(`${sessionsDir}/${file}`, 'utf-8')); // Insert/update in SQLite await db.run( 'INSERT OR REPLACE INTO conversations (session_key, profile_name, title, updated_at) VALUES (?, ?, ?, ?)', [session.key, profileName, session.title, session.updated_at] ); } // Watch for changes watch(sessionsDir, { persistent: false }, (event, filename) => { if (filename?.endsWith('.json')) { // Re-sync changed session } }); } ``` ### File Uploads Files stored under `~/.hermes_client/uploads/<conversationId>/` and passed to Hermes by absolute path: ```typescript // Example upload handling app.post('/api/conversations/:conversationId/upload', upload.single('file'), (req, res) => { const { conversationId } = req.params; const uploadDir = `${process.env.HERMES_CLIENT_UPLOADS_DIR}/${conversationId}`; // multer stores file at uploadDir/filename const absolutePath = path.resolve(uploadDir, req.file.filename); // For images, pass via --image flag if (req.file.mimetype.startsWith('image/')) { spawn('hermes', ['-p', profile, 'chat', '--image', absolutePath, '-q', message]); } else { // For other files, reference in prompt spawn('hermes', ['-p', profile, 'chat', '-q', `File: ${absolutePath}\n\n${message}`]); } res.json({ path: `/uploads/${conversationId}/${req.file.filename}` }); }); ``` ## API Endpoints ### Authentication ```typescript // POST /api/auth/login fetch('http://localhost:18889/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: 'admin@admin.com', password: '123456' }) }); // Response: { token: string, user: { id, email, name } } // JWT required for all other endpoints headers: { 'Authorization': `Bearer ${token}` } ``` ### Agents (Profiles) ```typescript // GET /api/agents - List all profiles fetch('http://localhost:18889/api/agents', { headers: { 'Authorization': `Bearer ${token}` } }); // POST /api/agents - Create new profile fetch('http://localhost:18889/api/agents', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'research-assistant' }) }); // DELETE /api/agents/:name - Delete profile fetch('http://localhost:18889/api/agents/research-assistant', { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}` } }); ``` ### Conversations ```typescript // GET /api/conversations?profileName=default - List conversations fetch('http://localhost:18889/api/conversations?profileName=default', { headers: { 'Authorization': `Bearer ${token}` } }); // POST /api/conversations - Create conversation fetch('http://localhost:18889/api/conversations', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ profileName: 'default', title: 'New Chat' }) }); // GET /api/conversations/:id/messages - Get messages fetch('http://localhost:18889/api/conversations/abc123/messages', { headers: { 'Authorization': `Bearer ${token}` } }); ``` ### Streaming Chat ```typescript // POST /api/conversations/:id/messages - Send message (SSE stream) const eventSource = new EventSource( 'http://localhost:18889/api/conversations/abc123/messages', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ content: 'Hello!', profileName: 'default' }) } ); eventSource.onmessage = (event) => { if (event.data === '[DONE]') { eventSource.close(); } else { const chunk = JSON.parse(event.data); console.log(chunk.content); } }; ``` ### Interactive Terminal (PTY) ```typescript // WebSocket upgrade for xterm.js const ws = new WebSocket('ws://localhost:18889/ws/pty?token=' + token); ws.onopen = () => { // Start model config command ws.send(JSON.stringify({ type: 'start', command: 'hermes', args: ['-p', 'default', 'model'], cwd: process.env.HOME })); }; ws.onmessage = (event) => { const msg = JSON.parse(event.data); if (msg.type === 'output') { terminal.write(msg.data); // xterm.js instance } }; // Send input terminal.onData((data) => { ws.send(JSON.stringify({ type: 'input', data })); }); // Resize PTY terminal.onResize(({ cols, rows }) => { ws.send(JSON.stringify({ type: 'resize', cols, rows })); }); ``` ## Client Patterns ### React Hook for Streaming ```typescript import { useEffect, useState } from 'react'; function useStreamingChat(conversationId: string, token: string) { const [messages, setMessages] = useState<string[]>([]); const [streaming, setStreaming] = useState(false); const sendMessage = async (content: string, profileName: string) => { setStreaming(true); const response = await fetch(`/api/conversations/${conversationId}/messages`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ content, profileName }) }); const reader = response.body.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value); const lines = chunk.split('\n').filter(l => l.startsWith('data: ')); for (const line of lines) { const data = line.slice(6);
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen