Skip to content
Merged
25 changes: 25 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]

Expand Down
42 changes: 40 additions & 2 deletions setup_wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import asyncio
import getpass
import importlib
import importlib.metadata
import json
import os
import re
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand Down
8 changes: 4 additions & 4 deletions tests/test_setup_wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]]
Expand All @@ -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"],
],
]

Expand All @@ -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


Expand Down
Loading