mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-18 04:41:56 +00:00
The Zed ACP Registry path (uvx --from 'hermes-agent[acp]==X' hermes-acp)
gets a Python-only install. Browser tools depend on the agent-browser npm
package + Chromium, neither of which are in the wheel. Without an
explicit bootstrap, registry users have no path to working browser tools.
Ship a bundled, idempotent bootstrap script (Linux/macOS bash + Windows
PowerShell) inside acp_adapter/bootstrap/ as wheel package-data. New
entry points:
hermes acp --setup-browser # interactive; prompts before Chromium download
hermes acp --setup-browser --yes # non-interactive
hermes-acp --setup-browser
The terminal-auth flow (hermes acp --setup) also offers the browser
bootstrap as a follow-up after model selection, so first-run registry
users get the option without knowing the flag exists.
Key design choices:
- npm install -g --prefix $NODE_PREFIX so we never need sudo. System Node
on PATH is respected; only the install target is redirected to the
user-writable Hermes-managed Node prefix.
- tools/browser_tool.py::_browser_candidate_path_dirs() already walks
$HERMES_HOME/node/bin, so installed binaries are discovered with no
agent-side code change.
- System Chrome/Chromium detection short-circuits the ~400 MB Playwright
download when a suitable browser already exists.
- Bash + PowerShell live as ONE copy each under acp_adapter/bootstrap/.
Not duplicated under scripts/. install.sh and install.ps1 keep their
inline browser blocks for the source-checkout path.
E2E validated end-to-end:
bash bootstrap_browser_tools.sh --skip-chromium
→ installs agent-browser into ~/.hermes/node/bin/
tools.browser_tool._find_agent_browser()
→ returns the installed path
check_browser_requirements()
→ returns True (browser tools register)
Tests:
- tests/acp/test_entry.py: 11 tests covering --setup-browser dispatch
(linux + windows + --yes forwarding + failure propagation), the
terminal-auth follow-up prompt path, and a package-data wheel-shipping
assertion that catches any future pyproject.toml regression.
Docs: website/docs/user-guide/features/acp.md gains a 'Browser tools
(optional)' subsection with the two-line install + what-it-does.
253 lines
11 KiB
TOML
253 lines
11 KiB
TOML
[build-system]
|
|
requires = ["setuptools>=61.0"]
|
|
build-backend = "setuptools.build_meta"
|
|
|
|
[project]
|
|
name = "hermes-agent"
|
|
version = "0.13.0"
|
|
description = "The self-improving AI agent — creates skills from experience, improves them during use, and runs anywhere"
|
|
readme = "README.md"
|
|
requires-python = ">=3.11"
|
|
authors = [{ name = "Nous Research" }]
|
|
license = { text = "MIT" }
|
|
dependencies = [
|
|
# Core — every direct dep is exact-pinned to ==X.Y.Z (no ranges).
|
|
# Rationale: ranges allow PyPI to ship a fresh version of a transitive
|
|
# at any time without a code review on our side. Exact pins mean the
|
|
# only way a new package version reaches a user is via an intentional
|
|
# update on our end (bump the pin in this file, regenerate uv.lock).
|
|
# This was tightened on 2026-05-12 in response to the Mini Shai-Hulud
|
|
# worm hitting mistralai 2.4.6 on PyPI; if that release had been
|
|
# captured by `mistralai>=2.3.0,<3` rather than an exact pin, every
|
|
# install in the hours before the quarantine would have pulled it.
|
|
#
|
|
# When updating: bump the version below AND regenerate uv.lock with
|
|
# `uv lock` so the transitive resolution stays consistent. Don't
|
|
# introduce ranges back without a written justification.
|
|
#
|
|
# Scope rule: only packages used by EVERY hermes session belong here.
|
|
# Anything that's provider-specific (`anthropic`, `firecrawl-py`,
|
|
# `exa-py`, `fal-client`, `edge-tts`, `parallel-web`) belongs in an
|
|
# extra and gets lazy-installed via `tools/lazy_deps.py` when the
|
|
# user picks that backend. Smaller `dependencies` = smaller blast
|
|
# radius for the next supply-chain attack.
|
|
"openai==2.24.0",
|
|
"python-dotenv==1.2.1",
|
|
"fire==0.7.1",
|
|
"httpx[socks]==0.28.1",
|
|
"rich==14.3.3",
|
|
"tenacity==9.1.4",
|
|
"pyyaml==6.0.3",
|
|
"ruamel.yaml==0.18.17",
|
|
"requests==2.33.0", # CVE-2026-25645
|
|
"jinja2==3.1.6",
|
|
"pydantic==2.12.5",
|
|
# Interactive CLI (prompt_toolkit is used directly by cli.py)
|
|
"prompt_toolkit==3.0.52",
|
|
# Cron scheduler (built-in feature — scheduled cron/interval jobs use croniter).
|
|
"croniter==6.0.0",
|
|
# Skills Hub (GitHub App JWT auth — optional, only needed for bot identity)
|
|
"PyJWT[crypto]==2.12.1", # CVE-2026-32597
|
|
# Windows has no IANA tzdata shipped with the OS, so Python's ``zoneinfo``
|
|
# (PEP 615) raises ``ZoneInfoNotFoundError`` for every non-UTC timezone
|
|
# out of the box. ``tzdata`` ships the Olson database as a data package
|
|
# Python resolves automatically. No-op on Linux/macOS (which have
|
|
# /usr/share/zoneinfo). Credits: PR #13182 (@sprmn24).
|
|
"tzdata==2025.3; sys_platform == 'win32'",
|
|
# Cross-platform process / PID management. `psutil` is the canonical
|
|
# answer for "is this PID alive" and process-tree walking across Linux,
|
|
# macOS and Windows. It replaces POSIX-only idioms like `os.kill(pid, 0)`
|
|
# (which is a silent killer on Windows — see CONTRIBUTING.md) and
|
|
# `os.killpg` (which doesn't exist on Windows).
|
|
"psutil==7.2.2",
|
|
]
|
|
|
|
[project.optional-dependencies]
|
|
# Native Anthropic provider — only needed when provider=anthropic (not via
|
|
# OpenRouter or other aggregators).
|
|
anthropic = ["anthropic==0.86.0"]
|
|
# Web search backends — each only loaded when the user picks it as their
|
|
# search provider (configured via `hermes tools` or config.yaml).
|
|
exa = ["exa-py==2.10.2"]
|
|
firecrawl = ["firecrawl-py==4.17.0"]
|
|
parallel-web = ["parallel-web==0.4.2"]
|
|
# Image generation backends
|
|
fal = ["fal-client==0.13.1"]
|
|
# Edge TTS — default TTS provider but still optional (users can pick
|
|
# ElevenLabs / OpenAI / MiniMax instead).
|
|
edge-tts = ["edge-tts==7.2.7"]
|
|
modal = ["modal==1.3.4"]
|
|
daytona = ["daytona==0.155.0"]
|
|
vercel = ["vercel==0.5.7"]
|
|
hindsight = ["hindsight-client==0.6.1"]
|
|
dev = ["debugpy==1.8.20", "pytest==9.0.2", "pytest-asyncio==1.3.0", "pytest-xdist==3.8.0", "pytest-split==0.11.0", "mcp==1.26.0", "ty==0.0.21", "ruff==0.15.10"]
|
|
messaging = ["python-telegram-bot[webhooks]==22.6", "discord.py[voice]==2.7.1", "aiohttp==3.13.3", "brotlicffi==1.2.0.1", "slack-bolt==1.27.0", "slack-sdk==3.40.1", "qrcode==7.4.2"]
|
|
cron = [] # croniter is now a core dependency; this extra kept for back-compat
|
|
slack = ["slack-bolt==1.27.0", "slack-sdk==3.40.1", "aiohttp==3.13.3"]
|
|
matrix = ["mautrix[encryption]==0.21.0", "Markdown==3.10.2", "aiosqlite==0.22.1", "asyncpg==0.31.0", "aiohttp-socks==0.11.0"]
|
|
cli = ["simple-term-menu==1.6.6"]
|
|
tts-premium = ["elevenlabs==1.59.0"]
|
|
voice = [
|
|
# Local STT pulls in wheel-only transitive deps (ctranslate2, onnxruntime),
|
|
# so keep it out of the base install for source-build packagers like Homebrew.
|
|
"faster-whisper==1.2.1",
|
|
"sounddevice==0.5.5",
|
|
"numpy==2.4.3",
|
|
]
|
|
pty = [
|
|
"ptyprocess==0.7.0; sys_platform != 'win32'",
|
|
"pywinpty==2.0.15; sys_platform == 'win32'",
|
|
]
|
|
honcho = ["honcho-ai==2.0.1"]
|
|
mcp = ["mcp==1.26.0"]
|
|
homeassistant = ["aiohttp==3.13.3"]
|
|
sms = ["aiohttp==3.13.3"]
|
|
# Computer use — macOS background desktop control via cua-driver (MCP stdio).
|
|
# The cua-driver binary itself is installed via `hermes tools` post-setup
|
|
# (curl install script); this extra just pins the MCP client used to talk
|
|
# to it, which is already provided by the `mcp` extra.
|
|
computer-use = ["mcp==1.26.0"]
|
|
acp = ["agent-client-protocol==0.9.0"]
|
|
# mistral: extra REMOVED 2026-05-12 — `mistralai` PyPI project quarantined
|
|
# after malicious 2.4.6 release (Mini Shai-Hulud worm). Every version of
|
|
# `mistralai` returns 404 on PyPI right now, so any pin we'd write is
|
|
# unresolvable, which breaks `uv lock --check` in CI.
|
|
#
|
|
# To restore once PyPI un-quarantines:
|
|
# 1. Verify the new release is clean (read the changelog, check Socket
|
|
# advisory page, confirm no malicious code review findings).
|
|
# 2. Add back: mistral = ["mistralai==<verified-version>"]
|
|
# 3. Re-enable Mistral in:
|
|
# - tools/lazy_deps.py (LAZY_DEPS["tts.mistral"], LAZY_DEPS["stt.mistral"])
|
|
# - hermes_cli/tools_config.py (un-hide from provider picker)
|
|
# - hermes_cli/web_server.py (re-add to dashboard STT options)
|
|
# - tools/transcription_tools.py / tools/tts_tool.py (drop disabled stubs)
|
|
# 4. Run `uv lock` to regenerate transitives.
|
|
# 5. Optionally re-add to [all] only after a few days of clean operation.
|
|
bedrock = ["boto3==1.42.89"]
|
|
termux = [
|
|
# Baseline Android / Termux path for reliable fresh installs.
|
|
"python-telegram-bot[webhooks]==22.6",
|
|
"hermes-agent[cron]",
|
|
"hermes-agent[cli]",
|
|
"hermes-agent[pty]",
|
|
"hermes-agent[mcp]",
|
|
"hermes-agent[honcho]",
|
|
"hermes-agent[acp]",
|
|
]
|
|
termux-all = [
|
|
# Best-effort "install all" profile for Termux. Same policy as [all]:
|
|
# only includes extras that aren't covered by `tools/lazy_deps.py`.
|
|
# Backends like telegram/slack/dingtalk/feishu/honcho lazy-install at
|
|
# first use, so they're no longer eager-installed here.
|
|
"hermes-agent[termux]",
|
|
"hermes-agent[google]",
|
|
"hermes-agent[homeassistant]",
|
|
"hermes-agent[sms]",
|
|
"hermes-agent[web]",
|
|
]
|
|
dingtalk = ["dingtalk-stream==0.24.3", "alibabacloud-dingtalk==2.2.42", "qrcode==7.4.2"]
|
|
feishu = ["lark-oapi==1.5.3", "qrcode==7.4.2"]
|
|
google = [
|
|
# Required by the google-workspace skill (Gmail, Calendar, Drive, Contacts,
|
|
# Sheets, Docs). Declared here so packagers (Nix, Homebrew) ship them with
|
|
# the [all] extra and users don't hit runtime `pip install` paths that fail
|
|
# in environments without pip (e.g. Nix-managed Python).
|
|
"google-api-python-client==2.194.0",
|
|
"google-auth-oauthlib==1.3.1",
|
|
"google-auth-httplib2==0.3.1",
|
|
]
|
|
youtube = [
|
|
# Required by skills/media/youtube-content and
|
|
# optional-skills/productivity/memento-flashcards (youtube_quiz.py).
|
|
# Without this declaration uv sync omits the package and both skills fail
|
|
# at first invocation with ModuleNotFoundError (issue #22243).
|
|
"youtube-transcript-api==1.2.4",
|
|
]
|
|
# `hermes dashboard` (localhost SPA + API). Not in core to keep the default install lean.
|
|
web = ["fastapi==0.133.1", "uvicorn[standard]==0.41.0"]
|
|
all = [
|
|
# Policy (2026-05-12): `[all]` includes only extras that genuinely
|
|
# CAN'T be lazy-installed via `tools/lazy_deps.py` — i.e. things every
|
|
# session can use, things needed before the agent loop is alive
|
|
# (terminal/CLI), and skill deps that packagers (Nix, AUR, Homebrew)
|
|
# need in the wheel. Anything an opt-in backend (provider, search,
|
|
# TTS, image, memory, messaging platform, terminal sandbox) needs
|
|
# MUST live exclusively in `LAZY_DEPS` and resolve at first use —
|
|
# otherwise one quarantined PyPI release breaks every fresh install.
|
|
#
|
|
# Removed from [all] on 2026-05-12 (covered by lazy-install):
|
|
# anthropic, exa, firecrawl, parallel-web, fal, edge-tts,
|
|
# modal, daytona, vercel, messaging (telegram/discord/slack),
|
|
# matrix, slack, honcho, voice (faster-whisper),
|
|
# dingtalk, feishu, bedrock, tts-premium (elevenlabs)
|
|
#
|
|
# Why: the matrix extra in particular pulls `mautrix[encryption]`
|
|
# which depends on `python-olm`. python-olm has Linux-only wheels and
|
|
# no native build path on Windows or modern macOS. With matrix in
|
|
# [all], `uv sync --locked` on Windows tried to build it from sdist
|
|
# and failed on `make`. Lazy-install routes that build to first use,
|
|
# where the user is expected to have a toolchain available.
|
|
"hermes-agent[cron]",
|
|
"hermes-agent[cli]",
|
|
"hermes-agent[dev]",
|
|
"hermes-agent[pty]",
|
|
"hermes-agent[mcp]",
|
|
"hermes-agent[homeassistant]",
|
|
"hermes-agent[sms]",
|
|
"hermes-agent[acp]",
|
|
"hermes-agent[google]",
|
|
"hermes-agent[web]",
|
|
"hermes-agent[youtube]",
|
|
]
|
|
|
|
[project.scripts]
|
|
hermes = "hermes_cli.main:main"
|
|
hermes-agent = "run_agent:main"
|
|
hermes-acp = "acp_adapter.entry:main"
|
|
|
|
[tool.setuptools]
|
|
py-modules = ["run_agent", "model_tools", "toolsets", "batch_runner", "trajectory_compressor", "toolset_distributions", "cli", "hermes_bootstrap", "hermes_constants", "hermes_state", "hermes_time", "hermes_logging", "utils"]
|
|
|
|
[tool.setuptools.package-data]
|
|
hermes_cli = ["web_dist/**/*"]
|
|
gateway = ["assets/**/*"]
|
|
acp_adapter = ["bootstrap/*.sh", "bootstrap/*.ps1"]
|
|
|
|
[tool.setuptools.packages.find]
|
|
include = ["agent", "agent.*", "tools", "tools.*", "hermes_cli", "gateway", "gateway.*", "tui_gateway", "tui_gateway.*", "cron", "acp_adapter", "acp_adapter.*", "plugins", "plugins.*", "providers", "providers.*"]
|
|
|
|
[tool.pytest.ini_options]
|
|
testpaths = ["tests"]
|
|
markers = [
|
|
"integration: marks tests requiring external services (API keys, Modal, etc.)",
|
|
]
|
|
addopts = "-m 'not integration' -n auto"
|
|
|
|
[tool.ty.environment]
|
|
python-version = "3.13"
|
|
|
|
[tool.ty.rules]
|
|
unknown-argument = "warn"
|
|
redundant-cast = "ignore"
|
|
|
|
[tool.ruff]
|
|
preview = true # required for PLW1514 (unspecified-encoding) — preview rule
|
|
|
|
[tool.ruff.lint]
|
|
# All other lints are intentionally disabled (see comment history on this
|
|
# file) while we wrangle typechecks — but PLW1514 is too load-bearing to
|
|
# keep off. Bare open()/read_text()/write_text() in text mode defaults to
|
|
# the system locale encoding on Windows (cp1252 on US-locale installs),
|
|
# which silently corrupts any non-ASCII file content. We had three
|
|
# separate Windows sandbox regressions in one debug session before
|
|
# adding the explicit encoding. This rule keeps new code honest.
|
|
select = ["PLW1514"]
|
|
|
|
[tool.ruff.lint.per-file-ignores]
|
|
# Tests can intentionally exercise locale-encoding edge cases.
|
|
"tests/**" = ["PLW1514"]
|
|
# Skills and plugins are partially user-authored — their own conventions.
|
|
"skills/**" = ["PLW1514"]
|
|
"optional-skills/**" = ["PLW1514"]
|
|
"plugins/**" = ["PLW1514"]
|