diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..517bf22 --- /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 segno + + - name: Test + run: pytest -q diff --git a/inkbox_codex/doctor.py b/inkbox_codex/doctor.py index db29bb5..b67f958 100644 --- a/inkbox_codex/doctor.py +++ b/inkbox_codex/doctor.py @@ -33,7 +33,7 @@ def run_doctor() -> List[Tuple[str, bool, str]]: import inkbox # noqa: F401 checks.append(("inkbox SDK", True, "installed")) except ImportError: - checks.append(("inkbox SDK", False, "pip install 'inkbox>=0.4.7'")) + checks.append(("inkbox SDK", False, "pip install 'inkbox>=0.4.10'")) try: import aiohttp # noqa: F401 diff --git a/inkbox_codex/gateway.py b/inkbox_codex/gateway.py index d59879b..0f04450 100644 --- a/inkbox_codex/gateway.py +++ b/inkbox_codex/gateway.py @@ -209,7 +209,7 @@ async def run(self) -> None: if not AIOHTTP_AVAILABLE: raise RuntimeError("aiohttp is not installed; run: pip install aiohttp") if not INKBOX_AVAILABLE: - raise RuntimeError("inkbox SDK is not installed; run: pip install 'inkbox>=0.4.7'") + raise RuntimeError("inkbox SDK is not installed; run: pip install 'inkbox>=0.4.10'") if not self.cfg.api_key or not self.cfg.identity: raise RuntimeError("INKBOX_API_KEY and INKBOX_IDENTITY must be set (see README)") diff --git a/inkbox_codex/setup_wizard.py b/inkbox_codex/setup_wizard.py index 740f6a5..ea07298 100644 --- a/inkbox_codex/setup_wizard.py +++ b/inkbox_codex/setup_wizard.py @@ -36,7 +36,8 @@ # Packages the wizard itself needs to talk to Inkbox during setup. The # gateway's Codex CLI dependency is checked by doctor. -INKBOX_REQUIREMENTS = ("inkbox>=0.4.7", "aiohttp>=3.9") +INKBOX_REQUIREMENTS = ("inkbox>=0.4.10", "aiohttp>=3.9") +MIN_INKBOX_VERSION = (0, 4, 10) _BRACKETED_PASTE_PATTERN = re.compile(r"\x1b\[\s*200~|\x1b\[\s*201~") # Bundled avatar attached to the agent's Inkbox contact card during setup. @@ -366,6 +367,29 @@ def _load_inkbox_symbols() -> dict[str, Any]: } +def _inkbox_version_too_old() -> bool: + """Return True when the installed Inkbox SDK predates MIN_INKBOX_VERSION. + + Returns: + bool: True if an outdated ``inkbox`` is installed, else False. + """ + try: + import importlib.metadata as importlib_metadata + + raw = importlib_metadata.version("inkbox") + except Exception: + return False + try: + # Prefer packaging when present for PEP 440-correct comparison. + from packaging.version import Version + + return Version(raw) < Version(".".join(str(p) for p in MIN_INKBOX_VERSION)) + except Exception: + # Fall back to a simple numeric-tuple comparison of the leading parts. + parts = tuple(int(p) for p in re.findall(r"\d+", raw)[: len(MIN_INKBOX_VERSION)]) + return parts < MIN_INKBOX_VERSION + + def _ensure_inkbox_sdk() -> dict[str, Any] | None: """Import the Inkbox SDK, offering to install it into this env if missing. @@ -373,7 +397,12 @@ def _ensure_inkbox_sdk() -> dict[str, Any] | None: dict[str, Any] | None: SDK symbols on success, None if unavailable. """ try: - return _load_inkbox_symbols() + symbols = _load_inkbox_symbols() + if not _inkbox_version_too_old(): + return symbols + first_error = ( + f"inkbox SDK is older than {'.'.join(str(p) for p in MIN_INKBOX_VERSION)}" + ) except Exception as exc: first_error = exc @@ -1109,6 +1138,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="codex", ) break except InkboxAPIError as exc: diff --git a/pyproject.toml b/pyproject.toml index 6c1c762..2b851e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ description = "Inkbox bridge for Codex — talk to your coding agent over email, requires-python = ">=3.10" dependencies = [ "aiohttp>=3.9", - "inkbox>=0.4.7", + "inkbox>=0.4.10", "segno>=1.5", # terminal QR codes for the iMessage connect step ] diff --git a/tests/test_setup_wizard.py b/tests/test_setup_wizard.py index 923688e..4175cab 100644 --- a/tests/test_setup_wizard.py +++ b/tests/test_setup_wizard.py @@ -83,7 +83,7 @@ def test_install_command_prefers_uv_when_available(monkeypatch): "install", "--python", "/tmp/venv/bin/python", - "inkbox>=0.4.7", + "inkbox>=0.4.10", "aiohttp>=3.9", ]] @@ -93,10 +93,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/venv/bin/python", "-m", "pip", "install", "inkbox>=0.4.7", "aiohttp>=3.9"]], + [["/tmp/venv/bin/python", "-m", "pip", "install", "inkbox>=0.4.10", "aiohttp>=3.9"]], [ ["/tmp/venv/bin/python", "-m", "ensurepip", "--upgrade"], - ["/tmp/venv/bin/python", "-m", "pip", "install", "inkbox>=0.4.7", "aiohttp>=3.9"], + ["/tmp/venv/bin/python", "-m", "pip", "install", "inkbox>=0.4.10", "aiohttp>=3.9"], ], ] @@ -115,7 +115,7 @@ def fail_import(): out = capsys.readouterr().out assert "/tmp/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 # ----------------------------------------------------------------------