diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e12f42a --- /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 claude-agent-sdk + + - name: Test + run: pytest -q diff --git a/inkbox_claude/doctor.py b/inkbox_claude/doctor.py index 507166b..d11346c 100644 --- a/inkbox_claude/doctor.py +++ b/inkbox_claude/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 claude_agent_sdk # noqa: F401 diff --git a/inkbox_claude/gateway.py b/inkbox_claude/gateway.py index fa6729b..b5703b4 100644 --- a/inkbox_claude/gateway.py +++ b/inkbox_claude/gateway.py @@ -211,7 +211,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_claude/setup_wizard.py b/inkbox_claude/setup_wizard.py index 4e18d00..87a5803 100644 --- a/inkbox_claude/setup_wizard.py +++ b/inkbox_claude/setup_wizard.py @@ -36,7 +36,8 @@ # Packages the wizard itself needs to talk to Inkbox during setup. The # gateway's other dependency (claude-agent-sdk) is checked by doctor. -INKBOX_REQUIREMENTS = ("inkbox>=0.4.7", "aiohttp>=3.9") +INKBOX_MIN_VERSION = (0, 4, 10) +INKBOX_REQUIREMENTS = ("inkbox>=0.4.10", "aiohttp>=3.9") _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,25 @@ def _load_inkbox_symbols() -> dict[str, Any]: } +def _inkbox_version_ok() -> bool: + try: + from importlib.metadata import version + + raw = version("inkbox") + except Exception: + return True # can't tell — let the import/runtime path surface it + try: + from packaging.version import Version + + return Version(raw) >= Version(".".join(str(p) for p in INKBOX_MIN_VERSION)) + except Exception: + parts = [] + for chunk in raw.split(".")[:3]: + digits = "".join(c for c in chunk if c.isdigit()) + parts.append(int(digits) if digits else 0) + return tuple(parts) >= INKBOX_MIN_VERSION + + def _ensure_inkbox_sdk() -> dict[str, Any] | None: """Import the Inkbox SDK, offering to install it into this env if missing. @@ -373,11 +393,17 @@ 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 _inkbox_version_ok(): + return symbols + first_error = ( + "the installed inkbox SDK is older than " + f"{'.'.join(str(p) for p in INKBOX_MIN_VERSION)}" + ) except Exception as exc: first_error = exc - print_warning("The Python Inkbox SDK is not available in this environment.") + print_warning("The Python Inkbox SDK is missing or too old in this environment.") print_info("The setup command is running under:") print_info(f" {sys.executable}") print_info("Install or upgrade the SDK in that exact environment with:") @@ -1109,6 +1135,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="claude-code", ) break except InkboxAPIError as exc: diff --git a/pyproject.toml b/pyproject.toml index 832b009..3572f3d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ description = "Inkbox bridge for Claude Code — talk to your coding agent over requires-python = ">=3.10" dependencies = [ "aiohttp>=3.9", - "inkbox>=0.4.7", + "inkbox>=0.4.10", "claude-agent-sdk>=0.1.0", "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 5469d23..a4fdfbc 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 # ----------------------------------------------------------------------