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

跳到安装

来源信息

仓库
reason-machines/devtools-skills
最近来源活动
2026年5月21日 01:49
检测到的 SKILL.md 语言
英语
星标
4
分支
0

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
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);
在 GitHub 查看
这个 SKILL.md 很大,SkillsMP 这里只预览前一段内容。 在 GitHub 查看