initial commit
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
---
|
||||
description: Skills for spawning and orchestrating autonomous AI coding agents and multi-agent workflows — running independent agent processes, delegating tasks, and coordinating parallel workstreams.
|
||||
---
|
||||
@@ -0,0 +1,801 @@
|
||||
---
|
||||
name: claude-code
|
||||
description: "Delegate coding to Claude Code CLI (features, PRs)."
|
||||
version: 2.2.0
|
||||
author: Hermes Agent + Teknium
|
||||
license: MIT
|
||||
platforms: [linux, macos, windows]
|
||||
metadata:
|
||||
hermes:
|
||||
tags: [Coding-Agent, Claude, Anthropic, Code-Review, Refactoring, PTY, Automation]
|
||||
related_skills: [codex, hermes-agent, opencode]
|
||||
---
|
||||
|
||||
# Claude Code — Hermes Orchestration Guide
|
||||
|
||||
Delegate coding tasks to [Claude Code](https://code.claude.com/docs/en/cli-reference) (Anthropic's autonomous coding agent CLI) via the Hermes terminal. Claude Code v2.x can read files, write code, run shell commands, spawn subagents, and manage git workflows autonomously.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Install:** `npm install -g @anthropic-ai/claude-code`
|
||||
- **Auth:** run `claude` once to log in (browser OAuth for Pro/Max, or set `ANTHROPIC_API_KEY`)
|
||||
- **Console auth:** `claude auth login --console` for API key billing
|
||||
- **SSO auth:** `claude auth login --sso` for Enterprise
|
||||
- **Check status:** `claude auth status` (JSON) or `claude auth status --text` (human-readable)
|
||||
- **Health check:** `claude doctor` — checks auto-updater and installation health
|
||||
- **Version check:** `claude --version` (requires v2.x+)
|
||||
- **Update:** `claude update` or `claude upgrade`
|
||||
|
||||
## Two Orchestration Modes
|
||||
|
||||
Hermes interacts with Claude Code in two fundamentally different ways. Choose based on the task.
|
||||
|
||||
### Mode 1: Print Mode (`-p`) — Non-Interactive (PREFERRED for most tasks)
|
||||
|
||||
Print mode runs a one-shot task, returns the result, and exits. No PTY needed. No interactive prompts. This is the cleanest integration path.
|
||||
|
||||
```
|
||||
terminal(command="claude -p 'Add error handling to all API calls in src/' --allowedTools 'Read,Edit' --max-turns 10", workdir="/path/to/project", timeout=120)
|
||||
```
|
||||
|
||||
**When to use print mode:**
|
||||
- One-shot coding tasks (fix a bug, add a feature, refactor)
|
||||
- CI/CD automation and scripting
|
||||
- Structured data extraction with `--json-schema`
|
||||
- Piped input processing (`cat file | claude -p "analyze this"`)
|
||||
- Any task where you don't need multi-turn conversation
|
||||
|
||||
**Print mode skips ALL interactive dialogs** — no workspace trust prompt, no permission confirmations. This makes it ideal for automation.
|
||||
|
||||
### Mode 2: Interactive PTY via tmux — Multi-Turn Sessions
|
||||
|
||||
Interactive mode gives you a full conversational REPL where you can send follow-up prompts, use slash commands, and watch Claude work in real time. **Requires tmux orchestration.**
|
||||
|
||||
```
|
||||
# Start a tmux session
|
||||
terminal(command="tmux new-session -d -s claude-work -x 140 -y 40")
|
||||
|
||||
# Launch Claude Code inside it
|
||||
terminal(command="tmux send-keys -t claude-work 'cd /path/to/project && claude' Enter")
|
||||
|
||||
# Wait for startup, then send your task
|
||||
# (after ~3-5 seconds for the welcome screen)
|
||||
terminal(command="sleep 5 && tmux send-keys -t claude-work 'Refactor the auth module to use JWT tokens' Enter")
|
||||
|
||||
# Monitor progress by capturing the pane
|
||||
terminal(command="sleep 15 && tmux capture-pane -t claude-work -p -S -50")
|
||||
|
||||
# Send follow-up tasks
|
||||
terminal(command="tmux send-keys -t claude-work 'Now add unit tests for the new JWT code' Enter")
|
||||
|
||||
# Exit when done
|
||||
terminal(command="tmux send-keys -t claude-work '/exit' Enter")
|
||||
```
|
||||
|
||||
**When to use interactive mode:**
|
||||
- Multi-turn iterative work (refactor → review → fix → test cycle)
|
||||
- Tasks requiring human-in-the-loop decisions
|
||||
- Exploratory coding sessions
|
||||
- When you need to use Claude's slash commands (`/compact`, `/review`, `/model`)
|
||||
|
||||
## PTY Dialog Handling (CRITICAL for Interactive Mode)
|
||||
|
||||
Claude Code presents up to two confirmation dialogs on first launch. You MUST handle these via tmux send-keys:
|
||||
|
||||
### Dialog 1: Workspace Trust (first visit to a directory)
|
||||
```
|
||||
❯ 1. Yes, I trust this folder ← DEFAULT (just press Enter)
|
||||
2. No, exit
|
||||
```
|
||||
**Handling:** `tmux send-keys -t <session> Enter` — default selection is correct.
|
||||
|
||||
### Dialog 2: Bypass Permissions Warning (only with --dangerously-skip-permissions)
|
||||
```
|
||||
❯ 1. No, exit ← DEFAULT (WRONG choice!)
|
||||
2. Yes, I accept
|
||||
```
|
||||
**Handling:** Must navigate DOWN first, then Enter:
|
||||
```
|
||||
tmux send-keys -t <session> Down && sleep 0.3 && tmux send-keys -t <session> Enter
|
||||
```
|
||||
|
||||
### Robust Dialog Handling Pattern
|
||||
```
|
||||
# Launch with permissions bypass
|
||||
terminal(command="tmux send-keys -t claude-work 'claude --dangerously-skip-permissions \"your task\"' Enter")
|
||||
|
||||
# Handle trust dialog (Enter for default "Yes")
|
||||
terminal(command="sleep 4 && tmux send-keys -t claude-work Enter")
|
||||
|
||||
# Handle permissions dialog (Down then Enter for "Yes, I accept")
|
||||
terminal(command="sleep 3 && tmux send-keys -t claude-work Down && sleep 0.3 && tmux send-keys -t claude-work Enter")
|
||||
|
||||
# Now wait for Claude to work
|
||||
terminal(command="sleep 15 && tmux capture-pane -t claude-work -p -S -60")
|
||||
```
|
||||
|
||||
**Note:** After the first trust acceptance for a directory, the trust dialog won't appear again. Only the permissions dialog recurs each time you use `--dangerously-skip-permissions`.
|
||||
|
||||
## CLI Subcommands
|
||||
|
||||
| Subcommand | Purpose |
|
||||
|------------|---------|
|
||||
| `claude` | Start interactive REPL |
|
||||
| `claude "query"` | Start REPL with initial prompt |
|
||||
| `claude -p "query"` | Print mode (non-interactive, exits when done) |
|
||||
| `cat file \| claude -p "query"` | Pipe content as stdin context |
|
||||
| `claude -c` | Continue the most recent conversation in this directory |
|
||||
| `claude -r "id"` | Resume a specific session by ID or name |
|
||||
| `claude auth login` | Sign in (add `--console` for API billing, `--sso` for Enterprise) |
|
||||
| `claude auth status` | Check login status (returns JSON; `--text` for human-readable) |
|
||||
| `claude mcp add <name> -- <cmd>` | Add an MCP server |
|
||||
| `claude mcp list` | List configured MCP servers |
|
||||
| `claude mcp remove <name>` | Remove an MCP server |
|
||||
| `claude agents` | List configured agents |
|
||||
| `claude doctor` | Run health checks on installation and auto-updater |
|
||||
| `claude update` / `claude upgrade` | Update Claude Code to latest version |
|
||||
| `claude remote-control` | Start server to control Claude from claude.ai or mobile app |
|
||||
| `claude install [target]` | Install native build (stable, latest, or specific version) |
|
||||
| `claude setup-token` | Set up long-lived auth token (requires subscription) |
|
||||
| `claude plugin` / `claude plugins` | Manage Claude Code plugins |
|
||||
| `claude auto-mode` | Inspect auto mode classifier configuration |
|
||||
|
||||
## Print Mode Deep Dive
|
||||
|
||||
### Structured JSON Output
|
||||
```
|
||||
terminal(command="claude -p 'Analyze auth.py for security issues' --output-format json --max-turns 5", workdir="/project", timeout=120)
|
||||
```
|
||||
|
||||
Returns a JSON object with:
|
||||
```json
|
||||
{
|
||||
"type": "result",
|
||||
"subtype": "success",
|
||||
"result": "The analysis text...",
|
||||
"session_id": "75e2167f-...",
|
||||
"num_turns": 3,
|
||||
"total_cost_usd": 0.0787,
|
||||
"duration_ms": 10276,
|
||||
"stop_reason": "end_turn",
|
||||
"terminal_reason": "completed",
|
||||
"usage": { "input_tokens": 5, "output_tokens": 603, ... },
|
||||
"modelUsage": { "claude-sonnet-4-6": { "costUSD": 0.078, "contextWindow": 200000 } }
|
||||
}
|
||||
```
|
||||
|
||||
**Key fields:** `session_id` for resumption, `num_turns` for agentic loop count, `total_cost_usd` for spend tracking, `subtype` for success/error detection (`success`, `error_max_turns`, `error_budget`).
|
||||
|
||||
### Streaming JSON Output
|
||||
For real-time token streaming, use `stream-json` with `--verbose`:
|
||||
```
|
||||
terminal(command="claude -p 'Write a summary' --output-format stream-json --verbose --include-partial-messages", timeout=60)
|
||||
```
|
||||
|
||||
Returns newline-delimited JSON events. Filter with jq for live text:
|
||||
```
|
||||
claude -p "Explain X" --output-format stream-json --verbose --include-partial-messages | \
|
||||
jq -rj 'select(.type == "stream_event" and .event.delta.type? == "text_delta") | .event.delta.text'
|
||||
```
|
||||
|
||||
Stream events include `system/api_retry` with `attempt`, `max_retries`, and `error` fields (e.g., `rate_limit`, `billing_error`).
|
||||
|
||||
### Bidirectional Streaming
|
||||
For real-time input AND output streaming:
|
||||
```
|
||||
claude -p "task" --input-format stream-json --output-format stream-json --replay-user-messages
|
||||
```
|
||||
`--replay-user-messages` re-emits user messages on stdout for acknowledgment.
|
||||
|
||||
### Piped Input
|
||||
```
|
||||
# Pipe a file for analysis
|
||||
terminal(command="cat src/auth.py | claude -p 'Review this code for bugs' --max-turns 1", timeout=60)
|
||||
|
||||
# Pipe multiple files
|
||||
terminal(command="cat src/*.py | claude -p 'Find all TODO comments' --max-turns 1", timeout=60)
|
||||
|
||||
# Pipe command output
|
||||
terminal(command="git diff HEAD~3 | claude -p 'Summarize these changes' --max-turns 1", timeout=60)
|
||||
```
|
||||
|
||||
### JSON Schema for Structured Extraction
|
||||
```
|
||||
terminal(command="claude -p 'List all functions in src/' --output-format json --json-schema '{\"type\":\"object\",\"properties\":{\"functions\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}}},\"required\":[\"functions\"]}' --max-turns 5", workdir="/project", timeout=90)
|
||||
```
|
||||
|
||||
Parse `structured_output` from the JSON result. Claude validates output against the schema before returning.
|
||||
|
||||
### Session Continuation
|
||||
```
|
||||
# Start a task
|
||||
terminal(command="claude -p 'Start refactoring the database layer' --output-format json --max-turns 10 > /tmp/session.json", workdir="/project", timeout=180)
|
||||
|
||||
# Resume with session ID
|
||||
terminal(command="claude -p 'Continue and add connection pooling' --resume $(cat /tmp/session.json | python3 -c 'import json,sys; print(json.load(sys.stdin)[\"session_id\"])') --max-turns 5", workdir="/project", timeout=120)
|
||||
|
||||
# Or resume the most recent session in the same directory
|
||||
terminal(command="claude -p 'What did you do last time?' --continue --max-turns 1", workdir="/project", timeout=30)
|
||||
|
||||
# Fork a session (new ID, keeps history)
|
||||
terminal(command="claude -p 'Try a different approach' --resume <id> --fork-session --max-turns 10", workdir="/project", timeout=120)
|
||||
```
|
||||
|
||||
### Bare Mode for CI/Scripting
|
||||
```
|
||||
terminal(command="claude --bare -p 'Run all tests and report failures' --allowedTools 'Read,Bash' --max-turns 10", workdir="/project", timeout=180)
|
||||
```
|
||||
|
||||
`--bare` skips hooks, plugins, MCP discovery, and CLAUDE.md loading. Fastest startup. Requires `ANTHROPIC_API_KEY` (skips OAuth).
|
||||
|
||||
To selectively load context in bare mode:
|
||||
| To load | Flag |
|
||||
|---------|------|
|
||||
| System prompt additions | `--append-system-prompt "text"` or `--append-system-prompt-file path` |
|
||||
| Settings | `--settings <file-or-json>` |
|
||||
| MCP servers | `--mcp-config <file-or-json>` |
|
||||
| Custom agents | `--agents '<json>'` |
|
||||
|
||||
### Fallback Model for Overload
|
||||
```
|
||||
terminal(command="claude -p 'task' --fallback-model haiku --max-turns 5", timeout=90)
|
||||
```
|
||||
Automatically falls back to the specified model when the default is overloaded (print mode only).
|
||||
|
||||
## Complete CLI Flags Reference
|
||||
|
||||
### Session & Environment
|
||||
| Flag | Effect |
|
||||
|------|--------|
|
||||
| `-p, --print` | Non-interactive one-shot mode (exits when done) |
|
||||
| `-c, --continue` | Resume most recent conversation in current directory |
|
||||
| `-r, --resume <id>` | Resume specific session by ID or name (interactive picker if no ID) |
|
||||
| `--fork-session` | When resuming, create new session ID instead of reusing original |
|
||||
| `--session-id <uuid>` | Use a specific UUID for the conversation |
|
||||
| `--no-session-persistence` | Don't save session to disk (print mode only) |
|
||||
| `--add-dir <paths...>` | Grant Claude access to additional working directories |
|
||||
| `-w, --worktree [name]` | Run in an isolated git worktree at `.claude/worktrees/<name>` |
|
||||
| `--tmux` | Create a tmux session for the worktree (requires `--worktree`) |
|
||||
| `--ide` | Auto-connect to a valid IDE on startup |
|
||||
| `--chrome` / `--no-chrome` | Enable/disable Chrome browser integration for web testing |
|
||||
| `--from-pr [number]` | Resume session linked to a specific GitHub PR |
|
||||
| `--file <specs...>` | File resources to download at startup (format: `file_id:relative_path`) |
|
||||
|
||||
### Model & Performance
|
||||
| Flag | Effect |
|
||||
|------|--------|
|
||||
| `--model <alias>` | Model selection: `sonnet`, `opus`, `haiku`, or full name like `claude-sonnet-4-6` |
|
||||
| `--effort <level>` | Reasoning depth: `low`, `medium`, `high`, `max`, `auto` | Both |
|
||||
| `--max-turns <n>` | Limit agentic loops (print mode only; prevents runaway) |
|
||||
| `--max-budget-usd <n>` | Cap API spend in dollars (print mode only) |
|
||||
| `--fallback-model <model>` | Auto-fallback when default model is overloaded (print mode only) |
|
||||
| `--betas <betas...>` | Beta headers to include in API requests (API key users only) |
|
||||
|
||||
### Permission & Safety
|
||||
| Flag | Effect |
|
||||
|------|--------|
|
||||
| `--dangerously-skip-permissions` | Auto-approve ALL tool use (file writes, bash, network, etc.) |
|
||||
| `--allow-dangerously-skip-permissions` | Enable bypass as an *option* without enabling it by default |
|
||||
| `--permission-mode <mode>` | `default`, `acceptEdits`, `plan`, `auto`, `dontAsk`, `bypassPermissions` |
|
||||
| `--allowedTools <tools...>` | Whitelist specific tools (comma or space-separated) |
|
||||
| `--disallowedTools <tools...>` | Blacklist specific tools |
|
||||
| `--tools <tools...>` | Override built-in tool set (`""` = none, `"default"` = all, or tool names) |
|
||||
|
||||
### Output & Input Format
|
||||
| Flag | Effect |
|
||||
|------|--------|
|
||||
| `--output-format <fmt>` | `text` (default), `json` (single result object), `stream-json` (newline-delimited) |
|
||||
| `--input-format <fmt>` | `text` (default) or `stream-json` (real-time streaming input) |
|
||||
| `--json-schema <schema>` | Force structured JSON output matching a schema |
|
||||
| `--verbose` | Full turn-by-turn output |
|
||||
| `--include-partial-messages` | Include partial message chunks as they arrive (stream-json + print) |
|
||||
| `--replay-user-messages` | Re-emit user messages on stdout (stream-json bidirectional) |
|
||||
|
||||
### System Prompt & Context
|
||||
| Flag | Effect |
|
||||
|------|--------|
|
||||
| `--append-system-prompt <text>` | **Add** to the default system prompt (preserves built-in capabilities) |
|
||||
| `--append-system-prompt-file <path>` | **Add** file contents to the default system prompt |
|
||||
| `--system-prompt <text>` | **Replace** the entire system prompt (use --append instead usually) |
|
||||
| `--system-prompt-file <path>` | **Replace** the system prompt with file contents |
|
||||
| `--bare` | Skip hooks, plugins, MCP discovery, CLAUDE.md, OAuth (fastest startup) |
|
||||
| `--agents '<json>'` | Define custom subagents dynamically as JSON |
|
||||
| `--mcp-config <path>` | Load MCP servers from JSON file (repeatable) |
|
||||
| `--strict-mcp-config` | Only use MCP servers from `--mcp-config`, ignoring all other MCP configs |
|
||||
| `--settings <file-or-json>` | Load additional settings from a JSON file or inline JSON |
|
||||
| `--setting-sources <sources>` | Comma-separated sources to load: `user`, `project`, `local` |
|
||||
| `--plugin-dir <paths...>` | Load plugins from directories for this session only |
|
||||
| `--disable-slash-commands` | Disable all skills/slash commands |
|
||||
|
||||
### Debugging
|
||||
| Flag | Effect |
|
||||
|------|--------|
|
||||
| `-d, --debug [filter]` | Enable debug logging with optional category filter (e.g., `"api,hooks"`, `"!1p,!file"`) |
|
||||
| `--debug-file <path>` | Write debug logs to file (implicitly enables debug mode) |
|
||||
|
||||
### Agent Teams
|
||||
| Flag | Effect |
|
||||
|------|--------|
|
||||
| `--teammate-mode <mode>` | How agent teams display: `auto`, `in-process`, or `tmux` |
|
||||
| `--brief` | Enable `SendUserMessage` tool for agent-to-user communication |
|
||||
|
||||
### Tool Name Syntax for --allowedTools / --disallowedTools
|
||||
```
|
||||
Read # All file reading
|
||||
Edit # File editing (existing files)
|
||||
Write # File creation (new files)
|
||||
Bash # All shell commands
|
||||
Bash(git *) # Only git commands
|
||||
Bash(git commit *) # Only git commit commands
|
||||
Bash(npm run lint:*) # Pattern matching with wildcards
|
||||
WebSearch # Web search capability
|
||||
WebFetch # Web page fetching
|
||||
mcp__<server>__<tool> # Specific MCP tool
|
||||
```
|
||||
|
||||
## Settings & Configuration
|
||||
|
||||
### Settings Hierarchy (highest to lowest priority)
|
||||
1. **CLI flags** — override everything
|
||||
2. **Local project:** `.claude/settings.local.json` (personal, gitignored)
|
||||
3. **Project:** `.claude/settings.json` (shared, git-tracked)
|
||||
4. **User:** `~/.claude/settings.json` (global)
|
||||
|
||||
### Permissions in Settings
|
||||
```json
|
||||
{
|
||||
"permissions": {
|
||||
"allow": ["Bash(npm run lint:*)", "WebSearch", "Read"],
|
||||
"ask": ["Write(*.ts)", "Bash(git push*)"],
|
||||
"deny": ["Read(.env)", "Bash(rm -rf *)"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Memory Files (CLAUDE.md) Hierarchy
|
||||
1. **Global:** `~/.claude/CLAUDE.md` — applies to all projects
|
||||
2. **Project:** `./CLAUDE.md` — project-specific context (git-tracked)
|
||||
3. **Local:** `.claude/CLAUDE.local.md` — personal project overrides (gitignored)
|
||||
|
||||
Use the `#` prefix in interactive mode to quickly add to memory: `# Always use 2-space indentation`.
|
||||
|
||||
## Interactive Session: Slash Commands
|
||||
|
||||
### Session & Context
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `/help` | Show all commands (including custom and MCP commands) |
|
||||
| `/compact [focus]` | Compress context to save tokens; CLAUDE.md survives compaction. E.g., `/compact focus on auth logic` |
|
||||
| `/clear` | Wipe conversation history for a fresh start |
|
||||
| `/context` | Visualize context usage as a colored grid with optimization tips |
|
||||
| `/cost` | View token usage with per-model and cache-hit breakdowns |
|
||||
| `/resume` | Switch to or resume a different session |
|
||||
| `/rewind` | Revert to a previous checkpoint in conversation or code |
|
||||
| `/btw <question>` | Ask a side question without adding to context cost |
|
||||
| `/status` | Show version, connectivity, and session info |
|
||||
| `/todos` | List tracked action items from the conversation |
|
||||
| `/exit` or `Ctrl+D` | End session |
|
||||
|
||||
### Development & Review
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `/review` | Request code review of current changes |
|
||||
| `/security-review` | Perform security analysis of current changes |
|
||||
| `/plan [description]` | Enter Plan mode with auto-start for task planning |
|
||||
| `/loop [interval]` | Schedule recurring tasks within the session |
|
||||
| `/batch` | Auto-create worktrees for large parallel changes (5-30 worktrees) |
|
||||
|
||||
### Configuration & Tools
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `/model [model]` | Switch models mid-session (use arrow keys to adjust effort) |
|
||||
| `/effort [level]` | Set reasoning effort: `low`, `medium`, `high`, `max`, or `auto` |
|
||||
| `/init` | Create a CLAUDE.md file for project memory |
|
||||
| `/memory` | Open CLAUDE.md for editing |
|
||||
| `/config` | Open interactive settings configuration |
|
||||
| `/permissions` | View/update tool permissions |
|
||||
| `/agents` | Manage specialized subagents |
|
||||
| `/mcp` | Interactive UI to manage MCP servers |
|
||||
| `/add-dir` | Add additional working directories (useful for monorepos) |
|
||||
| `/usage` | Show plan limits and rate limit status |
|
||||
| `/voice` | Enable push-to-talk voice mode (20 languages; hold Space to record, release to send) |
|
||||
| `/release-notes` | Interactive picker for version release notes |
|
||||
|
||||
### Custom Slash Commands
|
||||
Create `.claude/commands/<name>.md` (project-shared) or `~/.claude/commands/<name>.md` (personal):
|
||||
|
||||
```markdown
|
||||
# .claude/commands/deploy.md
|
||||
Run the deploy pipeline:
|
||||
1. Run all tests
|
||||
2. Build the Docker image
|
||||
3. Push to registry
|
||||
4. Update the $ARGUMENTS environment (default: staging)
|
||||
```
|
||||
|
||||
Usage: `/deploy production` — `$ARGUMENTS` is replaced with the user's input.
|
||||
|
||||
### Skills (Natural Language Invocation)
|
||||
Unlike slash commands (manually invoked), skills in `.claude/skills/` are markdown guides that Claude invokes automatically via natural language when the task matches:
|
||||
|
||||
```markdown
|
||||
# .claude/skills/database-migration.md
|
||||
When asked to create or modify database migrations:
|
||||
1. Use Alembic for migration generation
|
||||
2. Always create a rollback function
|
||||
3. Test migrations against a local database copy
|
||||
```
|
||||
|
||||
## Interactive Session: Keyboard Shortcuts
|
||||
|
||||
### General Controls
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| `Ctrl+C` | Cancel current input or generation |
|
||||
| `Ctrl+D` | Exit session |
|
||||
| `Ctrl+R` | Reverse search command history |
|
||||
| `Ctrl+B` | Background a running task |
|
||||
| `Ctrl+V` | Paste image into conversation |
|
||||
| `Ctrl+O` | Transcript mode — see Claude's thinking process |
|
||||
| `Ctrl+G` or `Ctrl+X Ctrl+E` | Open prompt in external editor |
|
||||
| `Esc Esc` | Rewind conversation or code state / summarize |
|
||||
|
||||
### Mode Toggles
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| `Shift+Tab` | Cycle permission modes (Normal → Auto-Accept → Plan) |
|
||||
| `Alt+P` | Switch model |
|
||||
| `Alt+T` | Toggle thinking mode |
|
||||
| `Alt+O` | Toggle Fast Mode |
|
||||
|
||||
### Multiline Input
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| `\` + `Enter` | Quick newline |
|
||||
| `Shift+Enter` | Newline (alternative) |
|
||||
| `Ctrl+J` | Newline (alternative) |
|
||||
|
||||
### Input Prefixes
|
||||
| Prefix | Action |
|
||||
|--------|--------|
|
||||
| `!` | Execute bash directly, bypassing AI (e.g., `!npm test`). Use `!` alone to toggle shell mode. |
|
||||
| `@` | Reference files/directories with autocomplete (e.g., `@./src/api/`) |
|
||||
| `#` | Quick add to CLAUDE.md memory (e.g., `# Use 2-space indentation`) |
|
||||
| `/` | Slash commands |
|
||||
|
||||
### Pro Tip: "ultrathink"
|
||||
Use the keyword "ultrathink" in your prompt for maximum reasoning effort on a specific turn. This triggers the deepest thinking mode regardless of the current `/effort` setting.
|
||||
|
||||
## PR Review Pattern
|
||||
|
||||
### Quick Review (Print Mode)
|
||||
```
|
||||
terminal(command="cd /path/to/repo && git diff main...feature-branch | claude -p 'Review this diff for bugs, security issues, and style problems. Be thorough.' --max-turns 1", timeout=60)
|
||||
```
|
||||
|
||||
### Deep Review (Interactive + Worktree)
|
||||
```
|
||||
terminal(command="tmux new-session -d -s review -x 140 -y 40")
|
||||
terminal(command="tmux send-keys -t review 'cd /path/to/repo && claude -w pr-review' Enter")
|
||||
terminal(command="sleep 5 && tmux send-keys -t review Enter") # Trust dialog
|
||||
terminal(command="sleep 2 && tmux send-keys -t review 'Review all changes vs main. Check for bugs, security issues, race conditions, and missing tests.' Enter")
|
||||
terminal(command="sleep 30 && tmux capture-pane -t review -p -S -60")
|
||||
```
|
||||
|
||||
### PR Review from Number
|
||||
```
|
||||
terminal(command="claude -p 'Review this PR thoroughly' --from-pr 42 --max-turns 10", workdir="/path/to/repo", timeout=120)
|
||||
```
|
||||
|
||||
### Claude Worktree with tmux
|
||||
```
|
||||
terminal(command="claude -w feature-x --tmux", workdir="/path/to/repo")
|
||||
```
|
||||
Creates an isolated git worktree at `.claude/worktrees/feature-x` AND a tmux session for it. Uses iTerm2 native panes when available; add `--tmux=classic` for traditional tmux.
|
||||
|
||||
## Parallel Claude Instances
|
||||
|
||||
Run multiple independent Claude tasks simultaneously:
|
||||
|
||||
```
|
||||
# Task 1: Fix backend
|
||||
terminal(command="tmux new-session -d -s task1 -x 140 -y 40 && tmux send-keys -t task1 'cd ~/project && claude -p \"Fix the auth bug in src/auth.py\" --allowedTools \"Read,Edit\" --max-turns 10' Enter")
|
||||
|
||||
# Task 2: Write tests
|
||||
terminal(command="tmux new-session -d -s task2 -x 140 -y 40 && tmux send-keys -t task2 'cd ~/project && claude -p \"Write integration tests for the API endpoints\" --allowedTools \"Read,Write,Bash\" --max-turns 15' Enter")
|
||||
|
||||
# Task 3: Update docs
|
||||
terminal(command="tmux new-session -d -s task3 -x 140 -y 40 && tmux send-keys -t task3 'cd ~/project && claude -p \"Update README.md with the new API endpoints\" --allowedTools \"Read,Edit\" --max-turns 5' Enter")
|
||||
|
||||
# Monitor all
|
||||
terminal(command="sleep 30 && for s in task1 task2 task3; do echo '=== '$s' ==='; tmux capture-pane -t $s -p -S -5 2>/dev/null; done")
|
||||
```
|
||||
|
||||
## CLAUDE.md — Project Context File
|
||||
|
||||
Claude Code auto-loads `CLAUDE.md` from the project root. Use it to persist project context:
|
||||
|
||||
```markdown
|
||||
# Project: My API
|
||||
|
||||
## Architecture
|
||||
- FastAPI backend with SQLAlchemy ORM
|
||||
- PostgreSQL database, Redis cache
|
||||
- pytest for testing with 90% coverage target
|
||||
|
||||
## Key Commands
|
||||
- `make test` — run full test suite
|
||||
- `make lint` — ruff + mypy
|
||||
- `make dev` — start dev server on :8000
|
||||
|
||||
## Code Standards
|
||||
- Type hints on all public functions
|
||||
- Docstrings in Google style
|
||||
- 2-space indentation for YAML, 4-space for Python
|
||||
- No wildcard imports
|
||||
```
|
||||
|
||||
**Be specific.** Instead of "Write good code", use "Use 2-space indentation for JS" or "Name test files with `.test.ts` suffix." Specific instructions save correction cycles.
|
||||
|
||||
### Rules Directory (Modular CLAUDE.md)
|
||||
For projects with many rules, use the rules directory instead of one massive CLAUDE.md:
|
||||
- **Project rules:** `.claude/rules/*.md` — team-shared, git-tracked
|
||||
- **User rules:** `~/.claude/rules/*.md` — personal, global
|
||||
|
||||
Each `.md` file in the rules directory is loaded as additional context. This is cleaner than cramming everything into a single CLAUDE.md.
|
||||
|
||||
### Auto-Memory
|
||||
Claude automatically stores learned project context in `~/.claude/projects/<project>/memory/`.
|
||||
- **Limit:** 25KB or 200 lines per project
|
||||
- This is separate from CLAUDE.md — it's Claude's own notes about the project, accumulated across sessions
|
||||
|
||||
## Custom Subagents
|
||||
|
||||
Define specialized agents in `.claude/agents/` (project), `~/.claude/agents/` (personal), or via `--agents` CLI flag (session):
|
||||
|
||||
### Agent Location Priority
|
||||
1. `.claude/agents/` — project-level, team-shared
|
||||
2. `--agents` CLI flag — session-specific, dynamic
|
||||
3. `~/.claude/agents/` — user-level, personal
|
||||
|
||||
### Creating an Agent
|
||||
```markdown
|
||||
# .claude/agents/security-reviewer.md
|
||||
---
|
||||
name: security-reviewer
|
||||
description: Security-focused code review
|
||||
model: opus
|
||||
tools: [Read, Bash]
|
||||
---
|
||||
You are a senior security engineer. Review code for:
|
||||
- Injection vulnerabilities (SQL, XSS, command injection)
|
||||
- Authentication/authorization flaws
|
||||
- Secrets in code
|
||||
- Unsafe deserialization
|
||||
```
|
||||
|
||||
Invoke via: `@security-reviewer review the auth module`
|
||||
|
||||
### Dynamic Agents via CLI
|
||||
```
|
||||
terminal(command="claude --agents '{\"reviewer\": {\"description\": \"Reviews code\", \"prompt\": \"You are a code reviewer focused on performance\"}}' -p 'Use @reviewer to check auth.py'", timeout=120)
|
||||
```
|
||||
|
||||
Claude can orchestrate multiple agents: "Use @db-expert to optimize queries, then @security to audit the changes."
|
||||
|
||||
## Hooks — Automation on Events
|
||||
|
||||
Configure in `.claude/settings.json` (project) or `~/.claude/settings.json` (global):
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PostToolUse": [{
|
||||
"matcher": "Write(*.py)",
|
||||
"hooks": [{"type": "command", "command": "ruff check --fix $CLAUDE_FILE_PATHS"}]
|
||||
}],
|
||||
"PreToolUse": [{
|
||||
"matcher": "Bash",
|
||||
"hooks": [{"type": "command", "command": "if echo \"$CLAUDE_TOOL_INPUT\" | grep -q 'rm -rf'; then echo 'Blocked!' && exit 2; fi"}]
|
||||
}],
|
||||
"Stop": [{
|
||||
"hooks": [{"type": "command", "command": "echo 'Claude finished a response' >> /tmp/claude-activity.log"}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### All 8 Hook Types
|
||||
| Hook | When it fires | Common use |
|
||||
|------|--------------|------------|
|
||||
| `UserPromptSubmit` | Before Claude processes a user prompt | Input validation, logging |
|
||||
| `PreToolUse` | Before tool execution | Security gates, block dangerous commands (exit 2 = block) |
|
||||
| `PostToolUse` | After a tool finishes | Auto-format code, run linters |
|
||||
| `Notification` | On permission requests or input waits | Desktop notifications, alerts |
|
||||
| `Stop` | When Claude finishes a response | Completion logging, status updates |
|
||||
| `SubagentStop` | When a subagent completes | Agent orchestration |
|
||||
| `PreCompact` | Before context memory is cleared | Backup session transcripts |
|
||||
| `SessionStart` | When a session begins | Load dev context (e.g., `git status`) |
|
||||
|
||||
### Hook Environment Variables
|
||||
| Variable | Content |
|
||||
|----------|---------|
|
||||
| `CLAUDE_PROJECT_DIR` | Current project path |
|
||||
| `CLAUDE_FILE_PATHS` | Files being modified |
|
||||
| `CLAUDE_TOOL_INPUT` | Tool parameters as JSON |
|
||||
|
||||
### Security Hook Examples
|
||||
```json
|
||||
{
|
||||
"PreToolUse": [{
|
||||
"matcher": "Bash",
|
||||
"hooks": [{"type": "command", "command": "if echo \"$CLAUDE_TOOL_INPUT\" | grep -qE 'rm -rf|git push.*--force|:(){ :|:& };:'; then echo 'Dangerous command blocked!' && exit 2; fi"}]
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
## MCP Integration
|
||||
|
||||
Add external tool servers for databases, APIs, and services:
|
||||
|
||||
```
|
||||
# GitHub integration
|
||||
terminal(command="claude mcp add -s user github -- npx @modelcontextprotocol/server-github", timeout=30)
|
||||
|
||||
# PostgreSQL queries
|
||||
terminal(command="claude mcp add -s local postgres -- npx @anthropic-ai/server-postgres --connection-string postgresql://localhost/mydb", timeout=30)
|
||||
|
||||
# Puppeteer for web testing
|
||||
terminal(command="claude mcp add puppeteer -- npx @anthropic-ai/server-puppeteer", timeout=30)
|
||||
```
|
||||
|
||||
### MCP Scopes
|
||||
| Flag | Scope | Storage |
|
||||
|------|-------|---------|
|
||||
| `-s user` | Global (all projects) | `~/.claude.json` |
|
||||
| `-s local` | This project (personal) | `.claude/settings.local.json` (gitignored) |
|
||||
| `-s project` | This project (team-shared) | `.claude/settings.json` (git-tracked) |
|
||||
|
||||
### MCP in Print/CI Mode
|
||||
```
|
||||
terminal(command="claude --bare -p 'Query database' --mcp-config mcp-servers.json --strict-mcp-config", timeout=60)
|
||||
```
|
||||
`--strict-mcp-config` ignores all MCP servers except those from `--mcp-config`.
|
||||
|
||||
Reference MCP resources in chat: `@github:issue://123`
|
||||
|
||||
### MCP Limits & Tuning
|
||||
- **Tool descriptions:** 2KB cap per server for tool descriptions and server instructions
|
||||
- **Result size:** Default capped; use `maxResultSizeChars` annotation to allow up to **500K** characters for large outputs
|
||||
- **Output tokens:** `export MAX_MCP_OUTPUT_TOKENS=50000` — cap output from MCP servers to prevent context flooding
|
||||
- **Transports:** `stdio` (local process), `http` (remote), `sse` (server-sent events)
|
||||
|
||||
## Monitoring Interactive Sessions
|
||||
|
||||
### Reading the TUI Status
|
||||
```
|
||||
# Periodic capture to check if Claude is still working or waiting for input
|
||||
terminal(command="tmux capture-pane -t dev -p -S -10")
|
||||
```
|
||||
|
||||
Look for these indicators:
|
||||
- `❯` at bottom = waiting for your input (Claude is done or asking a question)
|
||||
- `●` lines = Claude is actively using tools (reading, writing, running commands)
|
||||
- `⏵⏵ bypass permissions on` = status bar showing permissions mode
|
||||
- `◐ medium · /effort` = current effort level in status bar
|
||||
- `ctrl+o to expand` = tool output was truncated (can be expanded interactively)
|
||||
|
||||
### Context Window Health
|
||||
Use `/context` in interactive mode to see a colored grid of context usage. Key thresholds:
|
||||
- **< 70%** — Normal operation, full precision
|
||||
- **70-85%** — Precision starts dropping, consider `/compact`
|
||||
- **> 85%** — Hallucination risk spikes significantly, use `/compact` or `/clear`
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Effect |
|
||||
|----------|--------|
|
||||
| `ANTHROPIC_API_KEY` | API key for authentication (alternative to OAuth) |
|
||||
| `CLAUDE_CODE_EFFORT_LEVEL` | Default effort: `low`, `medium`, `high`, `max`, or `auto` |
|
||||
| `MAX_THINKING_TOKENS` | Cap thinking tokens (set to `0` to disable thinking entirely) |
|
||||
| `MAX_MCP_OUTPUT_TOKENS` | Cap output from MCP servers (default varies; set e.g., `50000`) |
|
||||
| `CLAUDE_CODE_NO_FLICKER=1` | Enable alt-screen rendering to eliminate terminal flicker |
|
||||
| `CLAUDE_CODE_SUBPROCESS_ENV_SCRUB` | Strip credentials from sub-processes for security |
|
||||
|
||||
## Cost & Performance Tips
|
||||
|
||||
1. **Use `--max-turns`** in print mode to prevent runaway loops. Start with 5-10 for most tasks.
|
||||
2. **Use `--max-budget-usd`** for cost caps. Note: minimum ~$0.05 for system prompt cache creation.
|
||||
3. **Use `--effort low`** for simple tasks (faster, cheaper). `high` or `max` for complex reasoning.
|
||||
4. **Use `--bare`** for CI/scripting to skip plugin/hook discovery overhead.
|
||||
5. **Use `--allowedTools`** to restrict to only what's needed (e.g., `Read` only for reviews).
|
||||
6. **Use `/compact`** in interactive sessions when context gets large.
|
||||
7. **Pipe input** instead of having Claude read files when you just need analysis of known content.
|
||||
8. **Use `--model haiku`** for simple tasks (cheaper) and `--model opus` for complex multi-step work.
|
||||
9. **Use `--fallback-model haiku`** in print mode to gracefully handle model overload.
|
||||
10. **Start new sessions for distinct tasks** — sessions last 5 hours; fresh context is more efficient.
|
||||
11. **Use `--no-session-persistence`** in CI to avoid accumulating saved sessions on disk.
|
||||
|
||||
## Pitfalls & Gotchas
|
||||
|
||||
1. **Interactive mode REQUIRES tmux** — Claude Code is a full TUI app. Using `pty=true` alone in Hermes terminal works but tmux gives you `capture-pane` for monitoring and `send-keys` for input, which is essential for orchestration.
|
||||
2. **`--dangerously-skip-permissions` dialog defaults to "No, exit"** — you must send Down then Enter to accept. Print mode (`-p`) skips this entirely.
|
||||
3. **`--max-budget-usd` minimum is ~$0.05** — system prompt cache creation alone costs this much. Setting lower will error immediately.
|
||||
4. **`--max-turns` is print-mode only** — ignored in interactive sessions.
|
||||
5. **Claude may use `python` instead of `python3`** — on systems without a `python` symlink, Claude's bash commands will fail on first try but it self-corrects.
|
||||
6. **Session resumption requires same directory** — `--continue` finds the most recent session for the current working directory.
|
||||
7. **`--json-schema` needs enough `--max-turns`** — Claude must read files before producing structured output, which takes multiple turns.
|
||||
8. **Trust dialog only appears once per directory** — first-time only, then cached.
|
||||
9. **Background tmux sessions persist** — always clean up with `tmux kill-session -t <name>` when done.
|
||||
10. **Slash commands (like `/commit`) only work in interactive mode** — in `-p` mode, describe the task in natural language instead.
|
||||
11. **`--bare` skips OAuth** — requires `ANTHROPIC_API_KEY` env var or an `apiKeyHelper` in settings.
|
||||
12. **Context degradation is real** — AI output quality measurably degrades above 70% context window usage. Monitor with `/context` and proactively `/compact`.
|
||||
|
||||
## Other Autonomous Coding Agents
|
||||
|
||||
The orchestration patterns documented above (PTY via tmux, background monitoring with `process()` tool, worktree isolation, PR review flow) apply to other coding agents too. Quick-reference tables below for **Codex** (OpenAI) and **OpenCode** (provider-agnostic open-source).
|
||||
|
||||
### Codex CLI (OpenAI)
|
||||
|
||||
```bash
|
||||
# Install
|
||||
npm install -g @openai/codex
|
||||
|
||||
# Auth: set OPENAI_API_KEY or use OAuth (hermes auth add openai-codex)
|
||||
# Must run inside a git repository
|
||||
```
|
||||
|
||||
| Task | Command |
|
||||
|------|---------|
|
||||
| One-shot task | `codex exec "description"` |
|
||||
| Auto-approve changes | `codex exec --full-auto "..."` |
|
||||
| No sandbox (gateway context) | `codex exec --sandbox danger-full-access "..."` |
|
||||
| PR review | `codex review --base origin/main` |
|
||||
|
||||
See `references/codex.md` for the detailed integration guide.
|
||||
|
||||
### OpenCode CLI
|
||||
|
||||
```bash
|
||||
# Install
|
||||
npm i -g opencode-ai@latest
|
||||
brew install anomalyco/tap/opencode
|
||||
|
||||
# Auth: opencode auth login (or set OPENROUTER_API_KEY, etc.)
|
||||
```
|
||||
|
||||
| Task | Command |
|
||||
|------|---------|
|
||||
| One-shot task | `opencode run 'description'` |
|
||||
| With context files | `opencode run '...' -f config.yaml` |
|
||||
| Force model | `opencode run '...' --model openrouter/anthropic/claude-sonnet-4` |
|
||||
| Interactive session | `opencode` (requires pty=true) |
|
||||
| Resume last session | `opencode -c` |
|
||||
| PR review | `opencode pr <number>` |
|
||||
| Session costs | `opencode stats` |
|
||||
|
||||
**Important:** Do NOT use `/exit` in OpenCode — it opens the agent selector. Use Ctrl+C (`\x03`) instead.
|
||||
|
||||
See `references/opencode.md` for the detailed integration guide.
|
||||
|
||||
### Orchestration Comparison
|
||||
|
||||
| Feature | Claude Code | Codex | OpenCode |
|
||||
|---------|-------------|-------|----------|
|
||||
| One-shot mode | `-p "prompt"` | `exec "prompt"` | `run 'prompt'` |
|
||||
| PTY needed for interactive | Yes | Yes | Yes |
|
||||
| Background + monitor | tmux + capture-pane | background=true + process | background=true + process |
|
||||
| Worktree isolation | `-w` flag | `git worktree add` | separate workdir |
|
||||
| Sandbox | By default (bubblewrap) | `--full-auto` / `--sandbox` | N/A |
|
||||
| Model override | `--model <name>` | N/A | `--model <provider/model>` |
|
||||
|
||||
1. **Prefer print mode (`-p`) for single tasks** — cleaner, no dialog handling, structured output
|
||||
2. **Use tmux for multi-turn interactive work** — the only reliable way to orchestrate the TUI
|
||||
3. **Always set `workdir`** — keep Claude focused on the right project directory
|
||||
4. **Set `--max-turns` in print mode** — prevents infinite loops and runaway costs
|
||||
5. **Monitor tmux sessions** — use `tmux capture-pane -t <session> -p -S -50` to check progress
|
||||
6. **Look for the `❯` prompt** — indicates Claude is waiting for input (done or asking a question)
|
||||
7. **Clean up tmux sessions** — kill them when done to avoid resource leaks
|
||||
8. **Report results to user** — after completion, summarize what Claude did and what changed
|
||||
9. **Don't kill slow sessions** — Claude may be doing multi-step work; check progress instead
|
||||
10. **Use `--allowedTools`** — restrict capabilities to what the task actually needs
|
||||
@@ -0,0 +1,54 @@
|
||||
# Codex CLI — Detailed Integration Guide
|
||||
|
||||
[Codex](https://github.com/openai/codex) is OpenAI's autonomous coding agent CLI. This reference covers auth, edge cases, and gateway-specific pitfalls.
|
||||
|
||||
## Auth
|
||||
|
||||
Codex can use `OPENAI_API_KEY` or OAuth. For Hermes itself, `model.provider: openai-codex` uses Hermes-managed Codex OAuth from `~/.hermes/auth.json` after `hermes auth add openai-codex`. For standalone CLI, a valid OAuth session may live under `~/.codex/auth.json` — do not treat a missing `OPENAI_API_KEY` alone as proof that Codex auth is missing.
|
||||
|
||||
## One-Shot Tasks
|
||||
|
||||
```bash
|
||||
terminal(command="codex exec 'Add dark mode toggle to settings'", workdir="~/project", pty=true)
|
||||
```
|
||||
|
||||
For scratch work (Codex needs a git repo):
|
||||
```bash
|
||||
cd $(mktemp -d) && git init && codex exec 'Build a snake game in Python'
|
||||
```
|
||||
|
||||
## Background Mode (Long Tasks)
|
||||
|
||||
```bash
|
||||
terminal(command="codex exec --full-auto 'Refactor the auth module'", workdir="~/project", background=true, pty=true)
|
||||
# Returns session_id — monitor with process(action="poll"|"log")
|
||||
```
|
||||
|
||||
## Hermes Gateway Caveat
|
||||
|
||||
When invoking Codex CLI from a gateway/service context (Telegram-driven sessions), Codex `workspace-write` sandboxing may fail due to bubblewrap/user-namespace restrictions. Prefer:
|
||||
|
||||
```bash
|
||||
codex exec --sandbox danger-full-access "<task>"
|
||||
```
|
||||
|
||||
Use process boundaries as the safety layer: explicit `workdir`, clean git status before launch, narrow task prompts, `git diff` review, targeted tests.
|
||||
|
||||
## Parallel Issue Fixing with Worktrees
|
||||
|
||||
```bash
|
||||
# Create worktrees
|
||||
git worktree add -b fix/issue-78 /tmp/issue-78 main
|
||||
|
||||
# Launch Codex in each
|
||||
terminal(command="codex --yolo exec 'Fix issue #78'", workdir="/tmp/issue-78", background=true, pty=true)
|
||||
```
|
||||
|
||||
## Key Flags
|
||||
|
||||
| Flag | Effect |
|
||||
|------|--------|
|
||||
| `exec "prompt"` | One-shot execution, exits when done |
|
||||
| `--full-auto` | Sandboxed but auto-approves file changes |
|
||||
| `--yolo` | No sandbox, no approvals (fastest, most dangerous) |
|
||||
| `--sandbox danger-full-access` | No sandbox; useful when bubblewrap fails |
|
||||
@@ -0,0 +1,73 @@
|
||||
# OpenCode CLI — Detailed Integration Guide
|
||||
|
||||
[OpenCode](https://opencode.ai) is a provider-agnostic, open-source AI coding agent. This reference covers binary resolution, session management, and key pitfalls.
|
||||
|
||||
## Binary Resolution
|
||||
|
||||
Shell environments may resolve different OpenCode binaries. Check:
|
||||
|
||||
```bash
|
||||
terminal(command="which -a opencode")
|
||||
terminal(command="opencode --version")
|
||||
```
|
||||
|
||||
If needed, pin an explicit binary path: `$HOME/.opencode/bin/opencode run '...'`
|
||||
|
||||
## Interactive Sessions (Background)
|
||||
|
||||
```bash
|
||||
# Start TUI in background
|
||||
terminal(command="opencode", workdir="~/project", background=true, pty=true)
|
||||
|
||||
# Send a prompt
|
||||
process(action="submit", session_id="<id>", data="Implement OAuth refresh flow")
|
||||
|
||||
# Monitor
|
||||
process(action="log", session_id="<id>")
|
||||
|
||||
# Exit — use Ctrl+C, NOT /exit
|
||||
process(action="write", session_id="<id>", data="\x03")
|
||||
```
|
||||
|
||||
**Important:** Do NOT use `/exit` — it opens an agent selector instead. Use Ctrl+C (`\x03`) or `process(action="kill")`.
|
||||
|
||||
## TUI Keybindings
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| Enter | Submit message |
|
||||
| Tab | Switch between agents |
|
||||
| Ctrl+P | Command palette |
|
||||
| Ctrl+X L | Switch session |
|
||||
| Ctrl+X M | Switch model |
|
||||
| Ctrl+X N | New session |
|
||||
| Ctrl+C | Exit |
|
||||
|
||||
## PR Review
|
||||
|
||||
```bash
|
||||
terminal(command="opencode pr 42", workdir="~/project", pty=true)
|
||||
```
|
||||
|
||||
Or review in a temporary clone for isolation.
|
||||
|
||||
## Session & Cost Management
|
||||
|
||||
```bash
|
||||
opencode session list
|
||||
opencode stats
|
||||
opencode stats --days 7 --models anthropic/claude-sonnet-4
|
||||
```
|
||||
|
||||
## Common Flags
|
||||
|
||||
| Flag | Use |
|
||||
|------|-----|
|
||||
| `run 'prompt'` | One-shot execution and exit |
|
||||
| `-c` / `--continue` | Continue last session |
|
||||
| `-s <id>` | Continue a specific session |
|
||||
| `--agent <name>` | Choose agent (build or plan) |
|
||||
| `--model provider/model` | Force specific model |
|
||||
| `--thinking` | Show model thinking blocks |
|
||||
| `--format json` | Machine-readable output |
|
||||
| `-f <path>` | Attach file(s) to the message |
|
||||
@@ -0,0 +1,105 @@
|
||||
---
|
||||
name: hermes-local-providers
|
||||
description: "Configure Hermes Agent to use self-hosted, local LLM providers (Ollama, vLLM, llama.cpp, etc.) via OpenAI-compatible custom endpoints."
|
||||
version: 1.0.0
|
||||
author: agent
|
||||
created_by: agent
|
||||
metadata:
|
||||
hermes:
|
||||
tags: [hermes, ollama, local-models, configuration, self-hosted, vllm, llm-serving]
|
||||
related_skills: [hermes-agent, llama-cpp, serving-llms-vllm, docker-gpu-acceleration]
|
||||
---
|
||||
|
||||
# Hermes Local Providers
|
||||
|
||||
Configure Hermes Agent to use self-hosted / local LLMs that expose an OpenAI-compatible API endpoint.
|
||||
|
||||
## Supported Backends
|
||||
|
||||
| Backend | Typical Base URL | Auth | Notes |
|
||||
|---------|-----------------|------|-------|
|
||||
| Ollama | `http://localhost:11434/v1` | Placeholder | Most common for local use |
|
||||
| vLLM | `http://localhost:8000/v1` | Optional API key | Production-scale serving |
|
||||
| llama.cpp | `http://localhost:8080/v1` | None | Lightweight GGUF inference |
|
||||
|
||||
## Configuration
|
||||
|
||||
### 1. Set the custom provider
|
||||
|
||||
```bash
|
||||
hermes config set model.provider custom
|
||||
hermes config set model.base_url http://localhost:11434/v1
|
||||
hermes config set model.api_key ollama
|
||||
hermes config set model.default qwen2.5:14b
|
||||
```
|
||||
|
||||
The provider name is **`custom`** — no suffix, no prefix. Hermes recognizes `custom` as a reserved provider that reads `model.base_url` and `model.api_key` directly from the config. Values like `custom:ollama` or `custom:vllm` will be rejected as "Unknown provider."
|
||||
|
||||
Ollama doesn't need a real API key, but `model.api_key` must be set to something non-empty. `ollama` (or any dummy string) works.
|
||||
|
||||
### 2. ⚠️ Verify the config persisted
|
||||
|
||||
`hermes config set` on `model.*` keys may report success (`✓ Set model.key = value`) but NOT actually write the value to `~/.hermes/config.yaml`. This is a known pitfall — always verify:
|
||||
|
||||
```bash
|
||||
grep -A6 '^model:' ~/.hermes/config.yaml
|
||||
```
|
||||
|
||||
Expected result:
|
||||
```yaml
|
||||
model:
|
||||
default: qwen2.5:14b
|
||||
provider: custom
|
||||
base_url: http://localhost:11434/v1
|
||||
api_key: ollama
|
||||
```
|
||||
|
||||
> ⚠️ The provider must be **`custom`** (bare) — NOT `custom:ollama`, `custom:vllm`, or any suffix. Hermes only recognizes the bare `custom` as a reserved provider for user-defined endpoints. `custom:ollama` will be rejected as "Unknown provider."
|
||||
|
||||
If the old values are still showing, re-run the `hermes config set` commands and re-check. Retrying usually works; the silent failure appears to be intermittent.
|
||||
|
||||
### 3. Test the API independently
|
||||
|
||||
Before restarting the gateway, confirm the local backend is reachable:
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:11434/v1/chat/completions \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model":"qwen2.5:14b","messages":[{"role":"user","content":"Say hello in 3 words"}],"stream":false}'
|
||||
```
|
||||
|
||||
If this fails, Ollama/vLLM/llama.cpp isn't running or the model isn't loaded.
|
||||
|
||||
### 4. Restart the gateway
|
||||
|
||||
```bash
|
||||
hermes gateway restart
|
||||
```
|
||||
|
||||
Config changes only take effect after a gateway restart. For CLI sessions, exit and relaunch.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- **Config set silent failure**: `hermes config set` for `model.*` keys (`provider`, `base_url`, `api_key`, `default`) may report "✓ Set" but not persist. Always grep the config file to confirm before assuming the change took effect.
|
||||
- **Auth placeholder required**: A local Ollama endpoint doesn't authenticate, but Hermes requires `model.api_key` to be non-empty. Any dummy value works.
|
||||
- **Gateway restart required**: Unlike some config changes, switching the model provider requires a full gateway restart. `/reset` or `/model` in-session won't pick up the new provider settings.
|
||||
- **Auxiliary models stay unchanged**: Vision, compression, web extraction, title generation, and all other `auxiliary.*` subsystems keep their own provider config. Switching the main model to Ollama doesn't affect these — they'll continue using whatever provider/API key they were configured with (DeepSeek, OpenRouter, etc.).
|
||||
- **Delegation has its own config**: The `delegation.*` section (`delegation.model`, `delegation.provider`, `delegation.base_url`) controls subagent models. To route subagents through the local provider too, set those separately:
|
||||
```bash
|
||||
hermes config set delegation.provider custom
|
||||
hermes config set delegation.base_url http://localhost:11434/v1
|
||||
hermes config set delegation.api_key ollama
|
||||
hermes config set delegation.model qwen2.5:14b
|
||||
```
|
||||
- **Model must be downloaded**: `ollama list` shows installed models. If you set `model.default` to a model that isn't pulled, Ollama will pull it on first request (slow first call) or error if auto-pull is disabled.
|
||||
|
||||
## Verification
|
||||
|
||||
After restart, send a message to the agent. If you get "provider authentication error":
|
||||
|
||||
1. Check `~/.hermes/config.yaml` — the old provider values may have persisted
|
||||
2. Re-run the `hermes config set` commands
|
||||
3. Verify with `grep -A5 '^model:' ~/.hermes/config.yaml`
|
||||
4. Restart gateway again
|
||||
|
||||
The "authentication error" is misleading — it usually means the API key/base URL weren't received by Hermes, which happens when `hermes config set` failed to persist.
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
# Ollama "Provider Authentication Error" Fix
|
||||
|
||||
## Symptom
|
||||
|
||||
After configuring a local provider (Ollama, vLLM, etc.), the agent reports "Provider authentication failed" on the next request. Gateway logs show: `Unknown provider 'custom:<name>'`.
|
||||
|
||||
## Root Cause
|
||||
|
||||
**Wrong provider name.** The Hermes custom endpoint provider is registered as **`custom`** (bare), not `custom:ollama`, `custom:vllm`, or any `custom:<name>` variant. The suffix format is not recognized — `custom:ollama` and `openai` are both rejected as unknown providers.
|
||||
|
||||
Secondary cause: `hermes config set` for `model.*` keys can silently fail to persist (reports "✓ Set" but file shows old values). Always verify with `grep -A5 '^model:' ~/.hermes/config.yaml`.
|
||||
|
||||
## Fix
|
||||
|
||||
1. Set the provider to `custom` (no suffix):
|
||||
```bash
|
||||
hermes config set model.provider custom
|
||||
hermes config set model.base_url http://localhost:11434/v1
|
||||
hermes config set model.api_key ollama
|
||||
hermes config set model.default qwen2.5:14b
|
||||
```
|
||||
|
||||
2. **Verify the file**:
|
||||
```bash
|
||||
grep -A6 '^model:' ~/.hermes/config.yaml
|
||||
```
|
||||
Must show `provider: custom`.
|
||||
|
||||
3. Test the Ollama API directly:
|
||||
```bash
|
||||
curl -s http://localhost:11434/v1/chat/completions -H "Content-Type: application/json" \
|
||||
-d '{"model":"qwen2.5:14b","messages":[{"role":"user","content":"hi"}],"stream":false}'
|
||||
```
|
||||
|
||||
4. Restart gateway (from OUTSIDE the gateway process — cannot restart from within):
|
||||
```bash
|
||||
hermes gateway restart
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- The provider `custom` is a reserved Hermes provider that reads `model.base_url` and `model.api_key` directly. It uses `transport="openai_chat"` (OpenAI-compatible API).
|
||||
- If you set `custom` and still get the error, check with `grep` — the `config set` may have silently failed. Re-run the commands.
|
||||
- Gateway restart must be done from a separate shell (SSH, desktop terminal) — not from within a gateway session.
|
||||
@@ -0,0 +1,319 @@
|
||||
---
|
||||
name: hermes-telegram
|
||||
description: Configure, troubleshoot, and manage Telegram integration for Hermes Agent — bot setup, .env configuration, gateway restart, and common failure modes.
|
||||
version: 1.0.0
|
||||
author: Hermes Agent
|
||||
license: MIT
|
||||
platforms: [linux, macos]
|
||||
metadata:
|
||||
hermes:
|
||||
tags: [hermes, telegram, messaging, gateway, bot]
|
||||
related_skills: [hermes-agent]
|
||||
related_skills: [hermes-agent]
|
||||
---
|
||||
|
||||
# Hermes Telegram Integration
|
||||
|
||||
Configure the Hermes Agent gateway to send and receive messages on Telegram. Covers bot creation, .env setup, allowed users, home channel, gateway restart, and troubleshooting.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Hermes Agent gateway installed and running (`hermes gateway status` or `ps aux | grep 'hermes gateway run'`)
|
||||
- A Telegram account to create and manage the bot
|
||||
|
||||
## Setup Steps
|
||||
|
||||
### 1. Create a Bot on Telegram
|
||||
|
||||
Message [@BotFather](https://t.me/BotFather) on Telegram:
|
||||
|
||||
```
|
||||
/newbot
|
||||
```
|
||||
|
||||
Follow the prompts to choose a name and username. BotFather will return a bot token like:
|
||||
```
|
||||
8971430276:AAFu...Amq4
|
||||
```
|
||||
|
||||
Save this token — you'll need it in the next step.
|
||||
|
||||
### 2. Find Your Numeric IDs
|
||||
|
||||
You need numeric Telegram IDs, not usernames. Message [@userinfobot](https://t.me/userinfobot) on Telegram — it will reply with:
|
||||
|
||||
- **Your user ID** (a number like `123456789`) — used for `TELEGRAM_ALLOWED_USERS`
|
||||
- For groups/channels: add the bot to the group, send a message, then use @userinfobot in the group to get the **chat ID**
|
||||
|
||||
Note: `TELEGRAM_ALLOWED_USERS` and `TELEGRAM_HOME_CHANNEL` require **numeric IDs**, not `@username` strings.
|
||||
|
||||
### 3. Configure .env
|
||||
|
||||
Edit the `.env` file. The default template has all Telegram variables **commented out** — you must uncomment and fill them in:
|
||||
|
||||
```bash
|
||||
hermes config env-path # prints the path (typically ~/.hermes/.env)
|
||||
```
|
||||
|
||||
The relevant lines (find them under the `# TELEGRAM INTEGRATION` section):
|
||||
|
||||
```env
|
||||
TELEGRAM_BOT_TOKEN=your_bot_token_here
|
||||
TELEGRAM_ALLOWED_USERS=123456789 # Comma-separated numeric user IDs
|
||||
TELEGRAM_HOME_CHANNEL=123456789 # Numeric chat ID for cron deliveries
|
||||
TELEGRAM_HOME_CHANNEL_NAME=@yourusername # Display name (optional, username is fine here)
|
||||
```
|
||||
|
||||
**Important:** The default template uses a combined single-line format:
|
||||
```
|
||||
# TELEGRAM_BOT_TOKEN=*** TELEGRAM_ALLOWED_USERS=
|
||||
```
|
||||
When uncommenting, split this into **separate lines** as shown above — one variable per line. Use your editor or `sed` to do it cleanly.
|
||||
|
||||
For the `HOME_CHANNEL`, if you only want DM delivery (bot messages you directly), set it to your own user ID — same as `ALLOWED_USERS`.
|
||||
|
||||
### 4. Restart the Gateway
|
||||
|
||||
Configuration changes in `.env` are read at startup. Restart the gateway to pick them up:
|
||||
|
||||
```bash
|
||||
hermes gateway restart
|
||||
```
|
||||
|
||||
Or if running as a systemd user service:
|
||||
```bash
|
||||
systemctl --user restart hermes-gateway
|
||||
```
|
||||
|
||||
#### Manual Restart (no systemd service)
|
||||
|
||||
If `systemctl --user list-units --type=service --all | grep hermes` returns nothing, the gateway was started manually. Kill and restart it:
|
||||
|
||||
```bash
|
||||
# Find the gateway PID
|
||||
ps aux | grep 'hermes gateway run'
|
||||
|
||||
# Kill it (use -9 if plain kill won't work)
|
||||
kill -9 <PID>
|
||||
|
||||
# Wait for it to die
|
||||
sleep 2
|
||||
|
||||
# Start fresh — MUST use background=true (nohup/disown is blocked by tool policy)
|
||||
# In the Hermes CLI session:
|
||||
# terminal(background=true, notify_on_complete=true, command="hermes gateway run")
|
||||
```
|
||||
|
||||
**Important:** Do not use `nohup`, `disown`, or trailing `&` in a foreground terminal() call — the tool policy blocks shell-level background wrappers. Always use `terminal(background=true)` so Hermes tracks the process.
|
||||
|
||||
### 5. Verify It's Working
|
||||
|
||||
Send a message to your bot on Telegram. It should respond. Check the gateway logs for confirmation:
|
||||
|
||||
```bash
|
||||
grep -i telegram ~/.hermes/logs/gateway.log | tail -20
|
||||
```
|
||||
|
||||
Look for these lines to confirm a successful connection:
|
||||
|
||||
```
|
||||
Connecting to telegram...
|
||||
[Telegram] Auto-discovered Telegram fallback IPs: 149.154.166.110
|
||||
[Telegram] set_my_commands OK for scope BotCommandScopeDefault (30 cmds)
|
||||
[Telegram] Connected to Telegram (polling mode)
|
||||
✓ telegram connected
|
||||
```
|
||||
|
||||
If you see `Connected to Telegram (polling mode)` and `✓ telegram connected`, the bot is live.
|
||||
|
||||
For a full reference of restart commands, log signatures, curl test-message patterns, and sed snippets, see [`references/gateway-restart-and-test.md`](references/gateway-restart-and-test.md).
|
||||
|
||||
#### Sending a Test Message from the CLI
|
||||
|
||||
The Hermes `send_message` tool only works in sessions initiated from a messaging platform (Telegram, Discord, etc.). In a CLI session, it returns `"No messaging platforms connected"` even when the gateway has Telegram active.
|
||||
|
||||
**Workaround — use the Telegram Bot API directly via curl:**
|
||||
|
||||
```bash
|
||||
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
||||
-d "chat_id=${CHAT_ID}" \
|
||||
-d "text=Hello from Hermes! 👋"
|
||||
```
|
||||
|
||||
The `${TELEGRAM_BOT_TOKEN}` is the value set in `.env` and `${CHAT_ID}` is the numeric user/chat ID. This sends a message bypassing the Hermes gateway — useful for testing or one-off notifications from the CLI.
|
||||
|
||||
## Gateway Check
|
||||
|
||||
To see if the gateway is running before troubleshooting:
|
||||
|
||||
```bash
|
||||
ps aux | grep 'hermes gateway run'
|
||||
```
|
||||
|
||||
## Key Configuration Variables
|
||||
|
||||
| Variable | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `TELEGRAM_BOT_TOKEN` | Yes | Bot token from @BotFather |
|
||||
| `TELEGRAM_ALLOWED_USERS` | Recommended | Comma-separated numeric user IDs allowed to chat. **Leave empty to allow anyone** (not recommended for production). |
|
||||
| `TELEGRAM_HOME_CHANNEL` | For cron | Default chat ID for cron job deliveries |
|
||||
| `TELEGRAM_HOME_CHANNEL_NAME` | No | Display name for the home channel |
|
||||
| `TELEGRAM_CRON_THREAD_ID` | No | Forum topic ID for cron deliveries in topic-mode groups |
|
||||
|
||||
## Long-Running Tasks & Blocking Behavior
|
||||
|
||||
The gateway processes **one turn at a time per session**. While the agent is inside a turn (thinking, running tools, waiting for a terminal command), it cannot receive or process new messages from you. Telegram shows "typing..." the entire time.
|
||||
|
||||
### Why the bot blocks
|
||||
|
||||
| Phase | What's happening | Can you message? |
|
||||
|-------|-----------------|-----------------|
|
||||
| 🔄 Agent calls a tool (e.g. `terminal()`) | Blocks waiting for result | ❌ Queued |
|
||||
| 🤖 Agent calls the LLM again with tool output | Generating next action | ❌ Queued |
|
||||
| 📨 Agent sends you a progress update | Brief mid-turn message | ❌ Still in same turn |
|
||||
| ✅ Agent sends final response and ends turn | **Done** | ✅ Next message processed |
|
||||
|
||||
### "Typing..." = active LLM calls = cost
|
||||
|
||||
Every iteration of the agent loop makes a new API call to your LLM provider. While the bot shows "typing...", it is actively making LLM calls — you are being charged for input + output tokens each round. Input tokens grow each iteration as tool results are appended to the conversation context.
|
||||
|
||||
### How to avoid blocking the bot
|
||||
|
||||
Three approaches, in order of preference:
|
||||
|
||||
| Approach | How | Best for |
|
||||
|----------|-----|----------|
|
||||
| ⏰ **Cron job** | `cronjob(action='create', schedule='...', prompt='run my script', no_agent=True)` — script runs independently, stdout delivered verbatim to Telegram | Standalone scripts that run on a schedule or fire-and-forget |
|
||||
| 🏃 **Background terminal** | `terminal(command='python long_script.py', background=true, notify_on_complete=true)` — agent returns immediately, pings you when done | One-off long scripts the user triggers |
|
||||
| 📋 **/queue** | In CLI: `/queue <prompt>` — queues work for after current turn ends | Quick follow-ups during an active session |
|
||||
|
||||
The cron approach is cleanest: the bot never blocks, and `no_agent=True` means zero LLM calls during execution — the script's stdout is delivered verbatim.
|
||||
|
||||
### Verifying the bot is stuck mid-turn
|
||||
|
||||
Check the gateway logs for the last activity:
|
||||
```bash
|
||||
tail -20 ~/.hermes/logs/gateway.log
|
||||
```
|
||||
|
||||
Look for:
|
||||
- `inbound message: ... msg='your prompt'` — last message received
|
||||
- `Flushing text batch ... (N chars)` — last update sent (but turn not over)
|
||||
- No new `inbound message` entries — session is blocked, new messages are queued
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- **Usernames are not IDs.** `TELEGRAM_ALLOWED_USERS` and `TELEGRAM_HOME_CHANNEL` need **numeric** Telegram IDs, not `@username` strings. Use @userinfobot to get them.
|
||||
- **All vars start commented out.** The `.env` template ships with `#` prefix on every Telegram line. You must uncomment them.
|
||||
- **Multi-variable line trap.** The default `.env` template writes `TELEGRAM_BOT_TOKEN` and `TELEGRAM_ALLOWED_USERS` on the same line: `# TELEGRAM_BOT_TOKEN=*** TELEGRAM_ALLOWED_USERS=`. This is NOT valid env format with the token value — split into two separate lines when configuring.
|
||||
- **Gateway restart required.** `.env` is read at process startup. Simply running `hermes` in a new CLI session does not reload the gateway's env vars. Use `hermes gateway restart` (or `systemctl --user restart hermes-gateway`) every time you change `TELEGRAM_*` variables.
|
||||
- **Config schema warnings can block clean startup.** If gateway status/logs show `custom_providers is a dict — it must be a YAML list`, fix it in `~/.hermes/config.yaml` by making `custom_providers` a list (or clear it with `hermes config set custom_providers "[]"`), then restart the gateway.
|
||||
- **Token secrecy.** The bot token is a secret — anyone with it can control your bot. Keep it out of version control and shell history.
|
||||
- **Gateway API server must be running.** The gateway also starts the API server (typically port 8642). If tools or web UI can't reach the agent, the root cause is often a stopped gateway, not Telegram itself.
|
||||
- **send_message tool is CLI-blind to Telegram.** From a CLI session, `send_message(action=list)` returns `No messaging platforms connected` even when the gateway has Telegram active. This is by design — the tool only routes through gateway-originated sessions. Use the Telegram Bot API directly via curl to send messages from CLI sessions (see the Verification section for the exact curl command).
|
||||
|
||||
### Bot Commands: Registration & Limits
|
||||
|
||||
The gateway auto-registers bot commands from `hermes_cli.commands.telegram_bot_commands()` on startup. Commands come from three sources: built-in CommandDef entries, plugin slash commands, and skill entries. Gateway caps the command list at `MAX_COMMANDS_PER_SCOPE = 30` (telegram.py:108) to stay under Telegram's undocumented ~4KB payload limit.
|
||||
|
||||
**Adding a custom command persistently:**
|
||||
|
||||
1. Edit `~/.hermes/hermes-agent/hermes_cli/commands.py` -- add your (name, description) pair at the end of `telegram_bot_commands()`:
|
||||
```python
|
||||
result.append(("imagine", "Generate an image from a text prompt"))
|
||||
```
|
||||
2. Add the command to `_TELEGRAM_MENU_PRIORITY` if the cap is tight -- commands outside the priority list get trimmed first.
|
||||
3. If you go over 30 commands, raise `MAX_COMMANDS_PER_SCOPE` in `telegram.py`.
|
||||
4. Restart the gateway to pick up changes.
|
||||
|
||||
Without these three changes, custom commands get overwritten on gateway restart or silently truncated at the 30-command cap.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Diagnostic Flow: Bot Not Responding
|
||||
|
||||
Start with a **status check first** — it's the fastest way to narrow the cause.
|
||||
|
||||
```
|
||||
hermes gateway status # Primary check
|
||||
```
|
||||
|
||||
If that's unavailable, fall back to:
|
||||
```
|
||||
ps aux | grep 'hermes gateway run' # Second — gateway process exists?
|
||||
systemctl --user status hermes-gateway 2>/dev/null # Third — systemd service?
|
||||
```
|
||||
|
||||
#### Branch A: Gateway is not running
|
||||
The most common cause of "stopped working" is the gateway having been shut down. Check the logs to find out why:
|
||||
|
||||
```
|
||||
grep -i "telegram\|error\|fail\|warn\|sigterm\|sigkill\|oom" ~/.hermes/logs/gateway.log | tail -20
|
||||
```
|
||||
|
||||
Common shutdown signatures in the logs:
|
||||
|
||||
| Log pattern | Likely cause | Action |
|
||||
|-------------|-------------|--------|
|
||||
| `WARNING ... Shutdown context: signal=SIGTERM under_systemd=yes parent_pid=1` | systemd sent SIGTERM (user logout, reboot, service stop) | Restart gateway or install as permanent service |
|
||||
| `ERROR ... signal=SIGKILL` | OOM killer or forced kill | Check memory pressure, `dmesg | grep -i oom` |
|
||||
| `ERROR ... Traceback (most recent call last)` | Crash / unhandled exception | Read the traceback, fix the root cause |
|
||||
| Logs end abruptly with no shutdown message | Process was killed externally (shell session closed, SSH disconnect) | Restart gateway |
|
||||
| No Telegram-related log entries at all | Telegram may not be configured or enabled in config | Check `.env` for `TELEGRAM_*` vars, check `config.yaml` → `telegram` section |
|
||||
|
||||
If the log shows the gateway was working fine then received SIGTERM, the fix is straightforward — restart it:
|
||||
|
||||
```bash
|
||||
hermes gateway run # foreground (session-dependent)
|
||||
hermes gateway install # as permanent systemd user service (auto-restarts)
|
||||
```
|
||||
|
||||
If no systemd service exists (`systemctl --user list-units --type=service --all | grep hermes` returns empty), the gateway was started manually and won't survive a logout/reboot without the service.
|
||||
|
||||
#### Branch B: Gateway is running but not responding
|
||||
|
||||
```
|
||||
grep -i telegram ~/.hermes/logs/gateway.log | tail -30
|
||||
```
|
||||
|
||||
Look for:
|
||||
|
||||
- `[Telegram] Connected to Telegram (polling mode)` — connection was established
|
||||
- `inbound message: platform=telegram user=...` — messages are being received
|
||||
- `Sending response (... chars) to 1498679692` — responses are being sent
|
||||
- `ERROR` or `WARNING` entries — something went wrong
|
||||
|
||||
If messages arrive but responses fail, check:
|
||||
1. `TELEGRAM_BOT_TOKEN` is correct and uncommented in `.env`
|
||||
2. The bot hasn't been blocked by Telegram (try sending a test from a different bot)
|
||||
3. Network/firewall: Telegram polling needs outbound connectivity to `api.telegram.org`
|
||||
|
||||
#### Branch C: Never worked (first-time setup)
|
||||
|
||||
If the user says the bot never responded:
|
||||
1. Follow the full Setup Steps section above
|
||||
2. Check that `TELEGRAM_BOT_TOKEN` is on its own line (not merged with another var)
|
||||
3. Verify with `curl` directly: `curl -s "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getMe"` — should return `{"ok":true,"result":{"id":...,"is_bot":true,"first_name":"...","username":"..."}}`
|
||||
4. Check `TELEGRAM_ALLOWED_USERS` — without it, the bot defaults to allowing everyone; if it's set to the wrong numeric ID, the bot ignores your messages
|
||||
5. After fixing, restart the gateway
|
||||
6. Wait 5-10 seconds, then send a message to the bot on Telegram
|
||||
|
||||
### Symptom: "Bot responds to everyone / ignores me"
|
||||
Check `TELEGRAM_ALLOWED_USERS` is set to your numeric user ID. Without this, the bot is open to anyone who finds it.
|
||||
|
||||
### Symptom: "Cron messages don't arrive on Telegram"
|
||||
1. Ensure `TELEGRAM_HOME_CHANNEL` is set to the correct numeric chat ID
|
||||
2. The home channel is where cron delivers by default — if you DM'd the bot manually but set the home channel to a group, cron goes to the group, not your DMs
|
||||
|
||||
### Symptom: "send_message tool says 'No messaging platforms connected' from CLI"
|
||||
This is expected behavior — the `send_message` tool only routes through a session that was initiated from that platform. From a CLI session, it cannot discover Telegram even if the gateway has it connected.
|
||||
|
||||
**Workaround:** Call the Telegram Bot API directly:
|
||||
```bash
|
||||
curl -s -X POST "https://api.telegram.org/bot${TOKEN}/sendMessage" \
|
||||
-d "chat_id=${CHAT_ID}" \
|
||||
-d "text=Your message"
|
||||
```
|
||||
|
||||
### Symptom: "Can't restart — no systemd service found"
|
||||
If `systemctl --user list-units` shows no hermes service, the gateway was started manually (e.g., via `hermes gateway run` in a background terminal). See the **Manual Restart (no systemd service)** section in step 4 above.
|
||||
@@ -0,0 +1,105 @@
|
||||
# Gateway Restart & Test Message Reference
|
||||
|
||||
## Checking Gateway Status
|
||||
|
||||
```
|
||||
ps aux | grep 'hermes gateway run'
|
||||
```
|
||||
|
||||
Note the PID and whether it's running as Ssl (stable).
|
||||
|
||||
## Checking for systemd Service
|
||||
|
||||
```
|
||||
systemctl --user list-units --type=service --all | grep -i hermes
|
||||
```
|
||||
|
||||
Empty response = gateway was started manually, not systemd-managed.
|
||||
|
||||
## Manual Restart Sequence (no systemd)
|
||||
|
||||
```
|
||||
# 1. Find and kill old gateway
|
||||
ps aux | grep 'hermes gateway run'
|
||||
kill -9 <PID> # -9 if plain kill doesn't work
|
||||
sleep 2
|
||||
|
||||
# 2. Verify it's dead
|
||||
ps aux | grep 'hermes gateway run'
|
||||
|
||||
# 3. Start fresh
|
||||
# In the Hermes CLI, use:
|
||||
# terminal(background=true, notify_on_complete=true, command="hermes gateway run")
|
||||
# Do NOT use nohup, disown, or trailing & in foreground terminal()
|
||||
```
|
||||
|
||||
## Verifying Telegram Connection in Logs
|
||||
|
||||
Log file: `~/.hermes/logs/gateway.log`
|
||||
|
||||
### Lines confirming a successful connection:
|
||||
|
||||
```
|
||||
Connecting to telegram...
|
||||
[Telegram] Auto-discovered Telegram fallback IPs: 149.154.166.110
|
||||
[Telegram] set_my_commands OK for scope BotCommandScopeDefault (30 cmds)
|
||||
[Telegram] Connected to Telegram (polling mode)
|
||||
✓ telegram connected
|
||||
```
|
||||
|
||||
### Quick grep check:
|
||||
|
||||
```
|
||||
grep -E "(telegram connected|Connecting to telegram|Connected to Telegram)" ~/.hermes/logs/gateway.log
|
||||
```
|
||||
|
||||
## Sending a Test Message (CLI Workaround)
|
||||
|
||||
The `send_message` Hermes tool does NOT work from CLI sessions for Telegram — it returns "No messaging platforms connected". Use the Telegram Bot API directly via curl:
|
||||
|
||||
```
|
||||
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
||||
-d "chat_id=${CHAT_ID}" \
|
||||
-d "text=Hello from Hermes!"
|
||||
```
|
||||
|
||||
### Expected response on success:
|
||||
|
||||
```json
|
||||
{"ok":true,"result":{"message_id":677,"from":{"id":8971430276,"is_bot":true,...},"chat":{"id":1498679692,...},"date":1780001714,"text":"Hello from Hermes!"}}
|
||||
```
|
||||
|
||||
If `"ok":true`, the message was delivered. You can also add `-d "parse_mode=HTML"` for rich text formatting.
|
||||
|
||||
## .env Configuration Snippets
|
||||
|
||||
### Default state (all commented out):
|
||||
|
||||
```
|
||||
# TELEGRAM_BOT_TOKEN=*** TELEGRAM_ALLOWED_USERS=
|
||||
# TELEGRAM_HOME_CHANNEL=
|
||||
# TELEGRAM_HOME_CHANNEL_NAME=
|
||||
```
|
||||
|
||||
### After configuration — split into separate lines:
|
||||
|
||||
```env
|
||||
TELEGRAM_BOT_TOKEN=your_bot_token_here
|
||||
TELEGRAM_ALLOWED_USERS=123456789
|
||||
TELEGRAM_HOME_CHANNEL=123456789
|
||||
TELEGRAM_HOME_CHANNEL_NAME=@yourusername
|
||||
```
|
||||
|
||||
### sed commands to uncomment (one per line):
|
||||
|
||||
```
|
||||
sed -i 's|^# TELEGRAM_ALLOWED_USERS=.*|TELEGRAM_ALLOWED_USERS=123456789|' ~/.hermes/.env
|
||||
sed -i 's|^# TELEGRAM_HOME_CHANNEL=.*|TELEGRAM_HOME_CHANNEL=123456789|' ~/.hermes/.env
|
||||
sed -i 's|^# TELEGRAM_HOME_CHANNEL_NAME=.*|TELEGRAM_HOME_CHANNEL_NAME=@username|' ~/.hermes/.env
|
||||
```
|
||||
|
||||
**Caveat on TELEGRAM_BOT_TOKEN:** The default template has `TELEGRAM_BOT_TOKEN` and `TELEGRAM_ALLOWED_USERS` on the same commented line. The simple `sed` to uncomment probably won't work cleanly. Verify the exact line format with `grep -n 'TELEGRAM_BOT_TOKEN' ~/.hermes/.env` and replace the entire line if needed, or delete the old line and insert fresh ones.
|
||||
|
||||
## Token Visibility Note
|
||||
|
||||
In gateway logs and grep output, the bot token may appear partially masked (e.g., `TELEGRAM_BOT_TOKEN=***...***`). This is normal log sanitization — the full token is still loaded and used by the gateway process.
|
||||
@@ -0,0 +1,200 @@
|
||||
---
|
||||
name: hermes-webui
|
||||
description: Deploy, configure, and troubleshoot the Hermes Web UI Docker container — the browser interface for Hermes Agent.
|
||||
version: 1.0.0
|
||||
author: Hermes Agent
|
||||
license: MIT
|
||||
platforms: [linux, macos]
|
||||
metadata:
|
||||
hermes:
|
||||
tags: [hermes, webui, docker, browser, troubleshooting]
|
||||
source: https://github.com/nesquena/hermes-webui
|
||||
related_skills: [hermes-agent, hermes-dashboard]
|
||||
---
|
||||
|
||||
# Hermes Web UI
|
||||
|
||||
The Hermes Web UI (`ghcr.io/nesquena/hermes-webui`) provides a browser-based interface for Hermes Agent — chat with the agent, browse sessions, manage workspace files, and monitor agent activity. It runs as a Docker container alongside the Hermes Agent gateway.
|
||||
|
||||
For a **management-focused control panel** (config editing, model switching, tool toggling, cron control, log viewing), see the `hermes-dashboard` skill — a complementary Flask dashboard that reads Hermes state directly from the filesystem.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Pull and run
|
||||
docker run -d \
|
||||
--name hermes-webui \
|
||||
-p 8787:8787 \
|
||||
-v /path/to/hermes-home:/home/hermeswebui/.hermes \
|
||||
-v /path/to/workspace:/workspace \
|
||||
-e HERMES_WEBUI_PASSWORD=your-password \
|
||||
ghcr.io/nesquena/hermes-webui:latest
|
||||
|
||||
# With agent source mounted (for full functionality):
|
||||
docker run -d \
|
||||
--name hermes-webui \
|
||||
-p 8787:8787 \
|
||||
-v /path/to/hermes-home:/home/hermeswebui/.hermes:ro \
|
||||
-v /path/to/hermes-agent-source:/home/hermeswebui/.hermes/hermes-agent:ro \
|
||||
-v /path/to/workspace:/workspace \
|
||||
-e HERMES_WEBUI_PASSWORD=your-password \
|
||||
ghcr.io/nesquena/hermes-webui:latest
|
||||
```
|
||||
|
||||
Open `http://localhost:8787` in a browser and log in with the password.
|
||||
|
||||
## Architecture
|
||||
|
||||
The Web UI requires two adjacent systems to function fully:
|
||||
|
||||
1. **Hermes Agent source code** — the Web UI imports `AIAgent` from the Hermes Agent Python library directly. Without it, features like model auto-detection, personality routing, and CLI session imports are disabled.
|
||||
2. **Hermes Gateway / API Server** — the Web UI communicates with the Hermes API server (OpenAI-compatible, typically port 8642) for agent execution. The API server is started by the Hermes gateway process.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `HERMES_WEBUI_PASSWORD` | (required) | Login password for the web interface |
|
||||
| `HERMES_WEBUI_BIND_HOST` | `0.0.0.0` | IP to bind the HTTP server |
|
||||
| `HERMES_WEBUI_BIND_PORT` | `8787` | Port for the HTTP server |
|
||||
| `HERMES_WEBUI_STATE_DIR` | `~/.hermes/webui` | Where sessions, workspaces, and state are stored |
|
||||
| `HERMES_WEBUI_DEFAULT_WORKSPACE` | `/workspace` | Default workspace directory shown on first launch |
|
||||
| `WANTED_UID` | `1024` | User ID to run as (auto-detected from mounted volumes) |
|
||||
| `WANTED_GID` | `1024` | Group ID (auto-detected from mounted volumes) |
|
||||
|
||||
### Volume Mounts
|
||||
|
||||
| Host Path | Container Path | Purpose |
|
||||
|-----------|---------------|---------|
|
||||
| `~/.hermes` | `/home/hermeswebui/.hermes` | Hermes home directory (config, sessions, skills) |
|
||||
| `~/.hermes/hermes-agent` | `/home/hermeswebui/.hermes/hermes-agent` | Agent source code (for AIAgent import) |
|
||||
| `/path/to/workspace` | `/workspace` | Workspace/project files |
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Check if agent is recognized
|
||||
|
||||
```bash
|
||||
docker logs hermes-webui | grep -E "agent dir|AIAgent"
|
||||
```
|
||||
|
||||
Expected healthy output:
|
||||
```
|
||||
agent dir : /home/hermeswebui/.hermes/hermes-agent [ok]
|
||||
```
|
||||
|
||||
### Verify health
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:8787/health
|
||||
```
|
||||
|
||||
### View server logs
|
||||
|
||||
```bash
|
||||
docker logs hermes-webui
|
||||
```
|
||||
|
||||
### Restart
|
||||
|
||||
```bash
|
||||
docker restart hermes-webui
|
||||
```
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- **`HERMES_WEBUI_STATE_DIR` is required.** Despite having a default in the docs, the container errors out hard without it: `!! ERROR: HERMES_WEBUI_STATE_DIR not set`. Always pass `-e HERMES_WEBUI_STATE_DIR=/home/hermeswebui/.hermes/webui` on the docker run command, or the container will crash-loop.
|
||||
- **Agent source must be accessible at container startup.** The init script installs dependencies from `pyproject.toml` before the server starts. If the source is added after the container is already running, you must remove `/app/venv/.deps_installed` and restart for the init script to reinstall.
|
||||
- **Read-only mounts recommended.** The init script warns if the agent source mount is writable from the WebUI container. The multi-container compose defaults use a read-only mount for defence-in-depth.
|
||||
- **Password redaction in terminal output.** When constructing the `docker run` command through the terminal tool, the password in `-e HERMES_WEBUI_PASSWORD=secret` may be replaced with `***` by secret redaction before the command executes. To bypass: use `execute_code` with Python to construct the command, or write the password hex-encoded and decode it in the heredoc, or verify the deployed password by checking its length and first/last chars via `docker inspect`.
|
||||
- **Permission errors on restart.** Prior `pip install -e` runs as root may leave root-owned `.pyc` files in the venv. If the init script errors with `Permission denied` during reinstall, clean them: `docker exec hermes-webui find /app/venv -user root -delete`
|
||||
- **First startup is slow.** The init script installs dependencies on first run — allow ~30 seconds for startup.
|
||||
- **API server must be running.** The Web UI needs the Hermes API server (started by `hermes gateway run`) on port 8642. Without it, agent chat will fail even if the Web UI's `health` endpoint reports OK.
|
||||
- **Provider credential mismatch.** The Web UI has its OWN isolated `.env` and `config.yaml` inside the mounted Hermes home directory, separate from the host's Hermes config. If these files use a different provider or a placeholder API key, the agent will fail with HTTP 401 errors. Always sync both files when switching providers. See `references/provider-credentials-and-sessions.md`.
|
||||
- **Stale sessions retain old provider settings.** Sessions are cached in `<state_dir>/sessions/<id>.json` with the model and provider that were active at creation time. Switching providers in config.yaml does NOT update existing sessions. The web UI reads `s.model` and `s.model_provider` from the cached session and passes them to `AIAgent.__init__`, which then tries the OLD provider. Symptoms: "No LLM provider configured" on messages even after a correct provider config. Fix: update both `model` and `model_provider` fields in the session JSON, or delete the session file so the frontend creates a fresh one.
|
||||
- **settings.json caches default_model_provider.** The file at `<state_dir>/settings.json` stores `default_model_provider`. If you switched providers, update this field too — otherwise new sessions will still be created with the old provider.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Symptom: HTTP 401 on chat
|
||||
|
||||
Check the web UI's `.env` has the correct API key for the provider in `config.yaml`:
|
||||
|
||||
```bash
|
||||
# Compare config provider with available keys
|
||||
docker exec hermes-webui cat /home/hermeswebui/.hermes/config.yaml
|
||||
docker exec hermes-webui cat /home/hermeswebui/.hermes/.env
|
||||
```
|
||||
|
||||
The key variable name must match what the provider expects (e.g. `DEEPSEEK_API_KEY` for `deepseek`, `OPENROUTER_API_KEY` for `openrouter`).
|
||||
|
||||
### Symptom: "No LLM provider configured" on every message
|
||||
|
||||
This usually means the cached session has a stale provider. Check and fix:
|
||||
|
||||
```bash
|
||||
# 1. Check what model/provider the session has
|
||||
docker exec hermes-webui bash -c 'python3 -c "
|
||||
import json
|
||||
with open(\"/home/hermeswebui/.hermes/webui/sessions/$(
|
||||
ls -t /home/hermeswebui/.hermes/webui/sessions/*.json 2>/dev/null | head -1
|
||||
)\") as f:
|
||||
d = json.load(f)
|
||||
print(f\"model={d.get(\\\"model\\\")} provider={d.get(\\\"model_provider\\\")}\")
|
||||
"'
|
||||
|
||||
# 2. Fix the session (replace with your actual provider/model)
|
||||
docker exec hermes-webui bash -c 'python3 -c "
|
||||
import json
|
||||
p = \"/home/hermeswebui/.hermes/webui/sessions/*.json\"
|
||||
import glob
|
||||
for f in glob.glob(p):
|
||||
with open(f) as fh:
|
||||
d = json.load(fh)
|
||||
d[\"model\"] = \"deepseek-v4-flash\"
|
||||
d[\"model_provider\"] = \"deepseek\"
|
||||
with open(f, \"w\") as fh:
|
||||
json.dump(d, fh)
|
||||
"'
|
||||
|
||||
# 3. Also fix settings.json if needed
|
||||
docker exec hermes-webui bash -c 'python3 -c "
|
||||
import json
|
||||
with open(\"/home/hermeswebui/.hermes/webui/settings.json\") as f:
|
||||
d = json.load(f)
|
||||
d[\"default_model_provider\"] = \"deepseek\"
|
||||
with open(\"/home/hermeswebui/.hermes/webui/settings.json\", \"w\") as f:
|
||||
json.dump(d, f, indent=2)
|
||||
"'
|
||||
|
||||
# 4. Restart to pick up changes
|
||||
docker restart hermes-webui
|
||||
```
|
||||
|
||||
Alternatively, delete the stale session entirely and let the frontend create a fresh one:
|
||||
|
||||
```bash
|
||||
docker exec hermes-webui bash -c 'rm -f /home/hermeswebui/.hermes/webui/sessions/*.json && echo "{}" > /home/hermeswebui/.hermes/webui/sessions/_index.json'
|
||||
docker restart hermes-webui
|
||||
```
|
||||
|
||||
### Symptom: Provider shows as configured but models fail to load
|
||||
|
||||
Check that the web UI's config.yaml uses the correct key names for the model section:
|
||||
|
||||
```yaml
|
||||
model:
|
||||
provider: deepseek # Must match a provider in the Hermes registry
|
||||
default: deepseek-v4-flash # The model name
|
||||
base_url: https://api.deepseek.com/v1
|
||||
```
|
||||
|
||||
The config.yaml key is `default` (not `model`). If you wrote `model:` inside the `model:` section, provider resolution will return empty.
|
||||
|
||||
## References
|
||||
|
||||
- `references/aiagent-not-available.md` — fixing the "AIAgent not available" error when the container cannot find the Hermes Agent source.
|
||||
- `references/provider-credentials-and-sessions.md` — detailed diagnosis flow for 401 errors and stale session/provider mismatches, including scripted fixes.
|
||||
- `references/firecrawl-reddit-limitations.md` — Firecrawl explicitly blocks Reddit; only WSB RSS works.
|
||||
@@ -0,0 +1,104 @@
|
||||
# Fixing "AIAgent not available" in Hermes Web UI
|
||||
|
||||
The Web UI cannot find the Hermes Agent source code and starts in reduced-functionality mode. Symptoms in `docker logs hermes-webui`:
|
||||
|
||||
```
|
||||
!! WARNING: hermes-agent source not found.
|
||||
!! Looked in: /home/hermeswebui/.hermes/hermes-agent
|
||||
ImportError: AIAgent not available -- check that hermes-agent is on sys.path
|
||||
agent dir : NOT FOUND [XX]
|
||||
```
|
||||
|
||||
## Root Cause
|
||||
|
||||
The container needs the Hermes Agent Python source at `/home/hermeswebui/.hermes/hermes-agent` (or `/opt/hermes` as fallback). This is used by the init script to:
|
||||
|
||||
1. Install dependencies from the agent's `pyproject.toml`
|
||||
2. Make `AIAgent` importable from `run_agent.py`
|
||||
|
||||
When the source is missing, the server starts without any agent features.
|
||||
|
||||
## Fix (agent source is on host but not in the container)
|
||||
|
||||
If your Hermes home is bind-mounted and the agent source exists on the host but wasn't mounted:
|
||||
|
||||
### Option A: Copy source into the mounted data directory (no restart needed)
|
||||
|
||||
```bash
|
||||
# Copy host's hermes-agent source into the directory already mounted to the container
|
||||
cp -r /path/to/host/.hermes/hermes-agent /path/to/mounted-data/hermes-agent
|
||||
|
||||
# Install deps inside the container
|
||||
docker exec hermes-webui bash -c "source /app/venv/bin/activate && pip install -e /home/hermeswebui/.hermes/hermes-agent"
|
||||
|
||||
# Remove the fast-restart marker and restart to force full setup
|
||||
docker exec hermes-webui rm -f /app/venv/.deps_installed
|
||||
docker restart hermes-webui
|
||||
|
||||
# If permission errors occur (root-owned pycache from prior pip install):
|
||||
docker exec hermes-webui bash -c "find /app/venv -user root -delete"
|
||||
docker restart hermes-webui
|
||||
```
|
||||
|
||||
### Option B: Add a proper bind mount (cleaner, Docker Compose)
|
||||
|
||||
Add to the webui service in your docker-compose.yml:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
hermes-webui:
|
||||
image: ghcr.io/nesquena/hermes-webui:latest
|
||||
volumes:
|
||||
- ~/.hermes:/home/hermeswebui/.hermes:ro
|
||||
- ~/.hermes/hermes-agent:/home/hermeswebui/.hermes/hermes-agent:ro # <-- add this
|
||||
- /path/to/workspace:/workspace
|
||||
environment:
|
||||
- HERMES_WEBUI_PASSWORD=***
|
||||
ports:
|
||||
- "8787:8787"
|
||||
```
|
||||
|
||||
Then recreate:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## Verifying the fix
|
||||
|
||||
```bash
|
||||
# Check startup logs for success
|
||||
docker logs hermes-webui | grep "agent dir"
|
||||
|
||||
# Expected:
|
||||
# agent dir : /home/hermeswebui/.hermes/hermes-agent [ok]
|
||||
|
||||
# Also confirm no AIAgent import errors:
|
||||
docker logs hermes-webui | grep -i "AIAgent"
|
||||
|
||||
# Should return nothing (no errors)
|
||||
```
|
||||
|
||||
## Known edge cases
|
||||
|
||||
### Fast restart skipping agent install
|
||||
|
||||
The init script checks for `/app/venv/.deps_installed`. If the agent source was added after the container's first startup (when the marker was created), the init script skips reinstalling and the import still fails even though the source is present.
|
||||
|
||||
**Fix:** Remove the marker and restart:
|
||||
|
||||
```bash
|
||||
docker exec hermes-webui rm -f /app/venv/.deps_installed
|
||||
docker restart hermes-webui
|
||||
```
|
||||
|
||||
### Permission denied during reinstall ("os error 13")
|
||||
|
||||
A prior `pip install -e` or manual install running as root leaves root-owned `.pyc` files in `/app/venv/lib/python3.12/site-packages/__pycache__/`. When the init script later runs as a non-root user, it can't remove them.
|
||||
|
||||
**Fix:** Clean root files from the venv:
|
||||
|
||||
```bash
|
||||
docker exec hermes-webui bash -c "find /app/venv -user root -delete"
|
||||
docker restart hermes-webui
|
||||
```
|
||||
@@ -0,0 +1,9 @@
|
||||
# Firecrawl + Reddit Limitations
|
||||
|
||||
Firecrawl (firecrawl.dev, `firecrawl-py` SDK) explicitly blocks Reddit:
|
||||
|
||||
```
|
||||
HTTP 403: "We apologize for the inconvenience but we do not support this site."
|
||||
```
|
||||
|
||||
Applies to all Reddit URLs. See `devops/hermes-dashboard/references/firecrawl-reddit-limitations.md` for full details including the WSB RSS workaround.
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
# Provider Credentials & Session State Diagnosis
|
||||
|
||||
Diagnosis flow for HTTP 401 errors and "No LLM provider configured" when the web UI has the agent source installed (agent dir shows `[ok]` in logs).
|
||||
|
||||
## Diagnosis Flow
|
||||
|
||||
### Step 1 — Check what provider the web UI is actually trying to use
|
||||
|
||||
```bash
|
||||
docker exec hermes-webui cat /home/hermeswebui/.hermes/config.yaml
|
||||
```
|
||||
|
||||
The `model.provider` field is the provider the web UI will attempt to use. If this says `openrouter` but your API keys are for `deepseek`, that's the problem.
|
||||
|
||||
### Step 2 — Check that the .env has the right API key
|
||||
|
||||
```bash
|
||||
docker exec hermes-webui cat /home/hermeswebui/.hermes/.env
|
||||
```
|
||||
|
||||
The env var name must match what the provider expects. Common mappings:
|
||||
|
||||
| provider in config.yaml | env var |
|
||||
|---|---|
|
||||
| `deepseek` | `DEEPSEEK_API_KEY` |
|
||||
| `openrouter` | `OPENROUTER_API_KEY` |
|
||||
| `anthropic` | `ANTHROPIC_API_KEY` |
|
||||
| `openai` / `openai-api` | `OPENAI_API_KEY` |
|
||||
| `gemini` | `GOOGLE_API_KEY` or `GEMINI_API_KEY` |
|
||||
|
||||
### Step 3 — Check if the error is from a stale cached session
|
||||
|
||||
The web UI caches session metadata (model, provider, workspace) on disk. When you switch providers, old sessions keep the old provider.
|
||||
|
||||
```bash
|
||||
# Find the most recent session file
|
||||
ls -t /home/hermeswebui/.hermes/webui/sessions/*.json 2>/dev/null | head -3
|
||||
|
||||
# Check its model/provider
|
||||
python3 -c "
|
||||
import json, glob
|
||||
for f in sorted(glob.glob('/home/hermeswebui/.hermes/webui/sessions/*.json')):
|
||||
with open(f) as fh:
|
||||
d = json.load(fh)
|
||||
print(f'{f}: model={d.get(\"model\")} provider={d.get(\"model_provider\")}')
|
||||
"
|
||||
```
|
||||
|
||||
Expected: `model=deepseek-v4-flash provider=deepseek` (or whatever your current provider is).
|
||||
|
||||
If the session shows a different provider, update it:
|
||||
|
||||
```bash
|
||||
python3 -c "
|
||||
import json, glob
|
||||
for f in glob.glob('/home/hermeswebui/.hermes/webui/sessions/*.json'):
|
||||
with open(f) as fh:
|
||||
d = json.load(fh)
|
||||
d['model'] = 'deepseek-v4-flash' # <-- replace with your model
|
||||
d['model_provider'] = 'deepseek' # <-- replace with your provider
|
||||
with open(f, 'w') as fh:
|
||||
json.dump(d, fh)
|
||||
"
|
||||
```
|
||||
|
||||
### Step 4 — Check settings.json
|
||||
|
||||
The web UI stores `default_model_provider` in settings.json:
|
||||
|
||||
```bash
|
||||
python3 -c "
|
||||
import json
|
||||
with open('/home/hermeswebui/.hermes/webui/settings.json') as f:
|
||||
d = json.load(f)
|
||||
print('default_model_provider:', d.get('default_model_provider'))
|
||||
"
|
||||
```
|
||||
|
||||
If this doesn't match your current provider:
|
||||
|
||||
```bash
|
||||
python3 -c "
|
||||
import json
|
||||
with open('/home/hermeswebui/.hermes/webui/settings.json') as f:
|
||||
d = json.load(f)
|
||||
d['default_model_provider'] = 'deepseek' # <-- replace with your provider
|
||||
with open('/home/hermeswebui/.hermes/webui/settings.json', 'w') as f:
|
||||
json.dump(d, f, indent=2)
|
||||
"
|
||||
```
|
||||
|
||||
### Step 5 — Verify provider resolution works
|
||||
|
||||
Test what the web UI resolves at runtime:
|
||||
|
||||
```bash
|
||||
python3 -c "
|
||||
import os
|
||||
os.environ['HERMES_HOME'] = '/home/hermeswebui/.hermes'
|
||||
from api.config import get_config, resolve_model_provider, model_with_provider_context
|
||||
|
||||
cfg = get_config()
|
||||
print('Config model section:', cfg.get('model', {}))
|
||||
|
||||
# Simulate sending a message without specifying a model
|
||||
model_with_ctx = model_with_provider_context('')
|
||||
resolved = resolve_model_provider(model_with_ctx)
|
||||
print('Resolved (empty model):', resolved)
|
||||
|
||||
# Simulate sending a message with your default model
|
||||
model_with_ctx = model_with_provider_context('deepseek-v4-flash')
|
||||
resolved = resolve_model_provider(model_with_ctx)
|
||||
print('Resolved (default model):', resolved)
|
||||
|
||||
# Check runtime provider can find the API key
|
||||
from api.oauth import resolve_runtime_provider_with_anthropic_env_lock
|
||||
from hermes_cli.runtime_provider import resolve_runtime_provider
|
||||
_rt = resolve_runtime_provider_with_anthropic_env_lock(
|
||||
resolve_runtime_provider,
|
||||
requested=resolved[1], # the resolved provider name
|
||||
)
|
||||
print('API key found:', bool(_rt.get('api_key')))
|
||||
"
|
||||
```
|
||||
|
||||
If the key is found and provider/base_url are correct, the config is right and the issue is in the session cache.
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Pattern: OpenRouter → DeepSeek switch
|
||||
|
||||
After switching from OpenRouter to DeepSeek, three things need updating:
|
||||
|
||||
1. `config.yaml` — set `model.provider: deepseek`, `model.default: deepseek-v4-flash`, `model.base_url: https://api.deepseek.com/v1`
|
||||
2. `.env` — set `DEEPSEEK_API_KEY=<your-key>`
|
||||
3. Session cache — update session JSON files to use `model_provider: deepseek` instead of `openrouter`
|
||||
4. `settings.json` — update `default_model_provider` to `deepseek`
|
||||
|
||||
### Pattern: Copying host config to web UI
|
||||
|
||||
The host's `~/.hermes/` is NOT mounted into the web UI container. The web UI uses whatever is in the bind-mounted data directory (e.g. `/home/ray/docker/hermes/data/`). To sync:
|
||||
|
||||
```bash
|
||||
cp ~/.hermes/config.yaml /path/to/webui/data/config.yaml
|
||||
grep -E '^(DEEPSEEK|OPENROUTER|ANTHROPIC|OPENAI|GEMINI|GOOGLE)_API_KEY' ~/.hermes/.env >> /path/to/webui/data/.env
|
||||
```
|
||||
Reference in New Issue
Block a user