diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a61ebb6 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,25 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + # inkbox is mocked in the tests, so install only what they import. + - name: Install test deps + run: pip install pytest aiohttp + + - name: Test + run: pytest -q diff --git a/README.md b/README.md index 8d340ef..0e6b91c 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ hermes gateway restart `hermes inkbox setup` walks the active Hermes install through Inkbox configuration: -1. Installs or upgrades `inkbox>=0.4.7` and `aiohttp>=3.9` in the Hermes Python environment when needed. +1. Installs or upgrades `inkbox>=0.4.10` and `aiohttp>=3.9` in the Hermes Python environment when needed. 2. Authenticates to Inkbox, or starts self-signup if you do not have an API key yet. 3. Resolves or creates the Inkbox agent identity for this Hermes gateway. 4. Optionally provisions a local US phone number so SMS and voice are available. @@ -96,13 +96,13 @@ The setup wizard installs dependencies into the Python environment that runs Her If the wizard prints a missing-SDK warning, use the exact command it prints. It will look like this: ```bash -/path/to/hermes/venv/bin/python3 -m pip install 'inkbox>=0.4.7' 'aiohttp>=3.9' +/path/to/hermes/venv/bin/python3 -m pip install 'inkbox>=0.4.10' 'aiohttp>=3.9' ``` When `uv` is available, the wizard prefers: ```bash -uv pip install --python /path/to/hermes/venv/bin/python3 'inkbox>=0.4.7' 'aiohttp>=3.9' +uv pip install --python /path/to/hermes/venv/bin/python3 'inkbox>=0.4.10' 'aiohttp>=3.9' ``` Do not use plain `pip install inkbox aiohttp` unless the wizard tells you to; plain `pip` may point at pyenv, Homebrew, system Python, or another virtualenv. diff --git a/pyproject.toml b/pyproject.toml index a19c853..a7f1a86 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ description = "Inkbox platform plugin for Hermes Agent" requires-python = ">=3.11" dependencies = [ "aiohttp>=3.9", - "inkbox>=0.4.7", + "inkbox>=0.4.10", "segno>=1.5", ] diff --git a/setup_wizard.py b/setup_wizard.py index 7644f5f..9c1ebe7 100644 --- a/setup_wizard.py +++ b/setup_wizard.py @@ -5,6 +5,7 @@ import asyncio import getpass import importlib +import importlib.metadata import json import os import re @@ -57,7 +58,8 @@ def print_warning(message: str) -> None: masked_secret_prompt = None -INKBOX_REQUIREMENTS = ("inkbox>=0.4.7", "aiohttp>=3.9", "segno>=1.5") +INKBOX_MIN_VERSION = "0.4.10" +INKBOX_REQUIREMENTS = (f"inkbox>={INKBOX_MIN_VERSION}", "aiohttp>=3.9", "segno>=1.5") _BRACKETED_PASTE_PATTERN = re.compile(r"\x1b\[\s*200~|\x1b\[\s*201~") _AVATAR_PATH = Path(__file__).resolve().parent / "assets" / "hermes_with_iphone.png" OPENAI_REALTIME_TEST_MODEL = "gpt-realtime-2" @@ -290,9 +292,44 @@ def _load_inkbox_symbols() -> dict[str, Any]: } +def _parse_version(value: str) -> tuple[int, ...]: + # Best-effort numeric parse of "X.Y.Z" so we can compare without packaging. + parts: list[int] = [] + for chunk in value.split("."): + digits = "" + for char in chunk: + if char.isdigit(): + digits += char + else: + break + if digits == "": + break + parts.append(int(digits)) + return tuple(parts) + + +def _inkbox_version_ok() -> bool: + try: + installed = importlib.metadata.version("inkbox") + except Exception: + return False + try: + from packaging.version import Version + + return Version(installed) >= Version(INKBOX_MIN_VERSION) + except Exception: + # Fall back to a simple parsed-tuple comparison when packaging is unavailable. + return _parse_version(installed) >= _parse_version(INKBOX_MIN_VERSION) + + def _ensure_inkbox_sdk() -> dict[str, Any] | None: try: - return _load_inkbox_symbols() + symbols = _load_inkbox_symbols() + if _inkbox_version_ok(): + return symbols + first_error = ( + f"inkbox SDK is older than {INKBOX_MIN_VERSION}; an upgrade is required." + ) except Exception as exc: first_error = exc @@ -951,6 +988,7 @@ def _self_signup_flow(base_url: str, Inkbox: Any, InkboxAPIError: Any) -> tuple[ note_to_human=note, agent_handle=handle, base_url=base_url, + harness="hermes", ) break except InkboxAPIError as exc: diff --git a/tests/test_setup_wizard.py b/tests/test_setup_wizard.py index d929b36..864e051 100644 --- a/tests/test_setup_wizard.py +++ b/tests/test_setup_wizard.py @@ -21,7 +21,7 @@ def test_install_command_prefers_uv_when_available(monkeypatch): "install", "--python", "/tmp/hermes/venv/bin/python", - "inkbox>=0.4.7", + "inkbox>=0.4.10", "aiohttp>=3.9", "segno>=1.5", ]] @@ -32,10 +32,10 @@ def test_install_command_falls_back_to_pip_and_ensurepip(monkeypatch): monkeypatch.setattr(setup_wizard.shutil, "which", lambda _name: None) assert setup_wizard._install_commands() == [ - [["/tmp/hermes/venv/bin/python", "-m", "pip", "install", "inkbox>=0.4.7", "aiohttp>=3.9", "segno>=1.5"]], + [["/tmp/hermes/venv/bin/python", "-m", "pip", "install", "inkbox>=0.4.10", "aiohttp>=3.9", "segno>=1.5"]], [ ["/tmp/hermes/venv/bin/python", "-m", "ensurepip", "--upgrade"], - ["/tmp/hermes/venv/bin/python", "-m", "pip", "install", "inkbox>=0.4.7", "aiohttp>=3.9", "segno>=1.5"], + ["/tmp/hermes/venv/bin/python", "-m", "pip", "install", "inkbox>=0.4.10", "aiohttp>=3.9", "segno>=1.5"], ], ] @@ -54,7 +54,7 @@ def fail_import(): out = capsys.readouterr().out assert "/tmp/hermes/venv/bin/python" in out assert "uv pip install --python" in out - assert "inkbox>=0.4.7" in out + assert "inkbox>=0.4.10" in out assert "aiohttp>=3.9" in out