# Vendoring chattolib This plugin ships a **vendored copy of chattolib and all its dependencies** in `vendor/`. Nothing has to be installed separately — the plugin brings its own dependency tree. ## Why Vendoring? - **No dependencies to install**: `hermes plugin install` works immediately - **No lazy-import complexity**: no `ensure()` or `lazy_import` mechanisms - **Isolated**: the vendored copy cannot conflict with a system-installed one - **Offline-friendly**: works in air-gapped environments ## Layout Three of the vendored packages contain compiled extension modules and are therefore platform-specific: `pyqwest` (~15 MB, the HTTP core used by `connectrpc`), `protobuf` and `protobuf-py-ext`. Everything else is pure Python. The tree is split accordingly, so the shared part is stored once: ``` vendor/ ├── __init__.py ├── common/ # pure-Python packages (~8 MB), all platforms │ ├── chattolib/ │ ├── connectrpc/ │ ├── httpx/ httpcore/ h11/ anyio/ idna/ certifi/ │ └── opentelemetry/ protobuf/ typing_extensions.py └── platform/ # compiled extensions, ~16 MB per platform ├── linux-x86_64/ # pyqwest/, protobuf_ext/, google/ ├── linux-aarch64/ └── macos-arm64/ ``` `vendor_path.py` derives the platform tag from `platform.system()` / `platform.machine()` at import time and puts both directories on `sys.path`. Both `adapter.py` and `platform_config.py` call `setup_vendor_path()` before importing anything from `chattolib`. ### sys.path order — and why it differs per directory | Directory | Position | Why | | --- | --- | --- | | `platform/` | **prepended** | The compiled extensions must match the vendored chattolib. A host copy of `protobuf` at a different version would break the generated `_pb` code. | | `common` | **appended** | The Hermes agent ships most of this itself — `httpx` (same pin, 0.28.1), `certifi`, `anyio`, `httpcore`, `h11`, `idna`, `typing_extensions`. A plugin must not shadow the host's pinned versions; Hermes pins `certifi` exactly, and prepending would substitute our CA bundle process-wide. | So the host's pure-Python packages win, and our copies act as a fallback — which keeps out-of-process use (the cron sender, `hermes_standalone_sender_fn`) working in an environment without Hermes. The packages Hermes does *not* provide — `chattolib`, `connectrpc`, `pyqwest`, `protobuf`, `protobuf-py`, `protobuf-py-ext` — always come from `vendor/`. They are also the bulk of it: `pyqwest` alone is ~15 MB per platform, so dropping the packages Hermes already ships would save only ~2.7 MB of ~59 MB and buy a dependency on Hermes' exact pins. Not worth it. chattolib requires `httpx>=0.27` and `protobuf>=5.28`; connectrpc requires `protobuf-py==0.1.1` and `pyqwest>=0.5.1`. If a future Hermes release pins `httpx` below 0.27, the appended fallback no longer helps — the host copy would be found first and be too old. If the current platform was not vendored, the import fails with an explicit message naming the available platforms — rather than a cryptic `dlopen` error about an invalid Mach-O/ELF file. ## Updating or rebuilding ```bash # Default platform set: linux-x86_64, linux-aarch64, macos-arm64 ./vendor_chattolib.sh # A single platform PLATFORMS="linux-x86_64" ./vendor_chattolib.sh # Add another one (see the table below for valid tags) PLATFORMS="linux-x86_64 linux-aarch64 macos-arm64 windows-amd64" ./vendor_chattolib.sh # Pin a specific chattolib version CHATTOLIB_SPEC="chattolib==0.4.20" ./vendor_chattolib.sh ``` The script wipes `vendor/`, installs the dependency tree once per platform via `uv pip install --python-platform …`, splits the result with `vendor_split.py`, and verifies that `import chattolib` works on the machine you ran it on. Afterwards: `git add vendor/`. ### Supported platform tags | Tag | uv `--python-platform` | vendored by default | | -------------------- | ------------------------------ | ------------------- | | `linux-x86_64` | `x86_64-unknown-linux-gnu` | ✅ | | `linux-aarch64` | `aarch64-unknown-linux-gnu` | ✅ | | `macos-arm64` | `aarch64-apple-darwin` | ✅ | | `linux-x86_64-musl` | `x86_64-unknown-linux-musl` | — | | `linux-aarch64-musl` | `aarch64-unknown-linux-musl` | — | | `macos-x86_64` | `x86_64-apple-darwin` | — | | `windows-amd64` | `x86_64-pc-windows-msvc` | — | All seven are available as binary wheels upstream; only the default three are committed, to keep the repository at a reasonable size (~59 MB for `vendor/`). ### Python versions The script resolves wheels for **Python 3.11** (`PYTHON_VERSION`), which is the project minimum. At that version the three binary packages still provide `cp310-abi3` wheels, and the stable ABI is forward compatible — so one build covers 3.11 through 3.14. Resolving for 3.12+ would instead pull version-specific wheels (`cp312-…`) that only work on that exact version. **If you raise `PYTHON_VERSION`, check that the resulting `.so` files are still named `*.abi3.so`.** If they are not, the vendored tree silently becomes Python-version-specific. ## Development without vendoring ```bash pip install chattolib ``` A system-installed chattolib is only used if `vendor/` is absent — the vendored copy is deliberately prepended to `sys.path` and therefore wins. ## License The vendored chattolib is licensed under **MPL-2.0 + Apache-2.0** (see `vendor/common/chattolib-*.dist-info/`). Upstream source: https://github.com/chattocorp/chatto