From 5a346903d28005eb38b8f18a2740b804052090d0 Mon Sep 17 00:00:00 2001 From: ethernet Date: Fri, 3 Jul 2026 15:09:27 -0400 Subject: [PATCH] time to cook --- .github/workflows/upload_to_pypi.yml | 164 ------------------ acp_registry/agent.json | 16 -- acp_registry/icon.svg | 8 - hermes_cli/banner.py | 3 + hermes_cli/config.py | 14 +- packaging/homebrew/README.md | 14 -- packaging/homebrew/hermes-agent.rb | 48 ----- scripts/check-windows-footguns.py | 1 - scripts/release.py | 85 +-------- scripts/run_tests.sh | 27 +-- tests/acp/test_registry_manifest.py | 90 ---------- tests/scripts/test_release_acp_registry.py | 113 ------------ website/docs/developer-guide/acp-internals.md | 5 +- .../docs/getting-started/platform-support.md | 4 +- website/docs/user-guide/features/acp.md | 63 +------ .../current/developer-guide/acp-internals.md | 5 +- .../current/user-guide/features/acp.md | 65 +------ 17 files changed, 49 insertions(+), 676 deletions(-) delete mode 100644 .github/workflows/upload_to_pypi.yml delete mode 100644 acp_registry/agent.json delete mode 100644 acp_registry/icon.svg delete mode 100644 packaging/homebrew/README.md delete mode 100644 packaging/homebrew/hermes-agent.rb delete mode 100644 tests/acp/test_registry_manifest.py delete mode 100644 tests/scripts/test_release_acp_registry.py diff --git a/.github/workflows/upload_to_pypi.yml b/.github/workflows/upload_to_pypi.yml deleted file mode 100644 index 03fad4eba0c..00000000000 --- a/.github/workflows/upload_to_pypi.yml +++ /dev/null @@ -1,164 +0,0 @@ -name: Publish to PyPI - -# Triggered by CalVer tag pushes from scripts/release.py (e.g. v2026.5.15) -# Can also be triggered manually from the Actions tab as an escape hatch. -on: - push: - tags: - - "v20*" # CalVer tags: v2026.5.15, v2026.5.15.2, etc. - workflow_dispatch: - inputs: - confirm_tag: - description: "Tag to publish (e.g. v2026.5.15). Must already exist." - required: true - type: string - -# Restrict default token to read-only; each job escalates as needed. -permissions: - contents: read - -# Prevent overlapping publishes (e.g. two same-day tags pushed quickly). -concurrency: - group: pypi-publish - cancel-in-progress: false - -jobs: - build: - name: Build distribution 📦 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - # On workflow_dispatch, check out the confirmed tag. - ref: ${{ inputs.confirm_tag || github.ref }} - fetch-tags: true - - - name: Validate tag exists - if: github.event_name == 'workflow_dispatch' - run: | - if ! git tag -l "${{ inputs.confirm_tag }}" | grep -q .; then - echo "::error::Tag '${{ inputs.confirm_tag }}' does not exist in the repo" - exit 1 - fi - - - name: Set up Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: "3.13" - - - name: Install uv - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # 8.2.0 - - - name: Set up Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: "22" - - - name: Build web dashboard - run: cd web && npm ci && npm run build - - - name: Build TUI bundle - run: cd ui-tui && npm ci && npm run build - - - name: Bundle TUI into hermes_cli - run: | - mkdir -p hermes_cli/tui_dist - cp ui-tui/dist/entry.js hermes_cli/tui_dist/entry.js - - - name: Verify frontend assets exist - run: | - test -f hermes_cli/web_dist/index.html || { echo "ERROR: web_dist not built"; exit 1; } - test -f hermes_cli/tui_dist/entry.js || { echo "ERROR: tui_dist not built"; exit 1; } - - - name: Bundle install scripts into wheel - run: | - mkdir -p hermes_cli/scripts - cp scripts/install.sh hermes_cli/scripts/install.sh - cp scripts/install.ps1 hermes_cli/scripts/install.ps1 - - - name: Build wheel and sdist - run: uv build --sdist --wheel - - - name: Upload distribution artifacts - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: python-package-distributions - path: dist/ - - publish: - name: Publish to PyPI - needs: build - runs-on: ubuntu-latest - environment: - name: pypi - url: https://pypi.org/p/hermes-agent - permissions: - id-token: write # OIDC trusted publishing - - steps: - - name: Download distribution artifacts - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - name: python-package-distributions - path: dist/ - - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 - with: - skip-existing: true - - sign: - name: Sign and attach to GitHub Release - # Only runs on tag pushes — release.py creates the GitHub Release, - # and workflow_dispatch won't have a matching release to attach to. - if: startsWith(github.ref, 'refs/tags/') - needs: publish - runs-on: ubuntu-latest - permissions: - contents: write # attach assets to the existing release - id-token: write # sigstore signing - - steps: - - name: Download distribution artifacts - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - name: python-package-distributions - path: dist/ - - - name: Wait for GitHub Release to exist - env: - GITHUB_TOKEN: ${{ github.token }} - # release.py creates the GitHub Release after pushing the tag, - # but this workflow starts from the tag push — wait for it. - run: | - for i in $(seq 1 30); do - if gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then - echo "Release $GITHUB_REF_NAME found" - exit 0 - fi - echo "Waiting for release... ($i/30)" - sleep 10 - done - echo "::warning::Release $GITHUB_REF_NAME not found after 5 minutes — skipping signature upload" - echo "skip_sign=true" >> "$GITHUB_ENV" - - - name: Sign with Sigstore - if: env.skip_sign != 'true' - uses: sigstore/gh-action-sigstore-python@04cffa1d795717b140764e8b640de88853c92acc # v3.3.0 - with: - inputs: >- - ./dist/*.tar.gz - ./dist/*.whl - - - name: Attach signed artifacts to GitHub Release - if: env.skip_sign != 'true' - env: - GITHUB_TOKEN: ${{ github.token }} - # release.py already created the GitHub Release — just upload - # the Sigstore signatures alongside the existing assets. - run: >- - gh release upload - "$GITHUB_REF_NAME" dist/*.sigstore.json - --repo "$GITHUB_REPOSITORY" - --clobber diff --git a/acp_registry/agent.json b/acp_registry/agent.json deleted file mode 100644 index dc1e05bb27b..00000000000 --- a/acp_registry/agent.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "id": "hermes-agent", - "name": "Hermes Agent", - "version": "0.18.0", - "description": "Self-improving open-source AI agent by Nous Research with ACP editor integration, persistent memory, skills, and rich tool support.", - "repository": "https://github.com/NousResearch/hermes-agent", - "website": "https://hermes-agent.nousresearch.com/docs/user-guide/features/acp", - "authors": ["Nous Research"], - "license": "MIT", - "distribution": { - "uvx": { - "package": "hermes-agent[acp]==0.18.0", - "args": ["hermes-acp"] - } - } -} diff --git a/acp_registry/icon.svg b/acp_registry/icon.svg deleted file mode 100644 index f42c0daea45..00000000000 --- a/acp_registry/icon.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/hermes_cli/banner.py b/hermes_cli/banner.py index 06fc84a4ac1..7c611107868 100644 --- a/hermes_cli/banner.py +++ b/hermes_cli/banner.py @@ -1,3 +1,5 @@ + + """Welcome banner, ASCII art, skills summary, and update check for the CLI. Pure display functions with no HermesCLI state dependency. @@ -882,6 +884,7 @@ def build_welcome_banner(console: "Console", model: str, cwd: str, from hermes_cli.config import ( detect_install_method, format_unsupported_install_warning, + get_project_root, is_unsupported_install_method, get_project_root ) diff --git a/hermes_cli/config.py b/hermes_cli/config.py index 4625e58bd9b..80c430b7919 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -548,12 +548,14 @@ def recommended_update_command() -> str: # # pip/PyPI and Homebrew are NOT an officially supported distribution method # (see website/docs/getting-started/platform-support.md, "Unsupported" -# section). pip exists on PyPI for internal/CI reasons, not end-user installs; -# Homebrew is a legacy packaging path. Unlike NixOS/Homebrew "managed mode" -# (which hard-blocks config writes), this is a warn-don't-block deprecation -# notice surfaced everywhere the user might see install-method state: the CLI -# banner, the TUI/desktop session info panel, and ``hermes update``. NixOS -# stays fully supported (Tier 2) and must never hit this path. +# section). Hermes Agent is no longer published to PyPI or packaged for +# Homebrew — existing installs from either keep running on whatever version +# they last pulled, but will never see a newer one. Unlike NixOS/Homebrew +# "managed mode" (which hard-blocks config writes), this is a warn-don't-block +# deprecation notice surfaced everywhere the user might see install-method +# state: the CLI banner, the TUI/desktop session info panel, and +# ``hermes update``. NixOS stays fully supported (Tier 2) and must never hit +# this path. PLATFORM_SUPPORT_DOCS_URL = "https://hermes-agent.nousresearch.com/docs/getting-started/platform-support" diff --git a/packaging/homebrew/README.md b/packaging/homebrew/README.md deleted file mode 100644 index e53d3fd0b3e..00000000000 --- a/packaging/homebrew/README.md +++ /dev/null @@ -1,14 +0,0 @@ -Homebrew packaging notes for Hermes Agent. - -Use `packaging/homebrew/hermes-agent.rb` as a tap or `homebrew-core` starting point. - -Key choices: -- Stable builds should target the semver-named sdist asset attached to each GitHub release, not the CalVer tag tarball. -- `faster-whisper` now lives in the `voice` extra, which keeps wheel-only transitive dependencies out of the base Homebrew formula. -- The wrapper exports `HERMES_BUNDLED_SKILLS`, `HERMES_OPTIONAL_SKILLS`, and `HERMES_MANAGED=homebrew` so packaged installs keep runtime assets and defer upgrades to Homebrew. - -Typical update flow: -1. Bump the formula `url`, `version`, and `sha256`. -2. Refresh Python resources with `brew update-python-resources --print-only hermes-agent`. -3. Keep `ignore_packages: %w[certifi cryptography pydantic]`. -4. Verify `brew audit --new --strict hermes-agent` and `brew test hermes-agent`. diff --git a/packaging/homebrew/hermes-agent.rb b/packaging/homebrew/hermes-agent.rb deleted file mode 100644 index 7c00fc6acf8..00000000000 --- a/packaging/homebrew/hermes-agent.rb +++ /dev/null @@ -1,48 +0,0 @@ -class HermesAgent < Formula - include Language::Python::Virtualenv - - desc "Self-improving AI agent that creates skills from experience" - homepage "https://hermes-agent.nousresearch.com" - # Stable source should point at the semver-named sdist asset attached by - # scripts/release.py, not the CalVer tag tarball. - url "https://github.com/NousResearch/hermes-agent/releases/download/v2026.3.30/hermes_agent-0.6.0.tar.gz" - sha256 "" - license "MIT" - - depends_on "certifi" => :no_linkage - depends_on "cryptography" => :no_linkage - depends_on "libyaml" - depends_on "python@3.14" - - pypi_packages ignore_packages: %w[certifi cryptography pydantic] - - # Refresh resource stanzas after bumping the source url/version: - # brew update-python-resources --print-only hermes-agent - - def install - venv = virtualenv_create(libexec, "python3.14") - venv.pip_install resources - venv.pip_install buildpath - - pkgshare.install "skills", "optional-skills" - - %w[hermes hermes-agent hermes-acp].each do |exe| - next unless (libexec/"bin"/exe).exist? - - (bin/exe).write_env_script( - libexec/"bin"/exe, - HERMES_BUNDLED_SKILLS: pkgshare/"skills", - HERMES_OPTIONAL_SKILLS: pkgshare/"optional-skills", - HERMES_MANAGED: "homebrew" - ) - end - end - - test do - assert_match "Hermes Agent v#{version}", shell_output("#{bin}/hermes version") - - managed = shell_output("#{bin}/hermes update 2>&1") - assert_match "managed by Homebrew", managed - assert_match "brew upgrade hermes-agent", managed - end -end diff --git a/scripts/check-windows-footguns.py b/scripts/check-windows-footguns.py index 7ae7ca50c4e..c0ca6622ec3 100644 --- a/scripts/check-windows-footguns.py +++ b/scripts/check-windows-footguns.py @@ -576,7 +576,6 @@ def main(argv: list[str]) -> int: REPO_ROOT / "plugins", REPO_ROOT / "scripts", REPO_ROOT / "acp_adapter", - REPO_ROOT / "acp_registry", ] roots = [r for r in roots if r.exists()] elif args.diff: diff --git a/scripts/release.py b/scripts/release.py index 11f374e9f20..4f05af8dbfe 100755 --- a/scripts/release.py +++ b/scripts/release.py @@ -21,7 +21,6 @@ Usage: """ import argparse -import json import re import shutil import subprocess @@ -34,11 +33,6 @@ REPO_ROOT = Path(__file__).resolve().parent.parent VERSION_FILE = REPO_ROOT / "hermes_cli" / "__init__.py" PYPROJECT_FILE = REPO_ROOT / "pyproject.toml" -# ACP Registry manifest must stay version-locked with pyproject.toml. -# tests/acp/test_registry_manifest.py enforces this lockstep so the release -# bump touches both files atomically. -ACP_REGISTRY_MANIFEST = REPO_ROOT / "acp_registry" / "agent.json" - # ────────────────────────────────────────────────────────────────────── # Git email → GitHub username mapping # ────────────────────────────────────────────────────────────────────── @@ -1973,70 +1967,6 @@ def update_version_files(semver: str, calver_date: str): ) desktop_pkg.write_text(pkg_text, encoding="utf-8") - # Update ACP Registry manifest + npm launcher (must stay version-locked - # with pyproject — enforced by tests/acp/test_registry_manifest.py). - _update_acp_registry_versions(semver) - - -def _update_acp_registry_versions(semver: str) -> None: - """Bump the ACP Registry manifest's version + uvx package pin in lockstep - with pyproject. - - Skips silently if the manifest is missing — older release branches predate - the ACP Registry assets. - """ - if ACP_REGISTRY_MANIFEST.exists(): - manifest = json.loads(ACP_REGISTRY_MANIFEST.read_text(encoding="utf-8")) - manifest["version"] = semver - uvx = manifest.get("distribution", {}).get("uvx", {}) - if "package" in uvx: - uvx["package"] = f"hermes-agent[acp]=={semver}" - # Preserve trailing newline + 2-space indent the file already uses. - ACP_REGISTRY_MANIFEST.write_text( - json.dumps(manifest, indent=2) + "\n", encoding="utf-8" - ) - - -def build_release_artifacts(semver: str) -> list[Path]: - """Build sdist/wheel artifacts for the current release. - - Tries ``uv build`` first (matching the CI workflow), falls back to - ``python -m build`` if uv is unavailable. - """ - dist_dir = REPO_ROOT / "dist" - shutil.rmtree(dist_dir, ignore_errors=True) - - # Prefer uv build (matches CI workflow), fall back to python -m build. - uv_bin = shutil.which("uv") - if uv_bin: - cmd = [uv_bin, "build", "--sdist", "--wheel"] - else: - cmd = [sys.executable, "-m", "build", "--sdist", "--wheel"] - - result = subprocess.run( - cmd, - cwd=str(REPO_ROOT), - capture_output=True, - text=True, - ) - if result.returncode != 0: - print(" ⚠ Could not build Python release artifacts.") - stderr = result.stderr.strip() - stdout = result.stdout.strip() - if stderr: - print(f" {stderr.splitlines()[-1]}") - elif stdout: - print(f" {stdout.splitlines()[-1]}") - print(" Install uv or the 'build' package to attach sdist/wheel assets.") - return [] - - artifacts = sorted(p for p in dist_dir.iterdir() if p.is_file()) - matching = [p for p in artifacts if semver in p.name] - if not matching: - print(" ⚠ Built artifacts did not match the expected release version.") - return [] - return matching - def resolve_author(name: str, email: str) -> str: """Resolve a git author to a GitHub @mention.""" @@ -2376,8 +2306,6 @@ def main(): # Commit version bump add_files = [str(VERSION_FILE), str(PYPROJECT_FILE)] - if ACP_REGISTRY_MANIFEST.exists(): - add_files.append(str(ACP_REGISTRY_MANIFEST)) add_result = git_result("add", *add_files) if add_result.returncode != 0: print(f" ✗ Failed to stage version files: {add_result.stderr.strip()}") @@ -2410,14 +2338,6 @@ def main(): print(" Continue manually after fixing access:") print(" git push origin HEAD --tags") - # Build semver-named Python artifacts so downstream packagers - # (e.g. Homebrew) can target them without relying on CalVer tag names. - artifacts = build_release_artifacts(new_version) - if artifacts: - print(" ✓ Built release artifacts:") - for artifact in artifacts: - print(f" - {artifact.relative_to(REPO_ROOT)}") - # Create GitHub release changelog_file = REPO_ROOT / ".release_notes.md" changelog_file.write_text(changelog, encoding="utf-8") @@ -2427,7 +2347,6 @@ def main(): "--title", f"Hermes Agent v{new_version} ({calver_date})", "--notes-file", str(changelog_file), ] - gh_cmd.extend(str(path) for path in artifacts) gh_bin = shutil.which("gh") if gh_bin: @@ -2452,9 +2371,9 @@ def main(): print(f" Tag was created locally. Create the release manually:") print( f" gh release create {tag_name} --title 'Hermes Agent v{new_version} ({calver_date})' " - f"--notes-file .release_notes.md {' '.join(str(path) for path in artifacts)}" + f"--notes-file .release_notes.md" ) - print(f"\n ✓ Release artifacts prepared for manual publish: v{new_version} ({tag_name})") + print(f"\n ✓ Release prepared for manual publish: v{new_version} ({tag_name})") else: print(f"\n{'='*60}") print(f" Dry run complete. To publish, add --publish") diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh index a54b7163d3f..d7b471a8e0a 100755 --- a/scripts/run_tests.sh +++ b/scripts/run_tests.sh @@ -37,23 +37,26 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +if [ -z "$HERMES_PYTHON" ]; then # ── Activate venv ─────────────────────────────────────────────────────────── -VENV="" -for candidate in "$REPO_ROOT/.venv" "$REPO_ROOT/venv" "$HOME/.hermes/hermes-agent/venv"; do - if [ -f "$candidate/bin/activate" ]; then - VENV="$candidate" - break + VENV="" + for candidate in "$REPO_ROOT/.venv" "$REPO_ROOT/venv" "$HOME/.hermes/hermes-agent/venv"; do + if [ -f "$candidate/bin/activate" ]; then + VENV="$candidate" + break + fi + done + + if [ -z "$VENV" ]; then + echo "error: no virtualenv found in $REPO_ROOT/.venv or $REPO_ROOT/venv" >&2 + exit 1 fi -done -if [ -z "$VENV" ]; then - echo "error: no virtualenv found in $REPO_ROOT/.venv or $REPO_ROOT/venv" >&2 - exit 1 + PYTHON="$VENV/bin/python" +else + PYTHON=$HERMES_PYTHON fi -PYTHON="$VENV/bin/python" - - # ── Live-gateway plugin (computed before we drop env) ─────────────────────── EXTRA_PYTHONPATH="" EXTRA_PYTEST_PLUGINS="" diff --git a/tests/acp/test_registry_manifest.py b/tests/acp/test_registry_manifest.py deleted file mode 100644 index 633b4a8494c..00000000000 --- a/tests/acp/test_registry_manifest.py +++ /dev/null @@ -1,90 +0,0 @@ -"""Tests for ACP Registry metadata shipped with Hermes.""" - -from __future__ import annotations - -import json -import re -import tomllib -from pathlib import Path -import xml.etree.ElementTree as ET - -ROOT = Path(__file__).resolve().parents[2] -MANIFEST = ROOT / "acp_registry" / "agent.json" -ICON = ROOT / "acp_registry" / "icon.svg" -FORBIDDEN_MANIFEST_KEYS = {"schema_version", "display_name"} -ALLOWED_DISTRIBUTIONS = {"binary", "npx", "uvx"} - - -def _manifest() -> dict: - return json.loads(MANIFEST.read_text(encoding="utf-8")) - - -def _pyproject_version() -> str: - data = tomllib.loads((ROOT / "pyproject.toml").read_text(encoding="utf-8")) - return data["project"]["version"] - - -def test_agent_json_matches_official_registry_required_fields(): - data = _manifest() - - assert FORBIDDEN_MANIFEST_KEYS.isdisjoint(data) - assert data["id"] == "hermes-agent" - assert re.fullmatch(r"[a-z][a-z0-9-]*", data["id"]) - assert data["name"] == "Hermes Agent" - assert data["description"] - assert data["repository"] == "https://github.com/NousResearch/hermes-agent" - assert data["website"].startswith("https://hermes-agent.nousresearch.com/") - assert data["authors"] == ["Nous Research"] - assert data["license"] == "MIT" - assert set(data["distribution"]) <= ALLOWED_DISTRIBUTIONS - - -def test_agent_json_uses_uvx_distribution_without_local_command_fields(): - data = _manifest() - - assert set(data["distribution"]) == {"uvx"} - uvx = data["distribution"]["uvx"] - # Schema allows {package, args, env}; we use {package, args}. - assert set(uvx) <= {"package", "args", "env"} - assert "package" in uvx - assert uvx["package"] == f"hermes-agent[acp]=={data['version']}" - assert uvx["args"] == ["hermes-acp"] - # Old command-shape fields must not leak back in. - assert "type" not in data["distribution"] - assert "command" not in data["distribution"] - - -def test_agent_json_version_matches_pyproject(): - assert _manifest()["version"] == _pyproject_version() - - -def test_agent_json_pins_uvx_package_to_pyproject_version(): - """The registry CI rejects ``@latest`` and floating pins; the manifest must - always reference the exact PyPI version listed in pyproject.toml.""" - assert _manifest()["distribution"]["uvx"]["package"] == ( - f"hermes-agent[acp]=={_pyproject_version()}" - ) - - -def test_icon_svg_is_16x16_current_color(): - root = ET.fromstring(ICON.read_text(encoding="utf-8")) - - assert root.attrib["viewBox"] == "0 0 16 16" - assert root.attrib["width"] == "16" - assert root.attrib["height"] == "16" - - -def test_icon_svg_has_no_hardcoded_colors_or_gradients(): - text = ICON.read_text(encoding="utf-8") - - assert "linearGradient" not in text - assert "radialGradient" not in text - assert "url(#" not in text - assert not re.search(r"#[0-9a-fA-F]{3,8}\b", text) - - root = ET.fromstring(text) - for element in root.iter(): - for attr in ("fill", "stroke"): - value = element.attrib.get(attr) - if value is not None: - assert value in {"currentColor", "none"} diff --git a/tests/scripts/test_release_acp_registry.py b/tests/scripts/test_release_acp_registry.py deleted file mode 100644 index 4d20cda25bd..00000000000 --- a/tests/scripts/test_release_acp_registry.py +++ /dev/null @@ -1,113 +0,0 @@ -"""Tests for the ACP Registry version-lockstep bump in scripts/release.py. - -The official ACP Registry manifest must match ``pyproject.toml`` exactly — -``tests/acp/test_registry_manifest.py`` enforces this at lint time, and the -upstream registry CI rejects ``@latest`` / floating pins. The release script -is the single place that bumps the manifest in lockstep with pyproject; if -that bump ever silently breaks, weekly releases fail the manifest test -until someone hand-edits the JSON. -""" - -from __future__ import annotations - -import importlib.util -import json -from pathlib import Path - - -def _load_release_module(monkeypatch, tmp_root: Path): - """Import scripts/release.py with REPO_ROOT pinned to a temp tree.""" - spec = importlib.util.spec_from_file_location( - "_release_under_test", - Path(__file__).resolve().parents[2] / "scripts" / "release.py", - ) - assert spec and spec.loader - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - - monkeypatch.setattr(module, "REPO_ROOT", tmp_root) - monkeypatch.setattr( - module, "ACP_REGISTRY_MANIFEST", tmp_root / "acp_registry" / "agent.json" - ) - return module - - -def _write_manifest(root: Path, version: str) -> None: - manifest_dir = root / "acp_registry" - manifest_dir.mkdir(parents=True) - (manifest_dir / "agent.json").write_text( - json.dumps( - { - "id": "hermes-agent", - "name": "Hermes Agent", - "version": version, - "description": "test", - "distribution": { - "uvx": { - "package": f"hermes-agent[acp]=={version}", - "args": ["hermes-acp"], - } - }, - }, - indent=2, - ) - + "\n", - encoding="utf-8", - ) - - -def test_update_acp_registry_versions_bumps_manifest_and_pin(monkeypatch, tmp_path): - _write_manifest(tmp_path, "0.13.0") - module = _load_release_module(monkeypatch, tmp_path) - - module._update_acp_registry_versions("0.14.0") - - manifest = json.loads( - (tmp_path / "acp_registry" / "agent.json").read_text(encoding="utf-8") - ) - assert manifest["version"] == "0.14.0" - assert manifest["distribution"]["uvx"]["package"] == "hermes-agent[acp]==0.14.0" - # args stay untouched so we don't accidentally rewrite them. - assert manifest["distribution"]["uvx"]["args"] == ["hermes-acp"] - - -def test_update_acp_registry_versions_is_silent_when_manifest_missing( - monkeypatch, tmp_path -): - """Older release branches predate the ACP Registry asset — must no-op.""" - module = _load_release_module(monkeypatch, tmp_path) - - # No fixture written; function should not raise. - module._update_acp_registry_versions("0.14.0") - - -def test_update_version_files_bumps_manifest_alongside_pyproject( - monkeypatch, tmp_path -): - """End-to-end: update_version_files() is the function release.py actually - calls, so it must drive the manifest bump too.""" - _write_manifest(tmp_path, "0.13.0") - (tmp_path / "pyproject.toml").write_text( - '[project]\nname = "hermes-agent"\nversion = "0.13.0"\n', encoding="utf-8" - ) - version_dir = tmp_path / "hermes_cli" - version_dir.mkdir() - (version_dir / "__init__.py").write_text( - '__version__ = "0.13.0"\n__release_date__ = "2026-05-14"\n', - encoding="utf-8", - ) - - module = _load_release_module(monkeypatch, tmp_path) - monkeypatch.setattr(module, "VERSION_FILE", version_dir / "__init__.py") - monkeypatch.setattr(module, "PYPROJECT_FILE", tmp_path / "pyproject.toml") - - module.update_version_files("0.14.0", "2026-05-21") - - pyproject_text = (tmp_path / "pyproject.toml").read_text(encoding="utf-8") - assert 'version = "0.14.0"' in pyproject_text - - manifest = json.loads( - (tmp_path / "acp_registry" / "agent.json").read_text(encoding="utf-8") - ) - assert manifest["version"] == "0.14.0" - assert manifest["distribution"]["uvx"]["package"] == "hermes-agent[acp]==0.14.0" diff --git a/website/docs/developer-guide/acp-internals.md b/website/docs/developer-guide/acp-internals.md index 89ae398b6af..f177c33dc3b 100644 --- a/website/docs/developer-guide/acp-internals.md +++ b/website/docs/developer-guide/acp-internals.md @@ -17,7 +17,6 @@ Key implementation files: - `acp_adapter/permissions.py` - `acp_adapter/tools.py` - `acp_adapter/auth.py` -- `acp_registry/agent.json` ## Boot flow @@ -31,8 +30,6 @@ hermes acp / hermes-acp / python -m acp_adapter -> acp.run_agent(agent, use_unstable_protocol=True) ``` -The Zed ACP Registry path launches the same adapter through `uvx --from 'hermes-agent[acp]==' hermes-acp`, pointed at the `hermes-agent` PyPI release. - Stdout is reserved for ACP JSON-RPC transport. Human-readable logs go to stderr. ## Major components @@ -149,7 +146,7 @@ Instead it reuses Hermes' runtime resolver: - `acp_adapter/auth.py` - `hermes_cli/runtime_provider.py` -So ACP advertises and uses the currently configured Hermes provider/credentials. It also always advertises a terminal setup auth method (`hermes-setup`, args `--setup`) so first-run registry clients can open Hermes' interactive model/provider configuration before starting a normal ACP session. +So ACP advertises and uses the currently configured Hermes provider/credentials. It also always advertises a terminal setup auth method (`hermes-setup`, args `--setup`) so first-run clients can open Hermes' interactive model/provider configuration before starting a normal ACP session. ## Working directory binding diff --git a/website/docs/getting-started/platform-support.md b/website/docs/getting-started/platform-support.md index d0df354e592..588a154ad77 100644 --- a/website/docs/getting-started/platform-support.md +++ b/website/docs/getting-started/platform-support.md @@ -44,7 +44,7 @@ PRs to fix them will _not_ be accepted, and any code that keeps compatibility wi - installs via the AUR (we might upstream patches if it helps out <3) - macOS on x86 (Intel) processors -- installs via `pypi` (e.g. `uv tool install hermes-agent`, `pip install hermse-agent`, etc.) -- installs via `brew` (`brew install hermes-agent`) +- installs via `pypi` (e.g. `uv tool install hermes-agent`, `pip install hermse-agent`, etc.) — Hermes Agent is no longer published to PyPI, so these installs will not receive further updates +- installs via `brew` (`brew install hermes-agent`) — Hermes Agent is no longer packaged for Homebrew, so these installs will not receive further updates If you are using an unsupported distribution method, please read the [the installation guide](./installation.md) to learn how to switch to a supported one. diff --git a/website/docs/user-guide/features/acp.md b/website/docs/user-guide/features/acp.md index 4dce234ef52..1b01f757e34 100644 --- a/website/docs/user-guide/features/acp.md +++ b/website/docs/user-guide/features/acp.md @@ -45,14 +45,6 @@ This installs the `agent-client-protocol` dependency and enables: - `hermes-acp` - `python -m acp_adapter` -For Zed registry installs, Zed launches Hermes through the official ACP Registry entry. That entry uses a `uvx` distribution that runs: - -```bash -uvx --from 'hermes-agent[acp]==' hermes-acp -``` - -Make sure `uv` is available on `PATH` before using the registry install path. - ## Launching the ACP server Any of the following starts Hermes in ACP mode: @@ -89,7 +81,7 @@ hermes acp --setup-browser # interactive (prompts before ~400 MB downl hermes acp --setup-browser --yes # accept the download non-interactively ``` -This is the standalone command. The Zed registry's terminal-auth flow (`hermes acp --setup`) also offers the browser bootstrap as a follow-up question after model selection, so most users never need to run `--setup-browser` directly. +This is the standalone command. Hermes' terminal-auth flow (`hermes acp --setup`) also offers the browser bootstrap as a follow-up question after model selection, so most users never need to run `--setup-browser` directly. What it does: @@ -126,19 +118,7 @@ If you want to define Hermes manually, add it through VS Code settings under `ac ### Zed -Zed v0.221.x and newer installs external agents through the official ACP Registry. - -1. Open the Agent Panel. -2. Click **Add Agent**, or run the `zed: acp registry` command. -3. Search for **Hermes Agent**. -4. Install it and start a new Hermes external-agent thread. - -Prerequisites: - -- Configure Hermes provider credentials first with `hermes model`, or set them in `~/.hermes/.env` / `~/.hermes/config.yaml`. -- Install `uv` so the registry launcher can run `uvx --from 'hermes-agent[acp]==' hermes-acp`. - -For local development before the registry entry is available, use a custom agent server in Zed settings: +Zed supports external agents via a custom agent server entry in Zed settings: ```json { @@ -152,32 +132,11 @@ For local development before the registry entry is available, use a custom agent } ``` +Configure Hermes provider credentials first with `hermes model`, or set them in `~/.hermes/.env` / `~/.hermes/config.yaml`. + ### JetBrains -Use an ACP-compatible plugin and point it at: - -```text -/path/to/hermes-agent/acp_registry -``` - -## Registry manifest - -The source copy of Hermes' official ACP Registry metadata lives at: - -```text -acp_registry/agent.json -acp_registry/icon.svg -``` - -The upstream registry PR copies those files into the top-level `hermes-agent/` directory in `agentclientprotocol/registry`. - -The registry entry uses a `uvx` distribution that points directly at the `hermes-agent` PyPI release: - -```text -uvx --from 'hermes-agent[acp]==' hermes-acp -``` - -The registry CI verifies that the pinned version exists on PyPI, so the manifest's `version` and uvx `package` pin must always match `pyproject.toml`. `scripts/release.py` keeps them in lockstep automatically. +Use an ACP-compatible plugin and point it at the local `hermes acp` command (see the VS Code / Zed `command`/`args` examples above — the same shape applies). ## Configuration and credentials @@ -188,7 +147,7 @@ ACP mode uses the same Hermes configuration as the CLI: - `~/.hermes/skills/` - `~/.hermes/state.db` -Provider resolution uses Hermes' normal runtime resolver, so ACP inherits the currently configured provider and credentials. Hermes also advertises a terminal auth method (`--setup`) for first-run registry clients; this opens Hermes' interactive model/provider setup. +Provider resolution uses Hermes' normal runtime resolver, so ACP inherits the currently configured provider and credentials. Hermes also advertises a terminal auth method (`--setup`) so first-run clients can open Hermes' interactive model/provider setup. ## Session behavior @@ -239,11 +198,9 @@ The ACP bridge maps these options onto Hermes' internal approval semantics — ` Check: -- In Zed, open the ACP Registry with `zed: acp registry` and search for **Hermes Agent**. -- For manual/local development, verify the custom `agent_servers` command points to `hermes acp`. +- Verify the custom `agent_servers` (Zed) or equivalent editor config points to `hermes acp`. - Hermes is installed and on your PATH. - The ACP extra is installed (`pip install -e '.[acp]'`). -- `uv` is installed if launching from the official Zed registry entry. ### ACP starts but immediately errors @@ -264,11 +221,7 @@ ACP mode uses Hermes' existing provider setup. Configure credentials with: hermes model ``` -or by editing `~/.hermes/.env`. Registry clients can also trigger Hermes' terminal auth flow, which runs the same interactive provider/model setup. - -### Zed registry launcher cannot find uv - -Install `uv` from the official uv installation docs, then retry the Hermes Agent thread from Zed. +or by editing `~/.hermes/.env`. ACP clients can also trigger Hermes' terminal auth flow, which runs the same interactive provider/model setup. ## See also diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/developer-guide/acp-internals.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/developer-guide/acp-internals.md index 8230d5534c1..7b4035f7935 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/developer-guide/acp-internals.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/developer-guide/acp-internals.md @@ -17,7 +17,6 @@ ACP 适配器将 Hermes 的同步 `AIAgent` 封装为异步 JSON-RPC stdio 服 - `acp_adapter/permissions.py` - `acp_adapter/tools.py` - `acp_adapter/auth.py` -- `acp_registry/agent.json` ## 启动流程 @@ -31,8 +30,6 @@ hermes acp / hermes-acp / python -m acp_adapter -> acp.run_agent(agent, use_unstable_protocol=True) ``` -Zed ACP Registry 路径通过 `uvx --from 'hermes-agent[acp]==' hermes-acp` 启动同一适配器,指向 `hermes-agent` PyPI 发布包。 - stdout 保留用于 ACP JSON-RPC 传输。人类可读的日志输出至 stderr。 ## 主要组件 @@ -149,7 +146,7 @@ ACP 不实现自己的认证存储。 - `acp_adapter/auth.py` - `hermes_cli/runtime_provider.py` -因此 ACP 通告并使用当前配置的 Hermes provider/凭据。它还始终通告一个终端 setup 认证方法(`hermes-setup`,参数 `--setup`),以便首次运行的 registry 客户端在启动正常 ACP 会话前可以打开 Hermes 的交互式模型/provider 配置。 +因此 ACP 通告并使用当前配置的 Hermes provider/凭据。它还始终通告一个终端 setup 认证方法(`hermes-setup`,参数 `--setup`),以便首次运行的客户端在启动正常 ACP 会话前可以打开 Hermes 的交互式模型/provider 配置。 ## 工作目录绑定 diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/acp.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/acp.md index 629430438cb..9acdd4cf23e 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/acp.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/acp.md @@ -45,14 +45,6 @@ pip install -e '.[acp]' - `hermes-acp` - `python -m acp_adapter` -对于 Zed registry 安装,Zed 通过官方 ACP Registry 条目启动 Hermes。该条目使用 `uvx` 发行版运行: - -```bash -uvx --from 'hermes-agent[acp]==' hermes-acp -``` - -使用 registry 安装路径前,请确保 `uv` 已在 `PATH` 中可用。 - ## 启动 ACP 服务器 以下任意命令均可以 ACP 模式启动 Hermes: @@ -87,7 +79,7 @@ hermes acp --setup-browser # 交互式(下载约 400 MB 前会提示 hermes acp --setup-browser --yes # 非交互式接受下载 ``` -这是独立命令。Zed registry 的终端认证流程(`hermes acp --setup`)在模型选择后也会将浏览器引导作为后续问题提供,因此大多数用户无需直接运行 `--setup-browser`。 +这是独立命令。Hermes 的终端认证流程(`hermes acp --setup`)在模型选择后也会将浏览器引导作为后续问题提供,因此大多数用户无需直接运行 `--setup-browser`。 具体操作: @@ -124,19 +116,7 @@ hermes acp --setup-browser --yes # 非交互式接受下载 ### Zed -Zed v0.221.x 及更新版本通过官方 ACP Registry 安装外部 agent。 - -1. 打开 Agent 面板。 -2. 点击 **Add Agent**,或运行 `zed: acp registry` 命令。 -3. 搜索 **Hermes Agent**。 -4. 安装后启动新的 Hermes 外部 agent 线程。 - -前提条件: - -- 先通过 `hermes model` 配置 Hermes provider 凭据,或在 `~/.hermes/.env` / `~/.hermes/config.yaml` 中设置。 -- 安装 `uv`,以便 registry 启动器可以运行 `uvx --from 'hermes-agent[acp]==' hermes-acp`。 - -在 registry 条目可用之前进行本地开发时,在 Zed 设置中使用自定义 agent 服务器: +Zed 通过 Zed 设置中的自定义 agent 服务器条目支持外部 agent: ```json { @@ -150,32 +130,11 @@ Zed v0.221.x 及更新版本通过官方 ACP Registry 安装外部 agent。 } ``` +先通过 `hermes model` 配置 Hermes provider 凭据,或在 `~/.hermes/.env` / `~/.hermes/config.yaml` 中设置。 + ### JetBrains -使用兼容 ACP 的插件并将其指向: - -```text -/path/to/hermes-agent/acp_registry -``` - -## Registry 清单 - -Hermes 官方 ACP Registry 元数据的源文件位于: - -```text -acp_registry/agent.json -acp_registry/icon.svg -``` - -上游 registry PR 将这些文件复制到 `agentclientprotocol/registry` 中的顶层 `hermes-agent/` 目录。 - -Registry 条目使用直接指向 `hermes-agent` PyPI 发行版的 `uvx` 发行版: - -```text -uvx --from 'hermes-agent[acp]==' hermes-acp -``` - -Registry CI 会验证固定版本是否存在于 PyPI,因此清单的 `version` 和 uvx `package` 固定版本必须始终与 `pyproject.toml` 匹配。`scripts/release.py` 会自动保持它们同步。 +使用兼容 ACP 的插件,并将其指向本地的 `hermes acp` 命令(参见上面 VS Code / Zed 的 `command`/`args` 示例——形式相同)。 ## 配置与凭据 @@ -186,7 +145,7 @@ ACP 模式使用与 CLI 相同的 Hermes 配置: - `~/.hermes/skills/` - `~/.hermes/state.db` -Provider 解析使用 Hermes 的正常运行时解析器,因此 ACP 继承当前配置的 provider 和凭据。Hermes 还为首次运行的 registry 客户端提供终端认证方法(`--setup`);这将打开 Hermes 的交互式模型/provider 设置。 +Provider 解析使用 Hermes 的正常运行时解析器,因此 ACP 继承当前配置的 provider 和凭据。Hermes 还提供终端认证方法(`--setup`),以便首次运行的客户端打开 Hermes 的交互式模型/provider 设置。 ## 会话行为 @@ -237,11 +196,9 @@ ACP 桥接将这些选项映射到 Hermes 的内部审批语义——`allow_alwa 检查: -- 在 Zed 中,使用 `zed: acp registry` 打开 ACP Registry 并搜索 **Hermes Agent**。 -- 对于手动/本地开发,验证自定义 `agent_servers` 命令是否指向 `hermes acp`。 +- 验证自定义 `agent_servers`(Zed)或等效的编辑器配置是否指向 `hermes acp`。 - Hermes 已安装且在 PATH 中。 - ACP 扩展已安装(`pip install -e '.[acp]'`)。 -- 如果从官方 Zed registry 条目启动,`uv` 已安装。 ### ACP 启动后立即报错 @@ -262,14 +219,10 @@ ACP 模式使用 Hermes 现有的 provider 设置。通过以下方式配置凭 hermes model ``` -或编辑 `~/.hermes/.env`。Registry 客户端也可以触发 Hermes 的终端认证流程,该流程运行相同的交互式 provider/模型设置。 - -### Zed registry 启动器找不到 uv - -从官方 uv 安装文档安装 `uv`,然后从 Zed 重试 Hermes Agent 线程。 +或编辑 `~/.hermes/.env`。ACP 客户端也可以触发 Hermes 的终端认证流程,该流程运行相同的交互式 provider/模型设置。 ## 另请参阅 - [ACP 内部机制](../../developer-guide/acp-internals.md) - [Provider 运行时解析](../../developer-guide/provider-runtime.md) -- [工具运行时](../../developer-guide/tools-runtime.md) \ No newline at end of file +- [工具运行时](../../developer-guide/tools-runtime.md)