- name
- hermes-browser-extension
- description
- Browser extension that connects web context to local/remote Hermes Agent runtime via side panel
- triggers
- ["install the hermes browser extension","set up hermes browser side panel","connect browser to hermes agent","configure hermes browser extension","use hermes with browser context","send webpage content to hermes","attach browser context to hermes","troubleshoot hermes browser extension"]
# hermes-browser-extension
> Skill by [ara.so](https://ara.so) — Hermes Skills collection.
## What it does
Hermes Browser Extension is a Chrome/Edge/Chromium side panel that connects active web page context to a Hermes Agent runtime (local or remote). It is **not** a standalone chatbot—it's a browser interface for the real Hermes Gateway/API server, accessing your configured models, tools, skills, sessions, memory, and MCP servers.
Key capabilities:
- Side panel UI powered by Chrome Side Panel API (MV3)
- Connects to local (`http://127.0.0.1:8642`) or remote Hermes API server
- Syncs providers, models, profiles, skills, sessions, and capabilities from Hermes
- Captures and sends active tab context (title, URL, readable text, selected text, metadata, headings, links, forms, buttons)
- Supports per-tab or global side panel mode
- Streams responses from Hermes
- Voice dictation via Hermes STT or browser speech fallback
- Read-only context capture (no browser control, no form submission)
- Multiple themes (Nous, Midnight, Ember, Mono, Cyberpunk, Slate) and light/dark modes
## Installation
### 1. Clone and build
```bash
git clone https://github.com/abundantbeing/hermes-browser-extension.git
cd hermes-browser-extension
npm install
npm run build
```
The extension builds to `dist/`.
### 2. Load unpacked in browser
1. Open `chrome://extensions` or `edge://extensions`
2. Enable **Developer mode**
3. Click **Load unpacked**
4. Select the `dist/` folder (not repo root, not `extension/`)
5. Pin the Hermes extension icon and click to open side panel
After code changes, run `npm run build` and click **Reload** on the extension card.
### 3. Verify installation
```bash
# Check that dist/ contains manifest.json with version 0.1.6+
cat dist/manifest.json | grep version
```
## Configuration
### Local API server (recommended default)
On the machine running Hermes, edit `~/.hermes/.env`:
```bash
API_SERVER_ENABLED=true
API_SERVER_HOST=127.0.0.1
API_SERVER_PORT=8642
API_SERVER_KEY=your-secret-key-here
API_SERVER_CORS_ORIGINS=chrome-extension://your-extension-id
```
Find your extension ID at `chrome://extensions` (looks like `abcdefghijklmnopqrstuvwxyz123456`).
Start/restart Hermes Gateway:
```bash
hermes gateway run
```
Verify API server is reachable:
```bash
curl http://127.0.0.1:8642/health
curl -H "Authorization: Bearer your-secret-key-here" http://127.0.0.1:8642/v1/models
```
### Connect extension to local API
In the extension side panel:
1. Click **Connect to Hermes**
2. If approval flow is available, approve locally in Hermes Desktop/gateway
3. Otherwise, click **Manual setup** → **Local gateway**
4. Gateway URL: `http://127.0.0.1:8642`
5. API Key: paste your `API_SERVER_KEY` or scoped browser token
6. Click **Test connection**, then **Save settings**
7. Navigate to a normal webpage and test: `Summarize this page in one sentence.`
### Remote API server
For remote Hermes (same LAN, Tailscale, VPN), bind to reachable interface:
```bash
API_SERVER_ENABLED=true
API_SERVER_HOST=0.0.0.0
API_SERVER_PORT=8642
API_SERVER_KEY=your-secret-key-here
API_SERVER_CORS_ORIGINS=chrome-extension://your-extension-id
```
**Security**: Use private network/VPN with HTTP, or put behind HTTPS reverse proxy. Never expose Hermes API server naked to public internet.
Example URLs:
```
http://192.168.1.50:8642
http://hermes-desktop.local:8642
https://hermes.example.com
```
In extension:
1. Choose **Remote gateway**
2. Paste remote URL (include `http://` or `https://`)
3. Paste API key/browser token
4. Click **Test connection**
### Remote dashboard mode (no API server)
If you only expose the OAuth-gated Hermes dashboard (no API server):
1. Select **Remote**
2. Enter dashboard `https://` URL
3. Leave API key **blank**
4. Open dashboard URL in normal tab and sign in (keep tab open)
5. Extension mints WebSocket ticket from signed-in tab
6. Click **Test connection** (opens socket and loads models)
Limitations: image attachments inline-only, skills/profiles unavailable (REST-only).
## Usage patterns
### Basic prompting with page context
```text
# Side panel captures active tab automatically
Summarize this page
Explain the main concept here
What are the key action items on this page?
```
### Quick commands
The extension includes built-in quick commands:
```text
/summarize - Summarize active page
/explain - Explain main concepts
/rewrite - Rewrite or improve content
/tabs - Analyze all open tabs
/action-items - Extract action items
```
### Context scope control
Click the context menu in composer header to choose:
- **Chat only** - no browser context attached
- **Follow active tab** - context auto-updates when you switch tabs
- **Page only** - pin to specific tab
- **Choose tabs** - select which open tabs to include
### Voice dictation
Click microphone icon in composer:
- Uses Hermes audio transcription if available
- Falls back to browser speech recognition otherwise
### What Hermes saw
After each turn, expand "What Hermes saw" to inspect:
- Active tab title, URL
- Selected text (if any)
- Readable page text (first N chars)
- Metadata, headings, links, forms, buttons
- Open tabs list (if included)
This is the exact context sent to Hermes.
## Code examples
### Programmatically send message (content script)
```javascript
// From a content script, send message to background
chrome.runtime.sendMessage({
type: 'HERMES_PROMPT',
payload: {
message: 'Summarize this page',
context: {
url: window.location.href,
title: document.title,
selectedText: window.getSelection().toString()
}
}
});
```
### Access stored settings
```javascript
// From extension context (popup, side panel, background)
import { getSettings } from './lib/storage';
const settings = await getSettings();
console.log('Gateway URL:', settings.gatewayUrl);
console.log('Theme:', settings.appearance?.theme);
console.log('Context mode:', settings.browserBehavior?.contextScope);
```
### Capture page context
```javascript
// From content script (extension does this automatically)
const context = {
url: window.location.href,
title: document.title,
selectedText: window.getSelection().toString().trim(),
readableText: document.body.innerText.slice(0, 50000),
metadata: {
description: document.querySelector('meta[name="description"]')?.content,
author: document.querySelector('meta[name="author"]')?.content
},
headings: Array.from(document.querySelectorAll('h1, h2, h3')).map(h => ({
level: h.tagName,
text: h.textContent.trim()
})),
links: Array.from(document.querySelectorAll('a[href]')).slice(0, 50).map(a => ({
text: a.textContent.trim(),
href: a.href
}))
};
```
### Stream Hermes response
```javascript
// Extension internal pattern
async function streamHermesResponse(sessionId, messages, signal) {
const response = await fetch(`${gatewayUrl}/v1/chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify({
session_id: sessionId,
messages,
stream: true
}),
signal
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop();
for (const line of lines) {
if (line.startsWith('data: ') && line !== 'data: [DONE]') {
const data = JSON.parse(line.slice(6));
const content = data.choices?.[0]?.delta?.content;
if (content) {
// Append to UI
yield content;
}
}
}
}
}
```
### Custom theme integration
```javascript
// Extension appearance settings
const themes = {
nous: { primary: 'rgb(79, 70, 229)', secondary: 'rgb(99, 102, 241)' },
midnight: { primary: 'rgb(59, 130, 246)', secondary: 'rgb(96, 165, 250)' },
ember: { primary: 'rgb(249, 115, 22)', secondary: 'rgb(251, 146, 60)' },
mono: { primary: 'rgb(71, 85, 105)', secondary: 'rgb(100, 116, 139)' },
cyberpunk: { primary: 'rgb(236, 72, 153)', secondary: 'rgb(244, 114, 182)' },
slate: { primary: 'rgb(100, 116, 139)', secondary: 'rgb(148, 163, 184)' }
};
// Apply theme
function applyTheme(themeName, colorMode) {
const theme = themes[themeName];
document.documentElement.style.setProperty('--color-primary', theme.primary);
document.documentElement.style.setProperty('--color-secondary', theme.secondary);
document.documentElement.classList.toggle('dark', colorMode === 'dark');
}
```
## Environment variables
Extension does not use `.env` files directly. Configuration is stored in Chrome storage and managed via settings UI.
For Hermes Gateway API server (machine running Hermes):
```bash
# ~/.hermes/.env
API_SERVER_ENABLED=true
API_SERVER_HOST=127.0.0.1
API_SERVER_PORT=8642
API_SERVER_KEY=<use-env-var-or-secret-manager>
API_SERVER_CORS_ORIGINS=chrome-extension://<extension-id>
```
**Never commit API keys**. Use `$(hermes auth token create --scope browser)` to generate scoped tokens, or reference secrets from environment/vault.
## Troubleshooting
### Extension loaded but nothing works
- Ensure you loaded `dist/`, not repo root
- Selected folder must contain `manifest.json` directly
- Check `chrome://extensions` shows version 0.1.6+
### Cannot connect to Hermes
```bash
# Verify API server is running and reachable
curl http://127.0.0.1:8642/health
# Check models endpoint with auth
curl -H "Authorization: Bearer your-key-here" http://127.0.0.1:8642/v1/models
```
Common issues:
- `API_SERVER_ENABLED=true` not set in `~/.hermes/.env`
- Hermes Gateway not running (`hermes gateway run`)
- Wrong `API_SERVER_KEY` or extension API key
- CORS origin mismatch (extension ID not in `API_SERVER_CORS_ORIGINS`)
- Firewall blocking port 8642
### Wrong version after update
Browser cached old unpacked folder:
1. Run `npm run build` to generate fresh `dist/`
2. Open `chrome://extensions`
3. Click **Reload** on Hermes Browser Extension card
4. If still wrong, click **Remove**, then **Load unpacked** and select fresh `dist/`
5. Verify version: click **Details** → see Version field
### No page context captured
Extension cannot read restricted pages:
- `chrome://` or `edge://` internal pages
- `chrome-extension://` pages
- Banking, crypto, password, payment, health, government domains (intentional security restriction)
On normal `https://` pages, check:
- Context scope is not "Chat only"
- Page has readable text (not just images/video)
- Click "What Hermes saw" to inspect captured context
### Voice dictation not working
- Check Hermes capabilities: extension queries `/v1/capabilities` for STT support
- If Hermes STT unavailable, extension falls back to browser speech recognition
- Browser speech requires microphone permission (granted on first use)
- Some browsers/OSes may not support Web Speech API
### Computer use / browser automation not working
Hermes Browser Extension is **read-only**. It does not have browser control permissions (`debugger`, click, type, form submit).
For native computer use:
```bash
# On machine running Hermes
hermes tools list
hermes computer-use status
```
Computer use comes from Hermes Agent's `computer_use` toolset via `cua-driver`, not the browser extension.
### CORS errors in console
Extension origin must be allowlisted on Hermes API server:
```bash
# ~/.hermes/.env
API_SERVER_CORS_ORIGINS=chrome-extension://your-extension-id-here
```
Find extension ID at `chrome://extensions`. Restart `hermes gateway run` after changing.
### Session persistence issues
- Extension binds one Hermes session per pinned tab
- Switching tabs creates new sessions (unless "Follow active tab" is enabled)
- Check `/api/sessions` in Hermes to see active sessions
- Clear local history: Settings → Browser behavior → Clear history
### Streaming response fails
Extension auto-falls back to non-streaming if:
- Gateway does not support `stream: true`
- Network drops chunks
- Response is not SSE format
Check gateway logs for streaming errors. Non-streaming mode still works.
## Security notes
Extension is intentionally conservative:
- **Read-only**: no click, type, form submit, download, or browser control
- **No sensitive permissions**: no `debugger`, `nativeMessaging`, `cookies`, `history`, `downloads`, `bookmarks`
- **Content wrapped as untrusted**: page text marked as external context before Hermes sees it
Auf GitHub ansehen