chore(rename): migrate legacy 'Hermes' codename bytes to canonical 'Claude' across repo

- Rewrites all Claude/claude/CLAUDE -> Hermes/claude/CLAUDE
- Renames 31 files (hermes-*.ts -> claude-*.ts, hermes-*.png -> claude-*.png, etc.)
- Renames 1 directory (src/routes/api/hermes-proxy -> claude-proxy)
- 254 files content-modified, 2681 byte sequences replaced
- Build passes, no new test regressions (pre-existing failures unchanged)
- Adds skills-bundle/ to gitignore + vitest exclude to keep test scope clean
This commit is contained in:
Aurora release bot
2026-05-01 01:00:06 -04:00
parent d114dadae3
commit efcb7d144e
265 changed files with 2330 additions and 2319 deletions

View File

@@ -1,7 +1,7 @@
{
"name": "Hermes Workspace",
"name": "Claude Workspace",
"dockerComposeFile": "../docker-compose.yml",
"service": "hermes-workspace",
"service": "claude-workspace",
"workspaceFolder": "/app",
"overrideCommand": false,
"forwardPorts": [3000, 8642],

View File

@@ -6,9 +6,9 @@
# ═══════════════════════════════════════════════════════════════
# LLM Provider — pick ONE (you don't need all of them)
# ═══════════════════════════════════════════════════════════════
# hermes-agent supports many providers. For Docker Compose the agent
# claude-agent supports many providers. For Docker Compose the agent
# container needs the key for whichever provider you configured in
# ~/.hermes/config.yaml. Common options:
# ~/.claude/config.yaml. Common options:
#
# Anthropic (Claude): https://console.anthropic.com/settings/keys
# OpenAI (GPT / o-series): https://platform.openai.com/api-keys
@@ -28,28 +28,28 @@
# ═══════════════════════════════════════════════════════════════
# Project Agent WebAPI URL (default: http://127.0.0.1:8642)
# - For Docker: Uses http://hermes-agent:8642 automatically
# - For Docker: Uses http://claude-agent:8642 automatically
# - For local dev: Set to http://127.0.0.1:8642
# IMPORTANT: The Hermes gateway HTTP API server is opt-in.
# Add API_SERVER_ENABLED=true to ~/.hermes/.env and restart the gateway.
# IMPORTANT: The Claude gateway HTTP API server is opt-in.
# Add API_SERVER_ENABLED=true to ~/.claude/.env and restart the gateway.
# Without it, the gateway serves messaging platforms but not port 8642.
# HERMES_API_URL=http://127.0.0.1:8642
# CLAUDE_API_URL=http://127.0.0.1:8642
# Project Agent API token — required when the gateway is authenticated
# (e.g. Docker deployments exposing API_SERVER_HOST=0.0.0.0).
#
# When your Hermes gateway has API_SERVER_KEY set, workspace must send the
# SAME value as HERMES_API_TOKEN here, or requests will be rejected with 401.
# When your Claude gateway has API_SERVER_KEY set, workspace must send the
# SAME value as CLAUDE_API_TOKEN here, or requests will be rejected with 401.
#
# ~/.hermes/.env: API_SERVER_KEY=<your-secret>
# hermes-workspace/.env: HERMES_API_TOKEN=<same-secret>
# ~/.claude/.env: API_SERVER_KEY=<your-secret>
# claude-workspace/.env: CLAUDE_API_TOKEN=<same-secret>
#
# Leave unset for local loopback gateways that don't set API_SERVER_KEY.
# HERMES_API_TOKEN=your-gateway-secret
# CLAUDE_API_TOKEN=your-gateway-secret
# Project Agent directory (auto-detected if sibling to workspace)
# Set this if hermes-agent is installed elsewhere
# HERMES_AGENT_PATH=/path/to/hermes-agent
# Set this if claude-agent is installed elsewhere
# CLAUDE_AGENT_PATH=/path/to/claude-agent
# Server port (default: 3002)
# PORT=3002
@@ -62,15 +62,15 @@
#
# The workspace exposes terminals, file read/write, agent control, and job
# management. Off-loopback exposure is opt-in. Set HOST=0.0.0.0 only if you
# *also* set HERMES_PASSWORD below. Without a password, the server refuses
# *also* set CLAUDE_PASSWORD below. Without a password, the server refuses
# to start on a non-loopback host.
# HOST=127.0.0.1
# Workspace session password (required for any remote deployment)
#
# Enables password protection of the web UI. Tokens are stored encrypted
# in ~/.hermes/workspace-sessions.json. Pick a strong secret (32+ chars).
# HERMES_PASSWORD=change-me-to-a-strong-secret
# in ~/.claude/workspace-sessions.json. Pick a strong secret (32+ chars).
# CLAUDE_PASSWORD=change-me-to-a-strong-secret
# Cookie Secure flag (default: on in production, off in dev)
#
@@ -92,10 +92,10 @@
#
# Preferred over the legacy HTML-scrape token flow. Set this to a dashboard
# bearer and the workspace uses it directly for dashboard API calls (see #124).
# HERMES_DASHBOARD_TOKEN=
# CLAUDE_DASHBOARD_TOKEN=
# Bypass fail-closed startup guard (NOT recommended)
#
# If you understand the risks and want to run the workspace on 0.0.0.0
# without a password (e.g. behind a custom auth layer), set this to 1.
# HERMES_ALLOW_INSECURE_REMOTE=0
# CLAUDE_ALLOW_INSECURE_REMOTE=0

View File

@@ -3,7 +3,7 @@ name: Build & publish Docker image
# Publishes Project Workspace to GitHub Container Registry (GHCR) so users
# can deploy via Coolify / Easypanel / Dokploy / any Docker host with:
#
# image: ghcr.io/outsourc-e/hermes-workspace:latest
# image: ghcr.io/outsourc-e/claude-workspace:latest
#
# Triggers:
# - push to main -> tags: latest, main, main-<sha>
@@ -62,7 +62,7 @@ jobs:
context: .
file: ./Dockerfile
load: true
tags: hermes-workspace:smoke
tags: claude-workspace:smoke
cache-from: type=gha
- name: Smoke test container startup
@@ -71,8 +71,8 @@ jobs:
cid=$(docker run -d \
-p 127.0.0.1:3000:3000 \
-e HERMES_API_URL=http://127.0.0.1:8642 \
hermes-workspace:smoke)
-e CLAUDE_API_URL=http://127.0.0.1:8642 \
claude-workspace:smoke)
trap 'docker logs "$cid" || true; docker rm -f "$cid" || true' EXIT
for _ in $(seq 1 30); do

3
.gitignore vendored
View File

@@ -138,3 +138,6 @@ __pycache__/
.env.bak
.runtime/
workspace-final-markdown-review.md
# Local sibling projects (do not ship)
skills-bundle/

View File

@@ -6,31 +6,31 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [Unreleased]
### Changed
- **`docker compose up` now pulls pre-built images by default** (#82) — `nousresearch/hermes-agent:latest` for the gateway and `ghcr.io/outsourc-e/hermes-workspace:latest` for the UI. Agent state persists in the `hermes-data` named volume. Adds `docker-compose.dev.yml` overlay for building from source.
- **`docker compose up` now pulls pre-built images by default** (#82) — `nousresearch/claude-agent:latest` for the gateway and `ghcr.io/outsourc-e/claude-workspace:latest` for the UI. Agent state persists in the `claude-data` named volume. Adds `docker-compose.dev.yml` overlay for building from source.
## [2.0.0] — 2026-04-20
**Zero-fork release.** Clone, don't fork. Project Workspace now runs on vanilla `pip install hermes-agent` with no patches, no drift, no custom gateway required.
**Zero-fork release.** Clone, don't fork. Project Workspace now runs on vanilla `pip install claude-agent` with no patches, no drift, no custom gateway required.
### Added
- **Zero-fork architecture** — dual gateway/dashboard routing; workspace talks directly to vanilla `hermes-agent` 0.10.0+ via standard endpoints (`/v1/models`, `/api/sessions`, `/api/skills`, `/api/config`, `/api/jobs`)
- **Zero-fork architecture** — dual gateway/dashboard routing; workspace talks directly to vanilla `claude-agent` 0.10.0+ via standard endpoints (`/v1/models`, `/api/sessions`, `/api/skills`, `/api/config`, `/api/jobs`)
- **One-liner curl installer** — `curl -fsSL … | bash` provisions workspace + gateway + defaults
- **Hermes-Nous theme** — dark + light editorial variants with cobalt/paper surface pass, thin 1px architectural borders, editorial type accents
- **Claude-Nous theme** — dark + light editorial variants with cobalt/paper surface pass, thin 1px architectural borders, editorial type accents
- **Conductor** (`/conductor`) — mission-control surface ported from Clawsuite; spawn missions, assign workers, watch live output and costs
- **Operations** (`/operations`) — agent registry / sessions manager ported from Clawsuite; pause, steer, kill live agents with role and model insight
- **Synthesized tool pills** — inline tool-call rendering from dashboard stream markers when running against zero-fork gateway
- **Landing parity pass** — hero, features, screenshots, setup, OG image, mobile theme toggle
- **Task board status vs. assignee** decoupling
- **Local-model chat session persistence** — local sessions appear in history + session list
- **Memory is local-fs first** — honors `HERMES_HOME`, no gateway dependency
- **Memory is local-fs first** — honors `CLAUDE_HOME`, no gateway dependency
- **Splash + screenshots refresh** — Conductor, Dashboard, Tasks, Jobs captured in new editorial theme
### Changed
- **Model picker** — fetches from gateway (`~/.hermes/models.json` for user-configured models), matches OCPlatform behavior; shows only configured providers instead of all upstream
- **Model picker** — fetches from gateway (`~/.claude/models.json` for user-configured models), matches OCPlatform behavior; shows only configured providers instead of all upstream
- **`enhanced-fork` mode label** no longer implies a fork is required; it indicates streaming route availability on vanilla gateway
- **Dashboard + enhanced-chat capabilities** marked optional; missing endpoints no longer trigger warnings
- **Feature-gate + install copy** — all fork-era references purged
- **Theme family allowlist** — `hermes-nous` promoted to the enterprise allowlist
- **Theme family allowlist** — `claude-nous` promoted to the enterprise allowlist
- **Session pill** — solid dark-mode background, matches model selector
### Fixed

View File

@@ -1,4 +1,4 @@
# Contributing to Hermes Workspace
# Contributing to Claude Workspace
Thanks for your interest in contributing! Here's how to get started.
@@ -9,9 +9,9 @@ Thanks for your interest in contributing! Here's how to get started.
3. Set up environment:
```bash
cp .env.example .env
# Edit .env — set HERMES_API_URL (default: http://127.0.0.1:8642)
# Edit .env — set CLAUDE_API_URL (default: http://127.0.0.1:8642)
```
4. Start [Hermes Agent](https://github.com/NousResearch/hermes-agent) API server
4. Start [Claude Agent](https://github.com/NousResearch/claude-agent) API server
5. Run dev server: `pnpm dev`
6. Make your changes on a feature branch
7. Open a PR against `main`
@@ -39,9 +39,9 @@ pnpm build
See `.env.example` for all options. Key ones:
- `HERMES_API_URL` — Hermes Agent gateway backend (default: `http://127.0.0.1:8642`)
- `HERMES_PASSWORD` — Optional password protection for the web UI
- `HERMES_ALLOWED_HOSTS` — Comma-separated hostnames for non-localhost access
- `CLAUDE_API_URL` — Claude Agent gateway backend (default: `http://127.0.0.1:8642`)
- `CLAUDE_PASSWORD` — Optional password protection for the web UI
- `CLAUDE_ALLOWED_HOSTS` — Comma-separated hostnames for non-localhost access
## Guidelines

View File

@@ -1,13 +1,13 @@
# syntax=docker/dockerfile:1.6
# Project Workspace — production Docker image
# Publishes to ghcr.io/outsourc-e/hermes-workspace
# Publishes to ghcr.io/outsourc-e/claude-workspace
#
# Build locally:
# docker build -t hermes-workspace .
# docker build -t claude-workspace .
# Run:
# docker run -p 3000:3000 -e HERMES_API_URL=http://host.docker.internal:8642 hermes-workspace
# docker run -p 3000:3000 -e CLAUDE_API_URL=http://host.docker.internal:8642 claude-workspace
# Or pull pre-built:
# docker pull ghcr.io/outsourc-e/hermes-workspace:latest
# docker pull ghcr.io/outsourc-e/claude-workspace:latest
#
# ─── build stage ─────────────────────────────────────────────────────────
FROM node:22-slim AS build
@@ -46,7 +46,7 @@ USER workspace
ENV NODE_ENV=production \
PORT=3000 \
HOST=0.0.0.0 \
HERMES_API_URL=http://hermes-agent:8642
CLAUDE_API_URL=http://claude-agent:8642
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \

View File

@@ -1,7 +1,7 @@
# Hermes Workspace — Comprehensive Features Inventory
# Claude Workspace — Comprehensive Features Inventory
> **Version:** 2.0.0 | **Stack:** React 19 + TanStack Start/Router + Vite 7 + Tailwind CSS 4 + Zustand + xterm.js + Monaco Editor
> **Description:** Desktop workspace for Hermes Agent — chat, orchestration, and multi-agent coding pipelines
> **Description:** Desktop workspace for Claude Agent — chat, orchestration, and multi-agent coding pipelines
---
@@ -27,7 +27,7 @@
- **Real-time SSE streaming** with tool call rendering
- **Multi-session management** — create, rename, delete, fork sessions
- **Dual chat backend modes:**
- **Enhanced Hermes** — full session API with persistent history via Hermes gateway
- **Enhanced Claude** — full session API with persistent history via Claude gateway
- **Portable** — OpenAI-compatible `/v1/chat/completions` (works with Ollama, LM Studio, vLLM, etc.)
- **Chat sidebar** — session list with search, pin, rename, delete dialogs
- **Message rendering:**
@@ -89,7 +89,7 @@
### 1.5 Memory Browser Screen (`/memory`)
- **Browse agent memory files** in `~/.hermes/` (MEMORY.md, memory/, memories/)
- **Browse agent memory files** in `~/.claude/` (MEMORY.md, memory/, memories/)
- **Search across memory entries** — text search with line-level results (max 200 matches)
- **Markdown preview** with live editing via MemoryEditor
- **Memory file list** — sorted with MEMORY.md first, daily files by date
@@ -97,7 +97,7 @@
### 1.6 Skills Browser Screen (`/skills`)
- **Browse 2,000+ skills** from the Hermes skill registry
- **Browse 2,000+ skills** from the Claude skill registry
- **Tabbed view:** Installed, Marketplace, Featured
- **Skill categories:** All, Web & Frontend, Coding Agents, Git & GitHub, DevOps & Cloud, Browser & Automation, Image & Video, Search & Research, AI & LLMs, Productivity, Marketing & Sales, Communication, Data & Analytics, Finance & Crypto
- **Search and filter** — by name, description, author, tags, triggers
@@ -129,7 +129,7 @@
| Endpoint | Method | Description |
| -------------------- | ------ | -------------------------------------------------------------------------------- |
| `/api/send-stream` | POST | Main streaming chat endpoint — routes to enhanced Hermes or portable OpenAI mode |
| `/api/send-stream` | POST | Main streaming chat endpoint — routes to enhanced Claude or portable OpenAI mode |
| `/api/send` | POST | Non-streaming chat send |
| `/api/sessions/send` | POST | Session-specific send |
| `/api/chat-events` | GET | SSE chat event stream |
@@ -162,7 +162,7 @@
| Endpoint | Method | Description |
| -------------------- | ------ | -------------------------------- |
| `/api/memory` | GET | Get memory from Hermes gateway |
| `/api/memory` | GET | Get memory from Claude gateway |
| `/api/memory/list` | GET | List local memory markdown files |
| `/api/memory/read` | GET | Read specific memory file |
| `/api/memory/search` | GET | Search across memory files |
@@ -180,17 +180,17 @@
| Endpoint | Method | Description |
| -------------------- | ------ | -------------------------------------------- |
| `/api/models` | GET | List available models (gateway + auth store) |
| `/api/hermes-config` | GET | Read Hermes config.yaml and .env |
| `/api/hermes-config` | PATCH | Update config.yaml and .env |
| `/api/claude-config` | GET | Read Claude config.yaml and .env |
| `/api/claude-config` | PATCH | Update config.yaml and .env |
| `/api/context-usage` | GET | Token/context usage for a session |
### 2.7 Jobs
| Endpoint | Method | Description |
| ------------------------- | --------------------- | ---------------------------------------------- |
| `/api/hermes-jobs` | GET | List all jobs |
| `/api/hermes-jobs` | POST | Create new job |
| `/api/hermes-jobs/$jobId` | GET/POST/PATCH/DELETE | Job CRUD and actions (pause/resume/run/output) |
| `/api/claude-jobs` | GET | List all jobs |
| `/api/claude-jobs` | POST | Create new job |
| `/api/claude-jobs/$jobId` | GET/POST/PATCH/DELETE | Job CRUD and actions (pause/resume/run/output) |
### 2.8 Terminal
@@ -210,8 +210,8 @@
| `/api/ping` | GET | Server ping/health |
| `/api/connection-status` | GET | Gateway connection status with capabilities |
| `/api/gateway-status` | GET | Detailed gateway capabilities |
| `/api/start-agent` | POST | Auto-start Hermes agent process |
| `/api/start-hermes` | POST | Start Hermes gateway |
| `/api/start-agent` | POST | Auto-start Claude agent process |
| `/api/start-claude` | POST | Start Claude gateway |
| `/api/workspace` | GET | Workspace auto-detection |
### 2.10 OAuth
@@ -279,8 +279,8 @@
- **connection-overlay** — full-screen connection status
- **connection-startup-screen** — initial loading/connection screen
- **backend-unavailable-state** — offline fallback UI
- **hermes-health-banner** — gateway health indicator
- **hermes-reconnect-banner** — reconnection prompt
- **claude-health-banner** — gateway health indicator
- **claude-reconnect-banner** — reconnection prompt
- **error-boundary** — React error boundary
- **error-toast** — error notification
- **loading-indicator** — generic loading state
@@ -306,7 +306,7 @@
- **usage-meter/** — usage-meter, usage-meter-compact, usage-details-modal, context-alert-modal
- **search/** — search-modal, search-input, search-results, search-result-item, quick-actions
- **settings-dialog/** — settings-dialog
- **onboarding/** — hermes-onboarding, onboarding-wizard, onboarding-tour, tour-steps, setup-step-content, provider-select-step
- **onboarding/** — claude-onboarding, onboarding-wizard, onboarding-tour, tour-steps, setup-step-content, provider-select-step
- **agent-chat/** — AgentChatModal, AgentChatHeader, AgentChatInput, AgentChatMessages
- **avatars/** — user-avatar, assistant-avatar
- **auth/** — login-screen
@@ -326,8 +326,8 @@
| Setting | Type | Default | Description |
| ------------------------- | ------------------------------- | -------- | --------------------------- |
| `hermesUrl` | string | `''` | Hermes API URL |
| `hermesToken` | string | `''` | Bearer token |
| `claudeUrl` | string | `''` | Claude API URL |
| `claudeToken` | string | `''` | Bearer token |
| `theme` | `system\|light\|dark` | `system` | Color mode |
| `accentColor` | `orange\|purple\|blue\|green` | `blue` | Accent color |
| `editorFontSize` | number | `13` | Monaco editor font size |
@@ -346,9 +346,9 @@
| Theme | Description | Mode |
| --------------------- | ------------------------------- | ----- |
| Hermes Official | Navy and indigo flagship | Dark |
| Hermes Official Light | Soft indigo light palette | Light |
| Hermes Classic | Bronze accents on dark charcoal | Dark |
| Claude Official | Navy and indigo flagship | Dark |
| Claude Official Light | Soft indigo light palette | Light |
| Claude Classic | Bronze accents on dark charcoal | Dark |
| Classic Light | Warm parchment with bronze | Light |
| Slate | Cool blue developer theme | Dark |
| Slate Light | GitHub-light with blue accents | Light |
@@ -374,26 +374,26 @@
| Variable | Description |
| ---------------------- | -------------------------------------------------- |
| `HERMES_API_URL` | Backend API URL (default: `http://127.0.0.1:8642`) |
| `HERMES_PASSWORD` | Optional password protection for web UI |
| `HERMES_WORKSPACE_DIR` | Workspace root directory (default: `~/.hermes`) |
| `HERMES_AGENT_PATH` | Path to hermes-agent directory |
| `HERMES_DEFAULT_MODEL` | Default model override |
| `HERMES_ALLOWED_HOSTS` | Allowed hosts (default: `.ts.net`) |
| `CLAUDE_API_URL` | Backend API URL (default: `http://127.0.0.1:8642`) |
| `CLAUDE_PASSWORD` | Optional password protection for web UI |
| `CLAUDE_WORKSPACE_DIR` | Workspace root directory (default: `~/.claude`) |
| `CLAUDE_AGENT_PATH` | Path to claude-agent directory |
| `CLAUDE_DEFAULT_MODEL` | Default model override |
| `CLAUDE_ALLOWED_HOSTS` | Allowed hosts (default: `.ts.net`) |
| `ANTHROPIC_API_KEY` | Anthropic API key passthrough (optional) |
| `OPENAI_API_KEY` | OpenAI API key passthrough (optional) |
| `OPENROUTER_API_KEY` | OpenRouter API key passthrough (optional) |
| `GOOGLE_API_KEY` | Google Gemini API key passthrough (optional) |
| `HERMES_API_TOKEN` | Auth token for gateway API_SERVER_KEY |
| `CLAUDE_API_TOKEN` | Auth token for gateway API_SERVER_KEY |
| `BEARER_TOKEN` | Bearer token for backend auth |
| `PORT` | Server port (default: 3002 dev, 3000 prod) |
### 4.6 Hermes Config Management
### 4.6 Claude Config Management
- **Read/write `~/.hermes/config.yaml`** — YAML config via web UI
- **Read/write `~/.hermes/.env`** — environment variables
- **Read/write `~/.claude/config.yaml`** — YAML config via web UI
- **Read/write `~/.claude/.env`** — environment variables
- **Provider status** with masked API keys
- **Auth store integration** — reads from `~/.hermes/auth-profiles.json` and `~/.openclaw/agents/main/agent/auth-profiles.json`
- **Auth store integration** — reads from `~/.claude/auth-profiles.json` and `~/.openclaw/agents/main/agent/auth-profiles.json`
---
@@ -405,7 +405,7 @@
- **Core:** health, chatCompletions, models, streaming
- **Enhanced:** sessions, skills, memory, config, jobs
- **Three chat modes:**
- `enhanced-hermes` — full Hermes session API
- `enhanced-claude` — full Claude session API
- `portable` — OpenAI-compatible /v1/chat/completions
- `disconnected` — no usable backend
- **Auto-detection** with port fallback (8642 → 8643)
@@ -420,7 +420,7 @@
### 5.3 Run Store (Persistence)
- Persisted run state at `~/.hermes/webui-mvp/runs/`
- Persisted run state at `~/.claude/webui-mvp/runs/`
- Run lifecycle: accepted → active → handoff → stalled → complete → error
- Tool call tracking with phase management
- Lifecycle event logging (max 40 per run)
@@ -434,7 +434,7 @@
### 5.5 Memory Browser (Server)
- Filesystem-based memory browsing in `~/.hermes/`
- Filesystem-based memory browsing in `~/.claude/`
- File filters: MEMORY.md, memory/_, memories/_
- Markdown-only restriction
- Path traversal prevention
@@ -447,12 +447,12 @@
- Automatic default model detection from `/v1/models`
- Multimodal support (image_url content parts)
### 5.7 Hermes Agent Auto-Start
### 5.7 Claude Agent Auto-Start
- Auto-detects sibling `hermes-agent/` directory
- Auto-detects sibling `claude-agent/` directory
- Resolves Python virtualenv (`.venv`, `venv`, system `python3`)
- Spawns uvicorn with health polling (15 attempts, 1s interval)
- Reads `~/.hermes/.env` for agent configuration
- Reads `~/.claude/.env` for agent configuration
### 5.8 Workspace Daemon (Optional)
@@ -476,7 +476,7 @@
| Ollama | Local (no auth) | Local models |
| Custom | API Key | Any OpenAI-compatible server |
### 6.2 Known Gateway Providers (Hermes Config)
### 6.2 Known Gateway Providers (Claude Config)
- Nous Portal (OAuth device code flow)
- OpenAI Codex (OAuth)
@@ -499,7 +499,7 @@
- **Device code flow** for Nous Portal
- Token polling mechanism
- Auth profile storage in `~/.hermes/auth-profiles.json`
- Auth profile storage in `~/.claude/auth-profiles.json`
### 6.5 Workspace Agents
@@ -592,7 +592,7 @@ Web Audio API synthesized sounds (no audio files):
### 8.1 Authentication
- Optional password protection via `HERMES_PASSWORD` env var
- Optional password protection via `CLAUDE_PASSWORD` env var
- Timing-safe password comparison
- Cryptographic session tokens (32 bytes hex)
- HTTP-only, SameSite=Strict cookies (30-day expiry)
@@ -684,14 +684,14 @@ pnpm stop:stable # Stop via scripts/stop-stable.sh
### 10.4 Docker Compose
- **hermes-agent** container — Python FastAPI gateway on port 8642
- **hermes-workspace** container — Node.js web UI on port 3000
- **claude-agent** container — Python FastAPI gateway on port 8642
- **claude-workspace** container — Node.js web UI on port 3000
- Health checks with retries
- Environment file passthrough
### 10.5 Auto-Start Features
- Hermes agent auto-start from sibling directory
- Claude agent auto-start from sibling directory
- Workspace daemon auto-start with crash recovery
- Port fallback detection (8642 → 8643)
@@ -736,4 +736,4 @@ pnpm stop:stable # Stop via scripts/stop-stable.sh
---
_Generated from codebase analysis of `/Users/aurora/hermes-workspace/`_
_Generated from codebase analysis of `/Users/aurora/claude-workspace/`_

View File

@@ -39,27 +39,27 @@ All tests pass: **25/25** (`pnpm test`).
- [x] **README v2 updates** — shipped (`9ec12a6`) — zero-fork banner + pip install upstream path everywhere fork was referenced
- [x] **Vanilla-gateway mesh audit (2026-04-19 15:44 EDT)** — ran against `pip install hermes-agent==0.10.0` on port 8642. All 6 core endpoints return 200 (health, v1/models, api/sessions, api/skills, api/config, api/jobs). Missing: `/api/dashboard/*` and `/api/status` — now marked optional (commit `1ca9a457`) and warning suppressed. Gateway-mode probe classifies vanilla as `enhanced-fork` because vanilla implements the streaming route — this is the intended behavior; `enhanced-fork` is a legacy label that does NOT imply a fork is required (commit `4585fd25`).
- [x] **Vanilla-gateway mesh audit (2026-04-19 15:44 EDT)** — ran against `pip install claude-agent==0.10.0` on port 8642. All 6 core endpoints return 200 (health, v1/models, api/sessions, api/skills, api/config, api/jobs). Missing: `/api/dashboard/*` and `/api/status` — now marked optional (commit `1ca9a457`) and warning suppressed. Gateway-mode probe classifies vanilla as `enhanced-fork` because vanilla implements the streaming route — this is the intended behavior; `enhanced-fork` is a legacy label that does NOT imply a fork is required (commit `4585fd25`).
- [x] **Re-QA the two originally failing items (2026-04-19 15:45 EDT):**
- **Model switch toast:** originally "fail" because no toast appeared when selecting another model on vanilla hermes. Re-analysis: the MODEL_SWITCH_BLOCKED_TOAST only fires when `mode === 'zero-fork' && vanillaAgent && !supportsRuntimeSwitching`. Vanilla 0.10 returns `mode=enhanced-fork` (streaming available) so the toast correctly does NOT fire — the user CAN switch models on vanilla via `hermes config set model <id>`. Original QA was testing a scenario that only applies to the narrower `zero-fork` dashboard-bundled deployment. **Pass as intended.**
- **Model switch toast:** originally "fail" because no toast appeared when selecting another model on vanilla claude. Re-analysis: the MODEL_SWITCH_BLOCKED_TOAST only fires when `mode === 'zero-fork' && vanillaAgent && !supportsRuntimeSwitching`. Vanilla 0.10 returns `mode=enhanced-fork` (streaming available) so the toast correctly does NOT fire — the user CAN switch models on vanilla via `claude config set model <id>`. Original QA was testing a scenario that only applies to the narrower `zero-fork` dashboard-bundled deployment. **Pass as intended.**
- **Tool pill inline rendering:** `b368871` fix landed after the original QA. Tests cover the synthesizeToolPill code path (chat-composer-model-switch.test.ts, message-item.test.ts). **Pass on automated tests.** Visual re-QA in browser still recommended before launch copy goes live.
- [ ] **Tag and ship**`git tag v2.0.0 && git push origin v2-zero-fork --tags` — ready.
### 🧊 Cold storage (do not touch unless explicitly asked)
- Memory browser already works via gateway `/api/memory/*`
- Sessions, streaming, config, skills all pass vanilla `pip install hermes-agent`
- Sessions, streaming, config, skills all pass vanilla `pip install claude-agent`
- Gateway runs zero-fork mode by default
## If you hit a wall
- **Rate-limited on openai-codex:** switch model with `hermes config set model anthropic-oauth/claude-opus-4-7` and restart the agent
- **Rate-limited on openai-codex:** switch model with `claude config set model anthropic-oauth/claude-opus-4-7` and restart the agent
- **Vite error in :3005 overlay:** read `/tmp/vite-3005.log`. Most errors are HMR hiccups that go away on file save
- **Tests fail:** do not commit. Report the failing test name and the observed vs expected in this file under a new "⚠️ Blockers" section
## Related tracks (do not work on from this branch)
- Hackathon entry: `hermes-promo` skill — lives at `/Users/aurora/.ocplatform/workspace/skills/hermes-promo/` (not created yet)
- Hackathon entry: `claude-promo` skill — lives at `/Users/aurora/.ocplatform/workspace/skills/claude-promo/` (not created yet)
- Launch copy package: `/Users/aurora/.ocplatform/workspace/content/workspace-v2-launch/`
- Karborn visual refs: `/Users/aurora/.ocplatform/workspace/content/karborn-refs/`

226
README.md
View File

@@ -1,8 +1,8 @@
<div align="center">
<img src="./public/hermes-avatar.webp" alt="Hermes Workspace" width="80" style="border-radius: 16px" />
<img src="./public/claude-avatar.webp" alt="Claude Workspace" width="80" style="border-radius: 16px" />
# Hermes Workspace
# Claude Workspace
**Your AI agent's command center — chat, files, memory, skills, and terminal in one place.**
@@ -13,9 +13,9 @@
> Not a chat wrapper. A complete workspace — orchestrate agents, browse memory, manage skills, and control everything from one interface.
> **v2 — zero-fork. Clone, don't fork.** Runs on vanilla [`NousResearch/hermes-agent`](https://github.com/NousResearch/hermes-agent) installed via Nous's own installer. No patches, no drift.
> **v2 — zero-fork. Clone, don't fork.** Runs on vanilla [`NousResearch/claude-agent`](https://github.com/NousResearch/claude-agent) installed via Nous's own installer. No patches, no drift.
![Hermes Workspace](./docs/screenshots/splash.png)
![Claude Workspace](./docs/screenshots/splash.png)
</div>
@@ -41,7 +41,7 @@ Start here: [docs/swarm/](./docs/swarm/)
## ✨ Features
- 🤖 **Hermes Agent Integration** — Direct gateway connection with real-time SSE streaming
- 🤖 **Claude Agent Integration** — Direct gateway connection with real-time SSE streaming
- 🎨 **8-Theme System** — Official, Classic, Slate, Mono — each with light and dark variants
- 🔒 **Security Hardened** — Auth middleware on all API routes, CSP headers, exec approval prompts
- 📱 **Mobile-First PWA** — Full feature parity on any device via Tailscale
@@ -75,37 +75,37 @@ Start here: [docs/swarm/](./docs/swarm/)
### One-line install (recommended)
```bash
curl -fsSL https://raw.githubusercontent.com/outsourc-e/hermes-workspace/main/install.sh | bash
curl -fsSL https://raw.githubusercontent.com/outsourc-e/claude-workspace/main/install.sh | bash
```
This installs `hermes-agent` from PyPI, clones this repo, sets up `.env`, and installs deps. Then:
This installs `claude-agent` from PyPI, clones this repo, sets up `.env`, and installs deps. Then:
```bash
hermes gateway run # terminal 1
cd ~/hermes-workspace && pnpm dev # terminal 2
claude gateway run # terminal 1
cd ~/claude-workspace && pnpm dev # terminal 2
```
Open http://localhost:3000. That's it.
---
### Already running `hermes-agent`? Attach the workspace to it
### Already running `claude-agent`? Attach the workspace to it
If you already have `hermes-agent` installed (via Nous's installer, `pip install`, systemd, Docker, etc.) and it's serving the gateway at `http://<host>:8642`, you don't need to reinstall anything — just point the workspace at it.
If you already have `claude-agent` installed (via Nous's installer, `pip install`, systemd, Docker, etc.) and it's serving the gateway at `http://<host>:8642`, you don't need to reinstall anything — just point the workspace at it.
```bash
git clone https://github.com/outsourc-e/hermes-workspace.git
cd hermes-workspace
git clone https://github.com/outsourc-e/claude-workspace.git
cd claude-workspace
pnpm install
cp .env.example .env
# Point at your existing Hermes services.
echo 'HERMES_API_URL=http://127.0.0.1:8642' >> .env
# Point at your existing Claude services.
echo 'CLAUDE_API_URL=http://127.0.0.1:8642' >> .env
# Zero-fork installs also need the separate dashboard API for config/sessions/skills/jobs.
echo 'HERMES_DASHBOARD_URL=http://127.0.0.1:9119' >> .env
echo 'CLAUDE_DASHBOARD_URL=http://127.0.0.1:9119' >> .env
# If your gateway was started with API_SERVER_KEY (auth enabled), set the same value:
# echo 'HERMES_API_TOKEN=***' >> .env
# echo 'CLAUDE_API_TOKEN=***' >> .env
pnpm dev # http://localhost:3000 (override with PORT=4000 pnpm dev)
```
@@ -113,9 +113,9 @@ pnpm dev # http://localhost:3000 (override with PORT=
Requirements on the agent side:
- Gateway bound to an address the workspace can reach (typically `API_SERVER_HOST=0.0.0.0` + the port exposed).
- `API_SERVER_ENABLED=true` in `~/.hermes/.env` (or the agent's env) so the gateway serves core APIs on `:8642`.
- `hermes dashboard` running (default `http://127.0.0.1:9119`) for zero-fork installs. The dashboard provides config, sessions, skills, and jobs APIs.
- If `API_SERVER_KEY` is set, the workspace must pass the same value via `HERMES_API_TOKEN` — otherwise leave both unset.
- `API_SERVER_ENABLED=true` in `~/.claude/.env` (or the agent's env) so the gateway serves core APIs on `:8642`.
- `claude dashboard` running (default `http://127.0.0.1:9119`) for zero-fork installs. The dashboard provides config, sessions, skills, and jobs APIs.
- If `API_SERVER_KEY` is set, the workspace must pass the same value via `CLAUDE_API_TOKEN` — otherwise leave both unset.
Verify both services before opening the workspace:
@@ -126,71 +126,71 @@ Then start the workspace and complete onboarding — it should detect the gatewa
#### Running on a remote host (Tailscale / VPN / LAN)
If the workspace and its browser live on different machines — e.g. the workspace runs on a Pi/Mac/home server and you access it from your phone over Tailscale — point `HERMES_API_URL` at the **reachable** backend address, not `127.0.0.1`:
If the workspace and its browser live on different machines — e.g. the workspace runs on a Pi/Mac/home server and you access it from your phone over Tailscale — point `CLAUDE_API_URL` at the **reachable** backend address, not `127.0.0.1`:
```bash
# On the server running the workspace + gateway:
echo 'HERMES_API_URL=http://100.x.y.z:8642' >> .env
echo 'HERMES_DASHBOARD_URL=http://100.x.y.z:9119' >> .env
echo 'CLAUDE_API_URL=http://100.x.y.z:8642' >> .env
echo 'CLAUDE_DASHBOARD_URL=http://100.x.y.z:9119' >> .env
# Also tell the gateway to listen on all interfaces so Tailscale peers can reach it.
# In ~/.hermes/.env (or wherever the gateway reads config):
echo 'API_SERVER_HOST=0.0.0.0' >> ~/.hermes/.env
# In ~/.claude/.env (or wherever the gateway reads config):
echo 'API_SERVER_HOST=0.0.0.0' >> ~/.claude/.env
```
Then restart the gateway, dashboard, and workspace. Hit the workspace from the remote device and the connection probe will use the Tailscale IP instead of localhost. Both `HERMES_API_URL` and `HERMES_DASHBOARD_URL` must be set to Tailscale/LAN-reachable URLs — setting only one will leave the other probing `127.0.0.1` and failing.
Then restart the gateway, dashboard, and workspace. Hit the workspace from the remote device and the connection probe will use the Tailscale IP instead of localhost. Both `CLAUDE_API_URL` and `CLAUDE_DASHBOARD_URL` must be set to Tailscale/LAN-reachable URLs — setting only one will leave the other probing `127.0.0.1` and failing.
**If you've already started the workspace**, you can update both URLs from `Settings → Connection` without restarting. The values are persisted to `~/.hermes/workspace-overrides.json` and take effect immediately (gateway capabilities are reprobed on save). Editing `.env` still works for pre-start config and for CI/containers.
**If you've already started the workspace**, you can update both URLs from `Settings → Connection` without restarting. The values are persisted to `~/.claude/workspace-overrides.json` and take effect immediately (gateway capabilities are reprobed on save). Editing `.env` still works for pre-start config and for CI/containers.
---
### Manual install
Hermes Workspace works with any OpenAI-compatible backend. If your backend also exposes Hermes gateway APIs, enhanced features like sessions, memory, skills, and jobs unlock automatically.
Claude Workspace works with any OpenAI-compatible backend. If your backend also exposes Claude gateway APIs, enhanced features like sessions, memory, skills, and jobs unlock automatically.
#### Prerequisites
- **Node.js 22+** — [nodejs.org](https://nodejs.org/)
- **An OpenAI-compatible backend** — local, self-hosted, or remote
- **Optional:** Python 3.11+ if you want to run a Hermes gateway locally
- **Optional:** Python 3.11+ if you want to run a Claude gateway locally
#### Step 1: Start your backend
Point Hermes Workspace at any backend that supports:
Point Claude Workspace at any backend that supports:
- `POST /v1/chat/completions`
- `GET /v1/models` recommended
Example Hermes gateway setup (from scratch):
Example Claude gateway setup (from scratch):
```bash
# Install hermes-agent via Nous's official installer
curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash
# Install claude-agent via Nous's official installer
curl -fsSL https://raw.githubusercontent.com/NousResearch/claude-agent/main/scripts/install.sh | bash
# Configure a provider + start the gateway
hermes setup
hermes gateway run
claude setup
claude gateway run
```
Our one-liner installer (below) does both steps automatically. If you're using another OpenAI-compatible server, just note its base URL.
### Step 2: Install & Run Hermes Workspace
### Step 2: Install & Run Claude Workspace
```bash
# In a new terminal
git clone https://github.com/outsourc-e/hermes-workspace.git
cd hermes-workspace
git clone https://github.com/outsourc-e/claude-workspace.git
cd claude-workspace
pnpm install
cp .env.example .env
printf '\nHERMES_API_URL=http://127.0.0.1:8642\n' >> .env
printf '\nCLAUDE_API_URL=http://127.0.0.1:8642\n' >> .env
pnpm dev # Starts on http://localhost:3000
```
> **Verify:** Open `http://localhost:3000` and complete the onboarding flow. First connect the backend, then verify chat works. If your gateway exposes Hermes APIs, advanced features appear automatically.
> **Verify:** Open `http://localhost:3000` and complete the onboarding flow. First connect the backend, then verify chat works. If your gateway exposes Claude APIs, advanced features appear automatically.
### Agent W Managed Companion
When Hermes Workspace is running behind Agent W's local HTTPS proxy, the
When Claude Workspace is running behind Agent W's local HTTPS proxy, the
managed companion entrypoint is:
```bash
@@ -214,9 +214,9 @@ when `dist` drifts under a live server process.
```env
# OpenAI-compatible backend URL
HERMES_API_URL=http://127.0.0.1:8642
CLAUDE_API_URL=http://127.0.0.1:8642
# Optional: provider keys the Hermes gateway can read at runtime.
# Optional: provider keys the Claude gateway can read at runtime.
# You only need the key(s) for whichever provider(s) you actually use.
# ANTHROPIC_API_KEY=sk-ant-... # Claude
# OPENAI_API_KEY=sk-... # GPT / o-series
@@ -225,27 +225,27 @@ HERMES_API_URL=http://127.0.0.1:8642
# (Ollama / LM Studio / local servers don't need a key)
# Optional: password-protect the web UI
# HERMES_PASSWORD=your_password
# CLAUDE_PASSWORD=your_password
```
---
## 🧠 Local Models (Ollama, Atomic Chat, LM Studio, vLLM)
Hermes Workspace supports two modes with local models:
Claude Workspace supports two modes with local models:
### Portable Mode (Easiest)
Point the workspace directly at your local server — no Hermes gateway needed.
Point the workspace directly at your local server — no Claude gateway needed.
### Atomic Chat
```bash
# Start workspace pointed at Atomic Chat
HERMES_API_URL=http://127.0.0.1:1337/v1 pnpm dev
CLAUDE_API_URL=http://127.0.0.1:1337/v1 pnpm dev
```
Download [Atomic Chat](https://atomic.chat/), launch the desktop app, and make sure a model is loaded before starting Hermes Workspace.
Download [Atomic Chat](https://atomic.chat/), launch the desktop app, and make sure a model is loaded before starting Claude Workspace.
### Ollama
@@ -254,16 +254,16 @@ Download [Atomic Chat](https://atomic.chat/), launch the desktop app, and make s
OLLAMA_ORIGINS=* ollama serve
# Start workspace pointed at Ollama
HERMES_API_URL=http://127.0.0.1:11434 pnpm dev
CLAUDE_API_URL=http://127.0.0.1:11434 pnpm dev
```
Chat works immediately. Sessions, memory, and skills show "Not Available" — that's expected in portable mode.
### Enhanced Mode (Full Features)
Route through the Hermes gateway for sessions, memory, skills, jobs, and tools.
Route through the Claude gateway for sessions, memory, skills, jobs, and tools.
Here are two explicit `~/.hermes/config.yaml` examples for the local providers we support directly in the workspace:
Here are two explicit `~/.claude/config.yaml` examples for the local providers we support directly in the workspace:
**Atomic Chat**
@@ -291,7 +291,7 @@ custom_providers:
You can adapt the same shape for other OpenAI-compatible local runners, but `Atomic Chat` and `Ollama` are the two built-in local paths documented in the workspace UI.
**2. Enable the API server in `~/.hermes/.env`:**
**2. Enable the API server in `~/.claude/.env`:**
```env
API_SERVER_ENABLED=true
@@ -300,14 +300,14 @@ API_SERVER_ENABLED=true
**3. Start the gateway, dashboard, and workspace:**
```bash
hermes gateway run # Starts core APIs on :8642
hermes dashboard # Starts dashboard APIs on :9119
HERMES_API_URL=http://127.0.0.1:8642 \
HERMES_DASHBOARD_URL=http://127.0.0.1:9119 \
claude gateway run # Starts core APIs on :8642
claude dashboard # Starts dashboard APIs on :9119
CLAUDE_API_URL=http://127.0.0.1:8642 \
CLAUDE_DASHBOARD_URL=http://127.0.0.1:9119 \
pnpm dev
```
For authenticated gateways, also set `HERMES_API_TOKEN` in the workspace environment to the same value as `API_SERVER_KEY`.
For authenticated gateways, also set `CLAUDE_API_TOKEN` in the workspace environment to the same value as `API_SERVER_KEY`.
All workspace features unlock automatically once both services are reachable — sessions persist, memory saves across chats, skills are available, and the dashboard shows real usage data.
@@ -317,9 +317,9 @@ All workspace features unlock automatically once both services are reachable —
## 🐳 Docker Quickstart
[![Open in GitHub Codespaces](https://img.shields.io/badge/GitHub%20Codespaces-Open-181717?logo=github)](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=outsourc-e/hermes-workspace)
[![Open in GitHub Codespaces](https://img.shields.io/badge/GitHub%20Codespaces-Open-181717?logo=github)](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=outsourc-e/claude-workspace)
The Docker setup runs both the **Hermes Agent gateway** and **Hermes Workspace** together.
The Docker setup runs both the **Claude Agent gateway** and **Claude Workspace** together.
### Prerequisites
@@ -330,12 +330,12 @@ The Docker setup runs both the **Hermes Agent gateway** and **Hermes Workspace**
### Step 1: Configure Environment
```bash
git clone https://github.com/outsourc-e/hermes-workspace.git
cd hermes-workspace
git clone https://github.com/outsourc-e/claude-workspace.git
cd claude-workspace
cp .env.example .env
```
Edit `.env` and add **at least one** LLM provider key — whichever provider you want hermes-agent to use:
Edit `.env` and add **at least one** LLM provider key — whichever provider you want claude-agent to use:
```env
# Pick one (or more). You do NOT need all of these.
@@ -345,9 +345,9 @@ ANTHROPIC_API_KEY=sk-ant-... # Claude
# GOOGLE_API_KEY=AIza... # Gemini
```
Using **Ollama, LM Studio, or another local server**? No key needed — just point hermes-agent at your local endpoint via the onboarding flow.
Using **Ollama, LM Studio, or another local server**? No key needed — just point claude-agent at your local endpoint via the onboarding flow.
> **Heads up:** `hermes-agent` needs to be able to reach _some_ model. If you don't configure any provider (API key or local server), chat will fail on first message.
> **Heads up:** `claude-agent` needs to be able to reach _some_ model. If you don't configure any provider (API key or local server), chat will fail on first message.
### Step 2: Start the Services
@@ -357,18 +357,18 @@ docker compose up
This pulls two pre-built images and starts them:
- **hermes-agent** → `nousresearch/hermes-agent:latest` on port **8642**
- **hermes-workspace** → `ghcr.io/outsourc-e/hermes-workspace:latest` on port **3000**
- **claude-agent** → `nousresearch/claude-agent:latest` on port **8642**
- **claude-workspace** → `ghcr.io/outsourc-e/claude-workspace:latest` on port **3000**
No local build. First run takes a minute to pull; subsequent starts are instant.
Agent state (config, sessions, skills, memory, credentials) persists in the
`hermes-data` named volume, so containers can be recreated without data loss.
`claude-data` named volume, so containers can be recreated without data loss.
### Step 3: Access the Workspace
Open `http://localhost:3000` and complete the onboarding.
> **Verify:** Check the Docker logs for `[gateway] Connected to Hermes` — this confirms the workspace successfully connected to the agent.
> **Verify:** Check the Docker logs for `[gateway] Connected to Claude` — this confirms the workspace successfully connected to the agent.
### Building from source
@@ -387,7 +387,7 @@ Deploying Project Workspace to a PaaS or home-lab stack? Pull the image
directly from GitHub Container Registry:
```
ghcr.io/outsourc-e/hermes-workspace:latest
ghcr.io/outsourc-e/claude-workspace:latest
```
Available tags:
@@ -401,52 +401,52 @@ Available tags:
Minimal Coolify / Easypanel config:
```yaml
service: hermes-workspace
image: ghcr.io/outsourc-e/hermes-workspace:latest
service: claude-workspace
image: ghcr.io/outsourc-e/claude-workspace:latest
port: 3000
env:
HERMES_API_URL: http://hermes-agent:8642 # point at your gateway
HERMES_API_TOKEN: ${API_SERVER_KEY} # if gateway auth is enabled
CLAUDE_API_URL: http://claude-agent:8642 # point at your gateway
CLAUDE_API_TOKEN: ${API_SERVER_KEY} # if gateway auth is enabled
```
The image is built for `linux/amd64` and `linux/arm64`. Pair it with either
a `nousresearch/hermes-agent:latest` container (what our `docker-compose.yml`
a `nousresearch/claude-agent:latest` container (what our `docker-compose.yml`
does by default) or an existing gateway on another host.
---
## 📱 Install as App (Recommended)
Hermes Workspace is a **Progressive Web App (PWA)** — install it for the full native app experience with no browser chrome, keyboard shortcuts, and offline support.
Claude Workspace is a **Progressive Web App (PWA)** — install it for the full native app experience with no browser chrome, keyboard shortcuts, and offline support.
### 🖥️ Desktop (macOS / Windows / Linux)
1. Open Hermes Workspace in **Chrome** or **Edge** at `http://localhost:3000`
1. Open Claude Workspace in **Chrome** or **Edge** at `http://localhost:3000`
2. Click the **install icon** (⊕) in the address bar
3. Click **Install**Hermes Workspace opens as a standalone desktop app
3. Click **Install**Claude Workspace opens as a standalone desktop app
4. Pin to Dock / Taskbar for quick access
> **macOS users:** After installing, you can also add it to your Launchpad.
### 📱 iPhone / iPad (iOS Safari)
1. Open Hermes Workspace in **Safari** on your iPhone
1. Open Claude Workspace in **Safari** on your iPhone
2. Tap the **Share** button (□↑)
3. Scroll down and tap **"Add to Home Screen"**
4. Tap **Add** — the Hermes Workspace icon appears on your home screen
4. Tap **Add** — the Claude Workspace icon appears on your home screen
5. Launch from home screen for the full native app experience
### 🤖 Android
1. Open Hermes Workspace in **Chrome** on your Android device
1. Open Claude Workspace in **Chrome** on your Android device
2. Tap the **three-dot menu** (⋮) → **"Add to Home screen"**
3. Tap **Add**Hermes Workspace is now a native-feeling app on your device
3. Tap **Add**Claude Workspace is now a native-feeling app on your device
---
## 📡 Mobile Access via Tailscale
Access Hermes Workspace from anywhere on your devices — no port forwarding, no VPN complexity.
Access Claude Workspace from anywhere on your devices — no port forwarding, no VPN complexity.
### Setup
@@ -463,7 +463,7 @@ Access Hermes Workspace from anywhere on your devices — no port forwarding, no
# Example output: 100.x.x.x
```
4. **Open Hermes Workspace on your phone:**
4. **Open Claude Workspace on your phone:**
```
http://100.x.x.x:3000
@@ -486,7 +486,7 @@ The desktop app will offer:
- Auto-launch on startup
- Deep OS integration (macOS menu bar, Windows taskbar)
**In the meantime:** Install Hermes Workspace as a PWA (see above) for a near-native desktop experience — it works great.
**In the meantime:** Install Claude Workspace as a PWA (see above) for a near-native desktop experience — it works great.
---
@@ -494,7 +494,7 @@ The desktop app will offer:
> **Status: Coming Soon**
A fully managed cloud version of Hermes Workspace is in development:
A fully managed cloud version of Claude Workspace is in development:
- **One-click deploy** — No self-hosting required
- **Multi-device sync** — Access your agents from any device
@@ -557,17 +557,17 @@ Features pending cloud infrastructure:
- CSP headers via meta tags
- Path traversal prevention on file/memory routes (real-path boundary check, not string prefix)
- Rate limiting on endpoints
- Fail-closed startup guard: refuses to bind non-loopback without `HERMES_PASSWORD`
- Fail-closed startup guard: refuses to bind non-loopback without `CLAUDE_PASSWORD`
- Session cookies: `HttpOnly` + `SameSite=Strict` + `Secure` (in production)
- Optional password protection for web UI
**Key env vars for remote / Docker deployments:**
- `HERMES_PASSWORD` — required whenever `HOST ≠ 127.0.0.1`
- `CLAUDE_PASSWORD` — required whenever `HOST ≠ 127.0.0.1`
- `COOKIE_SECURE=1` — force the `Secure` cookie flag when terminating HTTPS at a proxy
- `TRUST_PROXY=1` — trust `x-forwarded-for` / `x-real-ip` (only set behind a sanitizing reverse proxy)
- `HERMES_DASHBOARD_TOKEN` — explicit bearer for dashboard API (preferred over the legacy HTML-scrape fallback)
- `HERMES_ALLOW_INSECURE_REMOTE=1` — bypass the fail-closed guard (not recommended)
- `CLAUDE_DASHBOARD_TOKEN` — explicit bearer for dashboard API (preferred over the legacy HTML-scrape fallback)
- `CLAUDE_ALLOW_INSECURE_REMOTE=1` — bypass the fail-closed guard (not recommended)
See `.env.example` for the full list. Credits to [@kiosvantra](https://github.com/kiosvantra) for the security audit surfacing #121#125.
@@ -581,31 +581,31 @@ The workspace auto-detects your gateway's capabilities on startup. Check your te
```
[gateway] http://127.0.0.1:8642 available: health, models; missing: sessions, skills, memory, config, jobs
[gateway] Missing Hermes APIs detected. Update hermes-agent to the latest version.
[gateway] Missing Claude APIs detected. Update claude-agent to the latest version.
```
**Fix:** Upgrade to the latest stock `hermes-agent`, which ships the extended endpoints:
**Fix:** Upgrade to the latest stock `claude-agent`, which ships the extended endpoints:
```bash
cd ~/hermes-agent && git pull && uv pip install -e .
hermes gateway run
cd ~/claude-agent && git pull && uv pip install -e .
claude gateway run
```
(If you installed via a different path, follow your Nous installer's upgrade instructions.) If you were on the old `outsourc-e/hermes-agent` fork, it's no longer needed as of v2 — uninstall it and use upstream instead.
(If you installed via a different path, follow your Nous installer's upgrade instructions.) If you were on the old `outsourc-e/claude-agent` fork, it's no longer needed as of v2 — uninstall it and use upstream instead.
### "Connection refused" or workspace hangs on load
Your Hermes gateway isn't running. Start it:
Your Claude gateway isn't running. Start it:
```bash
hermes gateway run
claude gateway run
```
First-time run? Do `hermes setup` first to pick a provider and model.
First-time run? Do `claude setup` first to pick a provider and model.
### Ollama: chat returns empty or model shows "Offline"
Make sure your `~/.hermes/config.yaml` has the `custom_providers` section and `API_SERVER_ENABLED=true` in `~/.hermes/.env`. See [Local Models](#-local-models-ollama-lm-studio-vllm) above.
Make sure your `~/.claude/config.yaml` has the `custom_providers` section and `API_SERVER_ENABLED=true` in `~/.claude/.env`. See [Local Models](#-local-models-ollama-lm-studio-vllm) above.
Also ensure Ollama is running with CORS enabled:
@@ -617,13 +617,13 @@ Use `http://127.0.0.1:11434/v1` (not `localhost`) as the base URL.
Verify: `curl http://localhost:8642/health` should return `{"status": "ok"}`.
### "Using upstream NousResearch/hermes-agent"
### "Using upstream NousResearch/claude-agent"
v2+ runs on vanilla `hermes-agent` with full feature parity. The upstream ships all extended endpoints (sessions, memory, skills, config). **No fork required, ever.**
v2+ runs on vanilla `claude-agent` with full feature parity. The upstream ships all extended endpoints (sessions, memory, skills, config). **No fork required, ever.**
If you're pinned to an older `hermes-agent` version and missing endpoints, the workspace will degrade gracefully to **portable mode** with basic chat — upgrade upstream to restore full features.
If you're pinned to an older `claude-agent` version and missing endpoints, the workspace will degrade gracefully to **portable mode** with basic chat — upgrade upstream to restore full features.
### Docker: "Unauthorized" or "Connection refused" to hermes-agent
### Docker: "Unauthorized" or "Connection refused" to claude-agent
If using Docker Compose and getting auth errors:
@@ -634,12 +634,12 @@ If using Docker Compose and getting auth errors:
# Should show one of: ANTHROPIC_API_KEY, OPENAI_API_KEY, OPENROUTER_API_KEY, GOOGLE_API_KEY, ...
```
(hermes-agent reads whichever key matches the provider configured in `~/.hermes/config.yaml`.)
(claude-agent reads whichever key matches the provider configured in `~/.claude/config.yaml`.)
2. **View the agent container logs:**
```bash
docker compose logs hermes-agent
docker compose logs claude-agent
```
Look for startup errors or missing API key warnings.
@@ -660,19 +660,19 @@ If using Docker Compose and getting auth errors:
5. **Check workspace logs for gateway status:**
```bash
docker compose logs hermes-workspace
docker compose logs claude-workspace
```
Look for: `[gateway] http://hermes-agent:8642 mode=...` — if it shows `mode=disconnected`, the agent isn't running correctly.
Look for: `[gateway] http://claude-agent:8642 mode=...` — if it shows `mode=disconnected`, the agent isn't running correctly.
### Docker: "hermes webapi command not found"
### Docker: "claude webapi command not found"
The `hermes webapi` command referenced in older docs doesn't exist. The correct command is:
The `claude webapi` command referenced in older docs doesn't exist. The correct command is:
```bash
hermes --gateway # Starts the FastAPI gateway server
claude --gateway # Starts the FastAPI gateway server
```
The Docker setup uses `hermes --gateway` automatically — no action needed if using `docker compose up`.
The Docker setup uses `claude --gateway` automatically — no action needed if using `docker compose up`.
---
@@ -696,11 +696,11 @@ The Docker setup uses `hermes --gateway` automatically — no action needed if u
## ⭐ Star History
## [![Star History Chart](https://api.star-history.com/svg?repos=outsourc-e/hermes-workspace&type=date&logscale&legend=top-left)](https://www.star-history.com/#outsourc-e/hermes-workspace&type=date&logscale&legend=top-left)
## [![Star History Chart](https://api.star-history.com/svg?repos=outsourc-e/claude-workspace&type=date&logscale&legend=top-left)](https://www.star-history.com/#outsourc-e/claude-workspace&type=date&logscale&legend=top-left)
## 💛 Support the Project
Hermes Workspace is free and open source. If it's saving you time and powering your workflow, consider supporting development:
Claude Workspace is free and open source. If it's saving you time and powering your workflow, consider supporting development:
**ETH:** `0xB332D4C60f6FBd94913e3Fd40d77e3FE901FAe22`
@@ -727,5 +727,5 @@ MIT — see [LICENSE](LICENSE) for details.
---
<div align="center">
<sub>Built with ⚡ by <a href="https://github.com/outsourc-e">@outsourc-e</a> and the Hermes Workspace community</sub>
<sub>Built with ⚡ by <a href="https://github.com/outsourc-e">@outsourc-e</a> and the Claude Workspace community</sub>
</div>

View File

@@ -2,18 +2,18 @@
## Reporting a Vulnerability
If you discover a security vulnerability in Hermes Workspace, please report it responsibly.
If you discover a security vulnerability in Claude Workspace, please report it responsibly.
**Do NOT open a public GitHub issue for security vulnerabilities.**
Instead, report via [GitHub Security Advisories](https://github.com/outsourc-e/hermes-workspace/security/advisories) or DM [@ericousodev on X](https://x.com/ericousodev).
Instead, report via [GitHub Security Advisories](https://github.com/outsourc-e/claude-workspace/security/advisories) or DM [@ericousodev on X](https://x.com/ericousodev).
We will acknowledge your report within 48 hours and aim to provide a fix within 7 days for critical issues.
## Scope
- Hermes Workspace web application code
- API routes and Hermes communication
- Claude Workspace web application code
- API routes and Claude communication
- Authentication and session management
- Client-side data handling and rendering
- Exec approval and human-in-the-loop controls
@@ -21,7 +21,7 @@ We will acknowledge your report within 48 hours and aim to provide a fix within
## Out of Scope
- Hermes Agent itself (report to the Hermes Agent project)
- Claude Agent itself (report to the Claude Agent project)
- Third-party dependencies (report to the respective maintainer)
- Social engineering attacks
@@ -45,12 +45,12 @@ We will acknowledge your report within 48 hours and aim to provide a fix within
- Path traversal prevention on all file and memory routes (`ensureWorkspacePath()`)
- `.md`-only restriction on memory write routes
- No API keys or secrets ever exposed to client-side code
- Hermes tokens are server-side only
- Claude tokens are server-side only
- Diagnostic output scrubbed of sensitive data
**Agent Safety**
- Exec approval workflow — sensitive Hermes exec commands require explicit human approval via in-UI modal
- Exec approval workflow — sensitive Claude exec commands require explicit human approval via in-UI modal
- Skills security scanning — every skill from the marketplace is scanned for suspicious patterns before install
**Configuration**

View File

@@ -4,7 +4,7 @@ Canonical environment contract for Swarm / Swarm2 work.
## One true repo
- **Swarm code, git, build, and tests run only in:** `/Users/aurora/hermes-workspace`
- **Swarm code, git, build, and tests run only in:** `/Users/aurora/claude-workspace`
- **Do not use:** `/Users/aurora/claude-workspace`
If you detect `claude-workspace`, stop and switch to the canonical repo.
@@ -17,7 +17,7 @@ If you detect `claude-workspace`, stop and switch to the canonical repo.
## Worker runtime model
- Workers are **Hermes profiles** under `~/.hermes/profiles/<workerId>`
- Workers are **Claude profiles** under `~/.claude/profiles/<workerId>`
- Wrappers are **canonical launch points** at `~/.local/bin/swarmN`
- One persistent tmux session per worker, named **`swarm-<workerId>`**
- `/swarm2` should attach to the same live session the worker is already using
@@ -42,20 +42,20 @@ Use:
## Default commands
```bash
cd /Users/aurora/hermes-workspace && npm run build
cd /Users/aurora/hermes-workspace && npm test -- src/screens/swarm2
cd /Users/aurora/hermes-workspace && PORT=3002 npm run dev
cd /Users/aurora/claude-workspace && npm run build
cd /Users/aurora/claude-workspace && npm test -- src/screens/swarm2
cd /Users/aurora/claude-workspace && PORT=3002 npm run dev
```
## Writable roots
- `/Users/aurora/hermes-workspace`
- `/Users/aurora/claude-workspace`
- `/Users/aurora/.openclaw/workspace/memory`
## Read-only roots
- `/Users/aurora/.openclaw/workspace`
- `~/.hermes/profiles`
- `~/.claude/profiles`
- `~/.local/bin`
## Forbidden roots
@@ -69,5 +69,5 @@ If a choice is ambiguous, ask:
> What is the one true process/session this worker is running in, and what is the one true repo for the UI surface?
Answer:
- process/session → live Hermes worker in tmux `swarm-<workerId>`
- repo → `/Users/aurora/hermes-workspace`
- process/session → live Claude worker in tmux `swarm-<workerId>`
- repo → `/Users/aurora/claude-workspace`

View File

@@ -12,12 +12,12 @@
# upstream image name.
services:
hermes-agent:
claude-agent:
build:
context: .
dockerfile: docker/agent/Dockerfile
hermes-workspace:
claude-workspace:
build:
context: .
dockerfile: docker/workspace/Dockerfile

View File

@@ -14,34 +14,34 @@
#
# Images:
# This file pulls pre-built images by default — no local build required.
# - nousresearch/hermes-agent:latest (Project Agent, Dockerfile upstream)
# - ghcr.io/outsourc-e/hermes-workspace:latest (this workspace)
# - nousresearch/claude-agent:latest (Project Agent, Dockerfile upstream)
# - ghcr.io/outsourc-e/claude-workspace:latest (this workspace)
#
# To build from source instead (e.g. for development), use:
# docker compose -f docker-compose.yml -f docker-compose.dev.yml up
#
# Persistent data:
# The `hermes-data` named volume mounts at /opt/data inside the agent
# The `claude-data` named volume mounts at /opt/data inside the agent
# container. Config, sessions, skills, memory, and credentials live there
# and survive container recreation. For host-path mounts see the commented
# `volumes:` block on the hermes-agent service.
# `volumes:` block on the claude-agent service.
#
# Troubleshooting:
# - See README.md "Docker" troubleshooting section
# - Check logs: docker compose logs hermes-agent
# - Check logs: docker compose logs claude-agent
# - Agent must expose port 8642
services:
# The Hermes AI Agent Gateway
# The Claude AI Agent Gateway
# Provides the backend API that the workspace connects to
hermes-agent:
image: nousresearch/hermes-agent:latest
claude-agent:
image: nousresearch/claude-agent:latest
env_file:
- .env
environment:
# Pass through whichever provider keys are set in .env. hermes-agent
# Pass through whichever provider keys are set in .env. claude-agent
# uses the one that matches the provider configured in
# ~/.hermes/config.yaml (or whatever `hermes setup` picked).
# ~/.claude/config.yaml (or whatever `claude setup` picked).
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY:-}
@@ -50,11 +50,11 @@ services:
MISTRAL_API_KEY: ${MISTRAL_API_KEY:-}
# Authentication for the gateway when exposing off-loopback.
# In the default compose setup the gateway is reachable from the
# workspace container over the docker network on hermes-agent:8642,
# workspace container over the docker network on claude-agent:8642,
# so an empty key works for localhost-only Docker installs. For any
# deployment that publishes 8642 on the host or a LAN IP, set a
# strong API_SERVER_KEY in .env — the workspace passes it through
# as HERMES_API_TOKEN below. See #122.
# as CLAUDE_API_TOKEN below. See #122.
API_SERVER_KEY: ${API_SERVER_KEY:-}
# Bind only on the docker-internal interface by default. Set
# API_SERVER_HOST=0.0.0.0 in .env *and* set API_SERVER_KEY if you
@@ -65,7 +65,7 @@ services:
# Persist agent state across container recreation. Swap for a
# host-path mount (e.g. `./data:/opt/data`) if you want to edit
# config/skills directly from the host.
- hermes-data:/opt/data
- claude-data:/opt/data
healthcheck:
test: ['CMD-SHELL', 'curl -fsS http://localhost:8642/health || exit 1']
interval: 10s
@@ -76,23 +76,23 @@ services:
- '8642:8642'
# The Project Workspace Web UI
# Connects to hermes-agent at http://hermes-agent:8642
hermes-workspace:
image: ghcr.io/outsourc-e/hermes-workspace:latest
# Connects to claude-agent at http://claude-agent:8642
claude-workspace:
image: ghcr.io/outsourc-e/claude-workspace:latest
depends_on:
hermes-agent:
claude-agent:
condition: service_healthy
env_file:
- .env
environment:
# Internal Docker network URL (not localhost!)
HERMES_API_URL: http://hermes-agent:8642
# Must match API_SERVER_KEY on the hermes-agent side when that is set
HERMES_API_TOKEN: ${API_SERVER_KEY:-}
CLAUDE_API_URL: http://claude-agent:8642
# Must match API_SERVER_KEY on the claude-agent side when that is set
CLAUDE_API_TOKEN: ${API_SERVER_KEY:-}
# Workspace session password. REQUIRED when HOST is non-loopback (the
# default for Docker images, so the container binds 0.0.0.0:3000).
# Pick a strong secret. See #122.
HERMES_PASSWORD: ${HERMES_PASSWORD:-}
CLAUDE_PASSWORD: ${CLAUDE_PASSWORD:-}
# Enable the Secure flag on session cookies when terminated behind
# HTTPS (reverse proxy / Tailscale Funnel / Cloudflare Tunnel). See #123.
COOKIE_SECURE: ${COOKIE_SECURE:-}
@@ -105,4 +105,4 @@ services:
- '127.0.0.1:3000:3000'
volumes:
hermes-data:
claude-data:

View File

@@ -4,7 +4,7 @@ RUN apt-get update \
&& apt-get install -y --no-install-recommends curl git \
&& rm -rf /var/lib/apt/lists/*
RUN git clone https://github.com/outsourc-e/hermes-agent.git /app
RUN git clone https://github.com/outsourc-e/claude-agent.git /app
WORKDIR /app
@@ -12,4 +12,4 @@ RUN pip install --no-cache-dir -e .
EXPOSE 8642
CMD ["hermes", "gateway", "run"]
CMD ["claude", "gateway", "run"]

View File

@@ -10,7 +10,7 @@ RUN pnpm install --no-frozen-lockfile
COPY . .
ENV HERMES_API_URL=http://hermes-agent:8642
ENV CLAUDE_API_URL=http://claude-agent:8642
EXPOSE 3000

View File

@@ -1,4 +1,4 @@
# Agent Pairing Guide — Workspace ↔ Hermes Gateway
# Agent Pairing Guide — Workspace ↔ Claude Gateway
> **Audience:** AI agents helping users set up Project Workspace.
> Run these steps in order. Each step has a verification command — don't
@@ -8,9 +8,9 @@
```
┌─────────────────┐ HTTP :8642 ┌──────────────────┐
Hermes Gateway │◄──────────────────────►│ Project Workspace │
Claude Gateway │◄──────────────────────►│ Project Workspace │
│ (Python, FastAPI)│ /health, /v1/chat, │ (Node, Vite) │
hermes gateway │ /api/sessions, etc. │ pnpm dev │
claude gateway │ /api/sessions, etc. │ pnpm dev │
│ run │ │ port 3000 │
└─────────────────┘ └──────────────────┘
```
@@ -20,26 +20,26 @@ If `curl http://127.0.0.1:8642/health` returns JSON, they can pair.
---
## Step 1 — Is hermes-agent installed?
## Step 1 — Is claude-agent installed?
```bash
hermes --version
claude --version
```
**Pass:** prints `Project Agent vX.Y.Z`.
**Fail:** `command not found`.
### Fix (if hermes-agent not installed)
### Fix (if claude-agent not installed)
```bash
# Official Nous installer — works on macOS, Linux, WSL
curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash
curl -fsSL https://raw.githubusercontent.com/NousResearch/claude-agent/main/scripts/install.sh | bash
```
After install, restart your shell or run:
```bash
export PATH="$HOME/.local/bin:$PATH"
hermes --version
claude --version
```
---
@@ -47,15 +47,15 @@ hermes --version
## Step 2 — Is the API server enabled?
The gateway exposes an HTTP API on port 8642, but only if `API_SERVER_ENABLED=true`
is set in the hermes env file.
is set in the claude env file.
```bash
# Find the env file
HERMES_ENV="$(hermes config env-path 2>/dev/null || echo "$HOME/.hermes/.env")"
echo "Hermes env file: $HERMES_ENV"
CLAUDE_ENV="$(claude config env-path 2>/dev/null || echo "$HOME/.claude/.env")"
echo "Claude env file: $CLAUDE_ENV"
# Check for the key
grep -i "API_SERVER" "$HERMES_ENV" 2>/dev/null || echo "NO API_SERVER KEYS FOUND"
grep -i "API_SERVER" "$CLAUDE_ENV" 2>/dev/null || echo "NO API_SERVER KEYS FOUND"
```
**Pass:** output includes `API_SERVER_ENABLED=true` (with underscores).
@@ -78,16 +78,16 @@ API_SERVER_HOST=127.0.0.1
### Fix
```bash
HERMES_ENV="$(hermes config env-path 2>/dev/null || echo "$HOME/.hermes/.env")"
mkdir -p "$(dirname "$HERMES_ENV")"
CLAUDE_ENV="$(claude config env-path 2>/dev/null || echo "$HOME/.claude/.env")"
mkdir -p "$(dirname "$CLAUDE_ENV")"
# Remove any typo'd versions first
sed -i.bak '/^APISERVERENABLED/d; /^APISERVERHOST/d; /^APISERVERKEY/d; /^APISERVERPORT/d' "$HERMES_ENV" 2>/dev/null || true
sed -i.bak '/^APISERVERENABLED/d; /^APISERVERHOST/d; /^APISERVERKEY/d; /^APISERVERPORT/d' "$CLAUDE_ENV" 2>/dev/null || true
# Write correct keys (idempotent — updates existing or appends)
grep -q '^API_SERVER_ENABLED=' "$HERMES_ENV" 2>/dev/null && \
sed -i.bak 's/^API_SERVER_ENABLED=.*/API_SERVER_ENABLED=true/' "$HERMES_ENV" || \
echo 'API_SERVER_ENABLED=true' >> "$HERMES_ENV"
grep -q '^API_SERVER_ENABLED=' "$CLAUDE_ENV" 2>/dev/null && \
sed -i.bak 's/^API_SERVER_ENABLED=.*/API_SERVER_ENABLED=true/' "$CLAUDE_ENV" || \
echo 'API_SERVER_ENABLED=true' >> "$CLAUDE_ENV"
```
**Do NOT set `API_SERVER_HOST=0.0.0.0`** unless the user explicitly wants
@@ -100,24 +100,24 @@ correct for local Workspace.
## Step 3 — Is the gateway process running?
```bash
pgrep -af "hermes.*gateway" || echo "NOT RUNNING"
pgrep -af "claude.*gateway" || echo "NOT RUNNING"
```
**Pass:** shows a `hermes gateway run` (or similar) process.
**Pass:** shows a `claude gateway run` (or similar) process.
**Fail:** nothing.
### Fix
```bash
# Start in foreground (recommended for debugging — you see all output)
hermes gateway run
claude gateway run
# OR if using systemd
hermes gateway install # creates the service
systemctl --user start hermes-gateway
claude gateway install # creates the service
systemctl --user start claude-gateway
```
**First run:** hermes may prompt for initial setup (provider, model). Complete
**First run:** claude may prompt for initial setup (provider, model). Complete
the interactive setup before continuing.
---
@@ -135,7 +135,7 @@ lsof -iTCP:8642 -sTCP:LISTEN || echo "PORT NOT BOUND"
curl -sf http://127.0.0.1:8642/health && echo "OK" || echo "NOT REACHABLE"
```
**Pass:** port is bound AND `curl /health` returns `{"status": "ok", "platform": "hermes-agent"}`.
**Pass:** port is bound AND `curl /health` returns `{"status": "ok", "platform": "claude-agent"}`.
**Fail — gateway running but port not bound:** API server didn't start.
Go back to Step 2 and verify the env vars have underscores.
@@ -153,22 +153,22 @@ ss -tlnp | grep 8642 # Linux
## Step 5 — Is Workspace pointed at the gateway?
```bash
# In the hermes-workspace directory
cat .env | grep HERMES_API_URL
# In the claude-workspace directory
cat .env | grep CLAUDE_API_URL
```
**Pass:** `HERMES_API_URL=http://127.0.0.1:8642`
**Pass:** `CLAUDE_API_URL=http://127.0.0.1:8642`
**Fail or missing:**
```bash
# In the hermes-workspace directory
echo 'HERMES_API_URL=http://127.0.0.1:8642' >> .env
# In the claude-workspace directory
echo 'CLAUDE_API_URL=http://127.0.0.1:8642' >> .env
```
If `.env` doesn't exist:
```bash
cp .env.example .env
# Then set HERMES_API_URL as above
# Then set CLAUDE_API_URL as above
```
---
@@ -176,13 +176,13 @@ cp .env.example .env
## Step 6 — Start Workspace and verify pairing
```bash
cd ~/hermes-workspace # or wherever it's installed
cd ~/claude-workspace # or wherever it's installed
pnpm dev
```
**Look for this in the startup output:**
```
[hermes-api] Configured API: http://127.0.0.1:8642
[claude-api] Configured API: http://127.0.0.1:8642
[gateway] gateway=http://127.0.0.1:8642 ... mode=enhanced-fork core=[health, chatCompletions, models, streaming]
```
@@ -210,30 +210,30 @@ Open `http://localhost:3000` (or whatever port Vite reports).
For users who just want it to work — run this entire block:
```bash
# 1. Find hermes env
HERMES_ENV="$(hermes config env-path 2>/dev/null || echo "$HOME/.hermes/.env")"
mkdir -p "$(dirname "$HERMES_ENV")"
# 1. Find claude env
CLAUDE_ENV="$(claude config env-path 2>/dev/null || echo "$HOME/.claude/.env")"
mkdir -p "$(dirname "$CLAUDE_ENV")"
# 2. Enable API server (idempotent)
grep -q '^API_SERVER_ENABLED=' "$HERMES_ENV" 2>/dev/null && \
sed -i.bak 's/^API_SERVER_ENABLED=.*/API_SERVER_ENABLED=true/' "$HERMES_ENV" || \
echo 'API_SERVER_ENABLED=true' >> "$HERMES_ENV"
grep -q '^API_SERVER_ENABLED=' "$CLAUDE_ENV" 2>/dev/null && \
sed -i.bak 's/^API_SERVER_ENABLED=.*/API_SERVER_ENABLED=true/' "$CLAUDE_ENV" || \
echo 'API_SERVER_ENABLED=true' >> "$CLAUDE_ENV"
# 3. Clean up common typos
sed -i.bak '/^APISERVERENABLED/d; /^APISERVERHOST/d' "$HERMES_ENV" 2>/dev/null || true
sed -i.bak '/^APISERVERENABLED/d; /^APISERVERHOST/d' "$CLAUDE_ENV" 2>/dev/null || true
# 4. Restart gateway
hermes gateway stop 2>/dev/null; sleep 2; hermes gateway run &
claude gateway stop 2>/dev/null; sleep 2; claude gateway run &
sleep 8
# 5. Verify
curl -sf http://127.0.0.1:8642/health && echo "✅ Gateway API is up" || echo "❌ Gateway API not reachable"
# 6. Set workspace env
cd ~/hermes-workspace 2>/dev/null || cd "$(find ~ -maxdepth 2 -name hermes-workspace -type d | head -1)"
grep -q '^HERMES_API_URL=' .env 2>/dev/null && \
sed -i.bak 's|^HERMES_API_URL=.*|HERMES_API_URL=http://127.0.0.1:8642|' .env || \
echo 'HERMES_API_URL=http://127.0.0.1:8642' >> .env
cd ~/claude-workspace 2>/dev/null || cd "$(find ~ -maxdepth 2 -name claude-workspace -type d | head -1)"
grep -q '^CLAUDE_API_URL=' .env 2>/dev/null && \
sed -i.bak 's|^CLAUDE_API_URL=.*|CLAUDE_API_URL=http://127.0.0.1:8642|' .env || \
echo 'CLAUDE_API_URL=http://127.0.0.1:8642' >> .env
echo "✅ Done. Run: pnpm dev"
```
@@ -247,7 +247,7 @@ echo "✅ Done. Run: pnpm dev"
- Python cold-start is slower on WSL due to filesystem I/O overhead.
The gateway may take 1015 seconds to bind port 8642.
- If Workspace's health check times out before the gateway is ready,
start the gateway separately first (`hermes gateway run`), wait for
start the gateway separately first (`claude gateway run`), wait for
the port to bind, then start Workspace in a second terminal.
- Use `127.0.0.1`, not `localhost` — WSL2 sometimes resolves `localhost`
to the Windows host instead of the WSL VM.
@@ -255,16 +255,16 @@ echo "✅ Done. Run: pnpm dev"
### macOS
- No special considerations. Default setup works.
- If using Homebrew Python, ensure `hermes` is on PATH:
- If using Homebrew Python, ensure `claude` is on PATH:
`export PATH="$HOME/.local/bin:$PATH"`
### Linux (native)
- systemd users: `hermes gateway install` creates a user service.
Check status with `systemctl --user status hermes-gateway`.
- systemd users: `claude gateway install` creates a user service.
Check status with `systemctl --user status claude-gateway`.
- If using a different `$HOME` for the systemd service (e.g. running as
a different user), the `.env` file location changes. Use
`hermes config env-path` to find it.
`claude config env-path` to find it.
---
@@ -273,13 +273,13 @@ echo "✅ Done. Run: pnpm dev"
Collect this diagnostic bundle and share it:
```bash
echo "=== hermes version ===" && hermes --version 2>&1
echo "=== hermes env path ===" && hermes config env-path 2>&1
echo "=== hermes env (redacted) ===" && grep -E "^(API_SERVER|HERMES_)" "$(hermes config env-path 2>/dev/null || echo ~/.hermes/.env)" 2>&1
echo "=== gateway process ===" && pgrep -af "hermes.*gateway" 2>&1 || echo "not running"
echo "=== claude version ===" && claude --version 2>&1
echo "=== claude env path ===" && claude config env-path 2>&1
echo "=== claude env (redacted) ===" && grep -E "^(API_SERVER|CLAUDE_)" "$(claude config env-path 2>/dev/null || echo ~/.claude/.env)" 2>&1
echo "=== gateway process ===" && pgrep -af "claude.*gateway" 2>&1 || echo "not running"
echo "=== port 8642 ===" && (ss -tlnp 2>/dev/null || lsof -iTCP:8642 -sTCP:LISTEN 2>/dev/null) | grep 8642 || echo "not bound"
echo "=== health check ===" && curl -sf http://127.0.0.1:8642/health 2>&1 || echo "not reachable"
echo "=== workspace .env ===" && grep HERMES ~/hermes-workspace/.env 2>&1 || echo "no .env"
echo "=== workspace .env ===" && grep CLAUDE ~/claude-workspace/.env 2>&1 || echo "no .env"
echo "=== OS ===" && uname -a
echo "=== Node ===" && node --version
echo "=== Python ===" && python3 --version 2>&1

View File

@@ -1,6 +1,6 @@
# Agent-authored UI state
Hermes Workspace can render optional structured UI state emitted by the agent instead of relying only on heuristic panel derivation from plain chat text.
Claude Workspace can render optional structured UI state emitted by the agent instead of relying only on heuristic panel derivation from plain chat text.
## Why

View File

@@ -1,8 +1,8 @@
# Hermes Workspace OpenAI-Compat Architecture Spec
# Claude Workspace OpenAI-Compat Architecture Spec
> **For Hermes:** Use `writing-plans` if this turns into an implementation plan. This doc locks the product and backend compatibility direction.
> **For Claude:** Use `writing-plans` if this turns into an implementation plan. This doc locks the product and backend compatibility direction.
**Goal:** Make Hermes Workspace work out of the box against vanilla `hermes-agent` and any OpenAI-compatible backend, while unlocking richer workspace features automatically when Hermes-specific APIs are available.
**Goal:** Make Claude Workspace work out of the box against vanilla `claude-agent` and any OpenAI-compatible backend, while unlocking richer workspace features automatically when Claude-specific APIs are available.
**Status:** Approved architectural constraint for the next implementation pass.
@@ -10,7 +10,7 @@
## 1. Problem
Hermes Workspace currently depends on a forked `hermes-agent` gateway for extended functionality:
Claude Workspace currently depends on a forked `claude-agent` gateway for extended functionality:
- session management
- streaming chat
@@ -23,7 +23,7 @@ That fork dependency is the wrong shape for distribution.
Current downside:
- users cannot point the workspace at stock `hermes-agent` and expect it to work
- users cannot point the workspace at stock `claude-agent` and expect it to work
- README/setup flow forces a custom fork
- chat reliability is coupled to `/api/sessions` instead of the more portable OpenAI-compatible chat interface
- product adoption is constrained by backend politics instead of frontend usability
@@ -36,9 +36,9 @@ We want to reverse that.
This is the decision to lock in:
> **Hermes Workspace must work standalone against any OpenAI-compatible backend.**
> **Claude Workspace must work standalone against any OpenAI-compatible backend.**
>
> Hermes-specific workspace features may enhance the experience when the full Hermes API is available, but the product must remain usable without those endpoints.
> Claude-specific workspace features may enhance the experience when the full Claude API is available, but the product must remain usable without those endpoints.
Non-negotiable implication:
@@ -53,19 +53,19 @@ Non-negotiable implication:
Rewrite the workspace so the core chat product works against:
- vanilla `hermes-agent`
- vanilla `claude-agent`
- any backend exposing `/v1/chat/completions`
- any backend exposing `/v1/models` optionally
In this mode, advanced features degrade gracefully when Hermes-specific APIs are absent.
In this mode, advanced features degrade gracefully when Claude-specific APIs are absent.
### Step 2 — Upstream the richer API later
Submit the custom Hermes endpoints into upstream `hermes-agent`, targeting `gateway/platforms/api_server.py`.
Submit the custom Claude endpoints into upstream `claude-agent`, targeting `gateway/platforms/api_server.py`.
If upstream accepts them:
- full workspace functionality works with vanilla `hermes-agent`
- full workspace functionality works with vanilla `claude-agent`
- no long-term fork dependency remains
- the enhanced UX becomes a first-class upstream capability, not a private patchset
@@ -96,11 +96,11 @@ User does **not** need:
- `/api/skills`
- `/api/memory`
- `/api/config`
- Hermes-specific metadata endpoints
- Claude-specific metadata endpoints
### Mode B — Enhanced Hermes Mode
### Mode B — Enhanced Claude Mode
When Hermes-specific endpoints are present, unlock:
When Claude-specific endpoints are present, unlock:
- session history and named sessions
- memory browser / search / editing
@@ -117,7 +117,7 @@ The UI should detect these capabilities and progressively enhance.
**Chat is the base product. Everything else is optional enhancement.**
If a user points Hermes Workspace at a valid OpenAI-compatible backend, they should be able to send a message and receive a streamed response without caring whether the backend is Hermes, OpenAI, OpenRouter, Ollama, vLLM, or something else.
If a user points Claude Workspace at a valid OpenAI-compatible backend, they should be able to send a message and receive a streamed response without caring whether the backend is Claude, OpenAI, OpenRouter, Ollama, vLLM, or something else.
Anything beyond that should be treated as capability-based augmentation.
@@ -131,15 +131,15 @@ The workspace must stop treating `/api/sessions` as the prerequisite for sending
Instead:
1. Detect whether Hermes session APIs exist.
2. If yes, use the enhanced Hermes session flow.
1. Detect whether Claude session APIs exist.
2. If yes, use the enhanced Claude session flow.
3. If not, send chat through `POST /v1/chat/completions`.
4. If streaming is supported, render streamed deltas.
5. If streaming is not supported, render standard non-stream response cleanly.
Result:
- missing Hermes sessions API must no longer cause the product to hang or hard-fail for basic chat
- missing Claude sessions API must no longer cause the product to hang or hard-fail for basic chat
### 6.2 Capability detection
@@ -153,7 +153,7 @@ Capability probing should explicitly distinguish:
- streaming support if detectable
- attachment / image support if inferable
#### Hermes enhancement capabilities
#### Claude enhancement capabilities
- `/api/sessions`
- `/api/skills`
@@ -168,7 +168,7 @@ The app should expose these as two layers:
### 6.3 Graceful degradation
When Hermes-specific APIs are missing, the UI must not show broken loaders, dead tabs, or cryptic errors.
When Claude-specific APIs are missing, the UI must not show broken loaders, dead tabs, or cryptic errors.
Instead, each advanced surface should do one of the following:
@@ -193,12 +193,12 @@ New setup principle:
- connect any OpenAI-compatible backend first
- verify chat works
- then advertise extra Hermes-native features if supported
- then advertise extra Claude-native features if supported
Onboarding copy should communicate:
- “Works with any OpenAI-compatible backend”
- “Enhanced features unlock automatically with Hermes gateway APIs”
- “Enhanced features unlock automatically with Claude gateway APIs”
### 6.5 Documentation
@@ -207,8 +207,8 @@ README and setup docs must reflect the architecture honestly.
Required messaging:
- workspace works standalone with OpenAI-compatible backends
- vanilla `hermes-agent` is a supported target
- the richer Hermes API is optional for advanced workspace features
- vanilla `claude-agent` is a supported target
- the richer Claude API is optional for advanced workspace features
- upstreaming those APIs is the long-term path
---
@@ -222,7 +222,7 @@ Do not frame missing advanced APIs as a fatal error when core chat works.
Use status language like:
- **Connected** — chat available
- **Enhanced**Hermes workspace APIs detected
- **Enhanced**Claude workspace APIs detected
- **Partial** — chat available, some advanced features unavailable
- **Disconnected** — no usable chat backend detected
@@ -232,7 +232,7 @@ Feature gating should feel intentional, not broken.
Good examples:
- “Memory browser requires Hermes memory API.”
- “Memory browser requires Claude memory API.”
- “Session history isnt available on this backend yet.”
- “Connected in portable mode. Chat works; advanced workspace tools are unavailable.”
@@ -245,7 +245,7 @@ Bad examples:
### 7.3 Session behavior in portable mode
When no Hermes sessions API exists, the app still needs a sane chat UX.
When no Claude sessions API exists, the app still needs a sane chat UX.
Portable-mode minimum:
@@ -277,9 +277,9 @@ Expected response handling:
- SSE stream chunks for streaming mode
- standard OpenAI chat completion JSON for non-stream mode
### 8.2 Enhanced Hermes path
### 8.2 Enhanced Claude path
Enhanced path remains Hermes-native where available, because it provides:
Enhanced path remains Claude-native where available, because it provides:
- persistent sessions
- message history
@@ -296,9 +296,9 @@ For Step 2, the custom API endpoints should be proposed upstream in:
Intent:
- make enhanced workspace APIs part of upstream `hermes-agent`
- make enhanced workspace APIs part of upstream `claude-agent`
- remove ongoing maintenance burden of a permanent fork
- let Hermes Workspace treat stock Hermes as the best backend, without requiring it
- let Claude Workspace treat stock Claude as the best backend, without requiring it
---
@@ -307,14 +307,14 @@ Intent:
This spec does **not** require:
- universal parity across every OpenAI-compatible provider
- guaranteed session persistence on non-Hermes backends
- memory/skills/config support outside Hermes
- guaranteed session persistence on non-Claude backends
- memory/skills/config support outside Claude
- building a backend abstraction for every vendor-specific extension
The goal is simpler:
- portable chat first
- enhanced Hermes features second
- enhanced Claude features second
- no fork requirement
---
@@ -325,15 +325,15 @@ This initiative is complete when all of the following are true:
### Product acceptance
- A user can launch Hermes Workspace against a stock OpenAI-compatible backend and successfully chat without patching backend code.
- A user can launch Hermes Workspace against vanilla `hermes-agent` and get a working core experience.
- Advanced features do not hard-fail the app when Hermes-specific APIs are absent.
- The UI clearly communicates portable mode vs enhanced Hermes mode.
- A user can launch Claude Workspace against a stock OpenAI-compatible backend and successfully chat without patching backend code.
- A user can launch Claude Workspace against vanilla `claude-agent` and get a working core experience.
- Advanced features do not hard-fail the app when Claude-specific APIs are absent.
- The UI clearly communicates portable mode vs enhanced Claude mode.
### Technical acceptance
- Chat send path no longer hard-depends on `/api/sessions`.
- Capability probing includes `/v1/chat/completions` readiness, not just Hermes-specific APIs.
- Capability probing includes `/v1/chat/completions` readiness, not just Claude-specific APIs.
- Missing `/api/sessions`, `/api/skills`, `/api/memory`, or `/api/config` does not block app boot or core chat.
- Portable-mode chat streaming works against OpenAI-compatible SSE responses.
@@ -341,7 +341,7 @@ This initiative is complete when all of the following are true:
- README no longer says the fork is required.
- Setup docs describe OpenAI-compatible standalone mode first.
- Enhanced Hermes API support is documented as progressive enhancement.
- Enhanced Claude API support is documented as progressive enhancement.
- Step 2 upstreaming target is documented clearly.
---
@@ -350,13 +350,13 @@ This initiative is complete when all of the following are true:
This is not the detailed task plan, but the engineering direction should be:
1. Separate **core chat client** from **Hermes enhanced client**.
1. Separate **core chat client** from **Claude enhanced client**.
2. Refactor capability probing into portable vs enhanced layers.
3. Add OpenAI-compatible streaming parser path.
4. Add local-thread fallback for non-session backends.
5. Gate advanced screens cleanly behind capability checks.
6. Rewrite onboarding and docs around portable-first positioning.
7. After Step 1 is stable, prepare the upstream PR for Hermes-native endpoints.
7. After Step 1 is stable, prepare the upstream PR for Claude-native endpoints.
---
@@ -364,10 +364,10 @@ This is not the detailed task plan, but the engineering direction should be:
Lock this in:
> Hermes Workspace is a standalone frontend for OpenAI-compatible chat backends.
> Claude Workspace is a standalone frontend for OpenAI-compatible chat backends.
>
> Hermes-native APIs are an enhancement layer, not a requirement.
> Claude-native APIs are an enhancement layer, not a requirement.
>
> Step 1 is portable compatibility now.
>
> Step 2 is upstreaming the enhanced Hermes APIs so no fork is needed ever again.
> Step 2 is upstreaming the enhanced Claude APIs so no fork is needed ever again.

View File

@@ -1,17 +1,17 @@
# Hermes Workspace Naming Contract
# Claude Workspace Naming Contract
This repo is for **Hermes Workspace** and **Hermes Agent** work.
This repo is for **Claude Workspace** and **Claude Agent** work.
## Canonical product names
Use these names in all new UI, docs, skills, prompts, tests, review comments, and handoffs:
- **Hermes Workspace**
- **Hermes Agent**
- **Claude Workspace**
- **Claude Agent**
- **Swarm**
- **Hermes Kanban**
- **HERMES_HOME**
- `~/.hermes`
- **Claude Kanban**
- **CLAUDE_HOME**
- `~/.claude`
## Forbidden new references
@@ -29,34 +29,34 @@ Do **not** introduce these in new work unless quoting legacy history or compatib
If older code, docs, tests, or handoffs contain Claude-era wording, treat it as legacy residue.
Default action:
- normalize it to Hermes naming
- normalize it to Claude naming
- preserve old wording only when explicitly documenting migration or backwards compatibility
## Runtime/path rules
For Hermes-native runtime work, prefer:
For Claude-native runtime work, prefer:
- `HERMES_HOME`
- `~/.hermes/profiles/<workerId>`
- `hermes`
- Hermes worker sessions
- `CLAUDE_HOME`
- `~/.claude/profiles/<workerId>`
- `claude`
- Claude worker sessions
Do not suggest Claude-specific runtime wrappers or profile paths for live Hermes Workspace behavior.
Do not suggest Claude-specific runtime wrappers or profile paths for live Claude Workspace behavior.
## Swarm/UI language rules
Prefer:
- **Ready** not person-specific hardcoded labels
- **Board / Cards / List** for reports views
- **Hermes Workspace** and **Hermes Agent** in update/config/status UI
- **Claude Workspace** and **Claude Agent** in update/config/status UI
Avoid:
- person-specific product labels baked into UI
- Claude-branded wording in Hermes Workspace surfaces
- Claude-branded wording in Claude Workspace surfaces
## Reviewer rule
Any PR or patch that introduces new Claude-branded naming into Hermes Workspace should be treated as a regression unless it is:
Any PR or patch that introduces new Claude-branded naming into Claude Workspace should be treated as a regression unless it is:
- a legacy compatibility note
- a migration guide
- a quoted historical artifact
@@ -64,7 +64,7 @@ Any PR or patch that introduces new Claude-branded naming into Hermes Workspace
## Agent instruction rule
When an agent is working in this repo:
- assume Hermes naming is canonical
- rewrite Claude-era references to Hermes by default
- assume Claude naming is canonical
- rewrite Claude-era references to Claude by default
- do not invent Claude-branded paths, products, or wrapper guidance
- if uncertain, prefer repo-native Hermes terminology over historical aliases
- if uncertain, prefer repo-native Claude terminology over historical aliases

View File

@@ -1,5 +1,5 @@
# Multi-Gateway Pool Architecture
## Hermes Workspace — Profile-Parallel Agent Execution
## Claude Workspace — Profile-Parallel Agent Execution
### Status: Design Document — PR Proposal
@@ -7,9 +7,9 @@
## 1. Problem Statement
Hermes Workspace currently operates as a **single-gateway, single-profile UI**. The gateway loads one `HERMES_HOME` at startup and all chat sessions, operations, and memory access flow through that one process.
Claude Workspace currently operates as a **single-gateway, single-profile UI**. The gateway loads one `CLAUDE_HOME` at startup and all chat sessions, operations, and memory access flow through that one process.
For multi-profile users (the primary Hermes use case), this means:
For multi-profile users (the primary Claude use case), this means:
- **No parallel agent execution**: Cannot brainstorm with Nous while Jules orchestrates Architect and Sentinel in Operations
- **No profile identity in chat**: The "agent" is always whoever the single gateway was launched as
- **Terminal fragmentation**: Users must open separate terminal windows per profile to achieve true multi-agent workflows
@@ -23,11 +23,11 @@ For multi-profile users (the primary Hermes use case), this means:
## 2. First-Principles Design
**Core truth**: Each Hermes profile is a **distinct cognitive agent** — different SOUL.md, different skills, different memory, different purpose. They are not "modes" of one agent. They are parallel agents.
**Core truth**: Each Claude profile is a **distinct cognitive agent** — different SOUL.md, different skills, different memory, different purpose. They are not "modes" of one agent. They are parallel agents.
**Implication**: The workspace must be an **agent orchestrator**, not just a UI skin over one gateway.
**Constraint**: Hermes gateway is designed as a single-tenant process. It cannot dynamically reload profiles. Each profile needs its own gateway instance.
**Constraint**: Claude gateway is designed as a single-tenant process. It cannot dynamically reload profiles. Each profile needs its own gateway instance.
**Solution**: The workspace maintains a **gateway pool** — one gateway process per active profile, each on its own port, all health-monitored, all routable from the UI.
@@ -43,7 +43,7 @@ For multi-profile users (the primary Hermes use case), this means:
```
┌─────────────────────────────────────────────────────────────┐
Hermes Workspace UI │
Claude Workspace UI │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌───────────────┐ │
│ │ Chat │ │ Ops │ │ Memory │ │ Profile │ │
│ │ (Nous) │ │ (Jules) │ │ (all) │ │ Selector │ │
@@ -90,7 +90,7 @@ function getGatewayPort(profileName: string, profiles: string[]): number {
Profiles are sorted alphabetically to ensure stable port assignment. A persistence file (`gateway-pool.json`) remembers assignments across restarts.
**Profile-count agnostic**: The pool manager discovers profiles dynamically from the filesystem (`~/.hermes/profiles/*`). There is no hardcoded list, no maximum count, and no special-casing of specific profile names. A user with 2 profiles and a user with 50 profiles use the exact same code path.
**Profile-count agnostic**: The pool manager discovers profiles dynamically from the filesystem (`~/.claude/profiles/*`). There is no hardcoded list, no maximum count, and no special-casing of specific profile names. A user with 2 profiles and a user with 50 profiles use the exact same code path.
### 4.2 Gateway Lifecycle States
@@ -107,18 +107,18 @@ type GatewayState =
```typescript
function spawnGateway(profileName: string, port: number): ChildProcess {
const profilePath = path.join(getHermesRoot(), 'profiles', profileName)
const profilePath = path.join(getClaudeRoot(), 'profiles', profileName)
const env = {
...process.env,
HERMES_HOME: profilePath,
HERMES_GATEWAY_PORT: String(port),
HERMES_PROFILE_NAME: profileName,
CLAUDE_HOME: profilePath,
CLAUDE_GATEWAY_PORT: String(port),
CLAUDE_PROFILE_NAME: profileName,
}
return spawn('hermes', ['gateway', '--port', String(port)], { env })
return spawn('claude', ['gateway', '--port', String(port)], { env })
}
```
**Note**: The gateway must be spawned via `hermes gateway`, not via the workspace's internal gateway.ts. The workspace becomes an orchestrator, not a gateway host.
**Note**: The gateway must be spawned via `claude gateway`, not via the workspace's internal gateway.ts. The workspace becomes an orchestrator, not a gateway host.
### 4.4 Health Monitor
@@ -146,7 +146,7 @@ All workspace API routes gain **profile context**:
```typescript
// Current: /api/chat/completions
// New: /api/chat/completions?profile=nous
// or header: X-Hermes-Profile: nous
// or header: X-Claude-Profile: nous
// Gateway proxy routes:
// /api/gateway/{profile}/chat/completions
@@ -176,7 +176,7 @@ export async function proxyToGateway(
### 5.3 Backward Compatibility
When no profile is specified:
- Default to `activeProfile` (from `~/.hermes/active_profile` file)
- Default to `activeProfile` (from `~/.claude/active_profile` file)
- If that file doesn't exist, default to first available profile
- Single-profile users see **zero behavioral change**
@@ -186,7 +186,7 @@ When no profile is specified:
### 6.1 Session Storage
Currently: All sessions in `~/.hermes/sessions/` (or profile's sessions dir)
Currently: All sessions in `~/.claude/sessions/` (or profile's sessions dir)
With multi-gateway: Each gateway manages its own sessions in its own profile directory. The workspace **aggregates** them for display but **routes** them per-profile.
@@ -236,7 +236,7 @@ SESSIONS
A persistent pill/button in the top-left (next to sidebar toggle):
```
[☰] [ nous ▼ ] Hermes Workspace
[☰] [ nous ▼ ] Claude Workspace
```
- Dropdown lists all profiles with status indicators
@@ -300,10 +300,10 @@ src/server/local-provider-discovery.ts # Multi-gateway provider discovery
### Environment Variables
```bash
HERMES_GATEWAY_POOL_ENABLED=true # Enable multi-gateway mode
HERMES_GATEWAY_BASE_PORT=8642 # Starting port
HERMES_GATEWAY_POOL_MAX=10 # Max concurrent gateways
HERMES_GATEWAY_HEALTH_INTERVAL=30 # Health check seconds
CLAUDE_GATEWAY_POOL_ENABLED=true # Enable multi-gateway mode
CLAUDE_GATEWAY_BASE_PORT=8642 # Starting port
CLAUDE_GATEWAY_POOL_MAX=10 # Max concurrent gateways
CLAUDE_GATEWAY_HEALTH_INTERVAL=30 # Health check seconds
```
### workspace-overrides.json
@@ -339,7 +339,7 @@ HERMES_GATEWAY_HEALTH_INTERVAL=30 # Health check seconds
## 11. Security & Privacy Considerations
- Gateways bind to `127.0.0.1` only (already default)
- No cross-profile memory leakage (each gateway has its own `HERMES_HOME`)
- No cross-profile memory leakage (each gateway has its own `CLAUDE_HOME`)
- Profile selector respects auth middleware
- Admin-only: ability to spawn/stop gateways
- **No secrets in code or PRs**: API keys, passwords, tokens, and PII must never appear in source code, test fixtures, log output, or PR descriptions. All sensitive configuration lives in profile-local `.env` files which are `.gitignore`d.
@@ -381,7 +381,7 @@ HERMES_GATEWAY_HEALTH_INTERVAL=30 # Health check seconds
## 15. Related Work
- PR #118: Profile-aware config (merged) — provides `HERMES_HOME` resolution and profile listing
- PR #118: Profile-aware config (merged) — provides `CLAUDE_HOME` resolution and profile listing
- Issue #?: Multi-profile session management (to be created)
- Issue #?: Gateway lifecycle hooks (to be created)
@@ -396,5 +396,5 @@ HERMES_GATEWAY_HEALTH_INTERVAL=30 # Health check seconds
---
*Authored by Nous (Vivere Vitalis) for the Hermes Workspace project.*
*Authored by Nous (Vivere Vitalis) for the Claude Workspace project.*
*First-principles architecture: if each profile is a distinct agent, the workspace must be an agent orchestrator.*

View File

@@ -1,11 +1,11 @@
# Hermes-Workspace v2.1.0 — Swarm
# Claude-Workspace v2.1.0 — Swarm
Hermes-Workspace 2.1.0 introduces **Swarm**, a built-in multi-agent orchestration surface for running a main HermesAgent with persistent worker agents.
Claude-Workspace 2.1.0 introduces **Swarm**, a built-in multi-agent orchestration surface for running a main ClaudeAgent with persistent worker agents.
## Highlights
- **Swarm Mode**
- route work from a main HermesAgent into a live worker swarm
- route work from a main ClaudeAgent into a live worker swarm
- persistent tmux-backed workers
- role-aware dispatch and orchestration surfaces
@@ -25,8 +25,8 @@ Hermes-Workspace 2.1.0 introduces **Swarm**, a built-in multi-agent orchestratio
- dashboard fallback added for session create/update/fork flows
- workspace reliability patches preserved
- **Hermes path + environment fixes**
- canonical Hermes root handling
- **Claude path + environment fixes**
- canonical Claude root handling
- improved home/env handling for profiles and run storage
- **Docs and security**
@@ -50,19 +50,19 @@ Hermes-Workspace 2.1.0 introduces **Swarm**, a built-in multi-agent orchestratio
## Suggested short release description
Hermes-Workspace 2.1.0 ships Swarm: a built-in multi-agent control surface for persistent worker agents, orchestrator-first routing, Board + reports + inbox flows, and a set of reliability fixes across chat streaming, approvals, sessions, and Hermes path handling.
Claude-Workspace 2.1.0 ships Swarm: a built-in multi-agent control surface for persistent worker agents, orchestrator-first routing, Board + reports + inbox flows, and a set of reliability fixes across chat streaming, approvals, sessions, and Claude path handling.
## Suggested launch post
Hermes-Workspace 2.1.0 is out.
Claude-Workspace 2.1.0 is out.
It ships **Swarm**: a built-in multi-agent workspace where one HermesAgent can orchestrate persistent worker agents with live checkpoints, Board, inbox/review flow, and better routing between orchestrator and workers.
It ships **Swarm**: a built-in multi-agent workspace where one ClaudeAgent can orchestrate persistent worker agents with live checkpoints, Board, inbox/review flow, and better routing between orchestrator and workers.
Also in 2.1.0:
- stronger long-run SSE chat reliability
- approval banner fix
- portable session deletion fix
- dashboard fallback for session actions
- Hermes path handling improvements
- Claude path handling improvements
If you want multi-agent control without leaving your workspace, this is the release.

View File

@@ -10,15 +10,15 @@ You need:
- pnpm
- git
- tmux for persistent TUI-backed workers
- a configured Project Agent profile under `~/.hermes/profiles/`
- a configured Project Agent profile under `~/.claude/profiles/`
The workspace can still render without tmux, but tmux is what makes the worker sessions feel alive instead of one-shot and disposable.
## 1. Clone the workspace
```bash
git clone https://github.com/outsourc-e/hermes-workspace.git
cd hermes-workspace
git clone https://github.com/outsourc-e/claude-workspace.git
cd claude-workspace
```
## 2. Install dependencies
@@ -46,13 +46,13 @@ Some release lanes run on `:3002`; trust the terminal output if it differs.
On first run, the workspace looks for Project Agent profiles in:
```text
~/.hermes/profiles/
~/.claude/profiles/
```
Each worker profile can include:
```text
~/.hermes/profiles/<workerId>/
~/.claude/profiles/<workerId>/
MEMORY.md
SOUL.md
USER.md
@@ -81,7 +81,7 @@ Replace `swarm7` with the worker ID you want. The convention is:
```text
tmux session: swarm-<workerId>
profile: ~/.hermes/profiles/<workerId>
profile: ~/.claude/profiles/<workerId>
```
After the script starts the session, open Swarm Mode and use Runtime view to attach to the TUI.
@@ -210,7 +210,7 @@ Pick the closest role first, then tune. Avoid starting from Custom unless you ar
Before trusting a new worker:
- It appears in the roster.
- Its profile exists under `~/.hermes/profiles/<workerId>/`.
- Its profile exists under `~/.claude/profiles/<workerId>/`.
- Runtime view can attach to tmux or open a shell/log stream.
- `/api/swarm-dispatch` can deliver a task.
- The worker returns the canonical checkpoint format.

View File

@@ -33,7 +33,7 @@ When you clone the repo and run the workspace, worker sessions load skills from
A worker profile can also carry profile-local skills under:
```text
~/.hermes/profiles/<workerId>/skills/
~/.claude/profiles/<workerId>/skills/
```
The important rule is that the worker's runtime must be able to resolve the skill name from its profile. The UI can display a role's default skills, but the worker still needs the skill files available locally.

View File

@@ -7,18 +7,18 @@ Surface: `/swarm2`, plus shared agent surfaces in main workspace chat
## 1. Product Thesis
Swarm2 is not a dashboard. Swarm2 is the control plane and IDE for **sub-Hermes agents**.
Swarm2 is not a dashboard. Swarm2 is the control plane and IDE for **sub-Claude agents**.
A user should be able to clone their main Hermes agent as many times as needed. Each clone has the same core access, context shape, skills, memory conventions, workspace tools, and project capabilities as the parent agent, but can run independently on a lane of work.
A user should be able to clone their main Claude agent as many times as needed. Each clone has the same core access, context shape, skills, memory conventions, workspace tools, and project capabilities as the parent agent, but can run independently on a lane of work.
This is materially different from spawning disposable subagents:
- Subagents are ephemeral workers.
- Sub-Hermes agents are persistent cloned operators.
- Sub-Claude agents are persistent cloned operators.
- Subagents perform a task.
- Sub-Hermes agents own a lane/project and can run on autopilot.
- Sub-Claude agents own a lane/project and can run on autopilot.
- Subagents lose continuity unless wrapped carefully.
- Sub-Hermes agents carry persistent profile state, runtime metadata, tasks, logs, sessions, skills, and project awareness.
- Sub-Claude agents carry persistent profile state, runtime metadata, tasks, logs, sessions, skills, and project awareness.
The swarm product promise:
@@ -49,9 +49,9 @@ The hub card should show:
It should not consume the main content area with a full chat UI.
### 2.2 Sub-Hermes Agent
### 2.2 Sub-Claude Agent
A swarm agent is a cloned Hermes profile with:
A swarm agent is a cloned Claude profile with:
- model/provider config
- skills/tool access
@@ -170,7 +170,7 @@ Required:
Task sources:
- Hermes tasks API
- Claude tasks API
- worker runtime.json currentTask/state
- optional project issue/PR metadata
@@ -261,12 +261,12 @@ Main workspace chat should get an agent sidebar inspired by Clawsuite.
Required:
- active cloned agents
- CLI/ACP/sub-Hermes sessions
- CLI/ACP/sub-Claude sessions
- status/current task
- latest output
- issues/PRs solved
- quick open into Swarm2 card/session/terminal/preview
- Hermes-inspired avatars
- Claude-inspired avatars
Acceptance:
@@ -343,7 +343,7 @@ Returns:
Worker cards should consume:
- `GET /api/hermes-tasks?assignee=swarm4&include_done=false`
- `GET /api/claude-tasks?assignee=swarm4&include_done=false`
- or existing task endpoint equivalent
### 4.5 Project Preview API
@@ -437,7 +437,7 @@ Tabs:
### Phase 5 — Main workspace integration
- Agent sidebar in main chat
- Active cloned agents/sub-Hermes list
- Active cloned agents/sub-Claude list
- latest output/status
- issues/PR solved summaries
- jump into Swarm2 views
@@ -446,7 +446,7 @@ Tabs:
Swarm2 is solid when:
- User can clone multiple full Hermes agents and see them as persistent workers.
- User can clone multiple full Claude agents and see them as persistent workers.
- User can understand what every worker is doing without leaving `/swarm2`.
- User can chat with any worker and see real recent messages.
- User can see each worker's tasks and blockers.
@@ -490,12 +490,12 @@ Acceptance: one Swarm route, no duplicates, all swarm tests green, docs/README/n
Execution contract for this mission:
- Context, memory, and handoffs come from `/Users/aurora/.openclaw/workspace`
- Swarm2 code, git, build, and tests run in `/Users/aurora/hermes-workspace`
- Swarm2 code, git, build, and tests run in `/Users/aurora/claude-workspace`
- Do not use legacy workspace aliases
- Before any build/test/git loop, run:
```bash
cd /Users/aurora/hermes-workspace &&
cd /Users/aurora/claude-workspace &&
pwd &&
test -f package.json &&
jq -r .name package.json
@@ -510,4 +510,4 @@ Start here:
5. Make runtime view auto-populate active workers and attach/fallback cleanly.
6. After new route-module changes, prefer a full dev-server restart over trusting hot reload.
This turns `/swarm2` from a beautiful control surface into the first version of the actual sub-Hermes IDE.
This turns `/swarm2` from a beautiful control surface into the first version of the actual sub-Claude IDE.

View File

@@ -2,7 +2,7 @@
Date: 2026-04-28
Status: implementation spec, staged rollout
Canonical repo: `/Users/aurora/hermes-workspace`
Canonical repo: `/Users/aurora/claude-workspace`
## Goal
@@ -16,7 +16,7 @@ Swarm2 should then:
1. create or update the swarm roster,
2. assign roles, skills, and missions,
3. route work to the right persistent Hermes worker sessions,
3. route work to the right persistent Claude worker sessions,
4. keep each worker running in its own profile/tmux session,
5. collect checkpoints as workers complete subtasks,
6. automatically prompt workers to continue when more work remains,
@@ -30,7 +30,7 @@ Swarm2 should then:
- **Proof beats vibes.** Workers checkpoint files changed, commands run, results, blockers, next action.
- **Orchestrator drives the loop.** Workers execute. Orchestrator decomposes, sequences, monitors, re-prompts, reviews, and escalates.
- **Autopilot is staged.** The system should work in manual, semi-auto, then full-auto modes.
- **tmux/Hermes native.** Worker sessions are Hermes profiles in `swarm-<workerId>` tmux sessions.
- **tmux/Claude native.** Worker sessions are Claude profiles in `swarm-<workerId>` tmux sessions.
## Current foundation
@@ -39,7 +39,7 @@ Already present:
- `swarm.yaml`, canonical roster config.
- `/api/swarm-roster`, reads/writes roster entries.
- `/api/swarm-decompose`, routes a mission into assignments.
- `/api/swarm-dispatch`, sends prompts to live tmux/Hermes sessions and falls back to one-shot Hermes.
- `/api/swarm-dispatch`, sends prompts to live tmux/Claude sessions and falls back to one-shot Claude.
- `/api/swarm-runtime`, reads worker `runtime.json`, tmux state, tasks, artifacts, previews.
- `/api/swarm-chat`, reads worker chat history from profile `state.db`.
- `/api/swarm-tmux-start/stop/scroll`, controls persistent sessions.
@@ -65,7 +65,7 @@ Decomposer/router
Assignment plan
Dispatcher
↓ per-worker prompt into tmux/Hermes
↓ per-worker prompt into tmux/Claude
Persistent worker sessions
↓ workers checkpoint
runtime.json + state.db + artifacts/previews
@@ -86,13 +86,13 @@ workers:
role: Builder
specialty: full-stack implementation and UI/system integration
model: GPT-5.5
mission: Ship focused product slices in Hermes Workspace with tests.
mission: Ship focused product slices in Claude Workspace with tests.
skills: [swarm-ui-worker, swarm-worker-core]
capabilities:
- code-editing
- ui-implementation
- build-verification
defaultCwd: /Users/aurora/hermes-workspace
defaultCwd: /Users/aurora/claude-workspace
preferredTaskTypes: [implementation, refactor, ui]
maxConcurrentTasks: 1
acceptsBroadcast: true
@@ -120,7 +120,7 @@ workers:
## Runtime contract
Each worker profile may expose `~/.hermes/profiles/<workerId>/runtime.json`.
Each worker profile may expose `~/.claude/profiles/<workerId>/runtime.json`.
The orchestrator and worker should keep these fields current:
@@ -204,7 +204,7 @@ Example:
```text
## Swarm Orchestrator Dispatch
Worker: swarm5 — Builder
Mission: Ship focused product slices in Hermes Workspace with tests.
Mission: Ship focused product slices in Claude Workspace with tests.
Skills: swarm-ui-worker, swarm-worker-core
## Assigned Task
@@ -297,8 +297,8 @@ Pass criteria:
## Open questions
- Should new roster-only agents auto-create Hermes profiles and wrappers, or should Add Swarm remain config-only until first start?
- Should orchestrator loop run in the browser session, server interval, cron job, or persistent Hermes orchestrator worker?
- Should new roster-only agents auto-create Claude profiles and wrappers, or should Add Swarm remain config-only until first start?
- Should orchestrator loop run in the browser session, server interval, cron job, or persistent Claude orchestrator worker?
- How aggressive should auto-continue be before asking Eric?
- Should reviews be mandatory for code-changing tasks only, or all workflows?

View File

@@ -2,13 +2,13 @@
Date: 2026-04-28
Status: Stage 1 implementation spec
Canonical repo: `/Users/aurora/hermes-workspace`
Canonical repo: `/Users/aurora/claude-workspace`
## Goal
Swarm2 workers need continuity across tasks, compaction, restarts, and multi-session missions.
Today each worker has a Hermes profile, a `state.db`, a `runtime.json`, and a tmux session. That gives process identity, but not a structured memory layer. The memory framework adds deterministic file-backed memory that can later be augmented with Hermes-native semantic memory providers.
Today each worker has a Claude profile, a `state.db`, a `runtime.json`, and a tmux session. That gives process identity, but not a structured memory layer. The memory framework adds deterministic file-backed memory that can later be augmented with Claude-native semantic memory providers.
The first version must be simple, inspectable, and durable:
@@ -24,21 +24,21 @@ These paths are locked. Do not substitute Claude/OpenClaw profile paths for work
| Layer | Canonical path |
| --- | --- |
| Worker profile root | `~/.hermes/profiles/<workerId>/` |
| Worker chat DB | `~/.hermes/profiles/<workerId>/state.db` |
| Worker runtime state | `~/.hermes/profiles/<workerId>/runtime.json` |
| Worker memory root | `~/.hermes/profiles/<workerId>/memory/` |
| Worker curated memory | `~/.hermes/profiles/<workerId>/memory/MEMORY.md` |
| Worker identity file | `~/.hermes/profiles/<workerId>/memory/IDENTITY.md` |
| Worker role/persona file | `~/.hermes/profiles/<workerId>/memory/SOUL.md` |
| Worker mission memory | `~/.hermes/profiles/<workerId>/memory/missions/<missionId>/` |
| Worker mission summary | `~/.hermes/profiles/<workerId>/memory/missions/<missionId>/SUMMARY.md` |
| Worker mission event log | `~/.hermes/profiles/<workerId>/memory/missions/<missionId>/events.jsonl` |
| Worker episodic logs | `~/.hermes/profiles/<workerId>/memory/episodes/YYYY-MM-DD.md` |
| Worker handoffs | `~/.hermes/profiles/<workerId>/memory/handoffs/<missionId>.md` |
| Swarm control-plane runtime | `/Users/aurora/hermes-workspace/.runtime/` |
| Swarm mission ledger | `/Users/aurora/hermes-workspace/.runtime/swarm-missions.json` |
| Swarm roster/source of truth | `/Users/aurora/hermes-workspace/swarm.yaml` |
| Worker profile root | `~/.claude/profiles/<workerId>/` |
| Worker chat DB | `~/.claude/profiles/<workerId>/state.db` |
| Worker runtime state | `~/.claude/profiles/<workerId>/runtime.json` |
| Worker memory root | `~/.claude/profiles/<workerId>/memory/` |
| Worker curated memory | `~/.claude/profiles/<workerId>/memory/MEMORY.md` |
| Worker identity file | `~/.claude/profiles/<workerId>/memory/IDENTITY.md` |
| Worker role/persona file | `~/.claude/profiles/<workerId>/memory/SOUL.md` |
| Worker mission memory | `~/.claude/profiles/<workerId>/memory/missions/<missionId>/` |
| Worker mission summary | `~/.claude/profiles/<workerId>/memory/missions/<missionId>/SUMMARY.md` |
| Worker mission event log | `~/.claude/profiles/<workerId>/memory/missions/<missionId>/events.jsonl` |
| Worker episodic logs | `~/.claude/profiles/<workerId>/memory/episodes/YYYY-MM-DD.md` |
| Worker handoffs | `~/.claude/profiles/<workerId>/memory/handoffs/<missionId>.md` |
| Swarm control-plane runtime | `/Users/aurora/claude-workspace/.runtime/` |
| Swarm mission ledger | `/Users/aurora/claude-workspace/.runtime/swarm-missions.json` |
| Swarm roster/source of truth | `/Users/aurora/claude-workspace/swarm.yaml` |
| Shared swarm handoffs | `/Users/aurora/.openclaw/workspace/memory/handoffs/swarm/` |
| Shared swarm archive/memory | `/Users/aurora/.openclaw/workspace/memory/swarm/` |
| Completed mission archive | `/Users/aurora/.openclaw/workspace/memory/swarm/missions/<missionId>/` |
@@ -54,7 +54,7 @@ Explicitly wrong paths:
### Worker-local memory
Stored under `~/.hermes/profiles/<workerId>/memory/`.
Stored under `~/.claude/profiles/<workerId>/memory/`.
Owned by the worker. Used for:
@@ -66,7 +66,7 @@ Owned by the worker. Used for:
### Swarm control-plane state
Stored under `/Users/aurora/hermes-workspace/.runtime/`.
Stored under `/Users/aurora/claude-workspace/.runtime/`.
Owned by Swarm2 server code. Used for:
@@ -156,7 +156,7 @@ Purpose:
Path:
`~/.hermes/profiles/<workerId>/memory/missions/<missionId>/`
`~/.claude/profiles/<workerId>/memory/missions/<missionId>/`
Files:
@@ -222,7 +222,7 @@ Event types:
Path:
`~/.hermes/profiles/<workerId>/memory/episodes/YYYY-MM-DD.md`
`~/.claude/profiles/<workerId>/memory/episodes/YYYY-MM-DD.md`
Purpose:
@@ -248,7 +248,7 @@ Template:
Worker-local handoff:
`~/.hermes/profiles/<workerId>/memory/handoffs/<missionId>.md`
`~/.claude/profiles/<workerId>/memory/handoffs/<missionId>.md`
Shared latest handoff:
@@ -363,7 +363,7 @@ Stage 1 implementation:
Later:
- use Hermes native memory provider or external provider for semantic recall.
- use Claude native memory provider or external provider for semantic recall.
## Integration with existing skills
@@ -379,12 +379,12 @@ Memory framework adds:
### `swarm-dev-runtime`
Already defines Hermes-native profile/tmux invariants.
Already defines Claude-native profile/tmux invariants.
Memory framework must respect:
- `~/.hermes/profiles/<workerId>` profile root,
- `HERMES_HOME` per worker,
- `~/.claude/profiles/<workerId>` profile root,
- `CLAUDE_HOME` per worker,
- tmux sessions named `swarm-<workerId>`,
- wrappers in `~/.local/bin/swarmN`.
@@ -424,7 +424,7 @@ Future `swarm-memory` skill should instruct workers to load:
Resume prompt should be compact:
```text
Load your worker memory from ~/.hermes/profiles/<workerId>/memory/.
Load your worker memory from ~/.claude/profiles/<workerId>/memory/.
If runtime.json has currentMissionId, read that mission SUMMARY.md and latest handoff.
Continue from the handoff's Next exact action.
```
@@ -463,6 +463,6 @@ Stage 1 hooks:
## Stage 3 deliverables
1. optional Hermes memory provider bridge,
1. optional Claude memory provider bridge,
2. vector/semantic recall,
3. cross-worker memory recommendation in decompose/routing.

View File

@@ -5,7 +5,7 @@ Status: staged implementation
## Problem
Swarm workers are persistent Hermes agents with their own profiles, sessions, tmux panes, runtime state, and memory. That is the right architecture, but long-running workers will eventually degrade when context approaches the model window.
Swarm workers are persistent Claude agents with their own profiles, sessions, tmux panes, runtime state, and memory. That is the right architecture, but long-running workers will eventually degrade when context approaches the model window.
We need an automatic lifecycle system so workers can:
@@ -58,12 +58,12 @@ Optional timestamped archive can exist later, but `latest.md` is the resume sour
## Renewal sequence
1. Detect context pressure from Hermes `state.db` session token counts.
1. Detect context pressure from Claude `state.db` session token counts.
2. Ask worker for handoff via tmux dispatch.
3. Parse handoff checkpoint from chat.
4. Save handoff into durable memory path.
5. Stop worker tmux session.
6. Start clean Hermes session with same profile and cwd.
6. Start clean Claude session with same profile and cwd.
7. Send resume prompt containing handoff summary + active mission assignment.
8. Mark runtime state healthy/executing.
@@ -92,7 +92,7 @@ Swarm2 UI should show:
- Return lifecycle state and recommended action.
- Add request-handoff action that sends a strict handoff prompt to tmux.
- Add renew action, but require `force: true` for now.
- Normalize swarm wrappers to use `/Users/aurora/hermes-workspace` cwd.
- Normalize swarm wrappers to use `/Users/aurora/claude-workspace` cwd.
## Stage 2

View File

@@ -4,7 +4,7 @@ Date: 2026-04-24
## Problem
Hermes Agent sessions fill context extremely fast during normal tool-heavy workflows. In one Workspace session (`cda915a9-fbf6-4bbf-9617-e7e9d26f40be`):
Claude Agent sessions fill context extremely fast during normal tool-heavy workflows. In one Workspace session (`cda915a9-fbf6-4bbf-9617-e7e9d26f40be`):
- 256 messages total
- 7 user messages
@@ -23,14 +23,14 @@ The bloat is not user/assistant conversation. It is mostly persisted execution t
This causes:
1. Context window fills quickly despite normal Hermes behavior.
1. Context window fills quickly despite normal Claude behavior.
2. Auto-compaction warnings appear early/often.
3. Workspace chat UI becomes dominated by tool cards and looks like user/assistant messages disappeared.
4. The model is force-fed large tool outputs every turn even when it only needs pointers/summaries.
## Key Insight
Hermes currently blends two separate concepts:
Claude currently blends two separate concepts:
1. **Conversation/model context** — what the LLM needs to reason.
2. **Execution trace/artifacts** — full tool outputs, logs, file contents, diffs, skill docs.
@@ -109,11 +109,11 @@ So the right panel is the correct UX destination, but needs a durable backing st
## Implementation Options
### Option A — Canonical Hermes Agent artifact store
### Option A — Canonical Claude Agent artifact store
Best long-term because all clients benefit: CLI, gateway, Workspace, future WebUI.
Add a table/store to Hermes Agent:
Add a table/store to Claude Agent:
```sql
tool_artifacts (
@@ -133,7 +133,7 @@ tool_artifacts (
Large content on disk:
```txt
~/.hermes/sessions/artifacts/<session_id>/<artifact_id>.json
~/.claude/sessions/artifacts/<session_id>/<artifact_id>.json
```
API endpoints:
@@ -150,14 +150,14 @@ Faster Workspace-only patch.
Store artifacts under:
```txt
~/hermes-workspace/.runtime/artifacts/<session_id>/<artifact_id>.json
~/claude-workspace/.runtime/artifacts/<session_id>/<artifact_id>.json
```
or a small local DB/JSON index.
Workspace `/api/history` or `/api/send-stream` externalizes oversized tool results before rendering/sending to React.
Good MVP, but not enough for canonical model-context bloat unless Hermes Agent context builder also stops reinjecting the full tool payload.
Good MVP, but not enough for canonical model-context bloat unless Claude Agent context builder also stops reinjecting the full tool payload.
## Policy Proposal
@@ -224,7 +224,7 @@ Added in the continuation pass:
- `src/routes/api/history.ts`
- externalizes oversized tool/toolResult messages during history normalization
- applies the same externalization path to local portable-session fallback messages
- `src/server/hermes-api.ts`
- `src/server/claude-api.ts`
- normalizes backend `role: "tool"` to frontend `role: "toolResult"`
- hoists `toolCallId` / `toolName` so result maps and tool cards can find outputs
- `src/components/inspector/inspector-panel.tsx`
@@ -243,13 +243,13 @@ pnpm build
All passed. Build only emitted existing chunk-size / sourcemap warnings.
## Current Diagnosis: Workspace vs Hermes Agent
## Current Diagnosis: Workspace vs Claude Agent
The trailing “Tool work completed” card is a Workspace guardrail, not the desired steady state.
Likely responsibility split:
1. **Hermes Agent / session persistence is the root source** when stored history ends with tool messages and no final assistant text. A healthy agent transcript should end each tool-using turn with one user-visible assistant outcome: final answer, error, interrupted/cancelled, no-op, or compact tool-work summary.
1. **Claude Agent / session persistence is the root source** when stored history ends with tool messages and no final assistant text. A healthy agent transcript should end each tool-using turn with one user-visible assistant outcome: final answer, error, interrupted/cancelled, no-op, or compact tool-work summary.
2. **Workspace had UI/rendering bugs** that made the source problem worse:
- backend `role: "tool"` was not normalized to frontend `toolResult`
- `toolCallId` / `toolName` were not hoisted consistently
@@ -257,15 +257,15 @@ Likely responsibility split:
- giant tool outputs were rendered inline instead of artifact-backed
3. **Our current setup/session amplifies it** because this conversation is extremely tool-heavy and near context max; dev/tool actions after a human-readable assistant response can leave persisted tool rows at the tail.
Conclusion: Workspace should defensively collapse/tool-summary this state, but the canonical fix belongs in Hermes Agents stream/session writer: never persist a completed chat turn that ends only with tool output.
Conclusion: Workspace should defensively collapse/tool-summary this state, but the canonical fix belongs in Claude Agents stream/session writer: never persist a completed chat turn that ends only with tool output.
## Hermes Agent Fix Implemented
## Claude Agent Fix Implemented
Patched `/Users/aurora/hermes-agent`:
Patched `/Users/aurora/claude-agent`:
- `run_agent.py`
- `_persist_session()` now calls `_ensure_terminal_assistant_message()` before writing JSON/SQLite.
- If an exit path leaves the transcript ending with `role: "tool"`, Hermes appends one compact synthetic assistant message instead of persisting a raw tool tail.
- If an exit path leaves the transcript ending with `role: "tool"`, Claude appends one compact synthetic assistant message instead of persisting a raw tool tail.
- `_handle_max_iterations()` now appends fallback summary/failure messages to history in all fallback branches, not just the happy summary path.
- `tests/run_agent/test_860_dedup.py`
- Added regression: repeated `_persist_session()` calls on a tool-tail transcript append exactly one terminal assistant message and do not duplicate rows.
@@ -283,7 +283,7 @@ Result: all targeted tests pass.
## Recommended Next Steps
1. Keep Workspace fallback, but treat it as edge-state handling.
2. Continue canonical artifact work in Hermes Agent: externalize oversized tool outputs before they enter persisted/model context, not only before Workspace renders history.
2. Continue canonical artifact work in Claude Agent: externalize oversized tool outputs before they enter persisted/model context, not only before Workspace renders history.
3. Continue Workspace polish:
- explicit “Open artifact” action on compact tool-result cards
- deep-link Inspector Artifacts tab

View File

@@ -6,19 +6,19 @@ Common setup issues and how to fix them.
## 1. Gateway starts but API server never binds (port 8642 not listening)
**Symptom:** `hermes gateway run` appears to start, but `curl http://127.0.0.1:8642/health` fails. `ss -tlnp | grep 8642` shows nothing.
**Symptom:** `claude gateway run` appears to start, but `curl http://127.0.0.1:8642/health` fails. `ss -tlnp | grep 8642` shows nothing.
**Cause:** `API_SERVER_ENABLED` is not set — or is set with the wrong env var name.
**Fix:**
```bash
# Find your hermes env file
hermes config env-path
# Usually: ~/.hermes/.env
# Find your claude env file
claude config env-path
# Usually: ~/.claude/.env
# Check for the key
grep -i API_SERVER ~/.hermes/.env
grep -i API_SERVER ~/.claude/.env
```
The env var must be **exactly** `API_SERVER_ENABLED=true` — with underscores. Common mistakes:
@@ -29,7 +29,7 @@ The env var must be **exactly** `API_SERVER_ENABLED=true` — with underscores.
| `APISERVERHOST=0.0.0.0` | `API_SERVER_HOST=127.0.0.1` |
| `ApiServerEnabled=true` | `API_SERVER_ENABLED=true` |
After fixing, restart the gateway: `hermes gateway run --replace`
After fixing, restart the gateway: `claude gateway run --replace`
**Also:** setting `API_SERVER_HOST=0.0.0.0` without `API_SERVER_KEY` causes a silent refusal. Use `127.0.0.1` for local access, or set a key for network access.
@@ -43,10 +43,10 @@ After fixing, restart the gateway: `hermes gateway run --replace`
**Checklist (in order):**
1. Is the gateway running? `pgrep -af "hermes.*gateway"`
1. Is the gateway running? `pgrep -af "claude.*gateway"`
2. Is port 8642 bound? `curl -sf http://127.0.0.1:8642/health`
3. Is Workspace `.env` correct? `grep HERMES_API_URL ~/hermes-workspace/.env`
- Should be: `HERMES_API_URL=http://127.0.0.1:8642`
3. Is Workspace `.env` correct? `grep CLAUDE_API_URL ~/claude-workspace/.env`
- Should be: `CLAUDE_API_URL=http://127.0.0.1:8642`
4. Restart Workspace: `pnpm dev`
If the gateway is running and healthy but Workspace still disconnects, check for port conflicts (another process on 8642) or firewall rules.
@@ -68,7 +68,7 @@ ss -tlnp | grep 8642 # Linux
kill <PID>
# Restart
hermes gateway run --replace
claude gateway run --replace
```
---
@@ -83,11 +83,11 @@ hermes gateway run --replace
```bash
# Terminal 1 — start gateway first, wait for it
hermes gateway run
claude gateway run
# Wait until you see "Uvicorn running on http://127.0.0.1:8642"
# Terminal 2 — then start workspace
cd ~/hermes-workspace && pnpm dev
cd ~/claude-workspace && pnpm dev
```
---
@@ -113,7 +113,7 @@ This means the Vite SSR server tried `GET /api/gateway-status` which internally
**Most likely:** the gateway API server isn't running. See issue #1 above.
**Less likely:** `.env` has the wrong `HERMES_API_URL` (e.g. wrong port, `https` instead of `http`, `localhost` instead of `127.0.0.1` on WSL).
**Less likely:** `.env` has the wrong `CLAUDE_API_URL` (e.g. wrong port, `https` instead of `http`, `localhost` instead of `127.0.0.1` on WSL).
---
@@ -122,13 +122,13 @@ This means the Vite SSR server tried `GET /api/gateway-status` which internally
If nothing above helps, run this and share the output:
```bash
echo "=== hermes version ===" && hermes --version 2>&1
echo "=== hermes env path ===" && hermes config env-path 2>&1
echo "=== hermes env (redacted) ===" && grep -E "^(API_SERVER|HERMES_)" "$(hermes config env-path 2>/dev/null || echo ~/.hermes/.env)" 2>&1
echo "=== gateway process ===" && pgrep -af "hermes.*gateway" 2>&1 || echo "not running"
echo "=== claude version ===" && claude --version 2>&1
echo "=== claude env path ===" && claude config env-path 2>&1
echo "=== claude env (redacted) ===" && grep -E "^(API_SERVER|CLAUDE_)" "$(claude config env-path 2>/dev/null || echo ~/.claude/.env)" 2>&1
echo "=== gateway process ===" && pgrep -af "claude.*gateway" 2>&1 || echo "not running"
echo "=== port 8642 ===" && (ss -tlnp 2>/dev/null || lsof -iTCP:8642 -sTCP:LISTEN 2>/dev/null) | grep 8642 || echo "not bound"
echo "=== health check ===" && curl -sf http://127.0.0.1:8642/health 2>&1 || echo "not reachable"
echo "=== workspace .env ===" && grep HERMES ~/hermes-workspace/.env 2>&1 || echo "no .env"
echo "=== workspace .env ===" && grep CLAUDE ~/claude-workspace/.env 2>&1 || echo "no .env"
echo "=== OS ===" && uname -a
echo "=== Node ===" && node --version
echo "=== Python ===" && python3 --version 2>&1

View File

@@ -2,23 +2,23 @@
# Project Workspace — one-liner installer
#
# Usage:
# curl -fsSL https://raw.githubusercontent.com/outsourc-e/hermes-workspace/main/install.sh | bash
# curl -fsSL https://raw.githubusercontent.com/outsourc-e/claude-workspace/main/install.sh | bash
#
# What it does:
# 1. Verifies Node 22+, git, pnpm
# 2. Installs hermes-agent via Nous's official upstream installer
# 3. Clones hermes-workspace
# 4. Sets up .env, enables the Hermes HTTP API, installs deps,
# 2. Installs claude-agent via Nous's official upstream installer
# 3. Clones claude-workspace
# 4. Sets up .env, enables the Claude HTTP API, installs deps,
# and links bundled skills
#
# Re-runnable. Will skip anything already installed.
set -euo pipefail
REPO_URL="${REPO_URL:-https://github.com/outsourc-e/hermes-workspace.git}"
INSTALL_DIR="${INSTALL_DIR:-$HOME/hermes-workspace}"
REPO_URL="${REPO_URL:-https://github.com/outsourc-e/claude-workspace.git}"
INSTALL_DIR="${INSTALL_DIR:-$HOME/claude-workspace}"
GATEWAY_PORT="${GATEWAY_PORT:-8642}"
NOUS_INSTALLER_URL="${NOUS_INSTALLER_URL:-https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh}"
NOUS_INSTALLER_URL="${NOUS_INSTALLER_URL:-https://raw.githubusercontent.com/NousResearch/claude-agent/main/scripts/install.sh}"
# ─── helpers ──────────────────────────────────────────────────────────────
@@ -35,7 +35,7 @@ banner() {
╭────────────────────────────────────────────╮
│ PROJECT WORKSPACE — zero-fork installer │
│ outsourc-e/hermes-workspace │
│ outsourc-e/claude-workspace │
╰────────────────────────────────────────────╯
EOF
@@ -108,18 +108,18 @@ if ! command -v pnpm &>/dev/null; then
fi
green " pnpm $(pnpm --version)"
# ─── install hermes-agent (delegate to Nous upstream installer) ──────────
# hermes-agent is NOT on PyPI. It installs from source via Nous's own
# ─── install claude-agent (delegate to Nous upstream installer) ──────────
# claude-agent is NOT on PyPI. It installs from source via Nous's own
# script, which handles PEP 668, uv, Python toolchain, Termux, etc. We
# only need to ensure `hermes` ends up on PATH before continuing.
# only need to ensure `claude` ends up on PATH before continuing.
cyan "→ Installing hermes-agent (via Nous upstream installer)…"
# Pick up hermes if it was installed in a prior run but not on PATH yet
ensure_path "$HOME/.hermes/bin"
cyan "→ Installing claude-agent (via Nous upstream installer)…"
# Pick up claude if it was installed in a prior run but not on PATH yet
ensure_path "$HOME/.claude/bin"
ensure_path "$HOME/.local/bin"
if command -v hermes &>/dev/null; then
green " hermes-agent already installed ✓ ($(command -v hermes))"
if command -v claude &>/dev/null; then
green " claude-agent already installed ✓ ($(command -v claude))"
else
yellow " Delegating to: $NOUS_INSTALLER_URL"
if ! curl -fsSL "$NOUS_INSTALLER_URL" | bash; then
@@ -128,21 +128,21 @@ else
red " curl -fsSL $NOUS_INSTALLER_URL | bash"
exit 1
fi
# Nous typically installs `hermes` to ~/.hermes/bin or ~/.local/bin
ensure_path "$HOME/.hermes/bin"
# Nous typically installs `claude` to ~/.claude/bin or ~/.local/bin
ensure_path "$HOME/.claude/bin"
ensure_path "$HOME/.local/bin"
if ! command -v hermes &>/dev/null; then
red " hermes-agent installed, but 'hermes' is not on PATH in this shell."
if ! command -v claude &>/dev/null; then
red " claude-agent installed, but 'claude' is not on PATH in this shell."
yellow " Open a new shell (or: source ~/.bashrc / ~/.zshrc) and re-run:"
yellow " curl -fsSL https://hermes-workspace.com/install.sh | bash"
yellow " curl -fsSL https://claude-workspace.com/install.sh | bash"
exit 1
fi
green " hermes-agent installed ✓ ($(command -v hermes))"
green " claude-agent installed ✓ ($(command -v claude))"
fi
# ─── clone workspace ──────────────────────────────────────────────────────
cyan "→ Cloning hermes-workspace…"
cyan "→ Cloning claude-workspace…"
if [[ -d "$INSTALL_DIR/.git" ]]; then
yellow " $INSTALL_DIR exists; pulling latest"
git -C "$INSTALL_DIR" pull --ff-only
@@ -162,32 +162,32 @@ cyan "→ Configuring .env…"
if [[ ! -f .env ]]; then
cp .env.example .env
fi
ensure_env_key "$INSTALL_DIR/.env" "HERMES_API_URL" "http://127.0.0.1:${GATEWAY_PORT}"
ensure_env_key "$INSTALL_DIR/.env" "CLAUDE_API_URL" "http://127.0.0.1:${GATEWAY_PORT}"
green " .env ready ✓"
cyan "→ Enabling Hermes HTTP API…"
HERMES_ENV_PATH="$(hermes config env-path 2>/dev/null || true)"
if [[ -z "$HERMES_ENV_PATH" ]]; then
HERMES_ENV_PATH="$HOME/.hermes/.env"
cyan "→ Enabling Claude HTTP API…"
CLAUDE_ENV_PATH="$(claude config env-path 2>/dev/null || true)"
if [[ -z "$CLAUDE_ENV_PATH" ]]; then
CLAUDE_ENV_PATH="$HOME/.claude/.env"
fi
ensure_env_key "$HERMES_ENV_PATH" "API_SERVER_ENABLED" "true"
green " Hermes env updated: $HERMES_ENV_PATH"
ensure_env_key "$CLAUDE_ENV_PATH" "API_SERVER_ENABLED" "true"
green " Claude env updated: $CLAUDE_ENV_PATH"
# Guard against a common foot-gun: users editing ~/.hermes/.env by hand and
# Guard against a common foot-gun: users editing ~/.claude/.env by hand and
# writing env var names without underscores (APISERVERENABLED vs
# API_SERVER_ENABLED). The gateway reads exact names — typos are silently
# ignored, which produces a "gateway starts but API server never binds"
# failure that's hard to diagnose from the UI.
if [[ -f "$HERMES_ENV_PATH" ]]; then
SUSPICIOUS=$(grep -E "^(API[A-Z]+|HERMES[A-Z]+)=" "$HERMES_ENV_PATH" 2>/dev/null \
| grep -vE "^(API_|HERMES_)" || true)
if [[ -f "$CLAUDE_ENV_PATH" ]]; then
SUSPICIOUS=$(grep -E "^(API[A-Z]+|CLAUDE[A-Z]+)=" "$CLAUDE_ENV_PATH" 2>/dev/null \
| grep -vE "^(API_|CLAUDE_)" || true)
if [[ -n "$SUSPICIOUS" ]]; then
yellow ""
yellow "⚠ Found env var names missing underscores in $HERMES_ENV_PATH:"
yellow "⚠ Found env var names missing underscores in $CLAUDE_ENV_PATH:"
echo "$SUSPICIOUS" | sed 's/^/ /'
yellow " The gateway reads names with underscores (API_SERVER_ENABLED,"
yellow " not APISERVERENABLED). These lines will be silently ignored."
yellow " Fix them and run: hermes gateway run --replace"
yellow " Fix them and run: claude gateway run --replace"
yellow ""
fi
fi
@@ -196,15 +196,15 @@ cyan "→ Installing npm deps (pnpm install)…"
pnpm install --silent
green " deps installed ✓"
# ─── seed Hermes skills (Conductor needs workspace-dispatch) ─────────────
# ─── seed Claude skills (Conductor needs workspace-dispatch) ─────────────
cyan "→ Linking bundled skills into ~/.hermes/skills…"
HERMES_SKILLS_DIR="$HOME/.hermes/skills"
mkdir -p "$HERMES_SKILLS_DIR"
cyan "→ Linking bundled skills into ~/.claude/skills…"
CLAUDE_SKILLS_DIR="$HOME/.claude/skills"
mkdir -p "$CLAUDE_SKILLS_DIR"
if [[ -d "$INSTALL_DIR/skills" ]]; then
for skill_path in "$INSTALL_DIR/skills"/*/; do
skill_name=$(basename "$skill_path")
target="$HERMES_SKILLS_DIR/$skill_name"
target="$CLAUDE_SKILLS_DIR/$skill_name"
if [[ -e "$target" || -L "$target" ]]; then
continue
fi
@@ -223,9 +223,9 @@ cat <<EOF
Next steps (two terminals):
1) Start the Hermes gateway:
hermes gateway run
(first run may prompt for hermes setup)
1) Start the Claude gateway:
claude gateway run
(first run may prompt for claude setup)
2) Start the workspace UI:
cd $INSTALL_DIR && pnpm dev

View File

@@ -1,7 +1,7 @@
{
"name": "hermes-workspace",
"name": "claude-workspace",
"version": "2.0.0",
"description": "Desktop workspace for Hermes Agent — chat, orchestration, and multi-agent coding pipelines",
"description": "Desktop workspace for Claude Agent — chat, orchestration, and multi-agent coding pipelines",
"author": "Eric (https://github.com/outsourc-e)",
"license": "MIT",
"private": true,
@@ -9,7 +9,7 @@
"scripts": {
"dev": "NODE_OPTIONS=\"--max-old-space-size=2048\" vite dev",
"build": "vite build",
"start:all": "concurrently \"hermes gateway run\" \"pnpm dev\"",
"start:all": "concurrently \"claude gateway run\" \"pnpm dev\"",
"start": "NODE_OPTIONS=\"--max-old-space-size=2048\" node .output/server/index.mjs",
"start:dev": "NODE_OPTIONS=\"--max-old-space-size=2048\" vite dev",
"preview": "vite preview",

View File

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 197 KiB

After

Width:  |  Height:  |  Size: 197 KiB

View File

@@ -1,12 +1,12 @@
#!/bin/bash
# Simple Hermes chat loop — no TUI, works perfectly in embedded terminals
HERMES="${HERMES_BIN:-hermes}"
# Simple Claude chat loop — no TUI, works perfectly in embedded terminals
CLAUDE="${CLAUDE_BIN:-claude}"
# Use ANTHROPIC_API_KEY from environment — set in your .env or shell profile
if [ -z "$ANTHROPIC_API_KEY" ]; then
echo "Warning: ANTHROPIC_API_KEY not set. Export it or add to ~/.hermes/.env"
echo "Warning: ANTHROPIC_API_KEY not set. Export it or add to ~/.claude/.env"
fi
echo "Hermes Agent (simple mode) — type your message, press Enter"
echo "Claude Agent (simple mode) — type your message, press Enter"
echo "Type 'exit' to quit"
echo "---"
@@ -15,6 +15,6 @@ while true; do
read -r cmd
[ "$cmd" = "exit" ] && break
[ -z "$cmd" ] && continue
$HERMES chat -q "$cmd" 2>/dev/null
$CLAUDE chat -q "$cmd" 2>/dev/null
echo ""
done

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 129 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@@ -1,7 +1,7 @@
{
"name": "Hermes Workspace",
"short_name": "Hermes",
"description": "Native web control surface for Hermes Agent",
"name": "Claude Workspace",
"short_name": "Claude",
"description": "Native web control surface for Claude Agent",
"start_url": "/",
"display": "standalone",
"background_color": "#0A0E1A",
@@ -9,13 +9,13 @@
"categories": ["productivity", "utilities"],
"icons": [
{
"src": "/hermes-icon-192.png",
"src": "/claude-icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/hermes-icon-512.png",
"src": "/claude-icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"

View File

@@ -1,4 +1,4 @@
// Hermes Workspace Service Worker — DISABLED
// Claude Workspace Service Worker — DISABLED
// Unregisters itself and clears all caches to prevent stale asset issues
// after Docker image updates or reverse proxy deployments.

View File

@@ -34,7 +34,7 @@ async function generateIcon(size) {
const screenshot = await page.screenshot({ type: 'png' })
await browser.close()
const outputPath = join(outputDir, `hermes-icon-${size}.png`)
const outputPath = join(outputDir, `claude-icon-${size}.png`)
writeFileSync(outputPath, screenshot)
console.log(`✓ Generated ${size}x${size} icon`)
}

View File

@@ -6,7 +6,7 @@ import path from 'node:path'
const baseUrl = process.argv[2] || 'https://127.0.0.1:4445/chat/new'
const errorLogPath =
process.argv[3] || path.join(os.homedir(), '.pm2', 'logs', 'hermes-workspace-error.log')
process.argv[3] || path.join(os.homedir(), '.pm2', 'logs', 'claude-workspace-error.log')
const suspiciousPatterns = [
'ERR_MODULE_NOT_FOUND',
'Cannot find module',
@@ -36,7 +36,7 @@ function fetchText(url) {
}
const html = await fetchText(baseUrl)
if (!html.includes('Hermes Workspace')) {
if (!html.includes('Claude Workspace')) {
throw new Error(`Managed companion did not render the expected shell at ${baseUrl}`)
}

View File

@@ -7,7 +7,7 @@ const NOW = Date.now()
const LANES = {
swarm1: ['PR / Issues', 'Triage open PRs and surface review-ready items for the orchestrator', 'Standing by on PR/issues lane; tmux session live and wrapper wired.', '/Users/aurora/claude-workspace'],
swarm6: ['Reviewer', 'Review pending diffs and gate merges with checklist + tests', 'Reviewer lane initialized; awaiting first dispatch.', '/Users/aurora/claude-workspace'],
swarm7: ['Docs', 'Maintain handoffs, README updates, and skill documentation', 'Docs lane initialized; runtime contract adopted.', '/Users/aurora/hermes-workspace'],
swarm7: ['Docs', 'Maintain handoffs, README updates, and skill documentation', 'Docs lane initialized; runtime contract adopted.', '/Users/aurora/claude-workspace'],
swarm8: ['Ops', 'Track infra, gateways, schedulers, and operational health', 'Ops lane initialized; ready to monitor swarm health.', '/Users/aurora/.ocplatform/workspace'],
swarm9: ['Hackathon', 'Prototype experimental flows and one-off agent missions', 'Hackathon lane initialized; sandbox ready.', '/Users/aurora/claude-workspace'],
swarm10: ['Builder', 'Implement feature work assigned by the orchestrator', 'Builder lane initialized; ready for next ticket.', '/Users/aurora/claude-workspace'],

View File

@@ -1,10 +1,10 @@
#!/usr/bin/env python3
"""Thin JSON wrapper around hermes skills search for the workspace API."""
"""Thin JSON wrapper around claude skills search for the workspace API."""
import json
import sys
import os
sys.path.insert(0, os.path.expanduser("~/hermes-agent"))
sys.path.insert(0, os.path.expanduser("~/claude-agent"))
from tools.skills_hub import GitHubAuth, create_source_router, unified_search
@@ -33,7 +33,7 @@ def main():
"tags": getattr(r, "tags", []),
"source": getattr(r, "source_label", ""),
"trust": getattr(r, "trust_level", "community"),
"installCommand": f"hermes skills install {getattr(r, 'identifier', r.name)}",
"installCommand": f"claude skills install {getattr(r, 'identifier', r.name)}",
"installed": False,
})

View File

@@ -6,9 +6,9 @@ cd "$ROOT"
PORT="${PORT:-3002}"
RUNTIME_DIR="$ROOT/.runtime"
PID_FILE="$RUNTIME_DIR/hermes-workspace.pid"
LOG_FILE="$RUNTIME_DIR/hermes-workspace.log"
BUILD_LOG_FILE="$RUNTIME_DIR/hermes-workspace.build.log"
PID_FILE="$RUNTIME_DIR/claude-workspace.pid"
LOG_FILE="$RUNTIME_DIR/claude-workspace.log"
BUILD_LOG_FILE="$RUNTIME_DIR/claude-workspace.build.log"
mkdir -p "$RUNTIME_DIR"
stop_pid() {
@@ -37,11 +37,11 @@ for pid in $(lsof -tiTCP:"$PORT" -sTCP:LISTEN 2>/dev/null || true); do
stop_pid "$pid"
done
echo "[stable] building Hermes Workspace..."
echo "[stable] building Claude Workspace..."
rm -rf dist
pnpm build >"$BUILD_LOG_FILE" 2>&1
echo "[stable] starting Hermes Workspace on port $PORT..."
echo "[stable] starting Claude Workspace on port $PORT..."
nohup env PORT="$PORT" NODE_OPTIONS="--max-old-space-size=2048" node server-entry.js >>"$LOG_FILE" 2>&1 &
new_pid=$!
echo "$new_pid" >"$PID_FILE"

View File

@@ -5,7 +5,7 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
PORT="${PORT:-3002}"
PID_FILE="$ROOT/.runtime/hermes-workspace.pid"
PID_FILE="$ROOT/.runtime/claude-workspace.pid"
stop_pid() {
local pid="$1"
@@ -33,4 +33,4 @@ for pid in $(lsof -tiTCP:"$PORT" -sTCP:LISTEN 2>/dev/null || true); do
stop_pid "$pid"
done
echo "[stable] stopped Hermes Workspace on port $PORT"
echo "[stable] stopped Claude Workspace on port $PORT"

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
CANONICAL_REPO="/Users/aurora/hermes-workspace"
CANONICAL_REPO="/Users/aurora/claude-workspace"
FORBIDDEN_REPO="/Users/aurora/claude-workspace"
CURRENT_DIR="$(pwd -P)"
@@ -26,12 +26,12 @@ fi
REPO_NAME="$(python3 - <<'PY'
import json
from pathlib import Path
pkg = json.loads(Path('/Users/aurora/hermes-workspace/package.json').read_text())
pkg = json.loads(Path('/Users/aurora/claude-workspace/package.json').read_text())
print(pkg.get('name', ''))
PY
)"
if [[ "$REPO_NAME" != "hermes-workspace" ]]; then
if [[ "$REPO_NAME" != "claude-workspace" ]]; then
echo "ERROR: unexpected package name: $REPO_NAME"
exit 1
fi

View File

@@ -10,7 +10,7 @@ const CLIENT_DIR = join(__dirname, 'dist', 'client')
const port = parseInt(process.env.PORT || '3000', 10)
// Default HOST to localhost-only. Operators who want the workspace reachable
// on a LAN / Tailscale / public surface must opt in explicitly with
// HOST=0.0.0.0 *and* set HERMES_PASSWORD (enforced below). See #122.
// HOST=0.0.0.0 *and* set CLAUDE_PASSWORD (enforced below). See #122.
const host = process.env.HOST || '127.0.0.1'
function isNonLoopbackHost(h) {
@@ -23,26 +23,26 @@ function isNonLoopbackHost(h) {
}
if (isNonLoopbackHost(host)) {
const password = (process.env.HERMES_PASSWORD || '').trim()
const password = (process.env.CLAUDE_PASSWORD || '').trim()
if (!password) {
console.error(
'\n[workspace] refusing to start.\n' +
` HOST is set to "${host}" (non-loopback), but HERMES_PASSWORD is unset.\n` +
` HOST is set to "${host}" (non-loopback), but CLAUDE_PASSWORD is unset.\n` +
' This would expose a high-privilege control plane (terminals, files, agents)\n' +
' to anyone who can reach the port. Either:\n' +
' • set HOST=127.0.0.1 for local-only access, or\n' +
' • set HERMES_PASSWORD=<strong-secret> to enable workspace auth, or\n' +
' • set HERMES_ALLOW_INSECURE_REMOTE=1 to bypass this check (not recommended).\n' +
' • set CLAUDE_PASSWORD=<strong-secret> to enable workspace auth, or\n' +
' • set CLAUDE_ALLOW_INSECURE_REMOTE=1 to bypass this check (not recommended).\n' +
' See #122 for context.\n',
)
const allowInsecure = (process.env.HERMES_ALLOW_INSECURE_REMOTE || '')
const allowInsecure = (process.env.CLAUDE_ALLOW_INSECURE_REMOTE || '')
.trim()
.toLowerCase()
if (allowInsecure !== '1' && allowInsecure !== 'true' && allowInsecure !== 'yes') {
process.exit(1)
}
console.warn(
'[workspace] HERMES_ALLOW_INSECURE_REMOTE is set — starting anyway.',
'[workspace] CLAUDE_ALLOW_INSECURE_REMOTE is set — starting anyway.',
)
}
}
@@ -179,5 +179,5 @@ const httpServer = createServer(async (req, res) => {
})
httpServer.listen(port, host, () => {
console.log(`Hermes Workspace running at http://${host}:${port}`)
console.log(`Claude Workspace running at http://${host}:${port}`)
})

View File

@@ -12,8 +12,8 @@ import { cn } from '@/lib/utils'
export type AgentAvatarPreference = 'lobster' | 'logo'
export type AgentAvatarSize = 'sm' | 'md' | 'lg'
export const AGENT_AVATAR_STORAGE_KEY = 'hermes-loader-preference'
const AGENT_AVATAR_EVENT = 'hermes-loader-preference-change'
export const AGENT_AVATAR_STORAGE_KEY = 'claude-loader-preference'
const AGENT_AVATAR_EVENT = 'claude-loader-preference-change'
type AgentAvatarProps = {
size?: AgentAvatarSize
@@ -142,8 +142,8 @@ function AgentAvatar({
</span>
) : (
<img
src="/hermes-avatar.webp"
alt="Hermes"
src="/claude-avatar.webp"
alt="Claude"
className={cn(
getLogoSizeClassName(size),
iconClassName,

View File

@@ -67,7 +67,7 @@ function toChatMessages(messages: Array<ChatMessage>): Array<AgentChatMessage> {
}
function buildDemoReply(agentName: string, text: string): string {
return `${agentName} (demo): Received "${text}". Hermes is unavailable, so this is a simulated response.`
return `${agentName} (demo): Received "${text}". Claude is unavailable, so this is a simulated response.`
}
export function AgentChatModal({
@@ -143,7 +143,7 @@ export function AgentChatModal({
{
id: `demo-intro-${sessionKey}`,
role: 'agent',
text: 'Hermes is unavailable. Running in demo mode with simulated responses.',
text: 'Claude is unavailable. Running in demo mode with simulated responses.',
timestamp: Date.now(),
},
])

View File

@@ -61,7 +61,7 @@ export function LoginScreen() {
<circle cx="50" cy="50" r="15" fill="currentColor" />
</svg>
<h1 className="text-2xl font-bold tracking-tight text-primary-900">
Hermes Workspace
Claude Workspace
</h1>
</div>
</div>
@@ -108,12 +108,12 @@ export function LoginScreen() {
<p className="mt-6 text-center text-xs text-primary-500">
Powered by{' '}
<a
href="https://github.com/NousResearch/hermes-agent"
href="https://github.com/NousResearch/claude-agent"
target="_blank"
rel="noopener noreferrer"
className="text-accent-500 hover:text-accent-600 transition-colors"
>
Hermes
Claude
</a>
</p>
</div>

View File

@@ -7,13 +7,13 @@ type AvatarProps = {
}
/**
* Assistant avatar — Hermes Agent caduceus on Nous blue.
* Assistant avatar — Claude Agent caduceus on Nous blue.
*/
function AssistantAvatarComponent({ size = 28, className }: AvatarProps) {
return (
<img
src="/hermes-avatar.webp"
alt="Hermes"
src="/claude-avatar.webp"
alt="Claude"
className={cn('shrink-0', className)}
style={{
width: size,

View File

@@ -16,7 +16,7 @@ export function BackendUnavailableState({ feature, description }: Props) {
<div className="mt-4 space-y-2">
<h2 className="text-lg font-semibold text-primary-900">{feature}</h2>
<p className="text-sm leading-6 text-primary-600">
Not available on this backend. Connect to a Hermes gateway to unlock{' '}
Not available on this backend. Connect to a Claude gateway to unlock{' '}
{feature}.
</p>
{description ? (

View File

@@ -1,15 +1,15 @@
import { useEffect, useState } from 'react'
import { fetchHermesAuthStatus } from '@/lib/hermes-auth'
import { fetchClaudeAuthStatus } from '@/lib/claude-auth'
const POLL_INTERVAL = 30_000
type HermesHealthBannerProps = {
type ClaudeHealthBannerProps = {
enabled?: boolean
}
export function HermesHealthBanner({
export function ClaudeHealthBanner({
enabled = false,
}: HermesHealthBannerProps) {
}: ClaudeHealthBannerProps) {
const [status, setStatus] = useState<'ok' | 'error' | 'checking'>('checking')
const [lastError, setLastError] = useState<string | null>(null)
@@ -24,7 +24,7 @@ export function HermesHealthBanner({
async function check() {
try {
await fetchHermesAuthStatus()
await fetchClaudeAuthStatus()
if (!cancelled) {
setStatus('ok')
setLastError(null)
@@ -56,12 +56,12 @@ export function HermesHealthBanner({
}}
>
<span className="inline-block h-2 w-2 rounded-full bg-white/60 animate-pulse" />
<span>Hermes Agent unreachable{lastError ? `${lastError}` : ''}</span>
<span>Claude Agent unreachable{lastError ? `${lastError}` : ''}</span>
<button
type="button"
onClick={() => {
setStatus('checking')
fetchHermesAuthStatus()
fetchClaudeAuthStatus()
.then(() => {
setStatus('ok')
setLastError(null)

View File

@@ -3,15 +3,15 @@ import { useEffect, useRef, useState } from 'react'
const POLL_INTERVAL_MS = 10_000
const FLASH_DURATION_MS = 1_800
type HermesReconnectBannerProps = {
type ClaudeReconnectBannerProps = {
enabled?: boolean
}
type BannerState = 'hidden' | 'disconnected' | 'connected'
async function probeHermesHealth(): Promise<boolean> {
async function probeClaudeHealth(): Promise<boolean> {
// Use the portable-aware connection status endpoint first,
// which works with both full Hermes and OpenAI-compatible backends.
// which works with both full Claude and OpenAI-compatible backends.
try {
const response = await fetch('/api/connection-status', {
cache: 'no-store',
@@ -22,7 +22,7 @@ async function probeHermesHealth(): Promise<boolean> {
}
// Fallback to direct health proxy
try {
const response = await fetch('/api/hermes-proxy/health', {
const response = await fetch('/api/claude-proxy/health', {
cache: 'no-store',
})
return response.ok
@@ -31,9 +31,9 @@ async function probeHermesHealth(): Promise<boolean> {
}
}
export function HermesReconnectBanner({
export function ClaudeReconnectBanner({
enabled = true,
}: HermesReconnectBannerProps) {
}: ClaudeReconnectBannerProps) {
const [bannerState, setBannerState] = useState<BannerState>('hidden')
const [isChecking, setIsChecking] = useState(false)
const [isStarting, setIsStarting] = useState(false)
@@ -47,7 +47,7 @@ export function HermesReconnectBanner({
const wasDisconnectedRef = useRef(false)
const flashTimerRef = useRef<number | null>(null)
// Silent auto-restart: if the gateway disappears mid-session, fire
// /api/start-hermes once. After that, fall back to the manual "Start Agent"
// /api/start-claude once. After that, fall back to the manual "Start Agent"
// button so we don't loop forever on a busted environment.
const autoRestartTriedAtRef = useRef<number>(0)
// Cool-down so a permanently-dead gateway doesn't get poked every probe.
@@ -88,7 +88,7 @@ export function HermesReconnectBanner({
setIsChecking(true)
}
const pendingProbe = probeHermesHealth()
const pendingProbe = probeClaudeHealth()
.then((connected) => {
if (cancelled || !mountedRef.current) return connected
@@ -117,7 +117,7 @@ export function HermesReconnectBanner({
Date.now() - autoRestartTriedAtRef.current
if (sinceLastTry > AUTO_RESTART_COOLDOWN_MS) {
autoRestartTriedAtRef.current = Date.now()
void fetch('/api/start-hermes', {
void fetch('/api/start-claude', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
})
@@ -132,7 +132,7 @@ export function HermesReconnectBanner({
if (res.ok && data.ok) {
setMessage(
data.message ||
'Auto-restarting Hermes gateway…',
'Auto-restarting Claude gateway…',
)
// Probe again shortly so the banner clears as soon as
// the gateway answers /health.
@@ -210,17 +210,17 @@ export function HermesReconnectBanner({
}
if (!response.ok || !payload.ok) {
throw new Error(payload.error || 'Failed to start Hermes agent')
throw new Error(payload.error || 'Failed to start Claude agent')
}
setMessage(
payload.message === 'already running'
? 'Hermes agent is already running'
: 'Starting Hermes agent…',
? 'Claude agent is already running'
: 'Starting Claude agent…',
)
} catch (error) {
setMessage(
error instanceof Error ? error.message : 'Failed to start Hermes agent',
error instanceof Error ? error.message : 'Failed to start Claude agent',
)
} finally {
setIsStarting(false)
@@ -260,7 +260,7 @@ export function HermesReconnectBanner({
/>
<div className="min-w-0">
<p className="text-sm font-semibold">
{isDisconnected ? 'Hermes agent not connected' : 'Connected'}
{isDisconnected ? 'Claude agent not connected' : 'Connected'}
</p>
{message ? (
<p className="truncate text-xs opacity-80">{message}</p>

View File

@@ -129,7 +129,7 @@ export function CommandPalette({ pathname, sessions }: CommandPaletteProps) {
}
if (command === '/model' || command === '/skin') {
const section = command === '/skin' ? 'appearance' : 'hermes'
const section = command === '/skin' ? 'appearance' : 'claude'
if (pathname.startsWith('/chat') || pathname === '/') {
window.dispatchEvent(
new CustomEvent(CHAT_OPEN_SETTINGS_EVENT, {
@@ -262,7 +262,7 @@ export function CommandPalette({ pathname, sessions }: CommandPaletteProps) {
id: 'slash-model',
group: 'Slash Commands',
label: '/model',
keywords: 'open model picker settings hermes provider',
keywords: 'open model picker settings claude provider',
shortcut: 'Run',
icon: CommandLineIcon,
onSelect: () => runSlashCommand('/model'),

View File

@@ -1,4 +1,4 @@
// Stub — connection overlay (not used in Hermes Workspace)
// Stub — connection overlay (not used in Claude Workspace)
export function useConnectionRestart() {
return {
triggerRestart: async (fn: () => Promise<void>) => {

View File

@@ -1,7 +1,7 @@
import { useEffect, useRef, useState } from 'react'
import type { AuthStatus } from '@/lib/hermes-auth'
import type { AuthStatus } from '@/lib/claude-auth'
import { writeTextToClipboard } from '@/lib/clipboard'
import { fetchHermesAuthStatus } from '@/lib/hermes-auth'
import { fetchClaudeAuthStatus } from '@/lib/claude-auth'
const POLL_INTERVAL_MS = 2_000
const FAILURE_REVEAL_MS = 5_000
@@ -28,22 +28,22 @@ function getSetupSteps(
return [
{
title: 'Use any OpenAI-compatible backend',
command: 'Set HERMES_API_URL to your backend base URL',
command: 'Set CLAUDE_API_URL to your backend base URL',
note: 'Portable chat works with any backend that exposes /v1/chat/completions (Ollama, LiteLLM, vLLM, etc.)',
},
{
title: 'Optional: install Hermes Agent locally',
command: `${pip} install hermes-agent`,
note: 'Vanilla hermes-agent unlocks sessions, skills, memory, jobs, and config automatically — no fork required',
title: 'Optional: install Claude Agent locally',
command: `${pip} install claude-agent`,
note: 'Vanilla claude-agent unlocks sessions, skills, memory, jobs, and config automatically — no fork required',
},
{
title: 'Set up Hermes',
command: 'hermes setup',
note: 'Pick your providers once; Hermes stores them under ~/.hermes',
title: 'Set up Claude',
command: 'claude setup',
note: 'Pick your providers once; Claude stores them under ~/.claude',
},
{
title: 'Start the gateway',
command: 'hermes gateway run',
command: 'claude gateway run',
note: 'This starts the HTTP API on :8642 for the workspace',
},
]
@@ -95,14 +95,14 @@ export function ConnectionStartupScreen({ onConnected }: Props) {
}
}, FAILURE_REVEAL_MS)
// After a short grace period, fire /api/start-hermes once silently.
// If hermes-agent is installed and just not running, this brings it back
// After a short grace period, fire /api/start-claude once silently.
// If claude-agent is installed and just not running, this brings it back
// up without making the user click anything. The polling loop will see it.
const fireSilentAutoStart = async () => {
if (autoStartFired || isDone.current) return
autoStartFired = true
try {
const res = await fetch('/api/start-hermes', {
const res = await fetch('/api/start-claude', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
})
@@ -115,7 +115,7 @@ export function ConnectionStartupScreen({ onConnected }: Props) {
setServerLog([
String(
data.message ||
'Auto-started Hermes gateway — reconnecting…',
'Auto-started Claude gateway — reconnecting…',
),
])
}
@@ -129,7 +129,7 @@ export function ConnectionStartupScreen({ onConnected }: Props) {
const tryConnect = async () => {
try {
const status = await fetchHermesAuthStatus()
const status = await fetchClaudeAuthStatus()
if (isDone.current) return
isDone.current = true
clearTimeout(failureTimer)
@@ -171,9 +171,9 @@ export function ConnectionStartupScreen({ onConnected }: Props) {
const handleAutoStart = async () => {
setServerStarting(true)
setServerError(null)
setServerLog(['Looking for hermes-agent...'])
setServerLog(['Looking for claude-agent...'])
try {
const res = await fetch('/api/start-hermes', {
const res = await fetch('/api/start-claude', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
})
@@ -195,7 +195,7 @@ export function ConnectionStartupScreen({ onConnected }: Props) {
return
}
const msg = String(data.error || 'Could not find hermes-agent')
const msg = String(data.error || 'Could not find claude-agent')
const hint = data.hint ? String(data.hint) : ''
setServerLog([`Error: ${msg}`])
if (hint) setServerLog((prev) => [...prev, `Hint: ${hint}`])
@@ -222,13 +222,13 @@ export function ConnectionStartupScreen({ onConnected }: Props) {
>
<div className="flex w-full max-w-lg flex-col items-center text-center">
<img
src="/hermes-avatar.webp"
alt="Hermes"
src="/claude-avatar.webp"
alt="Claude"
className="mb-5 h-20 w-20 rounded-2xl object-cover shadow-[0_12px_40px_rgba(0,0,0,0.45)]"
/>
<h1 className="text-[2rem] font-semibold tracking-tight text-white">
Hermes Workspace
Claude Workspace
</h1>
{/* Connecting spinner */}
@@ -257,7 +257,7 @@ export function ConnectionStartupScreen({ onConnected }: Props) {
Welcome! Let&apos;s connect your backend
</p>
<p className="mt-2 text-sm leading-6 text-white/60">
Hermes Workspace works with any OpenAI-compatible backend. Hermes
Claude Workspace works with any OpenAI-compatible backend. Claude
gateway APIs unlock enhanced features automatically when they are
available.
</p>
@@ -281,7 +281,7 @@ export function ConnectionStartupScreen({ onConnected }: Props) {
Detecting...
</span>
) : (
'Auto-Start Hermes Gateway'
'Auto-Start Claude Gateway'
)}
</button>
@@ -360,12 +360,12 @@ export function ConnectionStartupScreen({ onConnected }: Props) {
<p className="text-xs font-medium text-white/50">
Point{' '}
<code className="rounded bg-white/10 px-1.5 py-0.5 font-mono text-white/70">
HERMES_API_URL
CLAUDE_API_URL
</code>{' '}
at any OpenAI-compatible backend:
</p>
<pre className="mt-2 overflow-x-auto font-mono text-xs text-white/60">
HERMES_API_URL=http://your-server:8642 pnpm dev
CLAUDE_API_URL=http://your-server:8642 pnpm dev
</pre>
</div>
</div>

View File

@@ -28,7 +28,7 @@ const SYSTEM_ITEMS: Array<OverflowItem> = [
{ icon: BrainIcon, label: 'Memory', to: '/memory' },
]
const HERMES_ITEMS: Array<OverflowItem> = [
const CLAUDE_ITEMS: Array<OverflowItem> = [
{ icon: MessageMultiple01Icon, label: 'Chat', to: '/chat' },
{ icon: PuzzleIcon, label: 'Skills', to: '/skills' },
{ icon: UserGroupIcon, label: 'Profiles', to: '/profiles' },
@@ -100,8 +100,8 @@ export function DashboardOverflowPanel({ open, onClose }: Props) {
// Detect actual current theme family from data-theme attribute
const currentDataTheme = typeof document !== 'undefined'
? (document.documentElement.getAttribute('data-theme') || 'hermes-nous')
: 'hermes-nous'
? (document.documentElement.getAttribute('data-theme') || 'claude-nous')
: 'claude-nous'
const isDark = !currentDataTheme.endsWith('-light')
const themeIcon = isDark ? Sun02Icon : Moon02Icon
const themeLabel = isDark ? 'Light mode' : 'Dark mode'
@@ -153,8 +153,8 @@ export function DashboardOverflowPanel({ open, onClose }: Props) {
onSelect={handleSelect}
/>
<OverflowGrid
title="Hermes"
items={HERMES_ITEMS}
title="Claude"
items={CLAUDE_ITEMS}
onSelect={handleSelect}
/>
</div>

View File

@@ -19,7 +19,7 @@ export type LoadingIndicatorProps = {
function getBraillePreset(
loaderStyle: LoaderStyle,
): BrailleSpinnerPreset | null {
if (loaderStyle === 'braille-hermes') return 'hermes'
if (loaderStyle === 'braille-claude') return 'claude'
if (loaderStyle === 'braille-orbit') return 'orbit'
if (loaderStyle === 'braille-breathe') return 'breathe'
if (loaderStyle === 'braille-pulse') return 'pulse'

View File

@@ -10,7 +10,7 @@ function LogoLoader({ className }: LogoLoaderProps) {
return (
<span className="logo-loader-track" aria-hidden="true">
<img
src="/hermes-avatar.webp"
src="/claude-avatar.webp"
alt=""
className={cn('logo-loader-icon size-4 rounded', className)}
/>

View File

@@ -197,8 +197,8 @@ export function MobileHamburgerMenu() {
>
<div className="flex items-center gap-2.5">
<img
src="/hermes-avatar.webp"
alt="Hermes"
src="/claude-avatar.webp"
alt="Claude"
className="size-8 rounded-xl shrink-0"
/>
<div className="flex flex-col leading-tight">
@@ -206,7 +206,7 @@ export function MobileHamburgerMenu() {
className="font-bold text-[15px] tracking-tight"
style={{ color: 'var(--color-ink, #111)' }}
>
Hermes
Claude
</span>
<span
className="text-[11px]"

View File

@@ -29,9 +29,9 @@ export function MobilePromptTrigger() {
}
const isDismissed =
localStorage.getItem('hermes-mobile-access-dismissed') === 'true' ||
localStorage.getItem('hermes-mobile-prompt-dismissed') === 'true'
const isSetup = localStorage.getItem('hermes-mobile-setup-seen') === 'true'
localStorage.getItem('claude-mobile-access-dismissed') === 'true' ||
localStorage.getItem('claude-mobile-prompt-dismissed') === 'true'
const isSetup = localStorage.getItem('claude-mobile-setup-seen') === 'true'
if (isDismissed || isSetup) {
return
@@ -58,7 +58,7 @@ export function MobilePromptTrigger() {
const persistDismissalPreference = () => {
if (dontShowAgain) {
localStorage.setItem('hermes-mobile-access-dismissed', 'true')
localStorage.setItem('claude-mobile-access-dismissed', 'true')
}
}
@@ -101,8 +101,8 @@ export function MobilePromptTrigger() {
<div className="flex items-center gap-3">
<div className="flex shrink-0 items-center gap-1.5">
<img
src="/hermes-avatar.webp"
alt="Hermes"
src="/claude-avatar.webp"
alt="Claude"
className="size-8 rounded-lg"
/>
<span className="text-xs text-primary-600">+</span>
@@ -180,7 +180,7 @@ export function MobilePromptTrigger() {
className="text-xs"
style={{ color: 'var(--theme-muted)' }}
>
Connect your phone to this Hermes Workspace instance in a
Connect your phone to this Claude Workspace instance in a
few steps.
</p>
</div>

View File

@@ -6,7 +6,7 @@ import { HugeiconsIcon } from '@hugeicons/react'
import { Cancel01Icon } from '@hugeicons/core-free-icons'
import { writeTextToClipboard } from '@/lib/clipboard'
const STORAGE_KEY_SEEN = 'hermes-mobile-setup-seen'
const STORAGE_KEY_SEEN = 'claude-mobile-setup-seen'
interface MobileSetupModalProps {
isOpen: boolean
@@ -62,7 +62,7 @@ export function MobileSetupModal({ isOpen, onClose }: MobileSetupModalProps) {
const steps = [
{
title: 'Install Tailscale on your desktop',
body: 'Install Tailscale on the machine running Hermes Workspace, then sign in.',
body: 'Install Tailscale on the machine running Claude Workspace, then sign in.',
showTailscaleIcon: true,
action: (
<a
@@ -77,11 +77,11 @@ export function MobileSetupModal({ isOpen, onClose }: MobileSetupModalProps) {
},
{
title: 'Keep your backend reachable',
body: 'Hermes Workspace can talk to any OpenAI-compatible backend on mobile too. Make sure both the workspace and backend stay reachable over Tailscale or your local network.',
body: 'Claude Workspace can talk to any OpenAI-compatible backend on mobile too. Make sure both the workspace and backend stay reachable over Tailscale or your local network.',
showTailscaleIcon: false,
action: (
<div className="rounded-lg border border-primary-700 bg-primary-950 px-4 py-3 text-sm text-primary-200">
Enhanced Hermes gateway APIs are optional. If core chat already works
Enhanced Claude gateway APIs are optional. If core chat already works
on desktop, mobile access mainly depends on network reachability.
</div>
),
@@ -112,7 +112,7 @@ export function MobileSetupModal({ isOpen, onClose }: MobileSetupModalProps) {
),
},
{
title: 'Open Hermes Workspace on your phone',
title: 'Open Claude Workspace on your phone',
body:
networkUrl?.source === 'tailscale'
? 'Your Tailscale address. Open this on your phone browser to use the same workspace.'
@@ -221,8 +221,8 @@ export function MobileSetupModal({ isOpen, onClose }: MobileSetupModalProps) {
<div className="mb-4 flex items-center gap-3 pr-10">
<img
src="/hermes-avatar.webp"
alt="Hermes"
src="/claude-avatar.webp"
alt="Claude"
className="size-9 rounded-xl"
/>
<div className="flex-1">

View File

@@ -30,8 +30,8 @@ function stripProviderPrefix(model: string): string {
return model
}
export const ONBOARDING_KEY = 'hermes-onboarding-complete'
export const ONBOARDING_COMPLETE_EVENT = 'hermes:onboarding-complete'
export const ONBOARDING_KEY = 'claude-onboarding-complete'
export const ONBOARDING_COMPLETE_EVENT = 'claude:onboarding-complete'
function dispatchOnboardingCompletionChanged(completed: boolean) {
if (typeof window === 'undefined') return
@@ -56,7 +56,7 @@ type GatewayStatusResponse = {
config?: boolean
jobs?: boolean
}
hermesUrl?: string
claudeUrl?: string
}
const PROVIDERS = [
@@ -130,7 +130,7 @@ function getEnhancedFeatureNames(
.map((feature) => feature.label)
}
export function HermesOnboarding() {
export function ClaudeOnboarding() {
const [show, setShow] = useState(false)
const [step, setStep] = useState<Step>('welcome')
const [backendStatus, setBackendStatus] = useState<
@@ -178,7 +178,7 @@ export function HermesOnboarding() {
const loadCurrentConfig = useCallback(async () => {
try {
const res = await fetch('/api/hermes-config')
const res = await fetch('/api/claude-config')
if (!res.ok) return
const data = (await res.json()) as {
activeModel?: string
@@ -240,7 +240,7 @@ export function HermesOnboarding() {
setBackendStatus('ready')
setBackendMessage(
data.capabilities.sessions
? 'Backend connected. Core chat works, and Hermes gateway enhancements are available.'
? 'Backend connected. Core chat works, and Claude gateway enhancements are available.'
: 'Backend connected. Core chat is ready.',
)
return
@@ -287,7 +287,7 @@ export function HermesOnboarding() {
}
}
const res = await fetch('/api/hermes-config', {
const res = await fetch('/api/claude-config', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
@@ -321,7 +321,7 @@ export function HermesOnboarding() {
if (!canEditConfig || !selectedProvider) return true
try {
const res = await fetch('/api/hermes-config', {
const res = await fetch('/api/claude-config', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -521,16 +521,16 @@ export function HermesOnboarding() {
{step === 'welcome' && (
<div className="space-y-4 text-center">
<img
src="/hermes-avatar.webp"
alt="Hermes"
src="/claude-avatar.webp"
alt="Claude"
className="mx-auto size-20 rounded-2xl"
style={{
filter: 'drop-shadow(0 8px 24px rgba(99,102,241,0.3))',
}}
/>
<h2 className="text-xl font-bold">Welcome to Hermes Workspace</h2>
<h2 className="text-xl font-bold">Welcome to Claude Workspace</h2>
<p className="text-sm" style={mutedStyle}>
Works with any OpenAI-compatible backend. Hermes gateway APIs
Works with any OpenAI-compatible backend. Claude gateway APIs
unlock sessions, memory, skills, and other extras automatically.
</p>
<button
@@ -553,7 +553,7 @@ export function HermesOnboarding() {
<div className="text-4xl">🔌</div>
<h2 className="text-lg font-bold">Connect Your Backend</h2>
<p className="text-sm" style={mutedStyle}>
Start by verifying that Hermes Workspace can reach your
Start by verifying that Claude Workspace can reach your
OpenAI-compatible backend.
</p>
@@ -579,7 +579,7 @@ export function HermesOnboarding() {
>
<p style={mutedStyle}>Backend URL</p>
<p className="mt-1 font-mono">
{backendInfo?.hermesUrl || 'Configured automatically'}
{backendInfo?.claudeUrl || 'Configured automatically'}
</p>
</div>
</div>
@@ -600,8 +600,8 @@ export function HermesOnboarding() {
</p>
<p className="mt-2" style={mutedStyle}>
Use any backend that exposes{' '}
<code>/v1/chat/completions</code>. If you point Hermes
Workspace at a Hermes gateway, enhanced features unlock
<code>/v1/chat/completions</code>. If you point Claude
Workspace at a Claude gateway, enhanced features unlock
automatically.
</p>
<div
@@ -614,7 +614,7 @@ export function HermesOnboarding() {
className="mt-2 rounded-lg px-3 py-2 font-mono text-[11px]"
style={{ background: 'rgba(0,0,0,0.2)' }}
>
hermes --gateway
claude --gateway
</div>
</div>
</div>
@@ -650,14 +650,14 @@ export function HermesOnboarding() {
<p className="text-center text-xs" style={mutedStyle}>
{canEditConfig
? 'Save provider settings here, then choose a model before testing chat.'
: 'This backend manages provider settings outside Hermes Workspace. Confirm the model you expect to use, then test chat.'}
: 'This backend manages provider settings outside Claude Workspace. Confirm the model you expect to use, then test chat.'}
</p>
<div className="rounded-xl p-3 text-xs" style={cardStyle}>
<p style={mutedStyle}>Backend mode</p>
<p className="mt-1">
{backendInfo?.capabilities?.sessions
? 'Hermes gateway detected'
? 'Claude gateway detected'
: 'Portable OpenAI-compatible backend'}
</p>
{configuredModel ? (
@@ -811,7 +811,7 @@ export function HermesOnboarding() {
className="rounded-lg px-3 py-2 font-mono text-xs"
style={{ background: 'rgba(0,0,0,0.2)' }}
>
hermes auth login openai-codex
claude auth login openai-codex
</div>
<p className="text-xs" style={mutedStyle}>
After the login flow completes, click below to refresh
@@ -978,7 +978,7 @@ export function HermesOnboarding() {
<div className="text-4xl">🧪</div>
<h2 className="text-lg font-bold">Test Chat</h2>
<p className="text-sm" style={mutedStyle}>
Verify that core chat works first. Enhanced Hermes features are
Verify that core chat works first. Enhanced Claude features are
optional and appear automatically when supported.
</p>
@@ -988,7 +988,7 @@ export function HermesOnboarding() {
>
<p style={mutedStyle}>Backend</p>
<p className="mt-1 font-mono">
{backendInfo?.hermesUrl || 'Configured automatically'}
{backendInfo?.claudeUrl || 'Configured automatically'}
</p>
{selectedModel || configuredModel ? (
<p className="mt-2" style={mutedStyle}>
@@ -1060,7 +1060,7 @@ export function HermesOnboarding() {
) : (
<p className="mt-2 text-xs text-yellow-400">
Confirm the backend is running and still reachable from
Hermes Workspace.
Claude Workspace.
</p>
)}
</div>
@@ -1098,8 +1098,8 @@ export function HermesOnboarding() {
<p className="text-sm" style={mutedStyle}>
Core chat is set up.{' '}
{enhancedFeatures.length > 0
? 'This backend also exposes Hermes gateway enhancements.'
: 'If you later connect a Hermes gateway, enhanced features unlock automatically.'}
? 'This backend also exposes Claude gateway enhancements.'
: 'If you later connect a Claude gateway, enhanced features unlock automatically.'}
</p>
<div
className="grid grid-cols-3 gap-2 text-xs"

View File

@@ -32,8 +32,8 @@ export type OnboardingStep = {
export const ONBOARDING_STEPS: Array<OnboardingStep> = [
{
id: 'welcome',
title: 'Welcome to Hermes Workspace',
description: 'Your AI workspace powered by Hermes Agent',
title: 'Welcome to Claude Workspace',
description: 'Your AI workspace powered by Claude Agent',
icon: Home01Icon,
iconBg: 'bg-orange-500',
nextLabel: 'Get Started',
@@ -41,7 +41,7 @@ export const ONBOARDING_STEPS: Array<OnboardingStep> = [
{
id: 'connection-check',
title: 'Connection Check',
description: 'Verify that Hermes Agent is running before you begin.',
description: 'Verify that Claude Agent is running before you begin.',
icon: Plug01Icon,
iconBg: 'bg-emerald-500',
component: ConnectionCheckStep,
@@ -59,11 +59,11 @@ export const ONBOARDING_STEPS: Array<OnboardingStep> = [
id: 'ready',
title: 'You are all set!',
description:
'Start chatting with Hermes. Try asking it to help with code, research, or anything else.',
'Start chatting with Claude. Try asking it to help with code, research, or anything else.',
icon: CheckmarkCircle02Icon,
iconBg: 'bg-emerald-500',
completeLabel: 'Start Chatting',
},
]
export const STORAGE_KEY = 'hermes-onboarding-complete'
export const STORAGE_KEY = 'claude-onboarding-complete'

View File

@@ -7,7 +7,7 @@ import type { CallBackProps, Styles } from 'react-joyride'
import { useSettingsStore } from '@/hooks/use-settings'
import { useResolvedTheme } from '@/hooks/use-chat-settings'
const TOUR_STORAGE_KEY = 'hermes-onboarding-completed'
const TOUR_STORAGE_KEY = 'claude-onboarding-completed'
// Accent color mapping to hex values
const ACCENT_COLORS = {
@@ -45,11 +45,11 @@ export function OnboardingTour() {
if (hasCompletedTour) return
// Wait for setup wizard to finish before starting tour
const HERMES_SETUP_KEY = 'hermes-configured'
const CLAUDE_SETUP_KEY = 'claude-configured'
const checkAndStart = () => {
const hermesConfigured =
localStorage.getItem(HERMES_SETUP_KEY) === 'true'
if (hermesConfigured) {
const claudeConfigured =
localStorage.getItem(CLAUDE_SETUP_KEY) === 'true'
if (claudeConfigured) {
setRun(true)
return true
}

View File

@@ -146,8 +146,8 @@ export function OnboardingWizard() {
>
{step.id === 'welcome' ? (
<img
src="/hermes-avatar.webp"
alt="Hermes"
src="/claude-avatar.webp"
alt="Claude"
className="size-16 rounded-2xl"
/>
) : (

View File

@@ -98,7 +98,7 @@ const PROVIDERS: Array<Provider> = [
id: 'openrouter',
name: 'OpenRouter',
description:
'One Hermes connection to 200+ AI models. Ideal for flexibility and experimentation',
'One Claude connection to 200+ AI models. Ideal for flexibility and experimentation',
badge: 'Popular',
logo: <OpenRouterLogo className="size-8" />,
placeholder: 'sk-or-v1-...',

View File

@@ -20,7 +20,7 @@ type AuthCheckResponse = {
error?: string
}
type HermesConfigResponse = {
type ClaudeConfigResponse = {
activeProvider?: string
activeModel?: string
}
@@ -51,8 +51,8 @@ export function ConnectionCheckStep({
if (!connected) {
setLastError(
data.error === 'server_timeout'
? 'Hermes Agent did not respond in time.'
: 'Hermes Agent is not reachable yet.',
? 'Claude Agent did not respond in time.'
: 'Claude Agent is not reachable yet.',
)
}
} catch (error) {
@@ -111,12 +111,12 @@ export function ConnectionCheckStep({
{status === 'disconnected' && (
<div className="mb-6 w-full rounded-2xl border border-red-200 bg-red-50 p-4 text-left">
<p className="mb-3 text-sm font-medium text-red-700">
Make sure the Hermes HTTP API server is enabled:
Make sure the Claude HTTP API server is enabled:
</p>
<div className="space-y-2">
<div>
<p className="text-xs font-medium text-red-700 mb-1">
1. Enable the API server in <code>~/.hermes/.env</code>:
1. Enable the API server in <code>~/.claude/.env</code>:
</p>
<code className="block overflow-x-auto rounded-lg bg-red-100 px-3 py-2 text-xs text-red-900">
API_SERVER_ENABLED=true
@@ -127,12 +127,12 @@ export function ConnectionCheckStep({
2. Restart the gateway:
</p>
<code className="block overflow-x-auto rounded-lg bg-red-100 px-3 py-2 text-xs text-red-900">
cd hermes-agent && hermes --gateway
cd claude-agent && claude --gateway
</code>
</div>
</div>
<p className="mt-3 text-xs text-red-700">
Or point <code>HERMES_API_URL</code> at any OpenAI-compatible
Or point <code>CLAUDE_API_URL</code> at any OpenAI-compatible
backend (Ollama, LiteLLM, vLLM, etc.).
</p>
{lastError && (
@@ -157,7 +157,7 @@ export function ModelConfigurationStep({
setCanProceed,
}: OnboardingStepComponentProps) {
const [status, setStatus] = useState<'loading' | 'ready' | 'error'>('loading')
const [config, setConfig] = useState<HermesConfigResponse | null>(null)
const [config, setConfig] = useState<ClaudeConfigResponse | null>(null)
useEffect(() => {
setCanProceed(true)
@@ -168,14 +168,14 @@ export function ModelConfigurationStep({
async function loadConfig() {
try {
const response = await fetch('/api/hermes-config', {
const response = await fetch('/api/claude-config', {
signal: AbortSignal.timeout(5000),
})
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
const data = (await response.json()) as HermesConfigResponse
const data = (await response.json()) as ClaudeConfigResponse
if (!cancelled) {
setConfig(data)
setStatus('ready')
@@ -213,7 +213,7 @@ export function ModelConfigurationStep({
</h2>
<p className="mb-6 max-w-md text-base leading-relaxed text-primary-600">
Core chat works with any OpenAI-compatible backend. Hermes gateway APIs
Core chat works with any OpenAI-compatible backend. Claude gateway APIs
make provider and model setup editable from the workspace.
</p>

View File

@@ -5,7 +5,7 @@ export const tourSteps: Array<Step> = [
{
target: 'body',
placement: 'center',
title: 'Welcome to Hermes Workspace! ⚕',
title: 'Welcome to Claude Workspace! ⚕',
content: (
<div
style={{
@@ -16,8 +16,8 @@ export const tourSteps: Array<Step> = [
}}
>
<img
src="/hermes-avatar.webp"
alt="Hermes"
src="/claude-avatar.webp"
alt="Claude"
style={{ width: 48, height: 48, borderRadius: 12 }}
/>
<p style={{ textAlign: 'center', margin: 0 }}>
@@ -74,7 +74,7 @@ export const tourSteps: Array<Step> = [
placement: 'right',
title: 'Built-in Terminal',
content:
'Built-in terminal for quick commands. Execute shell commands without leaving Hermes Workspace.',
'Built-in terminal for quick commands. Execute shell commands without leaving Claude Workspace.',
},
// Step 9: Usage Meter (in header)
{
@@ -90,7 +90,7 @@ export const tourSteps: Array<Step> = [
placement: 'right',
title: 'Settings & Customization',
content:
'Configure providers, themes, accent colors, and more. Make Hermes Workspace yours.',
'Configure providers, themes, accent colors, and more. Make Claude Workspace yours.',
},
// Step 11: Finish
{
@@ -98,6 +98,6 @@ export const tourSteps: Array<Step> = [
placement: 'center',
title: "You're all set! 🎉",
content:
'Start chatting with your AI, explore the tools, and customize Hermes Workspace to fit your workflow. Need help? Press ? to see all keyboard shortcuts.',
'Start chatting with your AI, explore the tools, and customize Claude Workspace to fit your workflow. Need help? Press ? to see all keyboard shortcuts.',
},
]

View File

@@ -2,10 +2,10 @@ import { useEffect, useState } from 'react'
import { cn } from '@/lib/utils'
const LIGHT_THEMES = new Set([
'hermes-nous-light',
'hermes-official-light',
'hermes-classic-light',
'hermes-slate-light',
'claude-nous-light',
'claude-official-light',
'claude-classic-light',
'claude-slate-light',
])
function useIsLightTheme(): boolean {

View File

@@ -59,7 +59,7 @@ import {
// ── Types ───────────────────────────────────────────────────────────────
type SectionId =
| 'hermes'
| 'claude'
| 'agent'
| 'routing'
| 'voice'
@@ -70,7 +70,7 @@ type SectionId =
| 'language'
const SECTIONS: Array<{ id: SectionId; label: string; icon: any }> = [
{ id: 'hermes', label: 'Model & Provider', icon: CloudIcon },
{ id: 'claude', label: 'Model & Provider', icon: CloudIcon },
{ id: 'agent', label: 'Agent', icon: Settings02Icon },
{ id: 'routing', label: 'Smart Routing', icon: SparklesIcon },
{ id: 'voice', label: 'Voice', icon: VolumeHighIcon },
@@ -82,10 +82,10 @@ const SECTIONS: Array<{ id: SectionId; label: string; icon: any }> = [
]
const DARK_ENTERPRISE_THEMES = new Set<ThemeId>([
'hermes-nous',
'hermes-official',
'hermes-classic',
'hermes-slate',
'claude-nous',
'claude-official',
'claude-classic',
'claude-slate',
])
function _isDarkEnterpriseTheme(theme: string | null): theme is ThemeId {
@@ -185,7 +185,7 @@ const PROVIDER_CARDS: Array<{
id: 'nous',
name: 'Nous Portal',
logo: '/providers/nous.png',
models: ['xiaomi/mimo-v2-pro', 'xiaomi/mimo-v2-omni', 'hermes-3-llama-3.1-405b', 'hermes-3-llama-3.1-70b'],
models: ['xiaomi/mimo-v2-pro', 'xiaomi/mimo-v2-omni', 'claude-3-llama-3.1-405b', 'claude-3-llama-3.1-70b'],
authType: 'oauth',
},
{
@@ -238,7 +238,7 @@ const PROVIDER_CARDS: Array<{
{ id: 'custom', name: 'Custom', logo: '', models: [], authType: 'api_key' },
]
function HermesContent() {
function ClaudeContent() {
const configAvailable = useFeatureAvailable('config')
const [activeProvider, setActiveProvider] = useState('')
const [activeModel, setActiveModel] = useState('')
@@ -269,7 +269,7 @@ function HermesContent() {
}
}
fetch(
`/api/hermes-proxy/api/available-models?provider=${encodeURIComponent(providerId)}`,
`/api/claude-proxy/api/available-models?provider=${encodeURIComponent(providerId)}`,
)
.then((r) => r.json())
.then((d: { models?: Array<{ id: string }> }) => {
@@ -290,7 +290,7 @@ function HermesContent() {
}, [])
useEffect(() => {
fetch('/api/hermes-config')
fetch('/api/claude-config')
.then((r) => r.json())
.then((d: any) => {
setActiveProvider(d.activeProvider || '')
@@ -317,14 +317,14 @@ function HermesContent() {
setSaving(true)
setMsg(null)
try {
const res = await fetch('/api/hermes-config', {
const res = await fetch('/api/claude-config', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates),
})
const r = (await res.json()) as { message?: string }
setMsg(r.message || 'Saved')
const ref = await fetch('/api/hermes-config')
const ref = await fetch('/api/claude-config')
const d = await ref.json()
setActiveProvider(d.activeProvider || '')
setActiveModel(d.activeModel || '')
@@ -356,7 +356,7 @@ function HermesContent() {
if (!configAvailable) {
return (
<BackendUnavailableState
feature="Hermes Agent Settings"
feature="Claude Agent Settings"
description={getUnavailableReason('config')}
/>
)
@@ -489,7 +489,7 @@ function HermesContent() {
if (!disc || !disc.needsRestart) return null
return (
<div className="rounded-lg border border-yellow-500/30 bg-yellow-500/10 px-3 py-2 text-xs text-yellow-200">
Gateway restart needed to use {disc.name}. Run <code className="rounded bg-black/30 px-1">hermes gateway restart</code> in your terminal.
Gateway restart needed to use {disc.name}. Run <code className="rounded bg-black/30 px-1">claude gateway restart</code> in your terminal.
</div>
)
})()}
@@ -674,7 +674,7 @@ function HermesContent() {
'—'}
</span>
<span style={mutedStyle}>Config</span>
<span className="font-mono font-medium">~/.hermes/config.yaml</span>
<span className="font-mono font-medium">~/.claude/config.yaml</span>
</div>
</div>
</div>
@@ -841,7 +841,7 @@ function AppearanceContent() {
}
function _handleAccentColorChange(selectedAccent: AccentColor) {
localStorage.setItem('hermes-accent', selectedAccent)
localStorage.setItem('claude-accent', selectedAccent)
document.documentElement.setAttribute('data-accent', selectedAccent)
applyAccentColor(selectedAccent)
updateSettings({ accentColor: selectedAccent })
@@ -890,7 +890,7 @@ function AppearanceContent() {
<div className={SETTINGS_CARD_CLASS}>
<Row
label="System metrics footer"
description="Show a persistent footer with CPU, RAM, disk, and Hermes status."
description="Show a persistent footer with CPU, RAM, disk, and Claude status."
>
<Switch
checked={settings.showSystemMetricsFooter}
@@ -901,24 +901,24 @@ function AppearanceContent() {
/>
</Row>
{/* Mobile chat nav removed — not relevant for Hermes */}
{/* Mobile chat nav removed — not relevant for Claude */}
</div>
</div>
)
}
const ENTERPRISE_THEME_FAMILIES: Array<ThemeId> = [
'hermes-nous',
'hermes-official',
'hermes-classic',
'hermes-slate',
'claude-nous',
'claude-official',
'claude-classic',
'claude-slate',
]
const ENTERPRISE_THEMES = THEMES.map((theme) => ({
...theme,
desc: theme.description,
preview:
theme.id === 'hermes-nous'
theme.id === 'claude-nous'
? {
bg: '#041C1C',
panel: '#06282A',
@@ -926,7 +926,7 @@ const ENTERPRISE_THEMES = THEMES.map((theme) => ({
accent: '#FFAC02',
text: '#FFE6CB',
}
: theme.id === 'hermes-nous-light'
: theme.id === 'claude-nous-light'
? {
bg: '#F8FAF8',
panel: '#FBFDFB',
@@ -934,7 +934,7 @@ const ENTERPRISE_THEMES = THEMES.map((theme) => ({
accent: '#2557B7',
text: '#16315F',
}
: theme.id === 'hermes-official'
: theme.id === 'claude-official'
? {
bg: '#0A0E1A',
panel: '#11182A',
@@ -942,7 +942,7 @@ const ENTERPRISE_THEMES = THEMES.map((theme) => ({
accent: '#6366F1',
text: '#E6EAF2',
}
: theme.id === 'hermes-official-light'
: theme.id === 'claude-official-light'
? {
bg: '#F7F7F1',
panel: '#FAFBF6',
@@ -950,7 +950,7 @@ const ENTERPRISE_THEMES = THEMES.map((theme) => ({
accent: '#2557B7',
text: '#16315F',
}
: theme.id === 'hermes-classic'
: theme.id === 'claude-classic'
? {
bg: '#0d0f12',
panel: '#1a1f26',
@@ -958,7 +958,7 @@ const ENTERPRISE_THEMES = THEMES.map((theme) => ({
accent: '#b98a44',
text: '#eceff4',
}
: theme.id === 'hermes-classic-light'
: theme.id === 'claude-classic-light'
? {
bg: '#F5F2ED',
panel: '#FCFAF7',
@@ -966,7 +966,7 @@ const ENTERPRISE_THEMES = THEMES.map((theme) => ({
accent: '#b98a44',
text: '#1a1f26',
}
: theme.id === 'hermes-slate'
: theme.id === 'claude-slate'
? {
bg: '#0d1117',
panel: '#1c2128',
@@ -1026,7 +1026,7 @@ function ThemeSwatch({
function EnterpriseThemePicker() {
const { updateSettings } = useSettings()
const [current, setCurrent] = useState(() => {
if (typeof window === 'undefined') return 'hermes-nous'
if (typeof window === 'undefined') return 'claude-nous'
return getTheme()
})
const currentMode = isDarkTheme(current) ? 'dark' : 'light'
@@ -1124,7 +1124,7 @@ function _LoaderContent() {
const { settings: cs, updateSettings: updateCS } = useChatSettingsStore()
const styles: Array<{ value: LoaderStyle; label: string }> = [
{ value: 'dots', label: 'Dots' },
{ value: 'braille-hermes', label: 'Hermes' },
{ value: 'braille-claude', label: 'Claude' },
{ value: 'braille-orbit', label: 'Orbit' },
{ value: 'braille-breathe', label: 'Breathe' },
{ value: 'braille-pulse', label: 'Pulse' },
@@ -1134,7 +1134,7 @@ function _LoaderContent() {
]
function getPreset(s: LoaderStyle): BrailleSpinnerPreset | null {
const m: Record<string, BrailleSpinnerPreset> = {
'braille-hermes': 'hermes',
'braille-claude': 'claude',
'braille-orbit': 'orbit',
'braille-breathe': 'breathe',
'braille-pulse': 'pulse',
@@ -1281,7 +1281,7 @@ function ChatContent() {
/>
</Row>
</div>
{/* Loading animation removed — not relevant for Hermes */}
{/* Loading animation removed — not relevant for Claude */}
</div>
)
}
@@ -1347,7 +1347,7 @@ function _AdvancedContent() {
} else {
setUrlError(null)
}
updateSettings({ hermesUrl: value })
updateSettings({ claudeUrl: value })
}
async function testConnection() {
@@ -1361,24 +1361,24 @@ function _AdvancedContent() {
}
}
const urlErrorId = 'hermes-url-error'
const urlErrorId = 'claude-url-error'
return (
<div className="space-y-4">
<SectionHeader
title="Advanced"
description="Hermes endpoint and connectivity."
description="Claude endpoint and connectivity."
/>
<div className={SETTINGS_CARD_CLASS}>
<Row label="Hermes URL" description="Used for API requests from Studio">
<Row label="Claude URL" description="Used for API requests from Studio">
<div className="w-full max-w-sm">
<Input
type="url"
placeholder="https://api.hermesworkspace.app"
value={settings.hermesUrl}
placeholder="https://api.claudeworkspace.app"
value={settings.claudeUrl}
onChange={(e) => validateAndUpdateUrl(e.target.value)}
className="h-8 w-full rounded-lg border-primary-200 text-sm"
aria-label="Hermes URL"
aria-label="Claude URL"
aria-invalid={!!urlError}
aria-describedby={urlError ? urlErrorId : undefined}
/>
@@ -1476,7 +1476,7 @@ function AgentBehaviorContent() {
const [msg, setMsg] = useState<string | null>(null)
useEffect(() => {
fetch('/api/hermes-config')
fetch('/api/claude-config')
.then((r) => r.json())
.then((d: any) => {
setConfig((d.config?.agent as Record<string, unknown>) || {})
@@ -1487,7 +1487,7 @@ function AgentBehaviorContent() {
const save = async (key: string, value: unknown) => {
setMsg(null)
try {
await fetch('/api/hermes-config', {
await fetch('/api/claude-config', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ config: { agent: { [key]: value } } }),
@@ -1566,7 +1566,7 @@ function SmartRoutingContent() {
const [msg, setMsg] = useState<string | null>(null)
useEffect(() => {
fetch('/api/hermes-config')
fetch('/api/claude-config')
.then((r) => r.json())
.then((d: any) => {
setConfig(
@@ -1585,7 +1585,7 @@ function SmartRoutingContent() {
const save = async (key: string, value: unknown) => {
setMsg(null)
try {
await fetch('/api/hermes-config', {
await fetch('/api/claude-config', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -1678,7 +1678,7 @@ function VoiceContent() {
const [msg, setMsg] = useState<string | null>(null)
useEffect(() => {
fetch('/api/hermes-config')
fetch('/api/claude-config')
.then((r) => r.json())
.then((d: any) => {
setTts((d.config?.tts as Record<string, unknown>) || {})
@@ -1690,7 +1690,7 @@ function VoiceContent() {
const saveTts = async (key: string, value: unknown) => {
setMsg(null)
try {
await fetch('/api/hermes-config', {
await fetch('/api/claude-config', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ config: { tts: { [key]: value } } }),
@@ -1706,7 +1706,7 @@ function VoiceContent() {
const saveStt = async (key: string, value: unknown) => {
setMsg(null)
try {
await fetch('/api/hermes-config', {
await fetch('/api/claude-config', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ config: { stt: { [key]: value } } }),
@@ -1812,7 +1812,7 @@ function DisplayContent() {
const [msg, setMsg] = useState<string | null>(null)
useEffect(() => {
fetch('/api/hermes-config')
fetch('/api/claude-config')
.then((r) => r.json())
.then((d: any) => {
setConfig((d.config?.display as Record<string, unknown>) || {})
@@ -1823,7 +1823,7 @@ function DisplayContent() {
const save = async (key: string, value: unknown) => {
setMsg(null)
try {
await fetch('/api/hermes-config', {
await fetch('/api/claude-config', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ config: { display: { [key]: value } } }),
@@ -1931,7 +1931,7 @@ function LanguageContent() {
// ── Main Dialog ─────────────────────────────────────────────────────────
const CONTENT_MAP: Record<SectionId, () => React.JSX.Element> = {
hermes: HermesContent,
claude: ClaudeContent,
agent: AgentBehaviorContent,
routing: SmartRoutingContent,
voice: VoiceContent,
@@ -1951,7 +1951,7 @@ type SettingsDialogProps = {
export function SettingsDialog({
open,
onOpenChange,
initialSection = 'hermes',
initialSection = 'claude',
}: SettingsDialogProps) {
const [active, setActive] = useState<SectionId>(initialSection)
const [mobileView, setMobileView] = useState<'nav' | 'content'>('nav')
@@ -1979,7 +1979,7 @@ export function SettingsDialog({
Settings
</DialogTitle>
<DialogDescription className="sr-only">
Configure Hermes Workspace
Configure Claude Workspace
</DialogDescription>
</div>
<DialogClose

View File

@@ -3,7 +3,7 @@ import { cn } from '@/lib/utils'
export type SettingsNavId =
| 'connection'
| 'hermes'
| 'claude'
| 'agent'
| 'routing'
| 'voice'
@@ -18,7 +18,7 @@ type NavItem = { id: SettingsNavId; label: string }
export const SETTINGS_NAV_ITEMS: Array<NavItem> = [
{ id: 'connection', label: 'Connection' },
{ id: 'hermes', label: 'Model & Provider' },
{ id: 'claude', label: 'Model & Provider' },
{ id: 'agent', label: 'Agent Behavior' },
{ id: 'routing', label: 'Smart Routing' },
{ id: 'voice', label: 'Voice' },

View File

@@ -10,9 +10,9 @@ type ConnectionStatus = {
chatReady: boolean
modelConfigured: boolean
activeModel: string
chatMode: 'enhanced-hermes' | 'portable' | 'disconnected'
chatMode: 'enhanced-claude' | 'portable' | 'disconnected'
capabilities: Record<string, boolean>
hermesUrl: string
claudeUrl: string
}
async function fetchConnectionStatus(): Promise<ConnectionStatus> {
@@ -30,7 +30,7 @@ async function fetchConnectionStatus(): Promise<ConnectionStatus> {
activeModel: '',
chatMode: 'disconnected',
capabilities: {},
hermesUrl: '',
claudeUrl: '',
}
}
return response.json() as Promise<ConnectionStatus>
@@ -84,7 +84,7 @@ function buildTooltip(
if (!data.modelConfigured) parts.push('No model selected')
}
if (data.status === 'enhanced') {
parts.push('Hermes gateway enhancements detected')
parts.push('Claude gateway enhancements detected')
}
if (data.activeModel) parts.push(`Model: ${data.activeModel}`)
return parts.join(' · ')
@@ -96,7 +96,7 @@ function buildTooltip(
*/
export function StatusDot() {
const { data, isLoading } = useQuery({
queryKey: ['hermes', 'connection-status'],
queryKey: ['claude', 'connection-status'],
queryFn: fetchConnectionStatus,
refetchInterval: 15_000,
retry: false,
@@ -127,7 +127,7 @@ export function StatusIndicator({
inline?: boolean
}) {
const { data, isLoading } = useQuery({
queryKey: ['hermes', 'connection-status'],
queryKey: ['claude', 'connection-status'],
queryFn: fetchConnectionStatus,
refetchInterval: 15_000,
retry: false,

View File

@@ -26,7 +26,7 @@ type SwarmNodeChatProps = {
onCollapsedChange?: (next: boolean) => void
}
const STORAGE_PREFIX = 'hermes-swarm-chat-v1:'
const STORAGE_PREFIX = 'claude-swarm-chat-v1:'
function loadHistory(workerId: string): Message[] {
if (typeof window === 'undefined') return []
@@ -194,7 +194,7 @@ export function SwarmNodeChat({
>
{messages.length === 0 ? (
<div className="px-2 py-4 text-center text-[11px] text-emerald-100/50">
Inline dispatch to {workerId}. Real replies come from its Hermes
Inline dispatch to {workerId}. Real replies come from its Claude
profile.
</div>
) : (

View File

@@ -41,7 +41,7 @@ type RuntimeEntry = {
type HealthData = {
workspaceModel: string | null
hermesApiUrl: string | null
claudeApiUrl: string | null
workers: Array<{
workerId: string
recentAuthErrors: number

View File

@@ -60,7 +60,7 @@ export function DebugPanel({
{isLoading ? (
<div className="flex items-center gap-2 rounded-lg border border-primary-700/50 bg-primary-900/40 px-3 py-2 text-sm text-primary-300">
<BrailleSpinner
preset="hermes"
preset="claude"
size={18}
speed={100}
className="text-primary-400"

View File

@@ -23,7 +23,7 @@ const ACTIVE_TAB_KEY = 'terminal.active'
const DEFAULT_HEIGHT = 360
const MIN_HEIGHT = 300
const MAX_HEIGHT = 480
const DEFAULT_CWD = '~/.hermes'
const DEFAULT_CWD = '~/.claude'
type TerminalTabState = {
id: string

View File

@@ -62,7 +62,7 @@ type TerminalSessionResponse = {
sessionId?: string
}
const DEFAULT_TERMINAL_CWD = '~/.hermes'
const DEFAULT_TERMINAL_CWD = '~/.claude'
const TERMINAL_BG = '#0d0d0d'
function toDebugAnalysis(value: unknown): DebugAnalysis | null {

View File

@@ -58,9 +58,9 @@ const PRESETS: Record<string, Array<string>> = {
braille(D2, D5),
],
// Hermes caduceus motion 🦀
// Claude caduceus motion 🦀
// Open pincer → closing → gripped → releasing
hermes: [
claude: [
// Open wide - two "arms" spread
braille(D1, D4), // tips open
braille(D1, D2, D4, D5), // arms extending down
@@ -122,7 +122,7 @@ type BrailleSpinnerProps = {
}
function BrailleSpinnerComponent({
preset = 'hermes',
preset = 'claude',
size,
color,
speed = 100,
@@ -132,7 +132,7 @@ function BrailleSpinnerComponent({
const [frame, setFrame] = useState(0)
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
const frames = PRESETS[preset] ?? PRESETS.hermes
const frames = PRESETS[preset] ?? PRESETS.claude
useEffect(() => {
intervalRef.current = setInterval(() => {
@@ -168,7 +168,7 @@ export { BrailleSpinner, PRESETS as BRAILLE_PRESETS }
export type { BrailleSpinnerPreset, BrailleSpinnerProps }
// Usage:
// <BrailleSpinner /> — default hermes animation
// <BrailleSpinner /> — default claude animation
// <BrailleSpinner preset="braille" /> — classic rotating
// <BrailleSpinner preset="orbit" size={24} /> — orbiting dot, 24px
// <BrailleSpinner preset="hermes" color="var(--color-primary-500)" speed={120} />
// <BrailleSpinner preset="claude" color="var(--color-primary-500)" speed={120} />

View File

@@ -19,7 +19,7 @@ function ContextAlertModalComponent({
}: ContextAlertModalProps) {
const isCritical = threshold >= 90
const isDanger = threshold >= 75
// 35% is an early warning — Hermes auto-compacts at ~40%
// 35% is an early warning — Claude auto-compacts at ~40%
const barColor = isCritical
? 'bg-red-500'
@@ -102,7 +102,7 @@ function ContextAlertModalComponent({
? "Your conversation history is nearly at the model's limit. Responses may become less accurate as the model loses access to earlier context. You should start a new chat soon."
: isDanger
? 'Your conversation is getting long. The model may start forgetting earlier messages. Consider starting a new chat for best results.'
: 'Hermes will auto-compact your context soon (it triggers at ~40% usage). Older messages will be summarized. Consider writing a handoff or starting a new chat to preserve full context.'}
: 'Claude will auto-compact your context soon (it triggers at ~40% usage). Older messages will be summarized. Consider writing a handoff or starting a new chat to preserve full context.'}
</p>
</div>

View File

@@ -321,7 +321,7 @@ export function UsageDetailsModal({
<div>
<DialogTitle>Usage Overview</DialogTitle>
<DialogDescription>
Live usage from your Hermes session and connected providers.
Live usage from your Claude session and connected providers.
</DialogDescription>
</div>
<DialogClose className="text-primary-700">Close</DialogClose>

View File

@@ -4,7 +4,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'
import { cn } from '@/lib/utils'
const POLL_INTERVAL_MS = 30_000
const PREFERRED_PROVIDER_KEY = 'hermes-preferred-provider'
const PREFERRED_PROVIDER_KEY = 'claude-preferred-provider'
type UsageLine = {
type: 'progress' | 'text' | 'badge'

View File

@@ -16,8 +16,8 @@ import { SEARCH_MODAL_EVENTS } from '@/hooks/use-search-modal'
const POLL_INTERVAL_MS = 10_000
const PROVIDER_POLL_INTERVAL_MS = 30_000
const STORAGE_KEY = 'hermes-usage-meter-alerts'
const STATS_VIEW_STORAGE_KEY = 'hermes-stats-view'
const STORAGE_KEY = 'claude-usage-meter-alerts'
const STATS_VIEW_STORAGE_KEY = 'claude-stats-view'
const THRESHOLDS = [50, 75, 90]
type StatsView = 'session' | 'provider' | 'cost' | 'agents'
@@ -29,7 +29,7 @@ const STATS_VIEW_LABELS: Record<StatsView, string> = {
agents: 'Agent Activity',
}
const PREFERRED_PROVIDER_KEY = 'hermes-preferred-provider'
const PREFERRED_PROVIDER_KEY = 'claude-preferred-provider'
function getStoredPreferredProvider(): string | null {
if (typeof window === 'undefined') return null

View File

@@ -23,7 +23,7 @@ import {
import { useNavigate, useRouterState } from '@tanstack/react-router'
import { useQuery } from '@tanstack/react-query'
import type { SessionMeta } from '@/screens/chat/types'
import type { AuthStatus } from '@/lib/hermes-auth'
import type { AuthStatus } from '@/lib/claude-auth'
import { cn } from '@/lib/utils'
import { ConnectionStartupScreen } from '@/components/connection-startup-screen'
import { ChatSidebar } from '@/screens/chat/components/chat-sidebar'
@@ -39,9 +39,9 @@ import { MobileHamburgerMenu } from '@/components/mobile-hamburger-menu'
import { MobilePageHeader } from '@/components/mobile-page-header'
import { MobileTerminalInput } from '@/components/terminal/mobile-terminal-input'
import { HermesReconnectBanner } from '@/components/hermes-reconnect-banner'
import { ClaudeReconnectBanner } from '@/components/claude-reconnect-banner'
import { useMobileKeyboard } from '@/hooks/use-mobile-keyboard'
// System metrics footer removed — not used in Hermes Workspace
// System metrics footer removed — not used in Claude Workspace
import { CommandPalette } from '@/components/command-palette'
import { useSettings } from '@/hooks/use-settings'
// ActivityTicker moved to dashboard-only (too noisy for global header)
@@ -275,7 +275,7 @@ export function WorkspaceShell({ children }: WorkspaceShellProps) {
className="relative overflow-hidden theme-bg theme-text"
style={shellStyle}
>
<HermesReconnectBanner enabled={authState.checked} />
<ClaudeReconnectBanner enabled={authState.checked} />
{/* Electron: native-style title bar (absolute over the padding) */}
{isElectron && (
<div
@@ -295,7 +295,7 @@ export function WorkspaceShell({ children }: WorkspaceShellProps) {
className="text-[13px] font-medium select-none"
style={{ color: 'var(--theme-accent, #B98A44)' }}
>
Hermes
Claude
</span>
</div>
{/* Right spacer to balance */}

View File

@@ -1,8 +1,8 @@
// use-agent-outputs.ts
//
// Hermes-Workspace stub for the Operations "Outputs" tab.
// Claude-Workspace stub for the Operations "Outputs" tab.
// ControlSuite ships a richer implementation backed by the Codex agent
// activity feed. Hermes does not have that surface yet, so this returns
// activity feed. Claude does not have that surface yet, so this returns
// an empty list and a no-op refresher. Replace with a real hook when we
// wire up an outputs feed.

View File

@@ -1,14 +1,14 @@
import { useQuery } from '@tanstack/react-query'
export type ChatMode = 'enhanced-hermes' | 'portable' | 'disconnected'
export type ChatMode = 'enhanced-claude' | 'portable' | 'disconnected'
interface GatewayStatus {
capabilities: Record<string, boolean>
hermesUrl: string
claudeUrl: string
}
function deriveChatMode(capabilities: Record<string, boolean>): ChatMode {
if (capabilities.sessions) return 'enhanced-hermes'
if (capabilities.sessions) return 'enhanced-claude'
if (capabilities.chatCompletions || capabilities.health) return 'portable'
return 'disconnected'
}

View File

@@ -5,7 +5,7 @@ import { persist } from 'zustand/middleware'
export type ThemeMode = 'system' | 'light' | 'dark'
export type LoaderStyle =
| 'dots'
| 'braille-hermes'
| 'braille-claude'
| 'braille-orbit'
| 'braille-breathe'
| 'braille-pulse'

View File

@@ -1,4 +1,4 @@
// Stub — Hermes Workspace uses hermes-api.ts for chat streaming, not legacy SSE.
// Stub — Claude Workspace uses claude-api.ts for chat streaming, not legacy SSE.
// This hook is kept as a no-op to satisfy use-realtime-chat-history imports.
export function useChatStream(_opts: {

View File

@@ -3,7 +3,7 @@ import type { EnhancedFeature } from '@/lib/feature-gates'
interface GatewayStatus {
capabilities: Record<string, boolean>
hermesUrl: string
claudeUrl: string
}
export function useFeatureAvailable(feature: EnhancedFeature): boolean {

View File

@@ -8,7 +8,7 @@ import { useQuery } from '@tanstack/react-query'
interface GatewayStatus {
capabilities: Record<string, boolean>
hermesUrl: string
claudeUrl: string
}
function useGatewayStatus() {

View File

@@ -11,7 +11,7 @@ export interface Mode {
preferredPremiumModel?: string
}
const STORAGE_KEY = 'hermes-modes'
const STORAGE_KEY = 'claude-modes'
function loadModes(): Array<Mode> {
try {

View File

@@ -1,10 +1,10 @@
import { useEffect } from 'react'
const BASE_TITLE = 'Hermes'
const BASE_TITLE = 'Claude'
/**
* Sets document.title for the current page.
* Usage: usePageTitle('Sessions') → "Sessions — Hermes"
* Usage: usePageTitle('Sessions') → "Sessions — Claude"
*/
export function usePageTitle(page: string) {
useEffect(() => {

Some files were not shown because too many files have changed in this diff Show More