Commit Graph
15 Commits
Author SHA1 Message Date
william 732246d4fd Add MCP bridge to claude-agent so Hermes can delegate to the real Claude Code CLI
New POST /mcp endpoint (Streamable HTTP transport, stateless — fresh
McpServer+transport per request) exposing one tool, ask_claude_code: runs
the real `claude` binary against a prompt, billed against the Pro/Max
subscription rather than API credits. This works specifically because it's
the actual claude CLI making the request server-side — the same reason
Hermes itself can't authenticate with the subscription directly (proven
earlier: Anthropic rejects the OAuth token from any client that isn't the
real CLI's exact request fingerprint). Read-only: no Edit/Write/git-push/
git-commit tools, since this is a quick-answer bridge, not a repo editor.

Tested end-to-end locally (built + ran the image, curled the full MCP
handshake: initialize -> tools/list -> tools/call) before pushing — got a
real 'pong' back from the actual claude CLI through the MCP protocol.

To register it with Hermes (lives in its own data volume, not git — see
.env.example comment):
  docker exec hermes hermes config set mcp_servers.claude-code.url http://claude-agent:3001/mcp
  docker exec hermes hermes config set 'mcp_servers.claude-code.headers.Authorization' 'Bearer <MCP_BRIDGE_KEY>'
2026-08-23 17:24:46 +00:00
william 143be8200e Drop the Traefik-level basic auth layer — keep only Hermes's own login
By request: one auth layer, not two. Note this isn't really removing a
layer I added on top of nothing — Hermes's own gate is mandatory and
can't be disabled while the dashboard is reachable through a separate
Traefik container (it fails closed at startup on any non-loopback bind
without a configured auth provider). The only thing actually optional
was the Traefik-level middleware, so that's what comes out; Traefik now
just does TLS termination + routing.
2026-08-23 17:02:59 +00:00
william 9739676409 Add Hermes web dashboard at hermes.apps.williamturner.eu
Enabled via HERMES_DASHBOARD=1 (supervised in-container alongside the
gateway, per docs/user-guide/docker.md), bound to 0.0.0.0:9119 so Traefik
can reach it. Two independent auth layers, not one:

1. Traefik basicauth middleware in front of the whole route.
2. Hermes's own basic-auth gate (mandatory once the bind is non-loopback).

Hermes's docs explicitly call basic-auth-alone "not suitable for direct
public-internet exposure" and cite a real June 2026 incident where
internet scanners reached exposed dashboards and drove agents into
planting SSH-key backdoors — hence the extra Traefik-level gate rather
than relying on Hermes's own login page alone.

Also fixes: the htpasswd hash for Traefik's basicauth needs its literal
'$' characters escaped as '2824147' in .env, or docker compose's own variable
interpolation corrupts it (mistook '' for further
 references). Also switched the hash from Python's default SHA-512
crypt ('$...') to apr1 ('$...', via openssl passwd -apr1) —
Traefik's basicauth middleware doesn't accept SHA-512-crypt.

Also re-adds Hermes's OPENAI_BASE_URL/OPENAI_API_KEY routing through the
local litellm gateway (instead of OPENROUTER_API_KEY direct) — this was
part of the now-abandoned PR #12 and never actually landed on main.
2026-08-23 16:21:50 +00:00
william 6bc862e051 Remove claude-agent's Matrix presence entirely — Hermes is the only agent in Matrix
By request: one agent in Matrix, not several. Removes matrixBot.js,
router.js (chat-vs-code-task classifier), litellm.js (claude-agent's own
LiteLLM client), the matrix-bot-sdk dependency, runChatTask() and its
gitea.js branch/PR helpers (createBranch/createPullRequest — only ever
called from the now-removed chat flow), and every Matrix/LiteLLM env var
from claude-agent's compose service.

claude-agent already left the control room manually before this merge.
It keeps its Gitea-webhook-triggered PR review, which never touched Matrix
or LiteLLM to begin with.

Makes PR #12 (the claude-bot/Hermes cross-reply cascade fix) moot — the
bug can't happen once claude-agent has no Matrix client at all. Close #12
without merging once this lands.
2026-08-23 16:16:19 +00:00
william 98762e764a Fix Hermes container: add gateway command, disable network-reachable API server
Without an explicit command the image launches the interactive CLI by
default, which immediately exits with 'Input is not a terminal' in a
detached container — it was doing nothing on every restart. Also disables
API_SERVER_ENABLED: Hermes itself warns at startup that a network-reachable
API server combined with the default unsandboxed 'local' terminal backend
gives any caller on the network full terminal/file access. Not needed yet
(Matrix is the actual interface) — can re-enable properly (with a sandboxed
terminal backend) if claude-agent ever needs to call Hermes programmatically.
2026-08-23 15:52:08 +00:00
william 9fa025f7d5 Add Hermes Agent as a second native Matrix presence
Deployed as its own service (pinned nousresearch/hermes-agent:v2026.8.19),
own Matrix bot account (@hermes), own OpenRouter-backed model config, and
its own OpenAI-compatible API server (internal network only, for possible
future use by claude-agent). Joins the same control room but only responds
when explicitly @mentioned, restricted to the human user — no conflict with
claude-bot's default no-prefix chat routing. claude-agent's router now
ignores messages addressed to @hermes so both bots don't answer the same
message.

Bridge networking (the 'web' network), not the image's default host mode —
no reason for an agent container to share the host's network namespace when
everything it needs (the homeserver, OpenRouter) is reachable over the
existing bridge.
2026-08-23 15:46:58 +00:00
william f93bfb25a2 Route all chat through a local LiteLLM gateway, drop command prefixes
- New litellm service (pinned v1.98.0 — litellm 1.82.7/1.82.8 on PyPI were
  compromised with credential-stealing malware in March 2026; internal-only,
  no Traefik route, no reason to expose an LLM gateway with a master key
  publicly).
- Replaces !claude/!ai command prefixes with automatic routing: every plain
  message in the control room goes through a classifier (router.js) that
  decides chat vs code_task. Chat replies use OpenRouter's own auto-router
  (openrouter/auto) via LiteLLM; code_task requests go through the existing
  runChatTask() flow (Claude Code CLI, unchanged, still using the
  subscription token directly).
- Investigated routing Claude itself through LiteLLM via OAuth token
  forwarding (general_settings.forward_client_headers_to_llm_api) so the
  Pro/Max subscription could be one of the auto-routable options. Confirmed
  non-functional: Anthropic returns a generic rate_limit_error for any
  direct API call using this token type outside the real Claude Code CLI,
  reproduced with plain curl straight to api.anthropic.com. Not included.
- MATRIX_BOT_USER_ID now required and set explicitly (self-message filtering
  can no longer rely on a command-prefix mismatch once there isn't one).
2026-08-23 15:32:55 +00:00
william 77ce4a7334 Add !ai Matrix command for querying other models via OpenRouter
build-agent / build-and-push (push) Successful in 7s
2026-08-23 14:40:46 +00:00
william ec5ca6ff85 Use Claude subscription auth (CLAUDE_CODE_OAUTH_TOKEN) instead of API billing 2026-08-23 11:35:16 +00:00
william 31d0923fdf Remove act_runner config file/mount — unreliable under Portainer git-stack deploy, and unnecessary
The bind-mounted relative path resolved to nothing once Portainer's cloned
checkout no longer persisted for the container's runtime, so Docker silently
created an empty directory there and act_runner crash-looped. act_runner
already auto-detects its own docker.sock without any custom config.
2026-08-23 11:21:27 +00:00
william 11d2ae8cbb Move Traefik out of this stack into its own standalone stack
Fixes a circular dependency: a full stack redeploy tears every service down
before bringing any back up, but claude-agent's image pull goes through
Traefik->Gitea's registry. Self-hosting Traefik in the same stack it fronts
means redeploying this stack could tear down the very route needed to bring
it back. Traefik now lives in ~/traefik/docker-compose.yml as shared infra,
independent of this stack's lifecycle.
2026-08-23 11:19:55 +00:00
william aca1be8fd2 Fix claude-agent env vars for Portainer git-stack deploy (env_file doesn't work there) 2026-08-23 11:15:37 +00:00
william 0dcf5488d2 Fix Actions runner: use docker-capable job image, mount docker socket, add manual trigger
build-agent / build-and-push (push) Failing after 0s
2026-08-23 10:53:18 +00:00
william 622e35f979 Fix Traefik/Docker API version mismatch, tidy Matrix registration-token env handling 2026-08-23 10:49:11 +00:00
william 357791d6e3 Scaffold Matrix + GitOps + Claude automation stack
build-agent / build-and-push (push) Failing after 18s
2026-08-23 10:25:57 +00:00