hermes-control-interface-dashboard
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseHermes Control Interface Dashboard
Hermes控制界面仪表盘
Skill by ara.so — Hermes Skills collection.
A self-hosted web dashboard for managing the Hermes AI agent stack. Provides browser-based terminal, file explorer, session management, cron jobs, multi-agent gateway control, token analytics, and RBAC — all behind password authentication.
Stack: Vanilla JS + Vite, Node.js, Express, WebSocket, xterm.js
Port: 10272 (default)
Version: 3.5.0
Port: 10272 (default)
Version: 3.5.0
由ara.so提供的Skill — Hermes Skills合集。
一款用于管理Hermes AI Agent栈的自托管Web仪表盘。提供基于浏览器的终端、文件浏览器、会话管理、定时任务、多Agent网关控制、令牌分析和RBAC功能——所有功能均受密码认证保护。
技术栈: Vanilla JS + Vite, Node.js, Express, WebSocket, xterm.js
端口: 10272(默认)
版本: 3.5.0
端口: 10272(默认)
版本: 3.5.0
Installation
安装
Prerequisites
前置要求
bash
undefinedbash
undefinedRequired: Node.js 18+, build tools for node-pty
必需:Node.js 18+,node-pty的构建工具
sudo apt-get install -y python3 make g++ # Ubuntu/Debian
sudo apt-get install -y python3 make g++ # Ubuntu/Debian系统
OR
或
brew install python3 # macOS
undefinedbrew install python3 # macOS系统
undefinedManual Installation (Recommended)
手动安装(推荐)
bash
undefinedbash
undefinedClone repository
克隆仓库
git clone https://github.com/xaspx/hermes-control-interface.git
cd hermes-control-interface
git clone https://github.com/xaspx/hermes-control-interface.git
cd hermes-control-interface
Install dependencies
安装依赖
npm install
npm install
Configure environment
配置环境
cp .env.example .env
cp .env.example .env
Generate secure secret
生成安全密钥
openssl rand -hex 32 # Copy output for HERMES_CONTROL_SECRET
openssl rand -hex 32 # 将输出内容复制到HERMES_CONTROL_SECRET中
Edit .env with your settings
编辑.env文件配置参数
nano .env
undefinednano .env
undefinedEnvironment Configuration
环境配置
Minimal :
.envbash
undefined最简配置:
.envbash
undefinedRequired
必填项
HERMES_CONTROL_PASSWORD=your-secure-password-here
HERMES_CONTROL_SECRET=<output-from-openssl-rand-hex-32>
HERMES_CONTROL_PASSWORD=your-secure-password-here
HERMES_CONTROL_SECRET=<output-from-openssl-rand-hex-32>
Optional
可选项
PORT=10272
NODE_ENV=production
HERMES_CONTROL_ROOTS=/.hermes,/Documents # File explorer roots
undefinedPORT=10272
NODE_ENV=production
HERMES_CONTROL_ROOTS=/.hermes,/Documents # 文件浏览器根目录
undefinedBuild and Run
构建与运行
bash
undefinedbash
undefinedBuild frontend
构建前端
npm run build
npm run build
Start server
启动服务
npm start
npm start
Development mode (auto-reload)
开发模式(自动重载)
npm run dev
undefinednpm run dev
undefinedProduction Deployment (Systemd)
生产部署(Systemd)
bash
undefinedbash
undefinedCreate systemd service
创建systemd服务文件
sudo tee /etc/systemd/system/hermes-control.service > /dev/null <<EOF
[Unit]
Description=Hermes Control Interface
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=$(pwd)
ExecStart=/usr/bin/node server.js
Restart=always
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target
EOF
sudo tee /etc/systemd/system/hermes-control.service > /dev/null <<EOF
[Unit]
Description=Hermes Control Interface
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=$(pwd)
ExecStart=/usr/bin/node server.js
Restart=always
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target
EOF
Enable and start
启用并启动服务
sudo systemctl enable hermes-control
sudo systemctl start hermes-control
sudo systemctl status hermes-control
undefinedsudo systemctl enable hermes-control
sudo systemctl start hermes-control
sudo systemctl status hermes-control
undefinedCore Features
核心功能
Authentication & RBAC
认证与RBAC
HCI supports multi-user authentication with role-based access control:
Roles:
- — Full access (all 20 permissions)
admin - — Read-only access
viewer - — Granular permission selection
custom
Key Permissions:
- ,
agents:read,agents:writeagents:delete - ,
chat:readchat:write - ,
sessions:read,sessions:writesessions:delete - ,
config:readconfig:write - ,
gateway:controlgateway:logs - ,
files:readfiles:write terminal:execusers:managesystem:maintaincron:manage
Creating Users (via Maintenance → Users):
javascript
// API endpoint: POST /api/users
{
"username": "alice",
"password": "secure-password",
"role": "viewer"
}HCI支持基于角色的访问控制(RBAC)多用户认证:
角色:
- — 完全访问权限(全部20项权限)
admin - — 只读访问权限
viewer - — 细粒度权限选择
custom
关键权限:
- ,
agents:read,agents:writeagents:delete - ,
chat:readchat:write - ,
sessions:read,sessions:writesessions:delete - ,
config:readconfig:write - ,
gateway:controlgateway:logs - ,
files:readfiles:write terminal:execusers:managesystem:maintaincron:manage
创建用户(通过维护 → 用户页面):
javascript
// API端点: POST /api/users
{
"username": "alice",
"password": "secure-password",
"role": "viewer"
}Multi-Agent Gateway Management
多Agent网关管理
Profile Structure:
~/.hermes/
├── profiles/
│ ├── default/
│ │ └── config.yaml
│ ├── production/
│ │ └── config.yaml
│ └── testing/
│ └── config.yamlAPI: List Profiles
javascript
// GET /api/agents/profiles
fetch('/api/agents/profiles', {
headers: {
'Authorization': `Bearer ${sessionToken}`,
'X-CSRF-Token': csrfToken
}
})
.then(res => res.json())
.then(data => {
// data.profiles = [{ name: 'default', isDefault: true, status: 'running', model: 'hermes-3' }]
});API: Start/Stop Gateway
javascript
// POST /api/agents/gateway/start
fetch('/api/agents/gateway/start', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${sessionToken}`,
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({ profile: 'production' })
})
.then(res => res.json())
.then(data => console.log(data.message)); // "Gateway started for production"API: Create Profile
javascript
// POST /api/agents/profiles
fetch('/api/agents/profiles', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${sessionToken}`,
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({
name: 'staging',
cloneFrom: 'default' // Optional: clone existing config
})
});配置文件结构:
~/.hermes/
├── profiles/
│ ├── default/
│ │ └── config.yaml
│ ├── production/
│ │ └── config.yaml
│ └── testing/
│ └── config.yamlAPI: 列出配置文件
javascript
// GET /api/agents/profiles
fetch('/api/agents/profiles', {
headers: {
'Authorization': `Bearer ${sessionToken}`,
'X-CSRF-Token': csrfToken
}
})
.then(res => res.json())
.then(data => {
// data.profiles = [{ name: 'default', isDefault: true, status: 'running', model: 'hermes-3' }]
});API: 启动/停止网关
javascript
// POST /api/agents/gateway/start
fetch('/api/agents/gateway/start', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${sessionToken}`,
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({ profile: 'production' })
})
.then(res => res.json())
.then(data => console.log(data.message)); // "Gateway started for production"API: 创建配置文件
javascript
// POST /api/agents/profiles
fetch('/api/agents/profiles', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${sessionToken}`,
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({
name: 'staging',
cloneFrom: 'default' // 可选:克隆现有配置
})
});Chat Interface
聊天界面
Session Management:
javascript
// POST /api/chat/send
const sendMessage = async (message, sessionId = null) => {
const args = sessionId
? ['--continue', sessionId, message]
: ['--continue', '', message]; // Empty string creates new session
const response = await fetch('/api/chat/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${sessionToken}`,
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({
profile: 'default',
args: args,
suppressBanner: true // Use -Q flag for clean output
})
});
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');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(6));
if (data.type === 'session_id') {
console.log('Session ID:', data.sessionId);
} else if (data.type === 'tool_call') {
console.log('Tool:', data.tool, 'Status:', data.status);
} else if (data.type === 'output') {
console.log('Output:', data.text);
}
}
}
}
};
// Usage
await sendMessage('What is the weather?'); // New session
await sendMessage('And tomorrow?', 'abc123'); // Continue sessionList Sessions:
javascript
// GET /api/chat/sessions?profile=default
fetch('/api/chat/sessions?profile=default', {
headers: { 'Authorization': `Bearer ${sessionToken}` }
})
.then(res => res.json())
.then(data => {
// data.sessions = [{ id: 'abc123', title: 'Weather discussion', timestamp: '2026-05-17...' }]
});Rename Session:
javascript
// PUT /api/chat/sessions/:sessionId
fetch('/api/chat/sessions/abc123', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${sessionToken}`,
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({ title: 'Weather Research' })
});会话管理:
javascript
// POST /api/chat/send
const sendMessage = async (message, sessionId = null) => {
const args = sessionId
? ['--continue', sessionId, message]
: ['--continue', '', message]; // 空字符串表示创建新会话
const response = await fetch('/api/chat/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${sessionToken}`,
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({
profile: 'default',
args: args,
suppressBanner: true // 使用-Q标志获取简洁输出
})
});
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');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(6));
if (data.type === 'session_id') {
console.log('会话ID:', data.sessionId);
} else if (data.type === 'tool_call') {
console.log('工具:', data.tool, '状态:', data.status);
} else if (data.type === 'output') {
console.log('输出:', data.text);
}
}
}
}
};
// 使用示例
await sendMessage('今天天气如何?'); // 创建新会话
await sendMessage('明天呢?', 'abc123'); // 继续现有会话列出会话:
javascript
// GET /api/chat/sessions?profile=default
fetch('/api/chat/sessions?profile=default', {
headers: { 'Authorization': `Bearer ${sessionToken}` }
})
.then(res => res.json())
.then(data => {
// data.sessions = [{ id: 'abc123', title: 'Weather discussion', timestamp: '2026-05-17...' }]
});重命名会话:
javascript
// PUT /api/chat/sessions/:sessionId
fetch('/api/chat/sessions/abc123', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${sessionToken}`,
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({ title: '天气研究' })
});Token Analytics
令牌分析
Get Usage Stats:
javascript
// GET /api/usage/stats?range=7d&profile=default
fetch('/api/usage/stats?range=7d&profile=default', {
headers: { 'Authorization': `Bearer ${sessionToken}` }
})
.then(res => res.json())
.then(data => {
console.log('Sessions:', data.totalSessions);
console.log('Tokens:', data.totalTokens);
console.log('Cost:', data.estimatedCost);
console.log('Models:', data.modelBreakdown);
// modelBreakdown: [{ model: 'hermes-3', sessions: 42, tokens: 150000, avgTokens: 3571 }]
console.log('Platforms:', data.platformBreakdown);
// platformBreakdown: [{ platform: 'CLI', count: 30 }, { platform: 'Telegram', count: 12 }]
console.log('Top Tools:', data.topTools);
// topTools: [{ tool: 'web_search', calls: 15, success_rate: 0.93 }]
});Query Ranges: , , ,
today7d30d90d获取使用统计:
javascript
// GET /api/usage/stats?range=7d&profile=default
fetch('/api/usage/stats?range=7d&profile=default', {
headers: { 'Authorization': `Bearer ${sessionToken}` }
})
.then(res => res.json())
.then(data => {
console.log('会话数:', data.totalSessions);
console.log('令牌数:', data.totalTokens);
console.log('预估成本:', data.estimatedCost);
console.log('模型分布:', data.modelBreakdown);
// modelBreakdown: [{ model: 'hermes-3', sessions: 42, tokens: 150000, avgTokens: 3571 }]
console.log('平台分布:', data.platformBreakdown);
// platformBreakdown: [{ platform: 'CLI', count: 30 }, { platform: 'Telegram', count: 12 }]
console.log('高频工具:', data.topTools);
// topTools: [{ tool: 'web_search', calls: 15, success_rate: 0.93 }]
});查询时间范围: , , ,
today7d30d90dConfiguration Management
配置管理
Get Config:
javascript
// GET /api/agents/config?profile=default
fetch('/api/agents/config?profile=default', {
headers: { 'Authorization': `Bearer ${sessionToken}` }
})
.then(res => res.json())
.then(data => {
console.log('Config YAML:', data.config);
console.log('Categories:', data.categories);
// categories: ['llm', 'platforms', 'memory', 'tools', 'prompts', ...]
});Update Config:
javascript
// PUT /api/agents/config
fetch('/api/agents/config', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${sessionToken}`,
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({
profile: 'default',
config: `
llm:
model: hermes-3
provider: openrouter
temperature: 0.7
platforms:
telegram:
enabled: true
token: \${TELEGRAM_BOT_TOKEN}
memory:
provider: honcho
honcho_url: http://localhost:8000
`
})
});Reset Category to Defaults:
javascript
// POST /api/agents/config/reset
fetch('/api/agents/config/reset', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${sessionToken}`,
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({
profile: 'default',
category: 'llm'
})
});获取配置:
javascript
// GET /api/agents/config?profile=default
fetch('/api/agents/config?profile=default', {
headers: { 'Authorization': `Bearer ${sessionToken}` }
})
.then(res => res.json())
.then(data => {
console.log('配置YAML:', data.config);
console.log('配置分类:', data.categories);
// categories: ['llm', 'platforms', 'memory', 'tools', 'prompts', ...]
});更新配置:
javascript
// PUT /api/agents/config
fetch('/api/agents/config', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${sessionToken}`,
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({
profile: 'default',
config: `
llm:
model: hermes-3
provider: openrouter
temperature: 0.7
platforms:
telegram:
enabled: true
token: \${TELEGRAM_BOT_TOKEN}
memory:
provider: honcho
honcho_url: http://localhost:8000
`
})
});重置分类为默认配置:
javascript
// POST /api/agents/config/reset
fetch('/api/agents/config/reset', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${sessionToken}`,
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({
profile: 'default',
category: 'llm'
})
});Cron Job Management
定时任务管理
List Cron Jobs:
javascript
// GET /api/cron?profile=default
fetch('/api/cron?profile=default', {
headers: { 'Authorization': `Bearer ${sessionToken}` }
})
.then(res => res.json())
.then(data => {
// data.jobs = [{ id: 'job1', schedule: '0 9 * * *', command: 'hermes chat "Daily summary"', enabled: true, nextRun: '...' }]
});Create Cron Job:
javascript
// POST /api/cron
fetch('/api/cron', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${sessionToken}`,
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({
profile: 'default',
schedule: '0 */6 * * *', // Every 6 hours
command: 'hermes chat "Check system health"',
enabled: true
})
});Run Job Immediately:
javascript
// POST /api/cron/:jobId/run
fetch('/api/cron/job1/run', {
method: 'POST',
headers: {
'Authorization': `Bearer ${sessionToken}`,
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({ profile: 'default' })
});列出定时任务:
javascript
// GET /api/cron?profile=default
fetch('/api/cron?profile=default', {
headers: { 'Authorization': `Bearer ${sessionToken}` }
})
.then(res => res.json())
.then(data => {
// data.jobs = [{ id: 'job1', schedule: '0 9 * * *', command: 'hermes chat "Daily summary"', enabled: true, nextRun: '...' }]
});创建定时任务:
javascript
// POST /api/cron
fetch('/api/cron', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${sessionToken}`,
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({
profile: 'default',
schedule: '0 */6 * * *', // 每6小时执行一次
command: 'hermes chat "检查系统健康状态"',
enabled: true
})
});立即执行任务:
javascript
// POST /api/cron/:jobId/run
fetch('/api/cron/job1/run', {
method: 'POST',
headers: {
'Authorization': `Bearer ${sessionToken}`,
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({ profile: 'default' })
});File Explorer
文件浏览器
List Directory:
javascript
// GET /api/files?path=~/.hermes
fetch('/api/files?path=~/.hermes', {
headers: { 'Authorization': `Bearer ${sessionToken}` }
})
.then(res => res.json())
.then(data => {
console.log('Files:', data.files);
// files: [{ name: 'config.yaml', type: 'file', size: 1024 }, { name: 'sessions', type: 'directory' }]
});Read File:
javascript
// GET /api/files/read?path=~/.hermes/config.yaml
fetch('/api/files/read?path=~/.hermes/config.yaml', {
headers: { 'Authorization': `Bearer ${sessionToken}` }
})
.then(res => res.json())
.then(data => {
console.log('Content:', data.content);
});Write File:
javascript
// POST /api/files/write
fetch('/api/files/write', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${sessionToken}`,
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({
path: '~/.hermes/custom-prompt.txt',
content: 'You are a helpful assistant specializing in DevOps.'
})
});Security Note: All paths are validated and scoped to (default: ). Path traversal attacks are prevented.
HERMES_CONTROL_ROOTS~/.hermes列出目录:
javascript
// GET /api/files?path=~/.hermes
fetch('/api/files?path=~/.hermes', {
headers: { 'Authorization': `Bearer ${sessionToken}` }
})
.then(res => res.json())
.then(data => {
console.log('文件列表:', data.files);
// files: [{ name: 'config.yaml', type: 'file', size: 1024 }, { name: 'sessions', type: 'directory' }]
});读取文件:
javascript
// GET /api/files/read?path=~/.hermes/config.yaml
fetch('/api/files/read?path=~/.hermes/config.yaml', {
headers: { 'Authorization': `Bearer ${sessionToken}` }
})
.then(res => res.json())
.then(data => {
console.log('文件内容:', data.content);
});写入文件:
javascript
// POST /api/files/write
fetch('/api/files/write', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${sessionToken}`,
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({
path: '~/.hermes/custom-prompt.txt',
content: '你是专注于DevOps领域的得力助手。'
})
});安全说明: 所有路径均经过验证,并限制在配置的目录内(默认:),可防止路径遍历攻击。
HERMES_CONTROL_ROOTS~/.hermesTerminal (WebSocket)
终端(WebSocket)
Connect to Terminal:
javascript
const ws = new WebSocket(`ws://${location.host}/terminal`);
ws.onopen = () => {
console.log('Terminal connected');
ws.send(JSON.stringify({
type: 'auth',
token: sessionToken
}));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'output') {
console.log('Output:', data.data);
} else if (data.type === 'error') {
console.error('Error:', data.message);
}
};
// Send command
ws.send(JSON.stringify({
type: 'input',
data: 'hermes doctor\n'
}));
// Resize PTY
ws.send(JSON.stringify({
type: 'resize',
cols: 80,
rows: 24
}));Rate Limiting: 30 commands per minute per IP.
连接终端:
javascript
const ws = new WebSocket(`ws://${location.host}/terminal`);
ws.onopen = () => {
console.log('终端已连接');
ws.send(JSON.stringify({
type: 'auth',
token: sessionToken
}));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'output') {
console.log('输出:', data.data);
} else if (data.type === 'error') {
console.error('错误:', data.message);
}
};
// 发送命令
ws.send(JSON.stringify({
type: 'input',
data: 'hermes doctor\n'
}));
// 调整PTY窗口大小
ws.send(JSON.stringify({
type: 'resize',
cols: 80,
rows: 24
}));速率限制: 每个IP每分钟最多30条命令。
System Maintenance
系统维护
Run Doctor (Diagnostics):
javascript
// POST /api/maintenance/doctor
fetch('/api/maintenance/doctor', {
method: 'POST',
headers: {
'Authorization': `Bearer ${sessionToken}`,
'X-CSRF-Token': csrfToken
}
})
.then(res => res.json())
.then(data => {
console.log('Issues found:', data.issues);
console.log('Fixes applied:', data.fixes);
});Generate Debug Dump:
javascript
// GET /api/maintenance/dump
fetch('/api/maintenance/dump', {
headers: { 'Authorization': `Bearer ${sessionToken}` }
})
.then(res => res.json())
.then(data => {
console.log('System info:', data.system);
console.log('Config:', data.config);
console.log('Logs:', data.logs);
});Backup System:
javascript
// GET /api/maintenance/backup
// Returns a zip file download
window.location.href = '/api/maintenance/backup?token=' + sessionToken;Restart HCI Server:
javascript
// POST /api/maintenance/restart-hci
fetch('/api/maintenance/restart-hci', {
method: 'POST',
headers: {
'Authorization': `Bearer ${sessionToken}`,
'X-CSRF-Token': csrfToken
}
});
// Server will restart in 2 seconds运行诊断工具:
javascript
// POST /api/maintenance/doctor
fetch('/api/maintenance/doctor', {
method: 'POST',
headers: {
'Authorization': `Bearer ${sessionToken}`,
'X-CSRF-Token': csrfToken
}
})
.then(res => res.json())
.then(data => {
console.log('发现问题:', data.issues);
console.log('已应用修复:', data.fixes);
});生成调试快照:
javascript
// GET /api/maintenance/dump
fetch('/api/maintenance/dump', {
headers: { 'Authorization': `Bearer ${sessionToken}` }
})
.then(res => res.json())
.then(data => {
console.log('系统信息:', data.system);
console.log('配置信息:', data.config);
console.log('日志信息:', data.logs);
});备份系统:
javascript
// GET /api/maintenance/backup
// 返回zip文件下载
window.location.href = '/api/maintenance/backup?token=' + sessionToken;重启HCI服务:
javascript
// POST /api/maintenance/restart-hci
fetch('/api/maintenance/restart-hci', {
method: 'POST',
headers: {
'Authorization': `Bearer ${sessionToken}`,
'X-CSRF-Token': csrfToken
}
});
// 服务将在2秒后重启Common Patterns
常用模式
Programmatic Profile Switching
程序化切换配置文件
javascript
class HermesProfileManager {
constructor(baseUrl, token, csrfToken) {
this.baseUrl = baseUrl;
this.token = token;
this.csrfToken = csrfToken;
}
async switchProfile(from, to) {
// Stop current profile gateway
await fetch(`${this.baseUrl}/api/agents/gateway/stop`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.token}`,
'X-CSRF-Token': this.csrfToken
},
body: JSON.stringify({ profile: from })
});
// Start new profile gateway
await fetch(`${this.baseUrl}/api/agents/gateway/start`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.token}`,
'X-CSRF-Token': this.csrfToken
},
body: JSON.stringify({ profile: to })
});
console.log(`Switched from ${from} to ${to}`);
}
async createTestProfile(name, baseProfile = 'default') {
// Clone base profile
await fetch(`${this.baseUrl}/api/agents/profiles`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.token}`,
'X-CSRF-Token': this.csrfToken
},
body: JSON.stringify({ name, cloneFrom: baseProfile })
});
// Update config for testing (lower temperature, etc.)
const config = await fetch(`${this.baseUrl}/api/agents/config?profile=${name}`, {
headers: { 'Authorization': `Bearer ${this.token}` }
}).then(res => res.json());
const modifiedConfig = config.config.replace(/temperature: \d\.\d+/, 'temperature: 0.3');
await fetch(`${this.baseUrl}/api/agents/config`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.token}`,
'X-CSRF-Token': this.csrfToken
},
body: JSON.stringify({ profile: name, config: modifiedConfig })
});
console.log(`Test profile ${name} created`);
}
}
// Usage
const manager = new HermesProfileManager('http://localhost:10272', sessionToken, csrfToken);
await manager.createTestProfile('experiment-1');
await manager.switchProfile('default', 'experiment-1');javascript
class HermesProfileManager {
constructor(baseUrl, token, csrfToken) {
this.baseUrl = baseUrl;
this.token = token;
this.csrfToken = csrfToken;
}
async switchProfile(from, to) {
// 停止当前配置文件的网关
await fetch(`${this.baseUrl}/api/agents/gateway/stop`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.token}`,
'X-CSRF-Token': this.csrfToken
},
body: JSON.stringify({ profile: from })
});
// 启动新配置文件的网关
await fetch(`${this.baseUrl}/api/agents/gateway/start`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.token}`,
'X-CSRF-Token': this.csrfToken
},
body: JSON.stringify({ profile: to })
});
console.log(`已从${from}切换到${to}`);
}
async createTestProfile(name, baseProfile = 'default') {
// 克隆基础配置文件
await fetch(`${this.baseUrl}/api/agents/profiles`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.token}`,
'X-CSRF-Token': this.csrfToken
},
body: JSON.stringify({ name, cloneFrom: baseProfile })
});
// 更新测试配置(降低温度等)
const config = await fetch(`${this.baseUrl}/api/agents/config?profile=${name}`, {
headers: { 'Authorization': `Bearer ${this.token}` }
}).then(res => res.json());
const modifiedConfig = config.config.replace(/temperature: \d\.\d+/, 'temperature: 0.3');
await fetch(`${this.baseUrl}/api/agents/config`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.token}`,
'X-CSRF-Token': this.csrfToken
},
body: JSON.stringify({ profile: name, config: modifiedConfig })
});
console.log(`已创建测试配置文件${name}`);
}
}
// 使用示例
const manager = new HermesProfileManager('http://localhost:10272', sessionToken, csrfToken);
await manager.createTestProfile('experiment-1');
await manager.switchProfile('default', 'experiment-1');Bulk Session Export
批量导出会话
javascript
async function exportAllSessions(profile) {
const sessions = await fetch(`/api/chat/sessions?profile=${profile}`, {
headers: { 'Authorization': `Bearer ${sessionToken}` }
}).then(res => res.json());
const exports = [];
for (const session of sessions.sessions) {
const data = await fetch(`/api/chat/sessions/${session.id}/export?profile=${profile}`, {
headers: { 'Authorization': `Bearer ${sessionToken}` }
}).then(res => res.json());
exports.push({
id: session.id,
title: session.title,
messages: data.messages
});
}
// Save to file
const blob = new Blob([JSON.stringify(exports, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `hermes-sessions-${profile}-${Date.now()}.json`;
a.click();
}
// Usage
await exportAllSessions('production');javascript
async function exportAllSessions(profile) {
const sessions = await fetch(`/api/chat/sessions?profile=${profile}`, {
headers: { 'Authorization': `Bearer ${sessionToken}` }
}).then(res => res.json());
const exports = [];
for (const session of sessions.sessions) {
const data = await fetch(`/api/chat/sessions/${session.id}/export?profile=${profile}`, {
headers: { 'Authorization': `Bearer ${sessionToken}` }
}).then(res => res.json());
exports.push({
id: session.id,
title: session.title,
messages: data.messages
});
}
// 保存到文件
const blob = new Blob([JSON.stringify(exports, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `hermes-sessions-${profile}-${Date.now()}.json`;
a.click();
}
// 使用示例
await exportAllSessions('production');Custom Token Usage Reporter
自定义令牌使用报告器
javascript
class TokenUsageReporter {
constructor(baseUrl, token) {
this.baseUrl = baseUrl;
this.token = token;
}
async generateReport(profile, range = '30d') {
const stats = await fetch(
`${this.baseUrl}/api/usage/stats?range=${range}&profile=${profile}`,
{ headers: { 'Authorization': `Bearer ${this.token}` } }
).then(res => res.json());
const report = {
period: range,
profile: profile,
summary: {
totalSessions: stats.totalSessions,
totalMessages: stats.totalMessages,
totalTokens: stats.totalTokens,
estimatedCost: stats.estimatedCost,
avgTokensPerSession: Math.round(stats.totalTokens / stats.totalSessions)
},
topModels: stats.modelBreakdown.slice(0, 5),
topTools: stats.topTools.slice(0, 10),
platformDistribution: stats.platformBreakdown
};
console.table(report.topModels);
console.table(report.topTools);
return report;
}
async compareProfiles(profileA, profileB, range = '7d') {
const [statsA, statsB] = await Promise.all([
fetch(`${this.baseUrl}/api/usage/stats?range=${range}&profile=${profileA}`,
{ headers: { 'Authorization': `Bearer ${this.token}` } }).then(res => res.json()),
fetch(`${this.baseUrl}/api/usage/stats?range=${range}&profile=${profileB}`,
{ headers: { 'Authorization': `Bearer ${this.token}` } }).then(res => res.json())
]);
return {
profileA: { name: profileA, cost: statsA.estimatedCost, tokens: statsA.totalTokens },
profileB: { name: profileB, cost: statsB.estimatedCost, tokens: statsB.totalTokens },
costDiff: statsA.estimatedCost - statsB.estimatedCost,
tokenDiff: statsA.totalTokens - statsB.totalTokens
};
}
}
// Usage
const reporter = new TokenUsageReporter('http://localhost:10272', sessionToken);
const report = await reporter.generateReport('production', '30d');
console.log('Monthly cost:', report.summary.estimatedCost);
const comparison = await reporter.compareProfiles('production', 'staging', '7d');
console.log('Cost difference:', comparison.costDiff);javascript
class TokenUsageReporter {
constructor(baseUrl, token) {
this.baseUrl = baseUrl;
this.token = token;
}
async generateReport(profile, range = '30d') {
const stats = await fetch(
`${this.baseUrl}/api/usage/stats?range=${range}&profile=${profile}`,
{ headers: { 'Authorization': `Bearer ${this.token}` } }
).then(res => res.json());
const report = {
period: range,
profile: profile,
summary: {
totalSessions: stats.totalSessions,
totalMessages: stats.totalMessages,
totalTokens: stats.totalTokens,
estimatedCost: stats.estimatedCost,
avgTokensPerSession: Math.round(stats.totalTokens / stats.totalSessions)
},
topModels: stats.modelBreakdown.slice(0, 5),
topTools: stats.topTools.slice(0, 10),
platformDistribution: stats.platformBreakdown
};
console.table(report.topModels);
console.table(report.topTools);
return report;
}
async compareProfiles(profileA, profileB, range = '7d') {
const [statsA, statsB] = await Promise.all([
fetch(`${this.baseUrl}/api/usage/stats?range=${range}&profile=${profileA}`,
{ headers: { 'Authorization': `Bearer ${this.token}` } }).then(res => res.json()),
fetch(`${this.baseUrl}/api/usage/stats?range=${range}&profile=${profileB}`,
{ headers: { 'Authorization': `Bearer ${this.token}` } }).then(res => res.json())
]);
return {
profileA: { name: profileA, cost: statsA.estimatedCost, tokens: statsA.totalTokens },
profileB: { name: profileB, cost: statsB.estimatedCost, tokens: statsB.totalTokens },
costDiff: statsA.estimatedCost - statsB.estimatedCost,
tokenDiff: statsA.totalTokens - statsB.totalTokens
};
}
}
// 使用示例
const reporter = new TokenUsageReporter('http://localhost:10272', sessionToken);
const report = await reporter.generateReport('production', '30d');
console.log('月度成本:', report.summary.estimatedCost);
const comparison = await reporter.compareProfiles('production', 'staging', '7d');
console.log('成本差异:', comparison.costDiff);Configuration Examples
配置示例
Multi-Platform Setup
多平台配置
~/.hermes/profiles/production/config.yaml:
yaml
llm:
model: hermes-3
provider: openrouter
api_key: ${OPENROUTER_API_KEY}
temperature: 0.7
max_tokens: 4000
platforms:
telegram:
enabled: true
token: ${TELEGRAM_BOT_TOKEN}
whatsapp:
enabled: true
account_sid: ${TWILIO_ACCOUNT_SID}
auth_token: ${TWILIO_AUTH_TOKEN}
from_number: ${TWILIO_PHONE_NUMBER}
slack:
enabled: true
bot_token: ${SLACK_BOT_TOKEN}
app_token: ${SLACK_APP_TOKEN}
memory:
provider: honcho
honcho_url: http://localhost:8000
honcho_app_name: hermes-prod
tools:
web_search:
enabled: true
provider: tavily
api_key: ${TAVILY_API_KEY}
code_execution:
enabled: true
sandbox: docker
timeout: 30
prompts:
system: |
You are Hermes, a production AI assistant for the DevOps team.
Focus on accuracy and provide detailed technical responses.~/.hermes/profiles/production/config.yaml:
yaml
llm:
model: hermes-3
provider: openrouter
api_key: ${OPENROUTER_API_KEY}
temperature: 0.7
max_tokens: 4000
platforms:
telegram:
enabled: true
token: ${TELEGRAM_BOT_TOKEN}
whatsapp:
enabled: true
account_sid: ${TWILIO_ACCOUNT_SID}
auth_token: ${TWILIO_AUTH_TOKEN}
from_number: ${TWILIO_PHONE_NUMBER}
slack:
enabled: true
bot_token: ${SLACK_BOT_TOKEN}
app_token: ${SLACK_APP_TOKEN}
memory:
provider: honcho
honcho_url: http://localhost:8000
honcho_app_name: hermes-prod
tools:
web_search:
enabled: true
provider: tavily
api_key: ${TAVILY_API_KEY}
code_execution:
enabled: true
sandbox: docker
timeout: 30
prompts:
system: |
你是Hermes,DevOps团队的生产级AI助手。
专注于准确性,提供详细的技术响应。Custom RBAC Setup
自定义RBAC配置
Create a "Analyst" role (read-only + chat):
javascript
// Via Maintenance → Users UI or API
const analystPermissions = [
'agents:read',
'chat:read',
'chat:write',
'sessions:read',
'config:read',
'gateway:logs'
];
await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${adminToken}`,
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({
username: 'analyst1',
password: 'secure-password',
role: 'custom',
permissions: analystPermissions
})
});创建“分析师”角色(只读+聊天权限):
javascript
// 通过维护 → 用户UI或API创建
const analystPermissions = [
'agents:read',
'chat:read',
'chat:write',
'sessions:read',
'config:read',
'gateway:logs'
];
await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${adminToken}`,
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({
username: 'analyst1',
password: 'secure-password',
role: 'custom',
permissions: analystPermissions
})
});Scheduled Maintenance Cron
定时维护任务
javascript
// Daily health check at 3 AM
await fetch('/api/cron', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${sessionToken}`,
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({
profile: 'default',
schedule: '0 3 * * *',
command: 'hermes doctor --auto-fix && hermes chat "Daily system report"',
enabled: true
})
});
// Weekly backup every Sunday at 2 AM
await fetch('/api/cron', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${sessionToken}`,
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({
profile: 'default',
schedule: '0 2 * * 0',
command: 'cd ~/.hermes && tar -czf backup-$(date +%Y%m%d).tar.gz .',
enabled: true
})
});javascript
// 每日凌晨3点执行健康检查
await fetch('/api/cron', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${sessionToken}`,
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({
profile: 'default',
schedule: '0 3 * * *',
command: 'hermes doctor --auto-fix && hermes chat "每日系统报告"',
enabled: true
})
});
// 每周日凌晨2点执行备份
await fetch('/api/cron', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${sessionToken}`,
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({
profile: 'default',
schedule: '0 2 * * 0',
command: 'cd ~/.hermes && tar -czf backup-$(date +%Y%m%d).tar.gz .',
enabled: true
})
});Troubleshooting
故障排查
Gateway Won't Start
网关无法启动
Symptom: "Failed to start gateway" error
Solutions:
bash
undefined症状: 出现“Failed to start gateway”错误
解决方案:
bash
undefined1. Check if port is already in use
1. 检查端口是否被占用
sudo lsof -i :8000 # Default gateway port
sudo lsof -i :8000 # 默认网关端口
2. View gateway logs
2. 查看网关日志
journalctl -u hermes-gateway-default -n 50
journalctl -u hermes-gateway-default -n 50
3. Check systemd service status
3. 检查systemd服务状态
systemctl status hermes-gateway-default
systemctl status hermes-gateway-default
4. Manually test gateway
4. 手动测试网关
hermes gateway start --profile default --debug
hermes gateway start --profile default --debug
5. Check config.yaml syntax
5. 检查config.yaml语法
hermes config validate --profile default
undefinedhermes config validate --profile default
undefinedWebSocket Connection Fails
WebSocket连接失败
Symptom: Terminal or chat streaming doesn't work
Solutions:
javascript
// 1. Check WebSocket origin in server.js
// Ensure your domain is allowed in WebSocket verification
// 2. If behind reverse proxy, configure headers:
// Nginx example:
/*
location /terminal {
proxy_pass http://localhost:10272;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
*/
// 3. Check browser console for CORS errors
// Update .env if needed:
// HERMES_CONTROL_CORS_ORIGIN=https://yourdomain.com症状: 终端或聊天流无法正常工作
解决方案:
javascript
// 1. 检查server.js中的WebSocket源配置
// 确保你的域名在WebSocket验证白名单中
// 2. 如果使用反向代理,配置相应头信息:
// Nginx示例:
/*
location /terminal {
proxy_pass http://localhost:10272;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
*/
// 3. 检查浏览器控制台是否有CORS错误
// 如有需要更新.env配置:
// HERMES_CONTROL_CORS_ORIGIN=https://yourdomain.comSession Not Resuming
会话无法继续
Symptom: creates new session instead
--continue <session-id>Solutions:
bash
undefined症状: 使用时创建了新会话而非继续原有会话
--continue <session-id>解决方案:
bash
undefined1. Verify session exists
1. 验证会话是否存在
ls -la ~/.hermes/sessions/
ls -la ~/.hermes/sessions/
2. Check session ID format
2. 检查会话ID格式
New format: abc123
新格式: abc123
Legacy format: Session_abc123
旧格式: Session_abc123
3. Test CLI directly
3. 直接通过CLI测试
hermes chat --continue abc123 "test message"
hermes chat --continue abc123 "测试消息"
4. Check session file permissions
4. 检查会话文件权限
chmod 644 ~/.hermes/sessions/abc123.json
chmod 644 ~/.hermes/sessions/abc123.json
5. Review chat logs in HCI
5. 在HCI中查看聊天日志
Navigate to Agents → [Profile] → Sessions tab
导航至Agent → [配置文件] → 会话标签页
undefinedundefinedToken Analytics Not Showing
令牌分析无数据
Symptom: Usage page shows zero data
Solutions:
bash
undefined症状: 使用页面显示数据为零
解决方案:
bash
undefined1. Verify session log files exist
1. 验证会话日志文件是否存在
ls -la ~/.hermes/sessions/*.json
ls -la ~/.hermes/sessions/*.json
2. Check token tracking is enabled in config
2. 检查配置中是否启用了令牌追踪
cat ~/.hermes/profiles/default/config.yaml | grep -A5 analytics
cat ~/.hermes/profiles/default/config.yaml | grep -A5 analytics
3. Manually trigger analytics rebuild
3. 手动触发分析数据重建
hermes analytics rebuild
hermes analytics rebuild
4. Check for parsing errors in logs
4. 检查日志中的解析错误
tail -f ~/.hermes/hci.log | grep analytics
undefinedtail -f ~/.hermes/hci.log | grep analytics
undefinedPermission Denied Errors
权限拒绝错误
Symptom: "Permission denied" when accessing files/terminals
Solutions:
bash
undefined症状: 访问文件/终端时出现“Permission denied”错误
解决方案:
bash
undefined1. Check user permissions on ~/.hermes
1. 检查~/.hermes目录的用户权限
ls -ld ~/.hermes
chmod 755 ~/.hermes
ls -ld ~/.hermes
chmod 755 ~/.hermes
2. If running as non-root, ensure RBAC role allows action
2. 如果以非root用户运行,确保RBAC角色允许该操作
Check Maintenance → Users → [Your User] → Permissions
检查维护 → 用户 → [你的用户] → 权限设置
3. For terminal: verify user has shell access
3. 对于终端: 验证用户是否具备Shell访问权限
echo $SHELL
echo $SHELL
4. For files: check HERMES_CONTROL_ROOTS in .env
4. 对于文件: 检查.env中的HERMES_CONTROL_ROOTS配置
Default: ~/.hermes (user must have read/write access)
默认值: ~/.hermes(用户需具备读写权限)
undefinedundefinedHigh Memory Usage
内存占用过高
Symptom: HCI process consuming excessive RAM
Solutions:
bash
undefined症状: HCI进程消耗过多内存
解决方案:
bash
undefined1. Check active WebSocket connections
1. 检查活跃WebSocket连接数
Navigate to browser DevTools → Network → WS tab
打开浏览器开发者工具 → 网络 → WS标签页
2. Restart HCI to clear stale connections
2. 重启HCI以清除过期连接
systemctl restart hermes-control
systemctl restart hermes-control
OR via UI: Maintenance → HCI Restart
或通过UI操作: 维护 → 重启HCI
3. Limit concurrent sessions
3. 限制并发会话数
Edit server.js to add connection limits if needed
如有需要编辑server.js添加连接限制
4. Check for memory leaks in logs
4. 检查日志中的内存泄漏信息
node --expose-gc server.js
undefinednode --expose-gc server.js
undefinedCSRF Token Mismatch
CSRF令牌不匹配
Symptom: "Invalid CSRF token" on form submissions
Solutions:
javascript
// 1. Ensure CSRF token is included in request headers
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
fetch('/api/agents/profiles', {
method: 'POST',
headers: {
'X-CSRF-Token': csrfToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: 'test' })
});
// 2. Check session hasn't expired
// Default: 24 hour session timeout
// 3. Clear cookies and re-login
document.cookie.split(";").forEach(c => {
document.cookie = c.replace(/^ +/, "").replace(/=.*/, "=;expires=" + new Date().toUTCString() + ";path=/");
});症状: 表单提交时出现“Invalid CSRF token”错误
解决方案:
javascript
// 1. 确保请求头中包含CSRF令牌
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
fetch('/api/agents/profiles', {
method: 'POST',
headers: {
'X-CSRF-Token': csrfToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: 'test' })
});
// 2. 检查会话是否已过期Gateway Logs Not Streaming
默认会话超时时间: 24小时
—
3. 清除Cookie并重新登录
Symptom: Gateway log viewer shows "Connecting..." indefinitely
Solutions:
bash
undefineddocument.cookie.split(";").forEach(c => {
document.cookie = c.replace(/^ +/, "").replace(/=.*/, "=;expires=" + new Date().toUTCString() + ";path=/");
});
undefined1. Verify gateway is actually running
网关日志无法流式显示
systemctl status hermes-gateway-default
症状: 网关日志查看器一直显示“Connecting...”
解决方案:
bash
undefined2. Check systemd journal accessibility
1. 验证网关是否真的在运行
sudo journalctl -u hermes-gateway-default --no-pager -n 10
systemctl status hermes-gateway-default
3. Grant user access to systemd journal (if non-root)
2. 检查systemd日志的可访问性
sudo user
sudo journalctl -u hermes-gateway-default --no-pager -n 10
—
3. 授予用户访问systemd日志的权限(如果是非root用户)
—
sudo user
undefined