[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 # Directly imported by WeCom/Weixin crypto paths and pulled transitively by # PyJWT[crypto]; pin explicitly so the floor doesn't drift below the # CVE-2026-39892 fix (buffer overflow on non-contiguous buffers). "cryptography==46.0.7", # CVE-2026-39892 # 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.87.0"] # CVE-2026-34450, CVE-2026-34452 # 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.4", "brotlicffi==1.2.0.1", "slack-bolt==1.27.0", "slack-sdk==3.40.1", "qrcode==7.4.2"] # aiohttp: CVE-2026-34513/34518/34519/34520/34525 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.4"] 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.4"] sms = ["aiohttp==3.13.4"] # 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=="] # 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/**/*", "tui_dist/**/*", "scripts/install.sh"] 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"]