diff --git a/website/docs/developer-guide/creating-skills.md b/website/docs/developer-guide/creating-skills.md index 58a65e3a13..f2238d7d56 100644 --- a/website/docs/developer-guide/creating-skills.md +++ b/website/docs/developer-guide/creating-skills.md @@ -57,6 +57,15 @@ metadata: hermes: tags: [Category, Subcategory, Keywords] related_skills: [other-skill-name] + requires_toolsets: [web] # Optional — only show when these toolsets are active + requires_tools: [web_search] # Optional — only show when these tools are available + fallback_for_toolsets: [browser] # Optional — hide when these toolsets are active + fallback_for_tools: [browser_navigate] # Optional — hide when these tools exist +required_environment_variables: # Optional — env vars the skill needs + - name: MY_API_KEY + prompt: "Enter your API key" + help: "Get one at https://example.com" + required_for: "API access" --- # Skill Title @@ -91,6 +100,57 @@ platforms: [windows] # Windows only When set, the skill is automatically hidden from the system prompt, `skills_list()`, and slash commands on incompatible platforms. If omitted or empty, the skill loads on all platforms (backward compatible). +### Conditional Skill Activation + +Skills can declare dependencies on specific tools or toolsets. This controls whether the skill appears in the system prompt for a given session. + +```yaml +metadata: + hermes: + requires_toolsets: [web] # Hide if the web toolset is NOT active + requires_tools: [web_search] # Hide if web_search tool is NOT available + fallback_for_toolsets: [browser] # Hide if the browser toolset IS active + fallback_for_tools: [browser_navigate] # Hide if browser_navigate IS available +``` + +| Field | Behavior | +|-------|----------| +| `requires_toolsets` | Skill is **hidden** when ANY listed toolset is **not** available | +| `requires_tools` | Skill is **hidden** when ANY listed tool is **not** available | +| `fallback_for_toolsets` | Skill is **hidden** when ANY listed toolset **is** available | +| `fallback_for_tools` | Skill is **hidden** when ANY listed tool **is** available | + +**Use case for `fallback_for_*`:** Create a skill that serves as a workaround when a primary tool isn't available. For example, a `duckduckgo-search` skill with `fallback_for_tools: [web_search]` only shows when the web search tool (which requires an API key) is not configured. + +**Use case for `requires_*`:** Create a skill that only makes sense when certain tools are present. For example, a web scraping workflow skill with `requires_toolsets: [web]` won't clutter the prompt when web tools are disabled. + +### Environment Variable Requirements + +Skills can declare environment variables they need. When a skill is loaded via `skill_view`, its required vars are automatically registered for passthrough into sandboxed execution environments (terminal, execute_code). + +```yaml +required_environment_variables: + - name: TENOR_API_KEY + prompt: "Tenor API key" # Shown when prompting user + help: "Get your key at https://tenor.com" # Help text or URL + required_for: "GIF search functionality" # What needs this var +``` + +Each entry supports: +- `name` (required) — the environment variable name +- `prompt` (optional) — prompt text when asking the user for the value +- `help` (optional) — help text or URL for obtaining the value +- `required_for` (optional) — describes which feature needs this variable + +Users can also manually configure passthrough variables in `config.yaml`: + +```yaml +terminal: + env_passthrough: + - MY_CUSTOM_VAR + - ANOTHER_VAR +``` + See `skills/apple/` for examples of macOS-only skills. ## Secure Setup on Load diff --git a/website/docs/user-guide/configuration.md b/website/docs/user-guide/configuration.md index ebcf180ed5..6ec7278a1c 100644 --- a/website/docs/user-guide/configuration.md +++ b/website/docs/user-guide/configuration.md @@ -55,6 +55,22 @@ Settings are resolved in this order (highest priority first): Secrets (API keys, bot tokens, passwords) go in `.env`. Everything else (model, terminal backend, compression settings, memory limits, toolsets) goes in `config.yaml`. When both are set, `config.yaml` wins for non-secret settings. ::: +## Environment Variable Substitution + +You can reference environment variables in `config.yaml` using `${VAR_NAME}` syntax: + +```yaml +auxiliary: + vision: + api_key: ${GOOGLE_API_KEY} + base_url: ${CUSTOM_VISION_URL} + +delegation: + api_key: ${DELEGATION_KEY} +``` + +Multiple references in a single value work: `url: "${HOST}:${PORT}"`. If a referenced variable is not set, the placeholder is kept verbatim (`${UNDEFINED_VAR}` stays as-is). Only the `${VAR}` syntax is supported — bare `$VAR` is not expanded. + ## Inference Providers You need at least one way to connect to an LLM. Use `hermes model` to switch providers and models interactively, or configure directly: @@ -1329,6 +1345,23 @@ Usage: type `/status`, `/disk`, `/update`, or `/gpu` in the CLI or any messaging - **Type** — only `exec` is supported (runs a shell command); other types show an error - **Works everywhere** — CLI, Telegram, Discord, Slack, WhatsApp, Signal, Email, Home Assistant +## Gateway Streaming + +Enable progressive token delivery on messaging platforms. When streaming is enabled, responses appear character-by-character in Telegram, Discord, and Slack via message editing, rather than waiting for the full response. + +```yaml +streaming: + enabled: false # Enable streaming token delivery (default: off) + transport: edit # "edit" (progressive message editing) or "off" + edit_interval: 0.3 # Min seconds between message edits + buffer_threshold: 40 # Characters accumulated before forcing an edit + cursor: " ▉" # Cursor character shown during streaming +``` + +**Platform support:** Telegram, Discord, and Slack support edit-based streaming. Platforms that don't support message editing (Signal, Email, Home Assistant) are auto-detected on the first attempt — streaming is gracefully disabled for that session with no flood of messages. + +**Overflow handling:** If the streamed text exceeds the platform's message length limit (~4096 chars), the current message is finalized and a new one starts automatically. + ## Human Delay Simulate human-like response pacing in messaging platforms: @@ -1350,6 +1383,27 @@ code_execution: max_tool_calls: 50 # Max tool calls within code execution ``` +## Web Search Backends + +The `web_search`, `web_extract`, and `web_crawl` tools support three backend providers. Configure the backend in `config.yaml` or via `hermes tools`: + +```yaml +web: + backend: firecrawl # firecrawl | parallel | tavily +``` + +| Backend | Env Var | Search | Extract | Crawl | +|---------|---------|--------|---------|-------| +| **Firecrawl** (default) | `FIRECRAWL_API_KEY` | ✔ | ✔ | ✔ | +| **Parallel** | `PARALLEL_API_KEY` | ✔ | ✔ | — | +| **Tavily** | `TAVILY_API_KEY` | ✔ | ✔ | ✔ | + +**Backend selection:** If `web.backend` is not set, the backend is auto-detected from available API keys. If only `TAVILY_API_KEY` is set, Tavily is used. If only `PARALLEL_API_KEY` is set, Parallel is used. Otherwise Firecrawl is the default. + +**Self-hosted Firecrawl:** Set `FIRECRAWL_API_URL` to point at your own instance. When a custom URL is set, the API key becomes optional (set `USE_DB_AUTHENTICATION=false` on the server to disable auth). + +**Parallel search modes:** Set `PARALLEL_SEARCH_MODE` to control search behavior — `fast`, `one-shot`, or `agentic` (default: `agentic`). + ## Browser Configure browser automation behavior: diff --git a/website/docs/user-guide/features/context-references.md b/website/docs/user-guide/features/context-references.md new file mode 100644 index 0000000000..2b58f80cab --- /dev/null +++ b/website/docs/user-guide/features/context-references.md @@ -0,0 +1,109 @@ +--- +sidebar_position: 9 +title: "Context References" +description: "Inline @-syntax for attaching files, folders, git diffs, and URLs directly into your messages" +--- + +# Context References + +Type `@` followed by a reference to inject content directly into your message. Hermes expands the reference inline and appends the content under an `--- Attached Context ---` section. + +## Supported References + +| Syntax | Description | +|--------|-------------| +| `@file:path/to/file.py` | Inject file contents | +| `@file:path/to/file.py:10-25` | Inject specific line range (1-indexed, inclusive) | +| `@folder:path/to/dir` | Inject directory tree listing with file metadata | +| `@diff` | Inject `git diff` (unstaged working tree changes) | +| `@staged` | Inject `git diff --staged` (staged changes) | +| `@git:5` | Inject last N commits with patches (max 10) | +| `@url:https://example.com` | Fetch and inject web page content | + +## Usage Examples + +```text +Review @file:src/main.py and suggest improvements + +What changed? @diff + +Compare @file:old_config.yaml and @file:new_config.yaml + +What's in @folder:src/components? + +Summarize this article @url:https://arxiv.org/abs/2301.00001 +``` + +Multiple references work in a single message: + +```text +Check @file:main.py, and also @file:test.py. +``` + +Trailing punctuation (`,`, `.`, `;`, `!`, `?`) is automatically stripped from reference values. + +## CLI Tab Completion + +In the interactive CLI, typing `@` triggers autocomplete: + +- `@` shows all reference types (`@diff`, `@staged`, `@file:`, `@folder:`, `@git:`, `@url:`) +- `@file:` and `@folder:` trigger filesystem path completion with file size metadata +- Bare `@` followed by partial text shows matching files and folders from the current directory + +## Line Ranges + +The `@file:` reference supports line ranges for precise content injection: + +```text +@file:src/main.py:42 # Single line 42 +@file:src/main.py:10-25 # Lines 10 through 25 (inclusive) +``` + +Lines are 1-indexed. Invalid ranges are silently ignored (full file is returned). + +## Size Limits + +Context references are bounded to prevent overwhelming the model's context window: + +| Threshold | Value | Behavior | +|-----------|-------|----------| +| Soft limit | 25% of context length | Warning appended, expansion proceeds | +| Hard limit | 50% of context length | Expansion refused, original message returned unchanged | +| Folder entries | 200 files max | Excess entries replaced with `- ...` | +| Git commits | 10 max | `@git:N` clamped to range [1, 10] | + +## Security + +### Sensitive Path Blocking + +These paths are always blocked from `@file:` references to prevent credential exposure: + +- SSH keys and config: `~/.ssh/id_rsa`, `~/.ssh/id_ed25519`, `~/.ssh/authorized_keys`, `~/.ssh/config` +- Shell profiles: `~/.bashrc`, `~/.zshrc`, `~/.profile`, `~/.bash_profile`, `~/.zprofile` +- Credential files: `~/.netrc`, `~/.pgpass`, `~/.npmrc`, `~/.pypirc` +- Hermes env: `$HERMES_HOME/.env` + +These directories are fully blocked (any file inside): +- `~/.ssh/`, `~/.aws/`, `~/.gnupg/`, `~/.kube/`, `$HERMES_HOME/skills/.hub/` + +### Path Traversal Protection + +All paths are resolved relative to the working directory. References that resolve outside the allowed workspace root are rejected. + +### Binary File Detection + +Binary files are detected via MIME type and null-byte scanning. Known text extensions (`.py`, `.md`, `.json`, `.yaml`, `.toml`, `.js`, `.ts`, etc.) bypass MIME-based detection. Binary files are rejected with a warning. + +## Error Handling + +Invalid references produce inline warnings rather than failures: + +| Condition | Behavior | +|-----------|----------| +| File not found | Warning: "file not found" | +| Binary file | Warning: "binary files are not supported" | +| Folder not found | Warning: "folder not found" | +| Git command fails | Warning with git stderr | +| URL returns no content | Warning: "no content extracted" | +| Sensitive path | Warning: "path is a sensitive credential file" | +| Path outside workspace | Warning: "path is outside the allowed workspace" | diff --git a/website/docs/user-guide/security.md b/website/docs/user-guide/security.md index 5edb563539..b38cdcb148 100644 --- a/website/docs/user-guide/security.md +++ b/website/docs/user-guide/security.md @@ -358,6 +358,42 @@ When a blocked URL is requested, the tool returns an error explaining the domain See [Website Blocklist](/docs/user-guide/configuration#website-blocklist) in the configuration guide for full details. +### SSRF Protection + +All URL-capable tools (web search, web extract, vision, browser) validate URLs before fetching them to prevent Server-Side Request Forgery (SSRF) attacks. Blocked addresses include: + +- **Private networks** (RFC 1918): `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16` +- **Loopback**: `127.0.0.0/8`, `::1` +- **Link-local**: `169.254.0.0/16` (includes cloud metadata at `169.254.169.254`) +- **CGNAT / shared address space** (RFC 6598): `100.64.0.0/10` (Tailscale, WireGuard VPNs) +- **Cloud metadata hostnames**: `metadata.google.internal`, `metadata.goog` +- **Reserved, multicast, and unspecified addresses** + +SSRF protection is always active and cannot be disabled. DNS failures are treated as blocked (fail-closed). Redirect chains are re-validated at each hop to prevent redirect-based bypasses. + +### Tirith Pre-Exec Security Scanning + +Hermes integrates [tirith](https://github.com/sheeki03/tirith) for content-level command scanning before execution. Tirith detects threats that pattern matching alone misses: + +- Homograph URL spoofing (internationalized domain attacks) +- Pipe-to-interpreter patterns (`curl | bash`, `wget | sh`) +- Terminal injection attacks + +Tirith auto-installs from GitHub releases on first use with SHA-256 checksum verification (and cosign provenance verification if cosign is available). + +```yaml +# In ~/.hermes/config.yaml +security: + tirith_enabled: true # Enable/disable tirith scanning (default: true) + tirith_path: "tirith" # Path to tirith binary (default: PATH lookup) + tirith_timeout: 5 # Subprocess timeout in seconds + tirith_fail_open: true # Allow execution when tirith is unavailable (default: true) +``` + +When `tirith_fail_open` is `true` (default), commands proceed if tirith is not installed or times out. Set to `false` in high-security environments to block commands when tirith is unavailable. + +Tirith's verdict integrates with the approval flow: safe commands pass through, suspicious commands trigger user approval, and dangerous commands are blocked. + ### Context File Injection Protection Context files (AGENTS.md, .cursorrules, SOUL.md) are scanned for prompt injection before being included in the system prompt. The scanner checks for: diff --git a/website/docs/user-guide/sessions.md b/website/docs/user-guide/sessions.md index 07d46af646..736ac8a304 100644 --- a/website/docs/user-guide/sessions.md +++ b/website/docs/user-guide/sessions.md @@ -114,7 +114,13 @@ Session IDs follow the format `YYYYMMDD_HHMMSS_<8-char-hex>`, e.g. `20250305_091 Give sessions human-readable titles so you can find and resume them easily. -### Setting a Title +### Auto-Generated Titles + +Hermes automatically generates a short descriptive title (3–7 words) for each session after the first exchange. This runs in a background thread using a fast auxiliary model, so it adds no latency. You'll see auto-generated titles when browsing sessions with `hermes sessions list` or `hermes sessions browse`. + +Auto-titling only fires once per session and is skipped if you've already set a title manually. + +### Setting a Title Manually Use the `/title` slash command inside any chat session (CLI or gateway):