Merge branch 'main' into bb/gui

This commit is contained in:
emozilla
2026-05-15 22:18:15 -04:00
109 changed files with 6979 additions and 539 deletions
+10 -4
View File
@@ -19,13 +19,16 @@ Prefer a native installer? Use the desktop release channel that matches your ris
Stable desktop builds ship signed/notarized macOS artifacts and Windows installers with checksum files.
### Linux / macOS / WSL2
### One-Line CLI Installer (Linux / macOS / WSL2)
For a git-based install that tracks `main` and gives you the latest changes immediately:
>>>>>>> main
```bash
curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash
```
### Windows (native, PowerShell) — Early Beta
### Windows CLI (native, PowerShell) — Early Beta
:::warning Early BETA
Native Windows support is **early beta**. It installs and works for the common paths, but hasn't been road-tested as broadly as our POSIX installers. Please [file issues](https://github.com/NousResearch/hermes-agent/issues) when you hit rough edges. For the most battle-tested setup on Windows today, use the Linux/macOS one-liner above inside **WSL2** instead.
@@ -76,7 +79,8 @@ Where the installer puts things depends on whether you're installing as a normal
| Installer | Code lives at | `hermes` binary | Data directory |
|---|---|---|---|
| Per-user (normal) | `~/.hermes/hermes-agent/` | `~/.local/bin/hermes` (symlink) | `~/.hermes/` |
| pip install | Python site-packages | `~/.local/bin/hermes` (console_scripts) | `~/.hermes/` |
| Per-user (git installer) | `~/.hermes/hermes-agent/` | `~/.local/bin/hermes` (symlink) | `~/.hermes/` |
| Root-mode (`sudo curl … \| sudo bash`) | `/usr/local/lib/hermes-agent/` | `/usr/local/bin/hermes` | `/root/.hermes/` (or `$HERMES_HOME`) |
The root-mode **FHS layout** (`/usr/local/lib/…`, `/usr/local/bin/hermes`) matches where other system-wide developer tools land on Linux. It's useful for shared-machine deployments where one system install should serve every user. Per-user config (auth, skills, sessions) still lives under each user's `~/.hermes/` or explicit `HERMES_HOME`.
@@ -104,7 +108,9 @@ hermes setup # Or run the full setup wizard to configure everything at
## Prerequisites
The only prerequisite is **Git**. The installer automatically handles everything else:
**pip install:** No prerequisites beyond Python 3.11+. Everything else is handled automatically.
**Git installer:** The only prerequisite is **Git**. The installer automatically handles everything else:
- **uv** (fast Python package manager)
- **Python 3.11** (via uv, no sudo needed)
+10 -1
View File
@@ -48,7 +48,16 @@ Pick the row that matches your goal:
## 1. Install Hermes Agent
Run the one-line installer:
**Option A — pip (simplest):**
```bash
pip install hermes-agent
hermes postinstall # optional: installs Node.js, browser, ripgrep, ffmpeg + runs setup
```
PyPI releases track tagged versions (major/minor releases), not every commit on `main`. For bleeding-edge, use Option B.
**Option B — git installer (tracks main branch):**
```bash
# Linux / macOS / WSL2 / Android (Termux)
+29 -3
View File
@@ -8,19 +8,36 @@ description: "How to update Hermes Agent to the latest version or uninstall it"
## Updating
### Git installs
Update to the latest version with a single command:
```bash
hermes update
```
This pulls the latest code, updates dependencies, and prompts you to configure any new options that were added since your last update.
This pulls the latest code from `main`, updates dependencies, and prompts you to configure any new options that were added since your last update.
### pip installs
PyPI releases track **tagged versions** (major and minor releases), not every commit on `main`. Check for updates and upgrade with:
```bash
hermes update --check # see if a newer release is on PyPI
hermes update # runs pip install --upgrade hermes-agent
```
Or manually:
```bash
pip install --upgrade hermes-agent # or: uv pip install --upgrade hermes-agent
```
:::tip
`hermes update` automatically detects new configuration options and prompts you to add them. If you skipped that prompt, you can manually run `hermes config check` to see missing options, then `hermes config migrate` to interactively add them.
:::
### What happens during an update
### What happens during an update (git installs)
When you run `hermes update`, the following steps occur:
@@ -32,7 +49,7 @@ When you run `hermes update`, the following steps occur:
### Preview-only: `hermes update --check`
Want to know if you're behind `origin/main` before actually pulling? Run `hermes update --check` — it fetches, prints your local commit and the latest remote commit side-by-side, and exits `0` if in sync or `1` if behind. No files are modified, no gateway is restarted. Useful in scripts and cron jobs that gate on "is there an update".
Want to know if an update is available before pulling? Run `hermes update --check` — for git installs it fetches and compares commits against `origin/main`; for pip installs it queries PyPI for the latest release. No files are modified, no gateway is restarted. Useful in scripts and cron jobs that gate on "is there an update".
### Full pre-update backup: `--backup`
@@ -189,12 +206,21 @@ See [Nix Setup](./nix-setup.md) for more details.
## Uninstalling
### Git installs
```bash
hermes uninstall
```
The uninstaller gives you the option to keep your configuration files (`~/.hermes/`) for a future reinstall.
### pip installs
```bash
pip uninstall hermes-agent
rm -rf ~/.hermes # Optional — keep if you plan to reinstall
```
### Manual Uninstall
```bash
+137
View File
@@ -0,0 +1,137 @@
---
sidebar_position: 17
title: "OAuth over SSH / Remote Hosts"
description: "How to complete browser-based OAuth (xAI, Spotify) when Hermes runs on a remote machine, container, or behind a jump box"
---
# OAuth over SSH / Remote Hosts
Some Hermes providers — currently **xAI Grok OAuth** and **Spotify** — use a *loopback redirect* OAuth flow. The auth server (xAI, Spotify) redirects your browser to `http://127.0.0.1:<port>/callback` so a tiny HTTP listener started by the `hermes auth ...` command can grab the authorization code.
This works perfectly when Hermes and your browser are on the same machine. It breaks the moment they aren't: your laptop's browser tries to reach `127.0.0.1` on **your laptop**, but the listener is bound to `127.0.0.1` on **the remote server**.
The fix is a one-line SSH local-forward.
## TL;DR
```bash
# On your local machine (laptop), in a separate terminal:
ssh -N -L 56121:127.0.0.1:56121 user@remote-host
# In your existing SSH session on the remote machine:
hermes auth add xai-oauth --no-browser
# → Hermes prints an authorize URL. Open it in a browser on your laptop.
# → Your browser redirects to 127.0.0.1:56121/callback, the tunnel forwards
# the request to the remote listener, login completes.
```
Port `56121` is what xAI OAuth uses. For Spotify, replace it with `43827`. Hermes prints the exact port it bound to on the `Waiting for callback on ...` line — copy it from there.
## Which Providers Need This
| Provider | Loopback port | Tunnel needed? |
|----------|---------------|----------------|
| `xai-oauth` (Grok SuperGrok) | `56121` | Yes, when Hermes is remote |
| Spotify | `43827` | Yes, when Hermes is remote |
| `anthropic` (Claude Pro/Max) | n/a | No — paste-the-code flow |
| `openai-codex` (ChatGPT Plus/Pro) | n/a | No — device code flow |
| `minimax`, `nous-portal` | n/a | No — device code flow |
If your provider isn't in the table, you don't need a tunnel.
## Why the listener can't just bind 0.0.0.0
xAI and Spotify both validate the `redirect_uri` parameter against an allowlist. Both require the loopback form (`http://127.0.0.1:<exact-port>/callback`). Binding the listener to `0.0.0.0` or a different port would cause the auth server to reject the request as a redirect_uri mismatch. The SSH tunnel keeps the loopback URI intact end-to-end.
## Step-by-step: single SSH hop
### 1. Start the tunnel from your local machine
```bash
# xAI Grok OAuth (port 56121)
ssh -N -L 56121:127.0.0.1:56121 user@remote-host
# Or for Spotify (port 43827)
ssh -N -L 43827:127.0.0.1:43827 user@remote-host
```
`-N` means "don't open a remote shell, just hold the tunnel open." Keep this terminal running for the duration of the login.
### 2. In a separate SSH session, run the auth command
```bash
ssh user@remote-host
hermes auth add xai-oauth --no-browser
# or for Spotify:
# hermes auth add spotify --no-browser
```
Hermes detects the SSH session, skips the browser auto-open, and prints an authorize URL plus a `Waiting for callback on http://127.0.0.1:<port>/callback` line.
### 3. Open the URL in your local browser
Copy the authorize URL from the remote terminal and paste it into the browser on your laptop. Approve the consent screen. The auth server redirects to `http://127.0.0.1:<port>/callback`. Your browser hits the tunnel, the request is forwarded to the remote listener, and Hermes prints `Login successful!`.
You can tear down the tunnel (Ctrl+C in the first terminal) once you see the success line.
## Step-by-step: through a jump box
If you reach Hermes through a bastion / jump host, use SSH's built-in `-J` (ProxyJump):
```bash
ssh -N -L 56121:127.0.0.1:56121 -J jump-user@jump-host user@final-host
```
This chains a SSH connection through the jump host without putting the loopback port on the jump box itself. The local `127.0.0.1:56121` on your laptop tunnels straight through to `127.0.0.1:56121` on the final remote host.
For older OpenSSH that doesn't support `-J`, the long form is:
```bash
ssh -N \
-o "ProxyCommand=ssh -W %h:%p jump-user@jump-host" \
-L 56121:127.0.0.1:56121 \
user@final-host
```
## Mosh, tmux, ssh ControlMaster
The tunnel is a property of the underlying SSH connection. If you're running Hermes inside `tmux` over a mosh session, the mosh roaming doesn't carry the `-L` forwarding. Open a *separate* plain SSH session **only** for the `-L` tunnel — that's the connection that has to stay alive during the auth flow. Your interactive mosh/tmux session can keep running Hermes normally.
If you use `ssh -o ControlMaster=auto`, port forwards on a multiplexed connection share the master's lifetime. Restart the master if the tunnel doesn't come up:
```bash
ssh -O exit user@remote-host
ssh -N -L 56121:127.0.0.1:56121 user@remote-host
```
## Troubleshooting
### `bind [127.0.0.1]:56121: Address already in use`
Something on your laptop is already using that port. Either the previous tunnel didn't shut down cleanly, or a local Hermes is also listening on it. Find and kill the offender:
```bash
# macOS / Linux
lsof -iTCP:56121 -sTCP:LISTEN
kill <PID>
```
Then retry the `ssh -L` command.
### "Could not establish connection. We couldn't reach your app." (xAI)
xAI's authorize page shows this when its redirect to `127.0.0.1:<port>/callback` doesn't reach a listener. Either the tunnel isn't running, the port is wrong, or you're using the port Hermes printed in a previous run (the port can be auto-bumped if the preferred one is busy — always read the latest `Waiting for callback on ...` line).
### `xAI authorization timed out waiting for the local callback`
Same root cause as above — the redirect never made it back. Check the tunnel is still alive (`ssh -N` doesn't show output, so look at the terminal you started it from), restart it if needed, and re-run `hermes auth add xai-oauth --no-browser`.
### Tokens land in the wrong `~/.hermes`
The tokens are written under the Linux user that ran `hermes auth add ...`. If your gateway / systemd service runs as a different user (e.g. `root` or a dedicated `hermes` user), authenticate as **that** user so the tokens land in their `~/.hermes/auth.json`. `sudo -u hermes -i` or equivalent.
## See Also
- [xAI Grok OAuth](./xai-grok-oauth.md)
- [Spotify (`Running over SSH`)](../user-guide/features/spotify.md#running-over-ssh--in-a-headless-environment)
- [SSH `-J` / ProxyJump (man page)](https://man.openbsd.org/ssh#J)
+19 -5
View File
@@ -59,14 +59,23 @@ hermes auth add xai-oauth
### Remote / headless sessions
On servers, containers, or SSH sessions where no browser is available, Hermes detects the remote environment and prints the authorization URL instead of opening a browser. Open the URL on any device with a browser, complete the consent flow, and Hermes finishes the loopback exchange when the redirect comes back.
On servers, containers, or SSH sessions where no browser is available, Hermes detects the remote environment and prints the authorization URL instead of opening a browser.
If you need to force this behaviour explicitly:
**Important:** the loopback listener still runs on the remote machine at `127.0.0.1:56121`. The xAI redirect needs to reach *that* listener, so opening the URL on your laptop will fail (`Could not establish connection. We couldn't reach your app.`) unless you forward the port:
```bash
# In a separate terminal on your local machine:
ssh -N -L 56121:127.0.0.1:56121 user@remote-host
# Then in your SSH session on the remote machine:
hermes auth add xai-oauth --no-browser
# Open the printed authorize URL in your local browser.
```
Through a jump box / bastion: add `-J jump-user@jump-host`.
See [OAuth over SSH / Remote Hosts](./oauth-over-ssh.md) for the full step-by-step, including ProxyJump chains, mosh/tmux, and ControlMaster gotchas.
## How the Login Works
1. Hermes opens your browser to `accounts.x.ai`.
@@ -182,14 +191,18 @@ Hermes detected that the `state` value returned by the authorization server does
### Logging in from a remote server
On SSH or container sessions Hermes prints the authorization URL instead of opening a browser. Open the URL on any device with a browser and complete the consent there — the loopback callback comes back to your remote host.
You can also force this behaviour:
On SSH or container sessions Hermes prints the authorization URL instead of opening a browser. The loopback callback listener still binds `127.0.0.1:56121` on the remote host — your laptop's browser can't reach it without an SSH local-forward:
```bash
# Local machine, separate terminal:
ssh -N -L 56121:127.0.0.1:56121 user@remote-host
# Remote machine:
hermes auth add xai-oauth --no-browser
```
Full walkthrough (jump boxes, mosh/tmux, port conflicts): [OAuth over SSH / Remote Hosts](./oauth-over-ssh.md).
### "No xAI credentials found" error at runtime
The auth store has no `xai-oauth` entry and no `XAI_API_KEY` is set. You haven't logged in yet, or the credential file was deleted.
@@ -208,6 +221,7 @@ This clears both the singleton OAuth entry in `auth.json` and any credential-poo
## See Also
- [OAuth over SSH / Remote Hosts](./oauth-over-ssh.md) — required reading if Hermes is on a different machine than your browser
- [AI Providers reference](../integrations/providers.md)
- [Environment Variables](../reference/environment-variables.md)
- [Configuration](../user-guide/configuration.md)
+1 -1
View File
@@ -331,7 +331,7 @@ When using the Z.AI / GLM provider, Hermes automatically probes multiple endpoin
xAI is wired through the Responses API (`codex_responses` transport) for automatic reasoning support on Grok 4 models — no `reasoning_effort` parameter needed, the server reasons by default. Set `XAI_API_KEY` in `~/.hermes/.env` and pick xAI in `hermes model`, or drop `grok` as a shortcut into `/model grok-4-1-fast-reasoning`.
SuperGrok subscribers can sign in with browser OAuth instead of using an API key — pick **xAI Grok OAuth (SuperGrok Subscription)** in `hermes model`, or run `hermes auth add xai-oauth`. The same OAuth bearer token is automatically reused by direct-to-xAI tools (TTS, image gen, video gen, transcription). See the [xAI Grok OAuth guide](../guides/xai-grok-oauth.md) for the full flow.
SuperGrok subscribers can sign in with browser OAuth instead of using an API key — pick **xAI Grok OAuth (SuperGrok Subscription)** in `hermes model`, or run `hermes auth add xai-oauth`. The same OAuth bearer token is automatically reused by direct-to-xAI tools (TTS, image gen, video gen, transcription). See the [xAI Grok OAuth guide](../guides/xai-grok-oauth.md) for the full flow — and if Hermes runs on a remote host, also see [OAuth over SSH / Remote Hosts](../guides/oauth-over-ssh.md) for the required `ssh -L` tunnel.
When using xAI as a provider (any base URL containing `x.ai`), Hermes automatically enables prompt caching by sending the `x-grok-conv-id` header with every API request. This routes requests to the same server within a conversation session, allowing xAI's infrastructure to reuse cached system prompts and conversation history.
+3 -1
View File
@@ -76,7 +76,7 @@ hermes [global-options] <command> [subcommand/options]
| `hermes profile` | Manage profiles — multiple isolated Hermes instances. |
| `hermes completion` | Print shell completion scripts (bash/zsh/fish). |
| `hermes version` | Show version information. |
| `hermes update` | Pull latest code and reinstall dependencies. `--check` prints commit diff without pulling; `--backup` takes a pre-pull `HERMES_HOME` snapshot. |
| `hermes update` | Pull latest code and reinstall dependencies (git installs), or check PyPI and `pip install --upgrade` (pip installs). `--check` previews without installing; `--backup` takes a pre-pull `HERMES_HOME` snapshot. |
| `hermes uninstall` | Remove Hermes from the system. |
## `hermes chat`
@@ -1188,6 +1188,8 @@ hermes update [--check] [--backup] [--restart-gateway]
Pulls the latest `hermes-agent` code and reinstalls dependencies in your venv, then re-runs the post-install hooks (MCP servers, skills sync, completion install). Safe to run on a live install.
**pip installs:** `hermes update` detects pip-based installations automatically — it queries PyPI for the latest release and runs `pip install --upgrade hermes-agent` instead of `git pull`. PyPI releases track tagged versions (major/minor releases), not every commit on `main`. Use `--check` to see if a newer PyPI release is available without installing.
| Option | Description |
|--------|-------------|
| `--check` | Print the current commit and the latest `origin/main` commit side by side, and exit 0 if in sync or 1 if behind. Does not pull, install, or restart anything. |
@@ -39,6 +39,7 @@ hermes skills uninstall <skill-name>
| Skill | Description |
|-------|-------------|
| [**evm**](/docs/user-guide/skills/optional/blockchain/blockchain-evm) | Read-only EVM client: wallets, tokens, gas across 8 chains. |
| [**hyperliquid**](/docs/user-guide/skills/optional/blockchain/blockchain-hyperliquid) | Hyperliquid market data, account history, trade review. |
| [**solana**](/docs/user-guide/skills/optional/blockchain/blockchain-solana) | Query Solana blockchain data with USD pricing — wallet balances, token portfolios with values, transaction details, NFTs, whale detection, and live network stats. Uses Solana RPC + CoinGecko. No API key required. |
## communication
@@ -88,6 +89,7 @@ hermes skills uninstall <skill-name>
| [**lbo-model**](/docs/user-guide/skills/optional/finance/finance-lbo-model) | Build leveraged buyout models in Excel — sources & uses, debt schedule, cash sweep, exit multiple, IRR/MOIC sensitivity. Pairs with excel-author. Use for PE screening, sponsor-case valuation, or illustrative LBO in a pitch. |
| [**merger-model**](/docs/user-guide/skills/optional/finance/finance-merger-model) | Build accretion/dilution (merger) models in Excel — pro-forma P&L, synergies, financing mix, EPS impact. Pairs with excel-author. Use for M&A pitches, board materials, or deal evaluation. |
| [**pptx-author**](/docs/user-guide/skills/optional/finance/finance-pptx-author) | Build PowerPoint decks headless with python-pptx. Pairs with excel-author for model-backed decks where every number traces to a workbook cell. Use for pitch decks, IC memos, earnings notes. |
| [**stocks**](/docs/user-guide/skills/optional/finance/finance-stocks) | Stock quotes, history, search, compare, crypto via Yahoo. |
## health
@@ -176,6 +178,12 @@ hermes skills uninstall <skill-name>
| [**oss-forensics**](/docs/user-guide/skills/optional/security/security-oss-forensics) | Supply chain investigation, evidence recovery, and forensic analysis for GitHub repositories. Covers deleted commit recovery, force-push detection, IOC extraction, multi-source evidence collection, hypothesis formation/validation, and st... |
| [**sherlock**](/docs/user-guide/skills/optional/security/security-sherlock) | OSINT username search across 400+ social networks. Hunt down social media accounts by username. |
## software-development
| Skill | Description |
|-------|-------------|
| [**rest-graphql-debug**](/docs/user-guide/skills/optional/software-development/software-development-rest-graphql-debug) | Debug REST/GraphQL APIs: status codes, auth, schemas, repro. |
## web-development
| Skill | Description |
+1 -1
View File
@@ -144,7 +144,7 @@ If a skill is missing from this list but present in the repo, the catalog is reg
| [`linear`](/docs/user-guide/skills/bundled/productivity/productivity-linear) | Linear: manage issues, projects, teams via GraphQL + curl. | `productivity/linear` |
| [`maps`](/docs/user-guide/skills/bundled/productivity/productivity-maps) | Geocode, POIs, routes, timezones via OpenStreetMap/OSRM. | `productivity/maps` |
| [`nano-pdf`](/docs/user-guide/skills/bundled/productivity/productivity-nano-pdf) | Edit PDF text/typos/titles via nano-pdf CLI (NL prompts). | `productivity/nano-pdf` |
| [`notion`](/docs/user-guide/skills/bundled/productivity/productivity-notion) | Notion API via curl: pages, databases, blocks, search. | `productivity/notion` |
| [`notion`](/docs/user-guide/skills/bundled/productivity/productivity-notion) | Notion API + ntn CLI: pages, databases, markdown, Workers. | `productivity/notion` |
| [`ocr-and-documents`](/docs/user-guide/skills/bundled/productivity/productivity-ocr-and-documents) | Extract text from PDFs/scans (pymupdf, marker-pdf). | `productivity/ocr-and-documents` |
| [`powerpoint`](/docs/user-guide/skills/bundled/productivity/productivity-powerpoint) | Create, read, edit .pptx decks, slides, notes, templates. | `productivity/powerpoint` |
| [`teams-meeting-pipeline`](/docs/user-guide/skills/bundled/productivity/productivity-teams-meeting-pipeline) | Operate the Teams meeting summary pipeline via Hermes CLI — summarize meetings, inspect pipeline status, replay jobs, manage Microsoft Graph subscriptions. | `productivity/teams-meeting-pipeline` |
+1 -1
View File
@@ -820,7 +820,7 @@ Available providers for auxiliary tasks: `auto`, `main`, plus any provider in th
:::
:::tip xAI Grok OAuth
`xai-oauth` logs in via browser OAuth for SuperGrok subscribers (no API key needed). Run `hermes model` and select **xAI Grok OAuth (SuperGrok Subscription)** to authenticate. The same OAuth token is reused for every direct-to-xAI surface (chat, auxiliary tasks, TTS, image gen, video gen, transcription). See the [xAI Grok OAuth guide](../guides/xai-grok-oauth.md).
`xai-oauth` logs in via browser OAuth for SuperGrok subscribers (no API key needed). Run `hermes model` and select **xAI Grok OAuth (SuperGrok Subscription)** to authenticate. The same OAuth token is reused for every direct-to-xAI surface (chat, auxiliary tasks, TTS, image gen, video gen, transcription). See the [xAI Grok OAuth guide](../guides/xai-grok-oauth.md), and if Hermes is on a remote host see [OAuth over SSH / Remote Hosts](../guides/oauth-over-ssh.md).
:::
:::warning `"main"` is for auxiliary tasks only
+7 -1
View File
@@ -68,7 +68,13 @@ Agree to the terms and click **Save**. On the next page click **Settings** → c
### Running over SSH / in a headless environment
If `SSH_CLIENT` or `SSH_TTY` is set, Hermes skips the automatic browser open during both the wizard and the OAuth step. Copy the dashboard URL and the authorization URL Hermes prints, open them in a browser on your local machine, and proceed normally — the local HTTP listener still runs on the remote host on port 43827. If you need to reach it through an SSH tunnel, forward that port: `ssh -L 43827:127.0.0.1:43827 remote`.
If `SSH_CLIENT` or `SSH_TTY` is set, Hermes skips the automatic browser open during both the wizard and the OAuth step. Copy the dashboard URL and the authorization URL Hermes prints, open them in a browser on your local machine, and proceed normally — the local HTTP listener still runs on the remote host on port `43827`. Your laptop's browser can't reach the remote loopback without an SSH local-forward:
```bash
ssh -N -L 43827:127.0.0.1:43827 user@remote-host
```
For jump-box / bastion setups and other gotchas (mosh, tmux, port conflicts), see [OAuth over SSH / Remote Hosts](../../guides/oauth-over-ssh.md).
## Verify
@@ -16,8 +16,8 @@ Generate images, video, and audio with ComfyUI — install, launch, manage nodes
|---|---|
| Source | Bundled (installed by default) |
| Path | `skills/creative/comfyui` |
| Version | `5.0.0` |
| Author | ['kshitijk4poor', 'alt-glitch'] |
| Version | `5.1.0` |
| Author | ['kshitijk4poor', 'alt-glitch', 'purzbeats'] |
| License | MIT |
| Platforms | macos, linux, windows |
| Tags | `comfyui`, `image-generation`, `stable-diffusion`, `flux`, `sd3`, `wan-video`, `hunyuan-video`, `creative`, `generative-ai`, `video-generation` |
@@ -42,6 +42,12 @@ for workflow execution.
- `official-cli.md` — every `comfy ...` command, with flags
- `rest-api.md` — REST + WebSocket endpoints (local + cloud), payload schemas
- `workflow-format.md` — API-format JSON, common node types, param mapping
- `template-integrity.md` — converting `comfyui-workflow-templates` from
editor format to API format: Reroute bypass, dotted dynamic-input keys
(`values.a`, `resize_type.width`), Cloud quirks (302 redirect, 1 concurrent
free-tier job, 1080p VRAM ceiling), Discord-compatible ffmpeg stitch.
Authored by [@purzbeats](https://github.com/purzbeats). Load this whenever
you're starting from an official template.
**Scripts (`scripts/`):**
@@ -65,6 +65,29 @@ kanban_complete(
)
```
**Coding task that needs human review (review-required):**
For most code-changing tasks, the work isn't truly *done* until a human reviewer has eyes on it. Block instead of complete, with `reason` prefixed `review-required: ` so the dashboard surfaces the row as needing review. Drop the structured metadata (changed files, test counts, diff/PR url) into a comment first, since `kanban_block` only carries the human-readable reason — comments are the durable annotation channel. Reviewer either approves and runs `hermes kanban unblock <id>` (which re-spawns you with the comment thread for any follow-ups) or asks for changes via another comment.
```python
import json
kanban_comment(
body="review-required handoff:\n" + json.dumps({
"changed_files": ["rate_limiter.py", "tests/test_rate_limiter.py"],
"tests_run": 14,
"tests_passed": 14,
"diff_path": "/path/to/worktree", # or PR url if pushed
"decisions": ["user_id primary, IP fallback for unauthenticated requests"],
}, indent=2),
)
kanban_block(
reason="review-required: rate limiter shipped, 14/14 tests pass — needs eyes on the user_id/IP fallback choice before merging",
)
```
Use `kanban_complete` only when the task is genuinely terminal — e.g. a one-line typo fix, a docs change with no functional consequences, or a research task where the artifact IS the writeup itself.
**Research task:**
```python
kanban_complete(
@@ -1,14 +1,14 @@
---
title: "Notion — Notion API via curl: pages, databases, blocks, search"
title: "Notion — Notion API + ntn CLI: pages, databases, markdown, Workers"
sidebar_label: "Notion"
description: "Notion API via curl: pages, databases, blocks, search"
description: "Notion API + ntn CLI: pages, databases, markdown, Workers"
---
{/* This page is auto-generated from the skill's SKILL.md by website/scripts/generate-skill-docs.py. Edit the source SKILL.md, not this page. */}
# Notion
Notion API via curl: pages, databases, blocks, search.
Notion API + ntn CLI: pages, databases, markdown, Workers.
## Skill metadata
@@ -16,11 +16,11 @@ Notion API via curl: pages, databases, blocks, search.
|---|---|
| Source | Bundled (installed by default) |
| Path | `skills/productivity/notion` |
| Version | `1.0.0` |
| Version | `2.0.0` |
| Author | community |
| License | MIT |
| Platforms | linux, macos, windows |
| Tags | `Notion`, `Productivity`, `Notes`, `Database`, `API` |
| Tags | `Notion`, `Productivity`, `Notes`, `Database`, `API`, `CLI`, `Workers` |
## Reference: full SKILL.md
@@ -28,23 +28,146 @@ Notion API via curl: pages, databases, blocks, search.
The following is the complete skill definition that Hermes loads when this skill is triggered. This is what the agent sees as instructions when the skill is active.
:::
# Notion API
# Notion
Use the Notion API via curl to create, read, update pages, databases (data sources), and blocks. No extra tools needed — just curl and a Notion API key.
Talk to Notion two ways. Same integration token works for both — pick by what's available.
## Prerequisites
**`ntn` CLI** — Notion's official CLI. Shorter syntax, one-line file uploads, required for Workers. macOS + Linux only as of May 2026 (Windows support "coming soon"). **Default when installed.**
**HTTP + curl** — works everywhere including Windows. **Default fallback** when `ntn` isn't installed.
## Setup
### 1. Get an integration token (required for both paths)
1. Create an integration at https://notion.so/my-integrations
2. Copy the API key (starts with `ntn_` or `secret_`)
3. Store it in `~/.hermes/.env`:
3. Store in `~/.hermes/.env`:
```
NOTION_API_KEY=ntn_your_key_here
```
4. **Important:** Share target pages/databases with your integration in Notion (click "...""Connect to" → your integration name)
4. **Share target pages/databases with the integration** in Notion: page menu `...``Connect to` → your integration name. Without this, the API returns 404 for that page even though it exists.
### 2. Install `ntn` (preferred path on macOS / Linux)
```bash
# Recommended
curl -fsSL https://ntn.dev | bash
# Or via npm (needs Node 22+, npm 10+)
npm install --global ntn
ntn --version # verify
```
**Skip `ntn login` — use the integration token instead.** This works headlessly, no browser needed:
```bash
export NOTION_API_TOKEN=$NOTION_API_KEY # ntn reads NOTION_API_TOKEN
export NOTION_KEYRING=0 # don't try to use the OS keychain
```
Add those exports to your shell profile (or to `~/.hermes/.env`) so every session inherits them.
### 3. Choose path at runtime
```bash
if command -v ntn >/dev/null 2>&1; then
# use ntn
else
# fall back to curl
fi
```
Windows users: skip step 2 entirely until native `ntn` ships — Path B works fine. If you want CLI ergonomics now, install `ntn` inside WSL2.
## API Basics
All requests use this pattern:
`Notion-Version: 2025-09-03` is required on all HTTP requests. `ntn` handles this for you. In this version, what users call "databases" are called **data sources** in the API.
## Path A — `ntn` CLI (preferred, macOS / Linux)
### Raw API calls (shorthand for curl)
```bash
ntn api v1/users # GET
ntn api v1/pages parent[page_id]=abc123 \ # POST with inline body
properties[title][0][text][content]="Notes"
ntn api v1/pages/abc123 -X PATCH archived:=true # PATCH; := is non-string (bool/num/null)
```
Syntax notes:
- `key=value` — string fields
- `key[nested]=value` — nested object fields
- `key:=value` — typed assignment (booleans, numbers, null, arrays)
### Search
```bash
ntn api v1/search query="page title"
```
### Read page metadata
```bash
ntn api v1/pages/{page_id}
```
### Read page as Markdown (agent-friendly)
```bash
ntn api v1/pages/{page_id}/markdown
```
### Read page content as blocks
```bash
ntn api v1/blocks/{page_id}/children
```
### Create page from Markdown
```bash
ntn api v1/pages \
parent[page_id]=xxx \
properties[title][0][text][content]="Notes from meeting" \
markdown="# Agenda
- Q3 roadmap
- Hiring"
```
### Patch a page with Markdown
```bash
ntn api v1/pages/{page_id}/markdown -X PATCH \
markdown="## Update
Shipped the prototype."
```
### Query a database (data source)
```bash
ntn api v1/data_sources/{data_source_id}/query -X POST \
filter[property]=Status filter[select][equals]=Active
```
For complex queries with `sorts`, multiple filter clauses, or compound logic, pipe JSON in:
```bash
echo '{"filter": {"property": "Status", "select": {"equals": "Active"}}, "sorts": [{"property": "Date", "direction": "descending"}]}' | \
ntn api v1/data_sources/{data_source_id}/query -X POST --json -
```
### File uploads (one-liner — biggest CLI win)
```bash
ntn files create < photo.png
ntn files create --external-url https://example.com/photo.png
ntn files list
```
Compare to the 3-step HTTP flow (create upload → PUT bytes → reference).
### Useful env vars
| Var | Effect |
|---|---|
| `NOTION_API_TOKEN` | Auth token (overrides keychain) — set this to your integration token |
| `NOTION_KEYRING=0` | File-based creds at `~/.config/notion/auth.json` instead of OS keychain |
| `NOTION_WORKSPACE_ID` | Skip the workspace picker prompt |
## Path B — HTTP + curl (cross-platform, default on Windows)
All requests share this pattern:
```bash
curl -s -X GET "https://api.notion.com/v1/..." \
@@ -53,12 +176,9 @@ curl -s -X GET "https://api.notion.com/v1/..." \
-H "Content-Type: application/json"
```
The `Notion-Version` header is required. This skill uses `2025-09-03` (latest). In this version, databases are called "data sources" in the API.
## Common Operations
On Windows the `curl` shipped with Windows 10+ works as-is. PowerShell users can also use `Invoke-RestMethod`.
### Search
```bash
curl -s -X POST "https://api.notion.com/v1/search" \
-H "Authorization: Bearer $NOTION_API_KEY" \
@@ -67,24 +187,56 @@ curl -s -X POST "https://api.notion.com/v1/search" \
-d '{"query": "page title"}'
```
### Get Page
### Read page metadata
```bash
curl -s "https://api.notion.com/v1/pages/{page_id}" \
-H "Authorization: Bearer $NOTION_API_KEY" \
-H "Notion-Version: 2025-09-03"
```
### Get Page Content (blocks)
### Read page as Markdown (agent-friendly)
Easier to feed to a model than block JSON.
```bash
curl -s "https://api.notion.com/v1/pages/{page_id}/markdown" \
-H "Authorization: Bearer $NOTION_API_KEY" \
-H "Notion-Version: 2025-09-03"
```
### Read page content as blocks (when you need structure)
```bash
curl -s "https://api.notion.com/v1/blocks/{page_id}/children" \
-H "Authorization: Bearer $NOTION_API_KEY" \
-H "Notion-Version: 2025-09-03"
```
### Create Page in a Database
### Create page from Markdown
`POST /v1/pages` accepts a `markdown` body param.
```bash
curl -s -X POST "https://api.notion.com/v1/pages" \
-H "Authorization: Bearer $NOTION_API_KEY" \
-H "Notion-Version: 2025-09-03" \
-H "Content-Type: application/json" \
-d '{
"parent": {"page_id": "xxx"},
"properties": {"title": [{"text": {"content": "Notes from meeting"}}]},
"markdown": "# Agenda\n\n- Q3 roadmap\n- Hiring\n\n## Decisions\n- Ship MVP Friday"
}'
```
### Patch a page with Markdown
```bash
curl -s -X PATCH "https://api.notion.com/v1/pages/{page_id}/markdown" \
-H "Authorization: Bearer $NOTION_API_KEY" \
-H "Notion-Version: 2025-09-03" \
-H "Content-Type: application/json" \
-d '{"markdown": "## Update\n\nShipped the prototype."}'
```
### Create page in a database (typed properties)
```bash
curl -s -X POST "https://api.notion.com/v1/pages" \
-H "Authorization: Bearer $NOTION_API_KEY" \
@@ -99,8 +251,7 @@ curl -s -X POST "https://api.notion.com/v1/pages" \
}'
```
### Query a Database
### Query a database (data source)
```bash
curl -s -X POST "https://api.notion.com/v1/data_sources/{data_source_id}/query" \
-H "Authorization: Bearer $NOTION_API_KEY" \
@@ -112,8 +263,7 @@ curl -s -X POST "https://api.notion.com/v1/data_sources/{data_source_id}/query"
}'
```
### Create a Database
### Create a database
```bash
curl -s -X POST "https://api.notion.com/v1/data_sources" \
-H "Authorization: Bearer $NOTION_API_KEY" \
@@ -130,8 +280,7 @@ curl -s -X POST "https://api.notion.com/v1/data_sources" \
}'
```
### Update Page Properties
### Update page properties
```bash
curl -s -X PATCH "https://api.notion.com/v1/pages/{page_id}" \
-H "Authorization: Bearer $NOTION_API_KEY" \
@@ -140,8 +289,7 @@ curl -s -X PATCH "https://api.notion.com/v1/pages/{page_id}" \
-d '{"properties": {"Status": {"select": {"name": "Done"}}}}'
```
### Add Content to a Page
### Append blocks to a page
```bash
curl -s -X PATCH "https://api.notion.com/v1/blocks/{page_id}/children" \
-H "Authorization: Bearer $NOTION_API_KEY" \
@@ -154,6 +302,21 @@ curl -s -X PATCH "https://api.notion.com/v1/blocks/{page_id}/children" \
}'
```
### File uploads (3-step flow)
```bash
# 1. Create upload
curl -s -X POST "https://api.notion.com/v1/file_uploads" \
-H "Authorization: Bearer $NOTION_API_KEY" \
-H "Notion-Version: 2025-09-03" \
-H "Content-Type: application/json" \
-d '{"filename": "photo.png", "content_type": "image/png"}'
# 2. PUT bytes to the upload_url returned above
curl -s -X PUT "{upload_url}" --data-binary @photo.png
# 3. Reference {file_upload_id} in a page/block payload
```
## Property Types
Common property formats for database items:
@@ -169,19 +332,132 @@ Common property formats for database items:
- **Email:** `{"email": "user@example.com"}`
- **Relation:** `{"relation": [{"id": "page_id"}]}`
## Key Differences in API Version 2025-09-03
## API Version 2025-09-03 — Databases vs Data Sources
- **Databases → Data Sources:** Use `/data_sources/` endpoints for queries and retrieval
- **Two IDs:** Each database has both a `database_id` and a `data_source_id`
- Use `database_id` when creating pages (`parent: {"database_id": "..."}`)
- Use `data_source_id` when querying (`POST /v1/data_sources/{id}/query`)
- **Search results:** Databases return as `"object": "data_source"` with their `data_source_id`
- **Databases became data sources.** Use `/data_sources/` endpoints for queries and retrieval.
- **Two IDs per database:** `database_id` and `data_source_id`.
- `database_id` when creating pages: `parent: {"database_id": "..."}`
- `data_source_id` when querying: `POST /v1/data_sources/{id}/query`
- Search returns databases as `"object": "data_source"` with the `data_source_id` field.
## Notion Workers (advanced, requires `ntn`)
Workers are TypeScript programs Notion hosts for you. One worker can expose any combination of:
- **Syncs** — pull data from external APIs into a Notion database on a schedule (default 30 min).
- **Tools** — appear as callable tools inside Notion's Custom Agents.
- **Webhooks** — receive HTTP events from external services (GitHub, Stripe, etc.) and act in Notion.
**Plan / platform gating:**
- CLI works on all plans. **Deploying Workers requires Business or Enterprise.**
- `ntn` is macOS/Linux only as of May 2026. Windows users need WSL2 or to wait for native support.
- Free through August 11, 2026; metered on Notion credits after.
### Minimal Worker
```bash
ntn workers new my-worker # scaffold
cd my-worker
# Edit src/index.ts
ntn workers deploy --name my-worker
```
`src/index.ts`:
```typescript
import { Worker } from "@notionhq/workers";
const worker = new Worker();
export default worker;
worker.tool("greet", {
title: "Greet a User",
description: "Returns a friendly greeting",
inputSchema: { type: "object", properties: { name: { type: "string" } }, required: ["name"] },
execute: async ({ name }) => `Hello, ${name}!`,
});
```
### Webhook capability
```typescript
worker.webhook("onGithubPush", {
title: "GitHub Push Handler",
execute: async (events, { notion }) => {
for (const event of events) {
// event.body, event.rawBody (for signature verification), event.headers
console.log("got delivery", event.deliveryId);
}
},
});
```
After deploy: `ntn workers webhooks list` shows the URL Notion generates. Treat that URL as a secret — anyone with it can POST events unless you add signature verification.
### Worker lifecycle commands
```bash
ntn workers deploy
ntn workers list
ntn workers exec <capability-key> -d '{"name": "world"}'
ntn workers sync trigger <key> # run a sync now
ntn workers sync pause <key>
ntn workers env set GITHUB_WEBHOOK_SECRET=...
ntn workers runs list # recent invocations
ntn workers runs logs <run-id>
ntn workers webhooks list
```
When asked to build a Worker, scaffold with `ntn workers new`, write the code in `src/index.ts`, set any secrets with `ntn workers env set`, and deploy. Notion's docs at https://developers.notion.com/workers cover the full API surface.
## Notion-Flavored Markdown (used by `/markdown` endpoints)
Standard CommonMark plus XML-like tags for Notion-specific blocks. Use **tabs** for indentation.
**Blocks beyond CommonMark:**
```
<callout icon="🎯" color="blue_bg">
Ship the MVP by **Friday**.
</callout>
<details color="gray">
<summary>Toggle title</summary>
Children indented one tab
</details>
<columns>
<column>Left side</column>
<column>Right side</column>
</columns>
<table_of_contents color="gray"/>
```
**Inline:**
- Mentions: `<mention-user url="..."/>`, `<mention-page url="...">Title</mention-page>`, `<mention-date start="2026-05-15"/>`
- Underline: `<span underline="true">text</span>`
- Color: `<span color="blue">text</span>` or block-level `{color="blue"}` on the first line
- Math: inline `$x^2$`, block `$$ ... $$`
- Citations: `[^https://example.com]`
**Colors:** `gray brown orange yellow green blue purple pink red`, plus `*_bg` variants for backgrounds.
Headings 5/6 collapse to H4. Multiple `>` lines render as separate quote blocks — use `<br>` inside a single `>` for multi-line quotes.
## Choosing the Right Path
| Task | mac / Linux | Windows |
|---|---|---|
| Read/write pages, search, query databases | `ntn api ...` | curl |
| Read a page for an agent to summarize | `ntn api v1/pages/{id}/markdown` | curl `/markdown` endpoint |
| Upload a file | `ntn files create < file` | 3-step HTTP flow |
| One-off API exploration | `ntn api ...` | curl |
| Build a sync / webhook / agent tool hosted by Notion | `ntn workers ...` | WSL2 + `ntn workers ...` |
## Notes
- Page/database IDs are UUIDs (with or without dashes)
- Rate limit: ~3 requests/second average
- The API cannot set database view filters — that's UI-only
- Use `is_inline: true` when creating data sources to embed them in pages
- Add `-s` flag to curl to suppress progress bars (cleaner output for Hermes)
- Pipe output through `jq` for readable JSON: `... | jq '.results[0].properties'`
- Page/database IDs are UUIDs (with or without dashes — both accepted).
- Rate limit: ~3 requests/second average. The CLI doesn't bypass this.
- The API cannot set database **view** filters — that's UI-only.
- Use `"is_inline": true` when creating data sources to embed them in a page.
- Always pass `-s` to curl to suppress progress bars (cleaner agent output).
- Pipe JSON through `jq` when reading: `... | jq '.results[0].properties'`.
- Notion also ships an MCP server now (`Notion MCP`, ~91% more token-efficient on DB ops than the previous version) — wire it via Hermes' MCP support if you want streaming Notion access from inside a session, but the paths above are enough for most one-shot tasks.
@@ -0,0 +1,228 @@
---
title: "Hyperliquid — Hyperliquid market data, account history, trade review"
sidebar_label: "Hyperliquid"
description: "Hyperliquid market data, account history, trade review"
---
{/* This page is auto-generated from the skill's SKILL.md by website/scripts/generate-skill-docs.py. Edit the source SKILL.md, not this page. */}
# Hyperliquid
Hyperliquid market data, account history, trade review.
## Skill metadata
| | |
|---|---|
| Source | Optional — install with `hermes skills install official/blockchain/hyperliquid` |
| Path | `optional-skills/blockchain/hyperliquid` |
| Version | `0.1.0` |
| Author | Hugo Sequier (Hugo-SEQUIER), Hermes Agent |
| License | MIT |
| Platforms | linux, macos, windows |
| Tags | `Hyperliquid`, `Blockchain`, `Crypto`, `Trading`, `Perpetuals`, `Spot`, `DeFi` |
## Reference: full SKILL.md
:::info
The following is the complete skill definition that Hermes loads when this skill is triggered. This is what the agent sees as instructions when the skill is active.
:::
# Hyperliquid Skill
Query Hyperliquid market and account data through the public `/info` endpoint.
Read-only — no API key, no signing, no order placement.
12 commands: `dexs`, `markets`, `spots`, `candles`, `funding`, `l2`, `state`,
`spot-balances`, `fills`, `orders`, `review`, `export`. Stdlib only
(`urllib`, `json`, `argparse`).
---
## When to Use
- User asks for Hyperliquid perp or spot market data, candles, funding, or L2 book
- User wants to inspect a wallet's perp positions, spot balances, fills, or orders
- User wants a post-trade review combining recent fills with market context
- User wants to inspect builder-deployed perp dexs or HIP-3 markets
- User wants a normalized JSON export of candles + funding for backtesting prep
---
## Prerequisites
Stdlib only — no external packages, no API key.
The script reads `~/.hermes/.env` for two optional defaults:
- `HYPERLIQUID_API_URL` — defaults to `https://api.hyperliquid.xyz`. Set to
`https://api.hyperliquid-testnet.xyz` for testnet.
- `HYPERLIQUID_USER_ADDRESS` — default address for `state`, `spot-balances`,
`fills`, `orders`, and `review`. If unset, pass the address as the first
positional argument.
A project `.env` in the current working directory is honored as a dev fallback.
Helper script: `~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py`
---
## How to Run
Invoke through the `terminal` tool:
```bash
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py <command> [args]
```
Add `--json` to any command for machine-readable output.
---
## Quick Reference
```bash
hyperliquid_client.py dexs
hyperliquid_client.py markets [--dex DEX] [--limit N] [--sort volume|oi|funding_abs|change_abs|name]
hyperliquid_client.py spots [--limit N]
hyperliquid_client.py candles <coin> [--interval 1h] [--hours 24] [--limit N]
hyperliquid_client.py funding <coin> [--hours 72] [--limit N]
hyperliquid_client.py l2 <coin> [--levels N]
hyperliquid_client.py state [address] [--dex DEX]
hyperliquid_client.py spot-balances [address] [--limit N]
hyperliquid_client.py fills [address] [--hours N] [--limit N] [--aggregate-by-time]
hyperliquid_client.py orders [address] [--limit N]
hyperliquid_client.py review [address] [--coin COIN] [--hours N] [--fills N]
hyperliquid_client.py export <coin> [--interval 1h] [--hours N] [--output PATH]
```
For `state`, `spot-balances`, `fills`, `orders`, and `review`, the address is
optional when `HYPERLIQUID_USER_ADDRESS` is set in `~/.hermes/.env`.
---
## Procedure
### 1. Discover DEXs and Markets
```bash
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py dexs
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
markets --limit 15 --sort volume
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
spots --limit 15
```
- `--dex` only applies to perp endpoints; omit for the first perp dex.
- Spot pairs may show as `PURR/USDC` or aliases like `@107`.
- HIP-3 markets prefix the coin with the dex, e.g. `mydex:BTC`.
### 2. Pull Historical Market Data
```bash
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
candles BTC --interval 1h --hours 72 --limit 48
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
funding BTC --hours 168 --limit 30
```
Time-range endpoints paginate. For larger windows, repeat with a later
`startTime` or use `export` (below).
### 3. Inspect Live Order Book
```bash
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
l2 BTC --levels 10
```
Use when asked about book depth, near-term liquidity, or potential market
impact of a large order.
### 4. Review an Account
```bash
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
state 0xabc...
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
spot-balances
```
`state` returns perp positions; `spot-balances` returns spot inventory.
Use these for "how are my positions?", "what am I holding?", "how much is
withdrawable?".
### 5. Review Fills and Orders
```bash
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
fills 0xabc... --hours 72 --limit 25
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
orders --limit 25
```
### 6. Generate a Trade Review
```bash
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
review 0xabc... --hours 72 --fills 50
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
review --coin BTC --hours 168
```
Reports realized PnL, fees, win/loss counts, coin breakdowns, market trend
and average funding for each traded perp, plus heuristics (fee drag,
concentration, counter-trend losses).
For deeper post-trade analysis: start with `review` to find problem coins
or windows → pull `fills` and `orders` for that period → pull `candles`
and `funding` for each traded coin → judge decision quality separately
from outcome quality.
### 7. Export a Reusable Dataset
```bash
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
export BTC --interval 1h --hours 168 --output ./btc-1h-7d.json
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
export BTC --interval 15m --hours 72 --end-time-ms 1760000000000
```
Output JSON contains: schema version, source metadata, exact time window,
normalized candle rows, normalized funding rows, summary stats. Use
`--end-time-ms` for reproducible windows.
---
## Pitfalls
- Public info endpoints are rate-limited. Large historical queries may
return capped windows; iterate with later `startTime` values.
- `fills --hours ...` uses `userFillsByTime`, which only exposes a
recent rolling window — not full archive history.
- `historicalOrders` returns recent orders only; not a full export.
- The `review` command is heuristic. It cannot reconstruct intent,
order placement quality, or true slippage from fills alone.
- The `export` command writes a normalized dataset, not a backtest
engine. You still need your own slippage/fill model.
- Spot aliases like `@107` are valid identifiers even when the UI shows
a friendlier name.
- `l2` is a point-in-time snapshot, not a time series.
---
## Verification
```bash
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
markets --limit 5
```
Should print the top Hyperliquid perp markets by 24h notional volume.
@@ -0,0 +1,112 @@
---
title: "Stocks — Stock quotes, history, search, compare, crypto via Yahoo"
sidebar_label: "Stocks"
description: "Stock quotes, history, search, compare, crypto via Yahoo"
---
{/* This page is auto-generated from the skill's SKILL.md by website/scripts/generate-skill-docs.py. Edit the source SKILL.md, not this page. */}
# Stocks
Stock quotes, history, search, compare, crypto via Yahoo.
## Skill metadata
| | |
|---|---|
| Source | Optional — install with `hermes skills install official/finance/stocks` |
| Path | `optional-skills/finance/stocks` |
| Version | `0.1.0` |
| Author | Mibay (Mibayy), Hermes Agent |
| License | MIT |
| Platforms | linux, macos, windows |
| Tags | `Stocks`, `Finance`, `Market`, `Crypto`, `Investing` |
| Related skills | [`dcf-model`](/docs/user-guide/skills/optional/finance/finance-dcf-model), [`comps-analysis`](/docs/user-guide/skills/optional/finance/finance-comps-analysis), [`lbo-model`](/docs/user-guide/skills/optional/finance/finance-lbo-model) |
## Reference: full SKILL.md
:::info
The following is the complete skill definition that Hermes loads when this skill is triggered. This is what the agent sees as instructions when the skill is active.
:::
# Stocks Skill
Read-only market data via Yahoo Finance. Five commands: `quote`, `search`,
`history`, `compare`, `crypto`. Python stdlib only — no API key, no pip
installs. Yahoo's endpoint is unofficial and may rate-limit or change.
## When to Use
- User asks for a current stock price (AAPL, TSLA, MSFT, ...)
- User wants to look up a ticker by company name
- User wants OHLCV history or performance over a date range
- User wants to compare several tickers side by side
- User asks for a crypto price (BTC, ETH, SOL, ...)
## Prerequisites
Python 3.8+ stdlib only. Optional: set `ALPHA_VANTAGE_KEY` to enrich
`market_cap`, `pe_ratio`, and 52-week levels when Yahoo's crumb-protected
fields come back null. Free key: https://www.alphavantage.co/support/#api-key
## How to Run
Invoke through the `terminal` tool. Once installed:
```
SCRIPT=~/.hermes/skills/finance/stocks/scripts/stocks_client.py
python3 $SCRIPT quote AAPL
```
All output is JSON on stdout — pipe through `jq` if you want to slice it.
## Quick Reference
```
python3 $SCRIPT quote AAPL
python3 $SCRIPT quote AAPL MSFT GOOGL TSLA
python3 $SCRIPT search "Tesla"
python3 $SCRIPT history NVDA --range 6mo
python3 $SCRIPT compare AAPL MSFT GOOGL
python3 $SCRIPT crypto BTC ETH SOL
```
## Commands
### `quote SYMBOL [SYMBOL2 ...]`
Current price, change, change%, volume, 52-week high/low.
### `search QUERY`
Find tickers by company name. Returns top 5: symbol, name, exchange, type.
### `history SYMBOL [--range RANGE]`
Daily OHLCV plus stats (min, max, avg, total return %). Ranges: `1mo`,
`3mo`, `6mo`, `1y`, `5y`. Default: `1mo`.
### `compare SYMBOL1 SYMBOL2 [...]`
Side-by-side: price, change%, 52-week performance.
### `crypto SYMBOL [SYMBOL2 ...]`
Crypto prices. Pass `BTC` (the script appends `-USD` automatically).
## Pitfalls
- Yahoo Finance's API is unofficial. Endpoints can change or rate-limit
without notice — if requests start failing, that's why.
- `market_cap` and `pe_ratio` may return null on `quote` when Yahoo's
crumb session isn't established. Set `ALPHA_VANTAGE_KEY` to backfill.
- Add a small delay between bulk requests to avoid rate-limiting.
- This is read-only — no order placement, no account integration.
## Verification
```
python3 ~/.hermes/skills/finance/stocks/scripts/stocks_client.py quote AAPL
```
Returns a JSON object with `symbol: "AAPL"` and a numeric `price` field.
@@ -0,0 +1,531 @@
---
title: "Rest Graphql Debug — Debug REST/GraphQL APIs: status codes, auth, schemas, repro"
sidebar_label: "Rest Graphql Debug"
description: "Debug REST/GraphQL APIs: status codes, auth, schemas, repro"
---
{/* This page is auto-generated from the skill's SKILL.md by website/scripts/generate-skill-docs.py. Edit the source SKILL.md, not this page. */}
# Rest Graphql Debug
Debug REST/GraphQL APIs: status codes, auth, schemas, repro.
## Skill metadata
| | |
|---|---|
| Source | Optional — install with `hermes skills install official/software-development/rest-graphql-debug` |
| Path | `optional-skills/software-development/rest-graphql-debug` |
| Version | `1.2.0` |
| Author | eren-karakus0 |
| License | MIT |
| Tags | `api`, `rest`, `graphql`, `http`, `debugging`, `testing`, `curl`, `integration` |
| Related skills | [`systematic-debugging`](/docs/user-guide/skills/bundled/software-development/software-development-systematic-debugging), [`test-driven-development`](/docs/user-guide/skills/bundled/software-development/software-development-test-driven-development) |
## Reference: full SKILL.md
:::info
The following is the complete skill definition that Hermes loads when this skill is triggered. This is what the agent sees as instructions when the skill is active.
:::
# API Testing & Debugging
Drive REST and GraphQL diagnosis through Hermes tools — `terminal` for `curl`, `execute_code` for Python `requests`, `web_extract` for vendor docs. Isolate the failing layer before guessing at the fix.
## When to Use
- API returns unexpected status or body
- Auth fails (401/403 after token refresh, OAuth, API key)
- Works in Postman but fails in code
- Webhook / callback integration debugging
- Building or reviewing API integration tests
- Rate limiting or pagination issues
Skip for UI rendering, DB query tuning, or DNS/firewall infra (escalate).
## Core Principle
**Isolate the layer, then fix.** A 200 OK can hide broken data. A 500 can mask a one-character auth typo. Walk the chain in order; never skip a step.
```
1. Connectivity → can we reach the host at all?
1.5 Timeouts → connect-slow vs read-slow?
2. TLS/SSL → cert valid and trusted?
3. Auth → credentials correct and unexpired?
4. Request format → payload shape match server expectations?
5. Response parse → does our code accept what came back?
6. Semantics → does the data mean what we assume?
```
## 5-Minute Quickstart
### REST via terminal
```python
# Verbose request/response exchange
terminal('curl -v https://api.example.com/users/1')
# POST with JSON
terminal("""curl -X POST https://api.example.com/users \\
-H 'Content-Type: application/json' \\
-H "Authorization: Bearer $TOKEN" \\
-d '{"name":"test","email":"test@example.com"}'""")
# Headers only
terminal('curl -sI https://api.example.com/health')
# Pretty-print JSON
terminal('curl -s https://api.example.com/users | python3 -m json.tool')
```
### GraphQL via terminal
```python
terminal("""curl -X POST https://api.example.com/graphql \\
-H 'Content-Type: application/json' \\
-H "Authorization: Bearer $TOKEN" \\
-d '{"query":"{ user(id: 1) { name email } }"}'""")
```
**GraphQL gotcha:** servers often return HTTP 200 even when the query failed. Always inspect the `errors` field regardless of status code:
```python
execute_code('''
import os, requests
resp = requests.post(
"https://api.example.com/graphql",
json={"query": "{ user(id: 1) { name email } }"},
headers={"Authorization": f"Bearer {os.environ['TOKEN']}"},
timeout=10,
)
data = resp.json()
if data.get("errors"):
for err in data["errors"]:
print(f"GraphQL error: {err['message']} (path: {err.get('path')})")
print(data.get("data"))
''')
```
### Python (requests) via execute_code
```python
execute_code('''
import requests
resp = requests.get(
"https://api.example.com/users/1",
headers={"Authorization": "Bearer <TOKEN>"},
timeout=(3.05, 30), # (connect, read)
)
print(resp.status_code, dict(resp.headers))
print(resp.text[:500])
''')
```
## Layered Debug Flow
### Step 1 — Connectivity
```python
terminal('nslookup api.example.com')
terminal('curl -v --connect-timeout 5 https://api.example.com/health')
```
Failures: DNS not resolving, firewall, VPN required, proxy missing.
### Step 1.5 — Timeouts
Distinguish *can't reach* from *reaches but slow*:
```python
terminal('''curl -w "dns:%{time_namelookup}s connect:%{time_connect}s tls:%{time_appconnect}s ttfb:%{time_starttransfer}s total:%{time_total}s\\n" \\
-o /dev/null -s https://api.example.com/endpoint''')
```
In Python, always pass a tuple timeout — `requests` has no default and will hang forever:
```python
execute_code('''
import requests
from requests.exceptions import ConnectTimeout, ReadTimeout
try:
requests.get(url, timeout=(3.05, 30))
except ConnectTimeout:
print("Cannot reach host — DNS, firewall, VPN")
except ReadTimeout:
print("Connected but server is slow")
''')
```
Diagnosis: high `time_connect` is network/firewall; high `time_starttransfer` with low `time_connect` is a slow server.
### Step 2 — TLS/SSL
```python
terminal('curl -vI https://api.example.com 2>&1 | grep -E "SSL|subject|expire|issuer"')
```
Failures: expired cert, self-signed, hostname mismatch, missing CA bundle. Use `-k` only for ad-hoc debug, never in code.
### Step 3 — Authentication
```python
# Token validity check
terminal('curl -s -o /dev/null -w "%{http_code}\\n" -H "Authorization: Bearer $TOKEN" https://api.example.com/me')
# Decode JWT exp claim — handles base64url padding correctly
execute_code('''
import json, base64, os
tok = os.environ["TOKEN"]
payload = tok.split(".")[1]
payload += "=" * (-len(payload) % 4)
print(json.dumps(json.loads(base64.urlsafe_b64decode(payload)), indent=2))
''')
```
Checklist:
- Token expired? (`exp` claim in JWT)
- Right scheme? Bearer vs Basic vs Token vs `X-Api-Key`
- Right environment? Staging key on prod is a classic
- API key in header vs query param (`?api_key=…`)?
### Step 4 — Request Format
```python
terminal("""curl -v -X POST https://api.example.com/endpoint \\
-H 'Content-Type: application/json' \\
-d '{"key":"value"}' 2>&1""")
```
**Content-Type / body mismatch — the silent 415/400:**
```python
# WRONG — data= sends form-encoded, header lies
requests.post(url, data='{"k":"v"}', headers={"Content-Type": "application/json"})
# RIGHT — json= auto-sets header AND serializes
requests.post(url, json={"k": "v"})
# WRONG — Accept says XML, code calls .json()
requests.get(url, headers={"Accept": "text/xml"})
# RIGHT — let requests build multipart with boundary
requests.post(url, files={"file": open("doc.pdf", "rb")})
```
Common: form-encoded vs JSON, missing required fields, wrong HTTP method, unencoded query params.
### Step 5 — Response Parsing
Always inspect content-type before calling `.json()`:
```python
execute_code('''
import requests
resp = requests.post(url, json=payload, timeout=10)
print(f"status={resp.status_code}")
print(f"headers={dict(resp.headers)}")
ct = resp.headers.get("Content-Type", "")
if "application/json" in ct:
print(resp.json())
else:
print(f"unexpected content-type {ct!r}, body={resp.text[:500]!r}")
''')
```
Failures: HTML error page where JSON expected, empty body, wrong charset.
### Step 6 — Semantic Validation
Parsed cleanly — but is the data *correct*?
- Does `"status": "active"` mean what your code thinks?
- ID in response matches the one requested?
- Timestamps in expected timezone?
- Pagination returning all results, or just page 1?
## HTTP Status Playbook
### 401 Unauthorized — credentials missing or invalid
1. `Authorization` header actually present? (`curl -v` to confirm)
2. Token correct and unexpired?
3. Right auth scheme? (`Bearer` vs `Basic` vs `Token`)
4. Some APIs use query param (`?api_key=…`) instead of header.
### 403 Forbidden — authenticated but not authorized
1. Token has the required scopes/permissions?
2. Resource owned by a different account?
3. IP allowlist blocking you?
4. CORS in browser? (check `Access-Control-Allow-Origin`)
### 404 Not Found — resource doesn't exist or URL is wrong
1. Path correct? (trailing slash, typo, version prefix)
2. Resource ID exists?
3. Right API version (`/v1/` vs `/v2/`)?
4. Right base URL (staging vs prod)?
### 409 Conflict — state collision
1. Resource already exists (duplicate create)?
2. Stale `ETag` / `If-Match`?
3. Concurrent modification by another process?
### 422 Unprocessable Entity — valid JSON, invalid data
The error body usually names the bad fields. Check:
- Field types (string vs int, date format)
- Required vs optional
- Enum values inside the allowed set
### 429 Too Many Requests — rate limited
Check `Retry-After` and `X-RateLimit-*` headers. Exponential backoff:
```python
execute_code('''
import time, requests
def with_backoff(method, url, **kwargs):
for attempt in range(5):
resp = requests.request(method, url, **kwargs)
if resp.status_code != 429:
return resp
wait = int(resp.headers.get("Retry-After", 2 ** attempt))
time.sleep(wait)
return resp
''')
```
### 5xx — server-side, usually not your fault
- **500** — server bug. Capture correlation ID, file with provider.
- **502** — upstream down. Backoff + retry.
- **503** — overloaded / maintenance. Check status page.
- **504** — upstream timeout. Reduce payload or raise timeout.
For all 5xx: backoff with jitter, alert on persistence.
## Pagination & Idempotency
**Pagination.** Verify you're getting *all* results. Look for `next_cursor`, `next_page`, `total_count`. Two patterns:
- Offset (`?limit=100&offset=200`) — simple, can skip items if data shifts.
- Cursor (`?cursor=abc123`) — preferred for live or large datasets.
**Idempotency.** For non-idempotent operations (POST), send `Idempotency-Key: <uuid>` so retries don't double-charge / double-create. Mandatory for payments and orders.
## Contract Validation
Catch schema drift before it hits production:
```python
execute_code('''
import requests
def validate_user(data: dict) -> list[str]:
errors = []
required = {"id": int, "email": str, "created_at": str}
for field, expected in required.items():
if field not in data:
errors.append(f"missing field: {field}")
elif not isinstance(data[field], expected):
errors.append(f"{field}: want {expected.__name__}, got {type(data[field]).__name__}")
return errors
resp = requests.get(f"{BASE}/users/1", headers=HEADERS, timeout=10)
issues = validate_user(resp.json())
if issues:
print(f"contract violations: {issues}")
''')
```
Run after API upgrades, when integrating new third parties, or in CI smoke tests.
## Correlation IDs
Always capture the provider's request ID — fastest path to vendor support:
```python
execute_code('''
import requests
resp = requests.post(url, json=payload, headers=headers, timeout=10)
request_id = (
resp.headers.get("X-Request-Id")
or resp.headers.get("X-Trace-Id")
or resp.headers.get("CF-Ray") # Cloudflare
)
if resp.status_code >= 400:
print(f"failed status={resp.status_code} req_id={request_id} ts={resp.headers.get('Date')}")
''')
```
**Vendor bug-report template:**
```
Endpoint: POST /api/v1/orders
Request ID: req_abc123xyz
Timestamp: 2026-03-17T14:30:00Z
Status: 500
Expected: 201 with order object
Actual: 500 {"error":"internal server error"}
Repro: curl -X POST … (auth: <REDACTED>)
```
## Regression Test Template
Drop this into `tests/` and run via `terminal('pytest tests/test_api_smoke.py -v')`:
```python
import os, requests, pytest
BASE_URL = os.environ.get("API_BASE_URL", "https://api.example.com")
TOKEN = os.environ.get("API_TOKEN", "")
HEADERS = {"Authorization": f"Bearer {TOKEN}"}
class TestAPISmoke:
def test_health(self):
resp = requests.get(f"{BASE_URL}/health", timeout=5)
assert resp.status_code == 200
def test_list_users_returns_array(self):
resp = requests.get(f"{BASE_URL}/users", headers=HEADERS, timeout=10)
assert resp.status_code == 200
data = resp.json()
assert isinstance(data.get("data", data), list)
def test_get_user_required_fields(self):
resp = requests.get(f"{BASE_URL}/users/1", headers=HEADERS, timeout=10)
assert resp.status_code in (200, 404)
if resp.status_code == 200:
user = resp.json()
assert "id" in user and "email" in user
def test_invalid_auth_returns_401(self):
resp = requests.get(
f"{BASE_URL}/users",
headers={"Authorization": "Bearer invalid-token"},
timeout=10,
)
assert resp.status_code == 401
```
## Security
### Token handling
- Never log full tokens. Redact: `Bearer <REDACTED>`.
- Never hardcode tokens in scripts. Read from env (`os.environ["API_TOKEN"]`) or `~/.hermes/.env`.
- Rotate immediately if a token surfaces in logs, error messages, or git history.
### Safe logging
```python
def redact_auth(headers: dict) -> dict:
sensitive = {"authorization", "x-api-key", "cookie", "set-cookie"}
return {k: ("<REDACTED>" if k.lower() in sensitive else v) for k, v in headers.items()}
```
### Leak checklist
- [ ] **Credentials in URLs.** API keys in query strings end up in server logs, browser history, referrer headers — use headers.
- [ ] **PII in error responses.** `404 on /users/123` shouldn't reveal whether the user exists (enumeration).
- [ ] **Stack traces in prod.** 500s shouldn't leak file paths, framework versions.
- [ ] **Internal hostnames/IPs.** `10.x.x.x`, `internal-api.corp.local` in error bodies.
- [ ] **Tokens echoed back.** Some APIs include the auth token in error details. Verify they don't.
- [ ] **Verbose `Server` / `X-Powered-By`.** Stack-info leaks. Note for security review.
## Hermes Tool Patterns
### terminal — for curl, dig, openssl
```python
terminal('curl -sI https://api.example.com')
terminal('openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null 2>/dev/null | openssl x509 -noout -dates')
```
### execute_code — for multi-step Python flows
When debugging spans auth → fetch → paginate → validate, use `execute_code`. Variables persist for the script, results print to stdout, no risk of token spam in your context:
```python
execute_code('''
import os, requests
token = os.environ["API_TOKEN"]
base = "https://api.example.com"
H = {"Authorization": f"Bearer {token}"}
# 1. auth
me = requests.get(f"{base}/me", headers=H, timeout=10)
print(f"auth {me.status_code}")
# 2. paginate
all_users, cursor = [], None
while True:
params = {"cursor": cursor} if cursor else {}
r = requests.get(f"{base}/users", headers=H, params=params, timeout=10)
body = r.json()
all_users.extend(body["data"])
cursor = body.get("next_cursor")
if not cursor:
break
print(f"users={len(all_users)}")
''')
```
### web_extract — for vendor API docs
Pull the spec for the endpoint you're debugging instead of guessing:
```python
web_extract(urls=["https://docs.example.com/api/v1/users"])
```
### delegate_task — for full CRUD test sweeps
```python
delegate_task(
goal="Test all CRUD endpoints for /api/v1/users",
context="""
Follow the rest-graphql-debug skill (optional-skills/software-development/rest-graphql-debug).
Base URL: https://api.example.com
Auth: Bearer token from API_TOKEN env var.
For each verb (POST, GET, PATCH, DELETE):
- happy path: assert status + response schema
- error cases: 400, 404, 422
- log a repro curl for any failure (redact tokens)
Output: pass/fail per endpoint + correlation IDs for failures.
""",
toolsets=["terminal", "file"],
)
```
## Output Format
When reporting findings:
```
## Finding
Endpoint: POST /api/v1/users
Status: 422 Unprocessable Entity
Req ID: req_abc123xyz
## Repro
curl -X POST https://api.example.com/api/v1/users \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <REDACTED>' \
-d '{"name":"test"}'
## Root Cause
Missing required field `email`. Server validation rejects before processing.
## Fix
-d '{"name":"test","email":"test@example.com"}'
```
## Related
- `systematic-debugging` — once the failing API layer is isolated, root-cause your code
- `test-driven-development` — write the regression test before shipping the fix