{"id":48961348,"url":"https://github.com/archivebox/abxpkg","last_synced_at":"2026-04-18T02:01:16.836Z","repository":{"id":240359845,"uuid":"802422164","full_name":"ArchiveBox/abxpkg","owner":"ArchiveBox","description":"📦 Modern strongly typed Python library for managing system dependencies with package managers like apt, brew, pip, npm, etc.","archived":false,"fork":false,"pushed_at":"2026-04-13T03:15:38.000Z","size":1587,"stargazers_count":26,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-04-13T05:08:31.323Z","etag":null,"topics":["apt","brew","dependencies","dependencies-checking","dependency-manager","django","docker","dpkg","gem","js","nix","npm","package-manager","package-managers","packages","pip","pnpm","pydantic","python","uv"],"latest_commit_sha":null,"homepage":"https://archivebox.github.io/abxpkg/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ArchiveBox.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null},"funding":{"github":["ArchiveBox","pirate"],"custom":["https://donate.archivebox.io/"]}},"created_at":"2024-05-18T08:48:10.000Z","updated_at":"2026-04-13T03:15:41.000Z","dependencies_parsed_at":"2024-05-21T04:42:54.461Z","dependency_job_id":"8d1934df-88f5-404e-bfc3-f378362e7374","html_url":"https://github.com/ArchiveBox/abxpkg","commit_stats":null,"previous_names":["archivebox/pydantic-pkgr","archivebox/abx-pkg","archivebox/abxpkg"],"tags_count":60,"template":false,"template_full_name":null,"purl":"pkg:github/ArchiveBox/abxpkg","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ArchiveBox%2Fabxpkg","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ArchiveBox%2Fabxpkg/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ArchiveBox%2Fabxpkg/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ArchiveBox%2Fabxpkg/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ArchiveBox","download_url":"https://codeload.github.com/ArchiveBox/abxpkg/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ArchiveBox%2Fabxpkg/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31953515,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T00:39:45.007Z","status":"online","status_checked_at":"2026-04-18T02:00:07.018Z","response_time":103,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apt","brew","dependencies","dependencies-checking","dependency-manager","django","docker","dpkg","gem","js","nix","npm","package-manager","package-managers","packages","pip","pnpm","pydantic","python","uv"],"created_at":"2026-04-18T02:01:08.050Z","updated_at":"2026-04-18T02:01:16.820Z","avatar_url":"https://github.com/ArchiveBox.png","language":"Python","funding_links":["https://github.com/sponsors/ArchiveBox","https://github.com/sponsors/pirate","https://donate.archivebox.io/"],"categories":[],"sub_categories":[],"readme":"\u003ch1\u003e\u003ca href=\"https://archivebox.github.io/abxpkg/\"\u003e\u003ccode\u003eabxpkg\u003c/code\u003e\u003c/a\u003e \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; 📦  \u003csmall\u003e\u003ccode\u003eapt\u003c/code\u003e\u0026nbsp; \u003ccode\u003ebrew\u003c/code\u003e\u0026nbsp; \u003ccode\u003epip\u003c/code\u003e\u0026nbsp; \u003ccode\u003euv\u003c/code\u003e\u0026nbsp; \u003ccode\u003enpm\u003c/code\u003e\u0026nbsp; \u003ccode\u003epnpm\u003c/code\u003e\u0026nbsp; \u003ccode\u003eyarn\u003c/code\u003e\u0026nbsp; \u003ccode\u003ebun\u003c/code\u003e\u0026nbsp; \u003ccode\u003edeno\u003c/code\u003e\u0026nbsp; \u003ccode\u003ecargo\u003c/code\u003e\u0026nbsp; \u003ccode\u003egem\u003c/code\u003e\u0026nbsp; \u003ccode\u003egoget\u003c/code\u003e\u0026nbsp; \u003ccode\u003enix\u003c/code\u003e\u0026nbsp; \u003ccode\u003edocker\u003c/code\u003e\u0026nbsp; \u003ccode\u003ebash\u003c/code\u003e\u0026nbsp; \u003ccode\u003epuppeteer\u003c/code\u003e\u0026nbsp; \u003ccode\u003eplaywright\u003c/code\u003e\u0026nbsp; \u003ccode\u003echromewebstore\u003c/code\u003e\u0026nbsp; \u003ccode\u003eansible\u003c/code\u003e\u0026nbsp; \u003ccode\u003epyinfra\u003c/code\u003e\u003c/small\u003e\u003cbr/\u003e\u003csub\u003eSimple Python interfaces for package managers + installed binaries.\u003c/sub\u003e\u003c/h1\u003e\n\u003cbr/\u003e\n\n[![PyPI][pypi-badge]][pypi]\n[![Python Version][version-badge]][pypi]\n[![Django Version][django-badge]][pypi]\n[![GitHub][licence-badge]][licence]\n[![GitHub Last Commit][repo-badge]][repo]\n\u003c!--[![Downloads][downloads-badge]][pypi]--\u003e\n\n\u003cbr/\u003e\n\n**It's an ORM for your package managers, providing nice python types for packages + installers.**  \n  \n**This is a [Python library](https://pypi.org/project/abxpkg/) and all-in-one CLI for managing packages locally with a variety of package managers.**  \nIt's designed for when you have to detect or install binary or source dependencies at runtime.\n\nStop distributing your apps via `curl | sh`! Instead you can bake package installation into your app, or use our `uv`-style [`abxpkg run --script`](https://github.com/ArchiveBox/abxpkg/#shebang-line-in-scripts) shebang headers to auto-install dependencies for you.\n\n\n```bash\npip install abxpkg\n\nabxpkg --version\n```\n\n```python\nfrom abxpkg import Binary, npm\n\ncurl = Binary(name=\"curl\").load()\nprint(curl.abspath, curl.version, curl.exec(cmd=[\"--version\"]))\n\nnpm.install(\"puppeteer\")\n```\n\n\u003e 📦 Provides consistent interfaces for runtime dependency resolution \u0026 installation across multiple package managers \u0026 OSs\n\u003e ✨ Built with [`pydantic`](https://pydantic-docs.helpmanual.io/) v2 for strong static typing guarantees and easy conversion to/from json\n\u003e 🌈 Usable with [`django`](https://docs.djangoproject.com/en/5.0/) \u003e= 4.0, [`django-ninja`](https://django-ninja.dev/), and OpenAPI + [`django-jsonform`](https://django-jsonform.readthedocs.io/) to build UIs \u0026 APIs\n\u003e 🦄 Driver layer can be [`pyinfra`](https://github.com/pyinfra-dev/pyinfra) / [`ansible`](https://github.com/ansible/ansible) / or built-in `abxpkg` engine\n\n\u003csub\u003e\u003ci\u003eBuilt by \u003ca href=\"https://github.com/ArchiveBox\"\u003eArchiveBox\u003c/a\u003e to install \u0026 auto-update our extractor dependencies at runtime (\u003ccode\u003echrome\u003c/code\u003e, \u003ccode\u003ewget\u003c/code\u003e, \u003ccode\u003ecurl\u003c/code\u003e, etc.) on `macOS`/`Linux`/`Docker`.\u003c/i\u003e\u003c/sub\u003e\n\n\u003cbr/\u003e\n\n**Source Code**: [https://github.com/ArchiveBox/abxpkg/](https://github.com/ArchiveBox/abxpkg/)  \n**Documentation**: [https://github.com/ArchiveBox/abxpkg/blob/main/README.md](https://github.com/ArchiveBox/abxpkg/blob/main/README.md)\n\n\u003cbr/\u003e\n\n```python\nfrom abxpkg import Binary, apt, brew, pip, npm, env\n\n# Provider singletons are available as simple imports — no manual instantiation needed\ndependencies = [\n    Binary(name='curl',       binproviders=[env, apt, brew]),\n    Binary(name='yt-dlp',     binproviders=[env, pip, uv, apt, brew]),\n    Binary(name='playwright', binproviders=[env, npm, pnpm]),\n    Binary(name='chromium',   binproviders=[playwright, puppeteer, apt]),\n    Binary(name='postgres',   binproviders=[docker, env, apt, brew]),\n]\nfor binary in dependencies:\n    binary = binary.install()\n\n    print(binary.abspath, binary.version, binary.binprovider, binary.is_valid, binary.sha256, binary.mtime)\n    # Path(...) SemVer(...) EnvProvider()/AptProvider()/BrewProvider()/PipProvider()/NpmProvider() True '\u003csha256\u003e' 1712890123456789000\n\n    binary.exec(cmd=['--version'])   # curl 7.81.0 (x86_64-apple-darwin23.0) libcurl/7.81.0 ...\n```\n\n\u003cbr/\u003e\n\n---\n\n\u003e [!TIP]\n\u003e **🔒 Stay safe from supply-chain attcaks with `abxpkg`:** We default to safe behavior (when providers allow):\n\u003e \n\u003e  - `min_release_age=7` (we only install packages that have been published for 7 days or longer)\n\u003e  - `postinstall_scripts=False` (we don't run post-install scripts for packages by default)\n\u003e  - `install_root=\u003cplatform default abx lib dir\u003e` (the CLI defaults to a dedicated provider-rooted library dir so host system stays clean)\n\u003e\n\u003e You can customize these defaults on `Binary` or `BinProvider`, or with `ABXPKG_MIN_RELEASE_AGE`/`ABXPKG_POSTINSTALL_SCRIPTS`/`ABXPKG_LIB_DIR` (see [Configuration](#Configuration) below).\n\n---\n\n## Usage\n\n### Install\n\n```bash\npip install abxpkg\n# or\nuv tool add abxpkg\n```\n\n### CLI\n\nInstalling `abxpkg` also provides an `abxpkg` CLI entrypoint:\n\n```bash\nabxpkg --version\nabxpkg version\nabxpkg list\n\nabxpkg install yt-dlp\nabxpkg update yt-dlp\nabxpkg uninstall yt-dlp\nabxpkg load yt-dlp\n```\n\n`abxpkg --version` and `abxpkg version` stream the package version first, then a host/env summary line, then one section per selected provider showing its current resolved runtime state (`INSTALLER_BINARY`, `PATH`, `ENV`, `install_root`, `bin_dir`, and any active cached dependency / installed binaries).\n\n`abxpkg version \u003cbinary\u003e` is a thin alias for `abxpkg load \u003cbinary\u003e`.\n\n`abxpkg list` prints the full active cache for the selected providers, grouping provider installer binaries first and normal cached binaries after a blank line. You can optionally pass binary names and/or provider names positionally to filter the output:\n\n```bash\nabxpkg list\nabxpkg list yt-dlp chromium\nabxpkg list env puppeteer chromium\n```\n\n#### Execute an installed binary via the configured providers\n\n```bash\nabxpkg run yt-dlp --help                          # resolves yt-dlp via the configured providers and execs it\nabxpkg --binproviders=pip,brew run pip show black # restrict provider resolution (exercises PipProvider.exec)\nabxpkg --binproviders=pip --install run yt-dlp    # load first, then install via selected providers if needed\nabxpkg --binproviders=pip --update  run yt-dlp    # ensure the binary is available, then update before exec\nabxpkg --binproviders=pip --no-cache --install run yt-dlp  # bypass cached/current-state checks during resolution + install\n```\n\nabxpkg options (e.g. `--binproviders`, `--lib`, `--install`, `--update`, `--no-cache`) must appear before the `run` subcommand; every argument after the binary name is forwarded verbatim to the underlying binary. `run` exits with the child's exit code, passes its `stdout`/`stderr` through unbuffered, and routes any abxpkg install/load logs to `stderr` only — no headers, no footers, no parsing.\n\n#### `abx`: auto-install-and-run shortcut\n\nThink `npx` / `uvx` / `pipx run` — but for **every** package manager abxpkg supports. `abx` is a thin alias for `abxpkg --install run ...`: it resolves the binary via the configured providers, installs it if missing, then execs it with the forwarded arguments.\n\n```bash\nabx yt-dlp --help                               # auto-install (if needed) and run yt-dlp\nabx --update yt-dlp --help                      # ensure the binary is available, then update before running\nabx --binproviders=env,uv,pip,apt,brew yt-dlp   # restrict provider resolution\n```\n\nOptions before the binary name (`--lib`, `--binproviders`, `--dry-run`, `--debug`, `--no-cache`, `--update`) are forwarded to `abxpkg`; everything after the binary name is forwarded to the binary itself.\n\n#### Shebang Line in Scripts\n\nInspired by [`uv`'s inline script metadata](https://docs.astral.sh/uv/guides/scripts/#declaring-script-dependencies), `abxpkg` lets you declare **arbitrary package dependencies** at the top of any script using a `/// script` metadata block.\n\n```javascript\n#!/usr/bin/env -S abxpkg run --script node\n\n// /// script\n// dependencies = [\n//     {name = \"node\", binproviders = [\"env\", \"apt\", \"brew\"], min_version = \"22.0.0\"},\n//     {name = \"playwright\", binproviders = [\"pnpm\", \"npm\"]},\n//     {name = \"chromium\", binproviders = [\"playwright\", \"puppeteer\", \"apt\"], min_version = \"131.0.0\"},\n// ]\n// [tool.abxpkg]\n// ABXPKG_POSTINSTALL_SCRIPTS = true\n// ///\n\nconst { chromium } = require('playwright');\n\n(async () =\u003e {\n    const browser = await chromium.launch();\n    const page = await browser.newPage();\n    await page.goto('https://example.com');\n    console.log(await page.title());\n    await browser.close();\n})();\n```\n\nThe metadata parser is comment-syntax-agnostic — it looks for `/// script` and `///` delimiters and strips the first whitespace-delimited token from each line, so `#`, `//`, `--`, `;`, and any other single-token comment prefix all work.\n\n#### Per-`Binary` / per-`BinProvider` options as CLI flags\n\nEvery [`Binary` / `BinProvider` configuration field](#configuration) is exposed as a CLI flag on the group and on subcommands (`install`, `update`, `uninstall`, `load`), and is also available to `run` / `abx` via group-level flags placed before the binary name. Providers that can't enforce a given option emit a warning to `stderr` and continue — no hard failure.\n\n```bash\nabxpkg --min-version=1.2.3 --min-release-age=7 install yt-dlp\nabxpkg --postinstall-scripts=False --binproviders=apt,uv,pip install black\nabxpkg --no-cache install black\nabxpkg --install-root=/tmp/yt-dlp-root --bin-dir=/tmp/yt-dlp-bin install yt-dlp\nabxpkg --overrides='{\"pip\":{\"install_args\":[\"yt-dlp[default]\"]}}' install yt-dlp\nabxpkg --install-timeout=600 --version-timeout=20 --euid=1000 install yt-dlp\nabxpkg --global install yt-dlp\nabx --min-version=2024.1.1 --min-release-age=0 yt-dlp --help\n```\n\n| Flag | Type | Meaning |\n| --- | --- | --- |\n| `--min-version=SEMVER` | `str` | Minimum acceptable version (set on `Binary.min_version`). |\n| `--postinstall-scripts[=BOOL]` | `bool` | Allow post-install scripts. Bare `--postinstall-scripts` = `True`. Providers that can't disable them warn-and-ignore. |\n| `--min-release-age=DAYS` | `float` | Minimum days since publication. Non-supporting providers warn-and-ignore. |\n| `--no-cache[=BOOL]` | `bool` | Skip cached/current-state checks and force fresh install/update/load probes. Bare `--no-cache` = `True`. |\n| `--overrides=JSON` | `dict` | Per-provider `Binary.overrides` patches for shared provider fields (`PATH`, `INSTALLER_BIN`, `install_root`, `bin_dir`, `euid`, `postinstall_scripts`, `min_release_age`, `dry_run`, `install_timeout`, `version_timeout`) plus per-binary handler replacements (`install_args`, `abspath`, `version`, `install`, `update`, `uninstall`). |\n| `--global[=BOOL]` | `bool` | Thin alias for `--lib=None`. Bare `--global` = `True`. |\n| `--install-root=PATH` | `Path` | Override the per-provider install directory. |\n| `--bin-dir=PATH` | `Path` | Override the per-provider bin directory. |\n| `--euid=UID` | `int` | Pin the UID used when providers shell out. |\n| `--install-timeout=SECONDS` | `int` | Seconds to wait for install/update/uninstall subprocesses. |\n| `--version-timeout=SECONDS` | `int` | Seconds to wait for version/metadata probes. |\n| `--dry-run[=BOOL]` | `bool` | Show installer commands without executing them. Bare `--dry-run` = `True`. |\n| `--debug[=BOOL]` | `bool` | Emit DEBUG logs to `stderr`. Bare `--debug` = `True`. Defaults to `ABXPKG_DEBUG` or `False`. |\n\nEvery value-taking flag also accepts the literal string `None` / `null` / `\"\"` to reset to the provider's default resolution path. For `postinstall_scripts` / `min_release_age`, that means the action-specific effective default for that provider (`False` / `7` on supporting providers, `True` / `0` otherwise). The precedence is: explicit per-subcommand flag \u003e group-level flag \u003e environment variable \u003e built-in default.\n\n#### Select specific providers / re-order provider precedence\n\n```bash\nabxpkg install --binproviders=env,uv,pip,apt,brew prettier\n# or\nenv ABXPKG_BINPROVIDERS=env,uv,pip,apt,brew abxpkg install yt-dlp\n```\n\n#### Customize where installed packages are located\n\n```bash\nabxpkg --lib=~/my-abx-lib install yt-dlp        # pin a custom provider-rooted library dir\nabxpkg --lib=./vendor install yt-dlp            # store all packages under $PWD/vendor\nabxpkg --lib=/tmp/abxlib install yt-dlp         # store all packages under /tmp/abxlib\nabxpkg --global install yt-dlp                  # alias for --lib=None (use provider-native global mode where supported)\n\n# or\nenv ABXPKG_LIB_DIR=/any/dir/path abxpkg install yt-dlp\n```\n\n#### Run in \"dry mode\" to see what commands will do before executing\n\n```bash\nabxpkg install --dry-run some-dangerous-package      # outputs commands that would be run without executing them\n# or\nenv ABXPKG_DRY_RUN=1 abxpkg install some-dangerous-package\n```\n\nCLI result lines are written to `stdout`. Progress logging is written to `stderr` at `INFO` by default. Enable DEBUG logging with `ABXPKG_DEBUG=1` or `--debug`.\n\n\u003cbr/\u003e\n\n### Python Library\n\n#### Basic Usage\n\nAll built-in providers are available as lazy singletons — just import them by name:\n\n```python\nfrom abxpkg import apt, brew, pip, npm, env\n\napt.install('curl')\nenv.load('wget')\n```\n\nThese are instantiated on first access and cached for reuse. If you need custom configuration, you can still instantiate provider classes directly:\n\n```python\nfrom pathlib import Path\nfrom abxpkg import PipProvider\n\ncustom_pip = PipProvider(install_root=Path(\"/tmp/abxpkg-pip\"), min_release_age=0)\n```\n\nUse the `Binary` class to declare a package that can be installed by one of several ordered providers, with an optional version floor:\n\n```python\nfrom abxpkg import Binary, SemVer, env, brew\n\ncurl = Binary(\n    name=\"curl\",\n    min_version=SemVer(\"8.0.0\"),\n    binproviders=[env, brew],\n).install()\n```\n\n`min_version` is enforced after a provider resolves or installs a binary — provider discovery can still succeed, but the final `Binary` is rejected if the loaded version is below the floor. Use `min_version=None` to disable the check.\n\nPass `no_cache=True` to `load()` / `install()` / `update()` / `uninstall()` when you want to bypass cached/current-state checks. For `install()`, `no_cache=True` skips the initial `load()` check and forces a fresh install path. The equivalent CLI and env controls are `--no-cache` and `ABXPKG_NO_CACHE=1`.\n\n#### Advanced Usage\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch4\u003eDefine a reusable \u003ccode\u003eBinary\u003c/code\u003e subclass with per-provider overrides\u003c/h4\u003e\u003c/summary\u003e\n\n```python\nfrom pydantic import InstanceOf\nfrom abxpkg import BinProvider, Binary, BinProviderName, BinName, HandlerDict, BrewProvider\nfrom abxpkg import env, pip, apt\n\nclass CustomBrewProvider(BrewProvider):\n    name: BinProviderName = 'custom_brew'\n\n    def get_macos_packages(self, bin_name: str, **context) -\u003e list[str]:\n        return ['yt-dlp'] if bin_name == 'ytdlp' else [bin_name]\n\nclass YtdlpBinary(Binary):\n    name: BinName = 'ytdlp'\n    description: str = 'YT-DLP (Replacement for YouTube-DL) Media Downloader'\n\n    # define the providers this binary supports\n    binproviders: list[InstanceOf[BinProvider]] = [env, pip, apt, CustomBrewProvider()]\n\n    # customize installed package names for specific package managers\n    overrides: dict[BinProviderName, HandlerDict] = {\n        'pip': {'install_args': ['yt-dlp[default,curl-cffi]']},   # literal values\n        'apt': {'install_args': lambda: ['yt-dlp', 'ffmpeg']},    # any pure Callable\n        'custom_brew': {'install_args': 'self.get_macos_packages'},  # or a string ref to a method on self\n    }\n\n\nytdlp = YtdlpBinary().install()\nprint(ytdlp.binprovider)    # EnvProvider(...) / PipProvider(...) / AptProvider(...) / CustomBrewProvider(...)\nprint(ytdlp.abspath)        # Path(...)\nprint(ytdlp.version)        # SemVer(...)\nprint(ytdlp.is_valid)       # True\n\n# Lifecycle actions preserve the Binary type and refresh/clear loaded metadata as needed\nytdlp = ytdlp.update()\nassert ytdlp.is_valid\nytdlp = ytdlp.uninstall()\nassert ytdlp.abspath is None and ytdlp.version is None\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch4\u003eUse \u003ccode\u003eBinary\u003c/code\u003e objects as a stable typed interface to interact with installed packages\u003c/h4\u003e\u003c/summary\u003e\n\n```python\nfrom abxpkg import Binary, apt, brew, env\n\n# Use providers directly for package manager operations\napt.install('wget')\nprint(apt.PATH, apt.get_abspaths('wget'), apt.get_version('wget'))\n\n# our Binary API provides a nice type-checkable, validated, serializable handle\nffmpeg = Binary(name='ffmpeg', binproviders=[env, apt, brew]).load()\nprint(ffmpeg)                       # Binary(name='ffmpeg', abspath=Path(...), version=SemVer(...), sha256='...', mtime=1712890123456789000)\nprint(ffmpeg.abspaths)              # show all matching binaries found via each provider PATH\nprint(ffmpeg.model_dump(mode='json'))  # JSON-ready dict\nprint(ffmpeg.model_json_schema())   # ... OpenAPI-ready JSON schema showing all available fields\n```\n\n```python\nfrom pydantic import InstanceOf\nfrom abxpkg import Binary, BinProvider, BrewProvider, EnvProvider\n\n# You can also instantiate provider classes manually for custom configuration,\n# or define binaries as classes for type checking\nclass CurlBinary(Binary):\n    name: str = 'curl'\n    binproviders: list[InstanceOf[BinProvider]] = [BrewProvider(), EnvProvider()]\n\ncurl = CurlBinary().install()\nassert isinstance(curl, CurlBinary)                                 # CurlBinary is a unique type you can use in annotations now\nprint(curl.abspath, curl.version, curl.binprovider, curl.is_valid)  # Path(...) SemVer(...) BrewProvider()/EnvProvider() True\ncurl.exec(cmd=['--version'])                                        # curl 8.4.0 (x86_64-apple-darwin23.0) libcurl/8.4.0 ...\n```\n\n\u003c/details\u003e\n\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch4\u003eCustomize binary resolution/install/other behavior via per-provider or per-binary overrides\u003c/h4\u003e\u003c/summary\u003e\n\n```python\nimport os\nimport platform\nfrom pydantic import InstanceOf\nfrom abxpkg import BinProvider, Binary, BinProviderName, BinName, HandlerDict\nfrom abxpkg import env, apt\n\nclass DockerBinary(Binary):\n    name: BinName = 'docker'\n    binproviders: list[InstanceOf[BinProvider]] = [env, apt]\n\n    overrides: dict[BinProviderName, HandlerDict] = {\n        'env': {\n            # prefer podman if installed, fall back to docker\n            'abspath': lambda: os.which('podman') or os.which('docker') or os.which('docker-ce'),\n        },\n        'apt': {\n            # vary the installed package name based on CPU architecture\n            'install_args': {\n                'amd64': ['docker'],\n                'armv7l': ['docker-ce'],\n                'arm64': ['docker-ce'],\n            }.get(platform.machine(), 'docker'),\n        },\n    }\n\ndocker = DockerBinary().install()\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch4\u003eSubclass \u003ccode\u003eBinProvider\u003c/code\u003e to add support for a new package manager\u003c/h4\u003e\u003c/summary\u003e\n\n```python\nfrom pathlib import Path\nfrom abxpkg import (\n    BinProvider,\n    BinProviderName,\n    BinName,\n    HostBinPath,\n    InstallArgs,\n    SemVer,\n    bin_abspath,\n)\n\nclass CargoProvider(BinProvider):\n    name: BinProviderName = 'cargo'\n    INSTALLER_BIN: BinName = 'cargo'\n    PATH: str = str(Path.home() / '.cargo/bin')\n\n    def default_install_args_handler(self, bin_name: BinName, **context) -\u003e InstallArgs:\n        return [bin_name]\n\n    def default_install_handler(\n        self,\n        bin_name: BinName,\n        install_args: InstallArgs | None = None,\n        postinstall_scripts: bool | None = None,\n        min_release_age: float | None = None,\n        min_version: SemVer | None = None,\n        timeout: int | None = None,\n    ) -\u003e str:\n        install_args = install_args or self.get_install_args(bin_name)\n        installer = self.INSTALLER_BINARY()\n        assert installer and installer.loaded_abspath\n        proc = self.exec(bin_name=installer.loaded_abspath, cmd=['install', *install_args], timeout=timeout)\n        if proc.returncode != 0:\n            self._raise_proc_error('install', install_args, proc)\n        return proc.stdout.strip() or proc.stderr.strip()\n\n    def default_abspath_handler(self, bin_name: BinName, **context) -\u003e HostBinPath | None:\n        return bin_abspath(bin_name, PATH=self.PATH)\n\n    def default_version_handler(\n        self,\n        bin_name: BinName,\n        abspath: HostBinPath | None = None,\n        timeout: int | None = None,\n        **context,\n    ) -\u003e SemVer | None:\n        return self._version_from_exec(bin_name, abspath=abspath, timeout=timeout)\n\n\ncargo = CargoProvider()\nrg = cargo.install(bin_name='ripgrep')\nprint(rg.binprovider)    # CargoProvider(...)\nprint(rg.version)        # SemVer(...)\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch4\u003eConfigure python \u003ccode\u003elogging\u003c/code\u003e to customize the stderr/stdout logging\u003c/h4\u003e\u003c/summary\u003e\n\n`abxpkg` uses the standard Python `logging` module. By default it stays quiet unless your application configures logging explicitly.\n\n```python\nimport logging\nfrom abxpkg import Binary, env, configure_logging\n\nconfigure_logging(logging.INFO)\n\npython = Binary(name='python', binproviders=[env]).load()\n```\n\nTo enable Rich logging:\n\n```bash\npip install \"abxpkg[rich]\"\n```\n\n```python\nimport logging\nfrom abxpkg import Binary, EnvProvider, configure_rich_logging\n\nconfigure_rich_logging(logging.DEBUG)\n\npython = Binary(name='python', binproviders=[EnvProvider()]).load()\n```\n\nDebug logging is hardened so logging itself does not become the failure. If a provider/model object has a broken or overly-expensive `repr()`, `abxpkg` falls back to a short `ClassName(...)` summary instead of raising while formatting log output.\n\n`configure_rich_logging(...)` uses `rich.logging.RichHandler` under the hood, so log levels, paths, arguments, and command lines render with terminal colors when supported.\n\nYou can also manage it with standard logging primitives:\n\n```python\nimport logging\n\nlogging.basicConfig(level=logging.INFO)\nlogging.getLogger(\"abxpkg\").setLevel(logging.DEBUG)\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch4\u003eDjango integration: store \u003ccode\u003eBinProvider\u003c/code\u003e / \u003ccode\u003eBinary\u003c/code\u003e in DB models and render them in the Admin\u003c/h4\u003e\u003c/summary\u003e\n\nWith a few more packages, you get type-checked Django fields \u0026 forms that support `BinProvider` and `Binary`.\n\n\u003e [!TIP]\n\u003e For the full Django experience, we recommend installing these 3 excellent packages:\n\u003e - [`django-admin-data-views`](https://github.com/MrThearMan/django-admin-data-views)\n\u003e - [`django-pydantic-field`](https://github.com/surenkov/django-pydantic-field)\n\u003e - [`django-jsonform`](https://django-jsonform.readthedocs.io/)\n\u003e `pip install abxpkg django-admin-data-views django-pydantic-field django-jsonform`\n\n**Django model fields:**\n\n```python\nfrom django.db import models\nfrom abxpkg import BinProvider, Binary, SemVer\nfrom django_pydantic_field import SchemaField\n\nclass Dependency(models.Model):\n    label = models.CharField(max_length=63)\n    default_binprovider: BinProvider = SchemaField()\n    binaries: list[Binary] = SchemaField(default=[])\n    min_version: SemVer = SchemaField(default=(0, 0, 1))\n```\n\nSaving a `Binary` using the model:\n\n```python\nfrom abxpkg import Binary, env\n\ncurl = Binary(name='curl').load()\n\nobj = Dependency(\n    label='runtime tools',\n    default_binprovider=env,   # store BinProvider values directly\n    binaries=[curl],            # store Binary/SemVer values directly\n)\nobj.save()\n```\n\nWhen fetching back from the DB, `Binary` fields are auto-deserialized and immediately usable:\n\n```python\nobj = Dependency.objects.get(label='runtime tools')\nassert obj.binaries[0].abspath == curl.abspath\nobj.binaries[0].exec(cmd=['--version'])\n```\n\nFor a full example see the bundled [`django_example_project/`](https://github.com/ArchiveBox/abxpkg/tree/main/django_example_project).\n\n**Django Admin integration:**\n\n\u003cimg height=\"220\" alt=\"Django Admin binaries list view\" src=\"https://github.com/ArchiveBox/abxpkg/assets/511499/a9980217-f39e-434e-b266-20cd6feb17c3\" align=\"top\"\u003e\u003cimg height=\"220\" alt=\"Django Admin binaries detail view\" src=\"https://github.com/ArchiveBox/abxpkg/assets/511499/d4d9086e-c8f4-4b6e-8ee8-8c8a864715b0\" align=\"top\"\u003e\n\n```python\n# settings.py\nINSTALLED_APPS = [\n    # ...\n    'admin_data_views',\n    'abxpkg',\n]\n\nABXPKG_GET_ALL_BINARIES = 'project.views.get_all_binaries'\nABXPKG_GET_BINARY = 'project.views.get_binary'\n\nADMIN_DATA_VIEWS = {\n    \"NAME\": \"Environment\",\n    \"URLS\": [\n        {\n            \"route\": \"binaries/\",\n            \"view\": \"abxpkg.views.binaries_list_view\",\n            \"name\": \"binaries\",\n            \"items\": {\n                \"route\": \"\u003cstr:key\u003e/\",\n                \"view\": \"abxpkg.views.binary_detail_view\",\n                \"name\": \"binary\",\n            },\n        },\n    ],\n}\n```\n\nIf you override the default site admin, register the views manually:\n\n```python\nfrom abxpkg.admin import register_admin_views\n\ncustom_admin = YourSiteAdmin()\nregister_admin_views(custom_admin)\n```\n\n\u003c/details\u003e\n\n---\n\n### Configuration\n\nAll abxpkg env vars are read once at import time and only apply when set. Explicit constructor kwargs always override these defaults.\n\n**Behavioral controls** (apply across all providers):\n\n| Variable | Default | Effect |\n| --- | --- | --- |\n| `ABXPKG_DRY_RUN` / `DRY_RUN` | `0` | Flips the shared `dry_run` default. `ABXPKG_DRY_RUN` wins if both are set. Provider subprocesses are logged and skipped, `install()` / `update()` return a placeholder, `uninstall()` returns `True`. |\n| `ABXPKG_NO_CACHE` | `0` | Flips the shared `no_cache` default. When enabled, `install()` skips the initial `load()` check and forces a fresh install path, while `load()` / `update()` / `uninstall()` bypass cached probe results. |\n| `ABXPKG_DEBUG` | `0` | Enables DEBUG-level CLI logging on `stderr` for `abxpkg` / `abx`. The matching CLI flag is `--debug`. Default CLI logging level is `INFO`. |\n| `ABXPKG_INSTALL_TIMEOUT` | `120` | Seconds to wait for `install()` / `update()` / `uninstall()` handler subprocesses. |\n| `ABXPKG_VERSION_TIMEOUT` | `10` | Seconds to wait for version / metadata probes (`--version`, `npm show`, `pip show`, etc.). |\n| `ABXPKG_POSTINSTALL_SCRIPTS` | unset | Hydrates the provider-level default for the `postinstall_scripts` kwarg on every provider that supports it (`pip`, `uv`, `npm`, `pnpm`, `yarn`, `bun`, `deno`, `brew`, `chromewebstore`, `puppeteer`). When left unset, action execution resolves to the provider/action default (`False` on supporting providers, `True` otherwise). |\n| `ABXPKG_MIN_RELEASE_AGE` | `7` | Hydrates the provider-level default (in days) for the `min_release_age` kwarg on every provider that supports it (`pip`, `uv`, `npm`, `pnpm`, `yarn`, `bun`, `deno`). When left unset, action execution resolves to the provider/action default (`7` on supporting providers, `0` otherwise). |\n| `ABXPKG_BINPROVIDERS` | shared default order | Comma-separated list of provider names to enable (and their order) for the `abxpkg` CLI. By default this uses `DEFAULT_PROVIDER_NAMES` from `abxpkg.__init__` (which excludes `ansible` / `pyinfra`, and also excludes `apt` on macOS). |\n\n**Install-root controls** (one global default + one per-provider override):\n\n| Variable | Applies to | Effect |\n| --- | --- | --- |\n| `ABXPKG_LIB_DIR` | providers whose default `install_root` is abxpkg-managed | Centralized library root. When set, each matching provider points its default `install_root` at `$ABXPKG_LIB_DIR/\u003cprovider name\u003e` (e.g. `\u003clib\u003e/env`, `\u003clib\u003e/npm`, `\u003clib\u003e/pip`, `\u003clib\u003e/gem`, `\u003clib\u003e/playwright`). Accepts relative (`./lib`), tilde (`~/.config/abx/lib`), and absolute (`/tmp/abxlib`) paths. `--global` is a thin alias for `--lib=None`, which clears this root for the current CLI invocation. |\n| `ABXPKG_\u003cBINPROVIDER\u003e_ROOT` | the matching provider's `install_root` | Generic per-provider override; beats `ABXPKG_LIB_DIR/\u003cprovider name\u003e`. Examples: `ABXPKG_PIP_ROOT`, `ABXPKG_UV_ROOT`, `ABXPKG_NPM_ROOT`, `ABXPKG_GOGET_ROOT`, `ABXPKG_CHROMEWEBSTORE_ROOT`. The `\u003cBINPROVIDER\u003e` token is the provider name uppercased. |\n\nInstall-root precedence (most specific wins): explicit `install_root=` / provider alias kwarg \u003e `ABXPKG_\u003cNAME\u003e_ROOT` \u003e `ABXPKG_LIB_DIR/\u003cname\u003e` \u003e provider-specific built-in default / native global mode.\n\n**Provider-specific binary overrides:**\n\nEach provider also honors a `\u003cNAME\u003e_BINARY=/abs/path/to/\u003cname\u003e` env var to pin the exact executable it shells out to — `PIP_BINARY`, `UV_BINARY`, `NPM_BINARY`, `PNPM_BINARY`, `YARN_BINARY`, `BUN_BINARY`, `DENO_BINARY`, etc.\n\n**Per-`Binary` / per-`BinProvider` fields** (constructor kwargs, most-specific wins):\n\n- `min_version` can be set on any individual `Binary`.\n- `min_release_age` can be set on `Binary` or `BinProvider`, or via `ABXPKG_MIN_RELEASE_AGE` (days).\n- `postinstall_scripts` can be set on `Binary` or `BinProvider`, or via `ABXPKG_POSTINSTALL_SCRIPTS`.\n- `no_cache` can be passed per-call to `load()` / `install()` / `update()` / `uninstall()`, or enabled globally for the CLI via `ABXPKG_NO_CACHE`.\n- `install_root` / `bin_dir` can be set on any `BinProvider` with an isolated install location, or default to `ABXPKG_\u003cNAME\u003e_ROOT` / `ABXPKG_LIB_DIR/\u003cprovider name\u003e` / the provider's own built-in default.\n- `dry_run` can be set on `BinProvider` or passed per-call to `install()` / `update()` / `uninstall()`, or via `ABXPKG_DRY_RUN` / `DRY_RUN`.\n- `install_timeout` can be set on `BinProvider` or via `ABXPKG_INSTALL_TIMEOUT` (seconds).\n- `version_timeout` can be set on `BinProvider` or via `ABXPKG_VERSION_TIMEOUT` (seconds).\n- `euid` can be set on `BinProvider` to pin the UID used to `sudo`/drop into when running provider subprocesses; otherwise it's auto-detected from `install_root` ownership.\n- `overrides` is a `dict[BinProviderName, HandlerDict]` (on `Binary`) or `dict[BinName, HandlerDict]` (on `BinProvider`) mapping to per-provider field patches and per-binary handler replacements. Supported keys are `PATH`, `INSTALLER_BIN`, `euid`, `install_root`, `bin_dir`, `dry_run`, `postinstall_scripts`, `min_release_age`, `install_timeout`, `version_timeout`, `install_args` / `packages`, `abspath`, `version`, `install`, `update`, and `uninstall`. See [Advanced Usage](#define-a-reusable-binary-subclass-with-per-provider-overrides) for examples.\n\nPrecedence is always: explicit action kwarg \u003e `Binary(...)` field \u003e `BinProvider(...)` field \u003e env var \u003e built-in default.\n\n\u003cbr/\u003e\n\n---\n---\n\n\u003cbr/\u003e\n\n## API Reference\n\n### [`BinProvider`](https://github.com/ArchiveBox/abxpkg/blob/main/abxpkg/binprovider.py#:~:text=class%20BinProvider)\n\n**Built-in implementations:** `EnvProvider`, `AptProvider`, `BrewProvider`, `PipProvider`, `UvProvider`, `NpmProvider`, `PnpmProvider`, `YarnProvider`, `BunProvider`, `DenoProvider`, `CargoProvider`, `GemProvider`, `GoGetProvider`, `NixProvider`, `DockerProvider`, `PyinfraProvider`, `AnsibleProvider`, `BashProvider`, `ChromeWebstoreProvider`, `PuppeteerProvider`, `PlaywrightProvider`\n\nThis type represents a provider of binaries, e.g. a package manager like `apt` / `pip` / `npm`, or `env` (which only resolves binaries already present in `$PATH`).\n\n#### 🧩 Shared API\n\nEvery provider exposes the same lifecycle surface:\n\n- `load()` / `install()` / `update()` / `uninstall()`\n- `get_install_args()` to resolve package names / formulae / image refs / module specs\n- `get_abspath()` / `get_abspaths()` / `get_version()` / `get_sha256()`\n\nShared base defaults come from [`abxpkg/binprovider.py`](./abxpkg/binprovider.py) and apply unless a concrete provider overrides them:\n\n```python\nINSTALLER_BIN = \"env\"              # base-class placeholder; real providers override this\nPATH = str(Path(sys.executable).parent)\npostinstall_scripts = None           # some providers override this with ABXPKG_POSTINSTALL_SCRIPTS\nmin_release_age = None               # some providers override this with ABXPKG_MIN_RELEASE_AGE\ninstall_timeout = 120                # or ABXPKG_INSTALL_TIMEOUT=120\nversion_timeout = 10                 # or ABXPKG_VERSION_TIMEOUT=10\ndry_run = False                      # or ABXPKG_DRY_RUN=1 / DRY_RUN=1\n```\n\n- `dry_run`: use `provider.get_provider_with_overrides(dry_run=True)`, pass `dry_run=True` directly to `install()` / `update()` / `uninstall()`, or set `ABXPKG_DRY_RUN=1` / `DRY_RUN=1`. If both env vars are set, `ABXPKG_DRY_RUN` wins. Provider subprocesses are logged and skipped, `install()` / `update()` return a placeholder loaded binary, and `uninstall()` returns `True` without mutating the host.\n- `no_cache`: use `--no-cache` / `ABXPKG_NO_CACHE=1` on the CLI, or pass `no_cache=True` directly to `load()` / `install()` / `update()` / `uninstall()`. For `install()`, this skips the initial `load()` check and forces a fresh install path.\n- `install_timeout`: shared provider-level timeout used by `install()`, `update()`, and `uninstall()` handler execution paths. Can also be set with `ABXPKG_INSTALL_TIMEOUT`.\n- `version_timeout`: shared provider-level timeout used by version / metadata probes such as `--version`, `npm show`, `npm list`, `pip show`, `go version -m`, and brew lookups. Can also be set with `ABXPKG_VERSION_TIMEOUT`.\n- `postinstall_scripts` and `min_release_age` are standard provider/binary/action kwargs. Supporting providers hydrate defaults from `ABXPKG_POSTINSTALL_SCRIPTS` and `ABXPKG_MIN_RELEASE_AGE`; when those remain unset/`None`, install/update/uninstall resolve them to effective action defaults (`False` / `7` on supporting providers, `True` / `0` otherwise).\n- Providers that do not support one of those controls leave the provider default as `None`. If you pass an explicit unsupported value during `install()` / `update()`, it is logged as a warning and ignored.\n- Precedence is: explicit action args \u003e `Binary(...)` defaults \u003e provider defaults.\n\nFor the full list of env vars that hydrate these defaults, see [Configuration](#configuration) above.\n\nSupported override keys are the same everywhere:\n\n```python\nfrom pathlib import Path\nfrom abxpkg import PipProvider\n\nprovider = PipProvider(install_root=Path(\"/tmp/venv\")).get_provider_with_overrides(\n    overrides={\n        \"black\": {\n            \"install_args\": [\"black==24.4.2\"],\n            \"version\": \"self.default_version_handler\",\n            \"abspath\": \"self.default_abspath_handler\",\n        },\n    },\n    dry_run=True,\n    version_timeout=30,\n)\n```\n\n- `install_args` / `packages`: package-manager arguments for that provider. `packages` is the legacy alias.\n- `abspath`, `version`, `install`, `update`, `uninstall`: literal values, callables, or `\"self.method_name\"` references that replace the provider handler for a specific binary.\n- `PATH`, `INSTALLER_BIN`, `euid`, `install_root`, `bin_dir`, `dry_run`, `postinstall_scripts`, `min_release_age`, `install_timeout`, `version_timeout`: shared provider field patches applied to the copied provider instance before handler resolution.\n\nProviders with isolated install locations also expose a shared constructor surface:\n\n- `install_root`: shared provider root for package state, metadata, caches, venvs, project dirs, profiles, or downloaded assets, depending on the provider.\n- `bin_dir`: shared executable output dir when a provider separates package state from runnable binaries.\n- `provider.install_root` / `provider.bin_dir`: normalized computed properties you can inspect after construction, regardless of which provider-specific args were used.\n- Legacy provider-specific args still work. The shared aliases are additive, not replacements.\n- Providers that do not have an isolated install location reject `install_root` / `bin_dir` at construction time instead of silently ignoring them.\n- When an explicit install root or bin dir is configured, that provider-specific bin location wins during binary discovery and subprocess execution instead of being left behind ambient host `PATH` entries.\n\n\u003cbr/\u003e\n\n### Supported `BinProvider`s\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch4\u003e🌍 \u003ccode\u003eEnvProvider\u003c/code\u003e (\u003ccode\u003eenv\u003c/code\u003e)\u003c/h4\u003e\u003c/summary\u003e\n\nSource: [`abxpkg/binprovider.py`](./abxpkg/binprovider.py) • Tests: [`tests/test_envprovider.py`](./tests/test_envprovider.py)\n\n```python\nINSTALLER_BIN = \"which\"\nPATH = DEFAULT_ENV_PATH              # current PATH + current Python bin dir\n```\n\n- Install root: defaults to `ABXPKG_ENV_ROOT`, or `ABXPKG_LIB_DIR/env`, or the platform default abx lib dir under `env/`. `env` is still read-only: it only resolves binaries that already exist on the host PATH, but when an install root is configured it also keeps a managed `bin/` symlink dir and `derived.env` cache there.\n- Auto-switching: none.\n- Security: `min_release_age` and `postinstall_scripts` are unsupported here and are ignored with a warning if explicitly passed to `install()` / `update()`.\n- Overrides: `abspath` / `version` are the useful ones here. `python` has a built-in override to the current `sys.executable` and interpreter version.\n- Notes: resolved `abspath`s always point at the real underlying host binary, not the managed `env/bin/\u003cname\u003e` symlink. `install()` / `update()` return explanatory no-op messages, and `uninstall()` is a no-op.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch4\u003e🐧 \u003ccode\u003eAptProvider\u003c/code\u003e (\u003ccode\u003eapt\u003c/code\u003e)\u003c/h4\u003e\u003c/summary\u003e\n\nSource: [`abxpkg/binprovider_apt.py`](./abxpkg/binprovider_apt.py) • Tests: [`tests/test_aptprovider.py`](./tests/test_aptprovider.py)\n\n```python\nINSTALLER_BIN = \"apt-get\"\nPATH = \"\"                            # populated from `dpkg -L bash` bin dirs\neuid = 0                             # always runs as root\n```\n\n- Install root: **no hermetic prefix support**. Installs into the host package database.\n- Auto-switching: none. Shells out to `apt-get` directly.\n- `dry_run`: shared behavior.\n- Security: `min_release_age` and `postinstall_scripts=False` are unsupported and are ignored with a warning if explicitly requested.\n- Overrides: `install_args` becomes `apt-get install -y -qq --no-install-recommends ...`; `update()` uses `apt-get install --only-upgrade ...`; `uninstall()` uses `apt-get remove -y -qq ...`.\n- Notes: direct mode runs `apt-get update -qq` at most once per day and requests privilege escalation when needed.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch4\u003e🍺 \u003ccode\u003eBrewProvider\u003c/code\u003e (\u003ccode\u003ebrew\u003c/code\u003e)\u003c/h4\u003e\u003c/summary\u003e\n\nSource: [`abxpkg/binprovider_brew.py`](./abxpkg/binprovider_brew.py) • Tests: [`tests/test_brewprovider.py`](./tests/test_brewprovider.py)\n\n```python\nINSTALLER_BIN = \"brew\"\nPATH = \"/home/linuxbrew/.linuxbrew/bin:/opt/homebrew/bin:/usr/local/bin\"\nbrew_prefix = guessed host prefix    # /opt/homebrew, /usr/local, or linuxbrew\n```\n\n- Install root: `brew_prefix` is the Homebrew prefix used for discovery and shelling out to `brew`. By default it resolves from `ABXPKG_BREW_ROOT`, or `ABXPKG_LIB_DIR/brew`, or a guessed host prefix (`/opt/homebrew`, `/usr/local`, or linuxbrew). `bin_dir` is used for linked formula binaries when abxpkg manages them separately.\n- Auto-switching: none. Shells out to `brew` directly.\n- `dry_run`: shared behavior.\n- Security: `min_release_age` is unsupported and is ignored with a warning if explicitly requested. `postinstall_scripts=False` is supported on `brew install` via `--skip-post-install`, and `ABXPKG_POSTINSTALL_SCRIPTS` hydrates the provider default here. Homebrew has no equivalent flag for `brew upgrade`, so updates run without it.\n- Overrides: `install_args` maps to formula / cask args passed to `brew install`, `brew upgrade`, and `brew uninstall`.\n- Notes: direct mode runs `brew update` at most once per day. Explicit `--skip-post-install` args in `install_args` win over derived defaults for installs.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch4\u003e🐍 \u003ccode\u003ePipProvider\u003c/code\u003e (\u003ccode\u003epip\u003c/code\u003e)\u003c/h4\u003e\u003c/summary\u003e\n\nSource: [`abxpkg/binprovider_pip.py`](./abxpkg/binprovider_pip.py) • Tests: [`tests/test_pipprovider.py`](./tests/test_pipprovider.py), [`tests/test_security_controls.py`](./tests/test_security_controls.py)\n\n```python\nINSTALLER_BIN = \"pip\"\nPATH = \"\"                            # auto-built from global/user Python bin dirs\ninstall_root = None                  # None = ambient/global mode, Path(...) = provider root\n```\n\n- Install root: `install_root=None` uses the system/user Python environment. Set `install_root=Path(...)` for a hermetic provider root whose actual virtualenv lives at `\u003cinstall_root\u003e/venv`, with executables under `\u003cinstall_root\u003e/venv/bin` and provider metadata like `derived.env` kept at `\u003cinstall_root\u003e`.\n- Auto-switching: none. Shells out to `pip` directly. Honors `PIP_BINARY=/abs/path/to/pip`. Use `UvProvider` for uv-backed installs.\n- `dry_run`: shared behavior.\n- Security: supports `postinstall_scripts=False` (always) and `min_release_age` (on pip \u003e= 26.0 or in a freshly bootstrapped pip venv). Hydrated from `ABXPKG_POSTINSTALL_SCRIPTS` and `ABXPKG_MIN_RELEASE_AGE`. For stricter enforcement on hosts with older system pip, use `UvProvider` instead.\n- Overrides: `install_args` is passed as pip requirement specs; unpinned specs get a `\u003e=min_version` floor when `min_version` is supplied.\n- Notes: `postinstall_scripts=False` adds `pip --only-binary :all:` (wheels only, no arbitrary sdist build scripts). `min_release_age` is enforced with `pip --uploaded-prior-to=\u003cISO8601\u003e` on pip \u003e= 26.0 (see pypa/pip#13625); older pip silently skips the flag. Explicit conflicting flags already present in `install_args` win over the derived defaults. `get_version` / `get_abspath` fall back to parsing `pip show \u003cpackage\u003e` output when the console script can't report its own version.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch4\u003e🚀 \u003ccode\u003eUvProvider\u003c/code\u003e (\u003ccode\u003euv\u003c/code\u003e)\u003c/h4\u003e\u003c/summary\u003e\n\nSource: [`abxpkg/binprovider_uv.py`](./abxpkg/binprovider_uv.py) • Tests: [`tests/test_uvprovider.py`](./tests/test_uvprovider.py)\n\n```python\nINSTALLER_BIN = \"uv\"\nPATH = \"\"                            # prepends \u003cinstall_root\u003e/venv/bin or the uv tool bin dir\ninstall_root = None                  # None = global uv tool mode, Path(...) = provider root\n```\n\n- Install root: **two modes, picked by whether `install_root` is set.**\n  - *Hermetic venv mode (`install_root=Path(...)`)*: treats `install_root` as a provider root, creates the real venv at `\u003cinstall_root\u003e/venv` via `uv venv`, and installs packages into it with `uv pip install --python \u003cinstall_root\u003e/venv/bin/python ...`. Binaries land in `\u003cinstall_root\u003e/venv/bin/\u003cname\u003e`, while provider metadata like `derived.env` stays at `\u003cinstall_root\u003e`. This matches `PipProvider`'s layout.\n  - *Global tool mode (`install_root=None`)*: delegates to `uv tool install` which creates a fresh venv per tool under `UV_TOOL_DIR` (default `~/.local/share/uv/tools`) and writes shims into `UV_TOOL_BIN_DIR` (default `~/.local/bin`). Pass `bin_dir=Path(...)` to override the shim dir. This is the idiomatic \"install a CLI tool globally\" path.\n- Auto-switching: none. Honors `UV_BINARY=/abs/path/to/uv`. If `uv` isn't on the host, the provider is unavailable.\n- `dry_run`: shared behavior.\n- Security: supports both `min_release_age` and `postinstall_scripts=False`, and hydrates their provider defaults from `ABXPKG_MIN_RELEASE_AGE` and `ABXPKG_POSTINSTALL_SCRIPTS`. In both modes, `postinstall_scripts=False` becomes `--no-build` (wheels-only, no arbitrary sdist build scripts) and `min_release_age` becomes `--exclude-newer=\u003cISO8601\u003e` (uv 0.4+). Explicit conflicting flags already present in `install_args` win over the derived defaults.\n- Overrides: `install_args` is passed as requirement specs; unpinned specs get a `\u003e=min_version` floor when `min_version` is supplied.\n- Notes: update in venv mode is `uv pip install --upgrade`; update in global mode is `uv tool install --force` (re-installs the tool's venv). Uninstall in venv mode uses `uv pip uninstall --python \u003cvenv\u003e/bin/python`; in global mode it uses `uv tool uninstall \u003cname\u003e`.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch4\u003e📦 \u003ccode\u003eNpmProvider\u003c/code\u003e (\u003ccode\u003enpm\u003c/code\u003e)\u003c/h4\u003e\u003c/summary\u003e\n\nSource: [`abxpkg/binprovider_npm.py`](./abxpkg/binprovider_npm.py) • Tests: [`tests/test_npmprovider.py`](./tests/test_npmprovider.py), [`tests/test_security_controls.py`](./tests/test_security_controls.py)\n\n```python\nINSTALLER_BIN = \"npm\"\nPATH = \"\"                            # auto-built from npm local + global bin dirs\ninstall_root = None                  # None = global install, Path(...) = prefix/project root\n```\n\n- Install root: `install_root=None` installs globally (walks up from the host's `npm prefix` / `npm prefix -g` to seed `PATH`). Set `install_root=Path(...)` to install under `\u003cprefix\u003e/node_modules/.bin`; that prefix bin dir becomes the provider's active executable search path.\n- Auto-switching: none. Shells out to `npm` directly and expects `npm` to be installed on the host. Honors `NPM_BINARY=/abs/path/to/npm`. Use `PnpmProvider` for pnpm.\n- `dry_run`: shared behavior.\n- Security: supports both `postinstall_scripts=False` and `min_release_age`, hydrated from `ABXPKG_POSTINSTALL_SCRIPTS` and `ABXPKG_MIN_RELEASE_AGE`. `min_release_age` requires an npm build that ships `--min-release-age` (detected once by probing `npm install --help`).\n- Overrides: `install_args` is passed as npm package specs; unpinned specs get rewritten to `pkg@\u003e=\u003cmin_version\u003e` when `min_version` is supplied.\n- Notes: `postinstall_scripts=False` adds `--ignore-scripts`; `min_release_age` adds `--min-release-age=\u003cdays\u003e`; and installs always include npm's standard non-interactive flags (`--force --no-audit --no-fund --loglevel=error`). `puppeteer` is special-cased to install both `puppeteer` and `@puppeteer/browsers`, and `puppeteer-browsers` resolves to `@puppeteer/browsers`. Explicit conflicting flags already present in `install_args` win over the derived defaults. `get_version` / `get_abspath` fall back to parsing `npm show --json \u003cpackage\u003e` and `npm list --json --depth=0` output when the console script can't report its own version.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch4\u003e📦 \u003ccode\u003ePnpmProvider\u003c/code\u003e (\u003ccode\u003epnpm\u003c/code\u003e)\u003c/h4\u003e\u003c/summary\u003e\n\nSource: [`abxpkg/binprovider_pnpm.py`](./abxpkg/binprovider_pnpm.py) • Tests: [`tests/test_pnpmprovider.py`](./tests/test_pnpmprovider.py)\n\n```python\nINSTALLER_BIN = \"pnpm\"\nPATH = \"\"                            # auto-built from pnpm local + global bin dirs\ninstall_root = None                  # None = global install, Path(...) = prefix/project root\n```\n\n- Install root: `install_root=None` installs globally. Set `install_root=Path(...)` to install under `\u003cprefix\u003e/node_modules/.bin`; that prefix bin dir becomes the provider's active executable search path.\n- Shells out to `pnpm` directly. Honors `PNPM_BINARY=/abs/path/to/pnpm`. Use `NpmProvider` for `npm`.\n- `dry_run`: shared behavior.\n- Security: supports both `min_release_age` and `postinstall_scripts=False`, and hydrates their provider defaults from `ABXPKG_MIN_RELEASE_AGE` and `ABXPKG_POSTINSTALL_SCRIPTS`. `min_release_age` requires pnpm 10.16+, and `supports_min_release_age()` returns `False` on older hosts (then it logs a warning and continues).\n- Overrides: `install_args` is passed as pnpm package specs; unpinned specs get rewritten to `pkg@\u003e=\u003cmin_version\u003e` when `min_version` is supplied.\n- Notes: pnpm has no `--min-release-age` CLI flag; this provider passes `--config.minimumReleaseAge=\u003cminutes\u003e` (the camelCase / kebab-case form pnpm exposes via its `--config.\u003ckey\u003e=\u003cvalue\u003e` override). Installs always include `--loglevel=error`, and `PNPM_HOME` is auto-populated so `pnpm add -g` works without polluting the user's shell config. `puppeteer` is special-cased to install both `puppeteer` and `@puppeteer/browsers`, and `puppeteer-browsers` resolves to `@puppeteer/browsers`.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch4\u003e🧶 \u003ccode\u003eYarnProvider\u003c/code\u003e (\u003ccode\u003eyarn\u003c/code\u003e)\u003c/h4\u003e\u003c/summary\u003e\n\nSource: [`abxpkg/binprovider_yarn.py`](./abxpkg/binprovider_yarn.py) • Tests: [`tests/test_yarnprovider.py`](./tests/test_yarnprovider.py)\n\n```python\nINSTALLER_BIN = \"yarn\"\nPATH = \"\"                            # prepends \u003cinstall_root\u003e/node_modules/.bin\ninstall_root = None                  # project dir, defaults to ABXPKG_YARN_ROOT or ABXPKG_LIB_DIR/yarn\n```\n\n- Install root: Yarn operates inside a project directory. Set `install_root=Path(...)` for an isolated project dir; that directory is auto-initialized with a stub `package.json` and `.yarnrc.yml` (`nodeLinker: node-modules` so binaries land in `\u003cinstall_root\u003e/node_modules/.bin`). When unset, the provider relies on `$ABXPKG_YARN_ROOT` or `$ABXPKG_LIB_DIR/yarn`; if neither is configured, the provider is unavailable.\n- Auto-switching: none. Honors `YARN_BINARY=/abs/path/to/yarn`. Both Yarn classic (1.x) and Yarn Berry (2+) work for basic install/update/uninstall, but only Yarn 4.10+ supports the security flags.\n- `dry_run`: shared behavior.\n- Security: supports both `min_release_age` and `postinstall_scripts=False`, and hydrates their provider defaults from `ABXPKG_MIN_RELEASE_AGE` and `ABXPKG_POSTINSTALL_SCRIPTS`. Both controls require Yarn 4.10+; on older hosts `supports_min_release_age()` / `supports_postinstall_disable()` return `False` and explicit values are logged-and-ignored.\n- Overrides: `install_args` is passed as Yarn package specs; unpinned specs get rewritten to `pkg@\u003e=\u003cmin_version\u003e` when `min_version` is supplied.\n- Notes: Yarn has no `--ignore-scripts` / `--minimum-release-age` CLI flags; the provider writes `npmMinimalAgeGate: 7d` (or whatever days value is configured) and `enableScripts: false` into `\u003cinstall_root\u003e/.yarnrc.yml` and additionally passes `--mode skip-build` to `yarn add` / `yarn up` when `postinstall_scripts=False`. Updates use `yarn up \u003cpkg\u003e` (Berry) or `yarn upgrade \u003cpkg\u003e` (classic). `YARN_GLOBAL_FOLDER` and `YARN_CACHE_FOLDER` are pointed at the provider cache dir so installs share a single cache across workspaces. `puppeteer` is special-cased to install both `puppeteer` and `@puppeteer/browsers`, and `puppeteer-browsers` resolves to `@puppeteer/browsers`.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch4\u003e🥖 \u003ccode\u003eBunProvider\u003c/code\u003e (\u003ccode\u003ebun\u003c/code\u003e)\u003c/h4\u003e\u003c/summary\u003e\n\nSource: [`abxpkg/binprovider_bun.py`](./abxpkg/binprovider_bun.py) • Tests: [`tests/test_bunprovider.py`](./tests/test_bunprovider.py)\n\n```python\nINSTALLER_BIN = \"bun\"\nPATH = \"\"                            # prepends \u003cinstall_root\u003e/bin\ninstall_root = None                  # mirrors $BUN_INSTALL, None = ~/.bun (host-default)\n```\n\n- Install root: `install_root=None` writes into the host `$BUN_INSTALL` (default `~/.bun`). Set `install_root=Path(...)` to install under `\u003cinstall_root\u003e/bin`; the provider also creates `\u003cinstall_root\u003e/install/global` for the global `node_modules` dir, which is where bun puts the actual package state. The bin dir becomes the provider's active executable search path.\n- Auto-switching: none. Honors `BUN_BINARY=/abs/path/to/bun`.\n- `dry_run`: shared behavior.\n- Security: supports both `min_release_age` and `postinstall_scripts=False`, and hydrates their provider defaults from `ABXPKG_MIN_RELEASE_AGE` and `ABXPKG_POSTINSTALL_SCRIPTS`. `min_release_age` requires Bun 1.3+, and `supports_min_release_age()` returns `False` on older hosts.\n- Overrides: `install_args` is passed as Bun package specs; unpinned specs get rewritten to `pkg@\u003e=\u003cmin_version\u003e` when `min_version` is supplied.\n- Notes: install/update use `bun add -g` (with `--force` as the update fallback). The provider passes `--ignore-scripts` for `postinstall_scripts=False` and `--minimum-release-age=\u003cseconds\u003e` (Bun's unit is seconds; this provider converts from days). `puppeteer` is special-cased to install both `puppeteer` and `@puppeteer/browsers`, and `puppeteer-browsers` resolves to `@puppeteer/browsers`. Explicit conflicting flags already present in `install_args` win over the derived defaults.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch4\u003e🦕 \u003ccode\u003eDenoProvider\u003c/code\u003e (\u003ccode\u003edeno\u003c/code\u003e)\u003c/h4\u003e\u003c/summary\u003e\n\nSource: [`abxpkg/binprovider_deno.py`](./abxpkg/binprovider_deno.py) • Tests: [`tests/test_denoprovider.py`](./tests/test_denoprovider.py)\n\n```python\nINSTALLER_BIN = \"deno\"\nPATH = \"\"                            # prepends \u003cinstall_root\u003e/bin\ninstall_root = None                  # mirrors $DENO_INSTALL_ROOT, None = ~/.deno\n```\n\n- Install root: `install_root=None` writes into the host `$DENO_INSTALL_ROOT` (default `~/.deno`). Set `install_root=Path(...)` for a hermetic root with executables under `\u003cinstall_root\u003e/bin`; `DENO_DIR` is then derived as `\u003cinstall_root\u003e/.cache`.\n- Auto-switching: none. Honors `DENO_BINARY=/abs/path/to/deno`.\n- `dry_run`: shared behavior.\n- Security: supports both `min_release_age` and `postinstall_scripts=False` / `True`, and hydrates their provider defaults from `ABXPKG_MIN_RELEASE_AGE` and `ABXPKG_POSTINSTALL_SCRIPTS`. `min_release_age` requires Deno 2.5+, and `supports_min_release_age()` returns `False` on older hosts.\n- Overrides: `install_args` is passed as `deno install` package specs and is auto-prefixed with `npm:` when an unqualified bare name is supplied. Already-qualified specs (`npm:`, `jsr:`, `https://...`) are passed through verbatim. Unpinned specs get rewritten to `pkg@\u003e=\u003cmin_version\u003e` when `min_version` is supplied.\n- Notes: install / update both run `deno install -g --force --allow-all -n \u003cbin_name\u003e \u003cpkg\u003e` because Deno's idiomatic update path is just a fresh global install. Deno's npm lifecycle scripts are *opt-in* (the opposite of npm), so the provider only adds `--allow-scripts` when `postinstall_scripts=True`. `min_release_age` is passed as `--minimum-dependency-age=\u003cminutes\u003e` (Deno's preferred unit; this provider converts from days). `puppeteer` is special-cased to install both `puppeteer` and `@puppeteer/browsers`, and `puppeteer-browsers` resolves to `@puppeteer/browsers`. `DENO_TLS_CA_STORE=system` is set so installs work on hosts with corporate / sandboxed CA bundles.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch4\u003e🧪 \u003ccode\u003eBashProvider\u003c/code\u003e (\u003ccode\u003ebash\u003c/code\u003e)\u003c/h4\u003e\u003c/summary\u003e\n\nSource: [`abxpkg/binprovider_bash.py`](./abxpkg/binprovider_bash.py) • Tests: [`tests/test_bashprovider.py`](./tests/test_bashprovider.py)\n\n```python\nINSTALLER_BIN = \"bash\"\nPATH = \"\"\ninstall_root = $ABXPKG_BASH_ROOT or $ABXPKG_LIB_DIR/bash\nbin_dir = \u003cinstall_root\u003e/bin\n```\n\n- Install root: set `install_root` for the state dir, and `bin_dir` for the executable output dir.\n- Auto-switching: none.\n- `dry_run`: shared behavior.\n- Security: `min_release_age` and `postinstall_scripts=False` are unsupported and are ignored with a warning if explicitly requested.\n- Overrides: this provider is driven by literal per-binary shell overrides for `install`, `update`, and `uninstall`.\n- Notes: the provider exports `INSTALL_ROOT`, `BIN_DIR`, `BASH_INSTALL_ROOT`, and `BASH_BIN_DIR` into the shell environment for those commands.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch4\u003e🦀 \u003ccode\u003eCargoProvider\u003c/code\u003e (\u003ccode\u003ecargo\u003c/code\u003e)\u003c/h4\u003e\u003c/summary\u003e\n\nSource: [`abxpkg/binprovider_cargo.py`](./abxpkg/binprovider_cargo.py) • Tests: [`tests/test_cargoprovider.py`](./tests/test_cargoprovider.py)\n\n```python\nINSTALLER_BIN = \"cargo\"\nPATH = \"\"                            # prepends cargo_root/bin and cargo_home/bin\ncargo_root = None                    # set this for hermetic installs\n```\n\n- Install root: set `install_root=Path(...)` or `install_root=Path(...)` for isolated installs under `\u003ccargo_root\u003e/bin`; otherwise installs go through `cargo_home`.\n- Auto-switching: none.\n- `dry_run`: shared behavior.\n- Security: `min_release_age` and `postinstall_scripts=False` are unsupported and are ignored with a warning if explicitly requested.\n- Overrides: `install_args` is passed to `cargo install`; `min_version` becomes `cargo install --version \u003e=...`.\n- Notes: the provider also sets `CARGO_HOME`, `CARGO_TARGET_DIR`, and `CARGO_INSTALL_ROOT` when applicable.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch4\u003e💎 \u003ccode\u003eGemProvider\u003c/code\u003e (\u003ccode\u003egem\u003c/code\u003e)\u003c/h4\u003e\u003c/summary\u003e\n\nSource: [`abxpkg/binprovider_gem.py`](./abxpkg/binprovider_gem.py) • Tests: [`tests/test_gemprovider.py`](./tests/test_gemprovider.py)\n\n```python\nINSTALLER_BIN = \"gem\"\nPATH = DEFAULT_ENV_PATH\ninstall_root = None                  # defaults to $GEM_HOME or ~/.local/share/gem\nbin_dir = None                       # defaults to \u003cinstall_root\u003e/bin\n```\n\n- Install root: set `install_root`, and optionally `bin_dir`, for hermetic installs; otherwise it uses `$GEM_HOME` or `~/.local/share/gem`.\n- Auto-switching: none.\n- `dry_run`: shared behavior.\n- Security: `min_release_age` and `postinstall_scripts=False` are unsupported and are ignored with a warning if explicitly requested.\n- Overrides: `install_args` maps to `gem install ...`, `gem update ...`, and `gem uninstall ...`; `min_version` becomes `--version \u003e=...`.\n- Notes: generated wrapper scripts are patched so they activate the configured `GEM_HOME` instead of the host default.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch4\u003e🐹 \u003ccode\u003eGoGetProvider\u003c/code\u003e (\u003ccode\u003egoget\u003c/code\u003e)\u003c/h4\u003e\u003c/summary\u003e\n\nSource: [`abxpkg/binprovider_goget.py`](./abxpkg/binprovider_goget.py) • Tests: [`tests/test_gogetprovider.py`](./tests/test_gogetprovider.py)\n\n```python\nINSTALLER_BIN = \"go\"\nPATH = DEFAULT_ENV_PATH\ninstall_root = None                  # defaults to $GOPATH or ~/go\nbin_dir = None                       # defaults to \u003cinstall_root\u003e/bin\n```\n\n- Install root: set `install_root` for the Go install tree, and optionally `bin_dir` for the executable dir; otherwise installs land in `\u003cinstall_root\u003e/bin`.\n- Auto-switching: none.\n- `dry_run`: shared behavior.\n- Security: `min_release_age` and `postinstall_scripts=False` are unsupported and are ignored with a warning if explicitly requested.\n- Overrides: `install_args` is passed to `go install ...`; the default is `[\"\u003cbin_name\u003e@latest\"]`.\n- Notes: `update()` is just `install()` again. Version detection prefers `go version -m \u003cbinary\u003e` and falls back to the generic version probe. The provider name is `goget`, not `go_get`.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch4\u003e❄️ \u003ccode\u003eNixProvider\u003c/code\u003e (\u003ccode\u003enix\u003c/code\u003e)\u003c/h4\u003e\u003c/summary\u003e\n\nSource: [`abxpkg/binprovider_nix.py`](./abxpkg/binprovider_nix.py) • Tests: [`tests/test_nixprovider.py`](./tests/test_nixprovider.py)\n\n```python\nINSTALLER_BIN = \"nix\"\nPATH = \"\"                            # prepends \u003cinstall_root\u003e/bin\ninstall_root = $ABXPKG_NIX_PROFILE or ~/.nix-profile\n```\n\n- Install root: set `install_root=Path(...)` for a custom profile.\n- Auto-switching: none.\n- `dry_run`: shared behavior.\n- Security: `min_release_age` and `postinstall_scripts=False` are unsupported and are ignored with a warning if explicitly requested.\n- Overrides: `install_args` is passed to `nix profile install ...`; default is `[\"nixpkgs#\u003cbin_name\u003e\"]`.\n- Notes: update/uninstall operate on the resolved profile element name rather than reusing the full flake ref.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch4\u003e🐳 \u003ccode\u003eDockerProvider\u003c/code\u003e (\u003ccode\u003edocker\u003c/code\u003e)\u003c/h4\u003e\u003c/summary\u003e\n\nSource: [`abxpkg/binprovider_docker.py`](./abxpkg/binprovider_docker.py) • Tests: [`tests/test_dockerprovider.py`](./tests/test_dockerprovider.py)\n\n```python\nINSTALLER_BIN = \"docker\"\nPATH = \"\"                            # prepends bin_dir\nbin_dir = ($ABXPKG_DOCKER_ROOT or $ABXPKG_LIB_DIR/docker) / \"bin\"\n```\n\n- Install root: **partial only**. Images are pulled into Docker's host image store; the provider only controls the local shim dir and metadata dir. Use `install_root=Path(...)` for the shim/metadata root or `bin_dir=Path(...)` for the shim dir directly.\n- Auto-switching: none.\n- `dry_run`: shared behavior.\n- Security: `min_release_age` and `postinstall_scripts=False` are unsupported and are ignored with a warning if explicitly requested.\n- Overrides: `install_args` is a list of Docker image refs. The first item is treated as the main image and becomes the generated shim target.\n- Notes: default install args are `[\"\u003cbin_name\u003e:latest\"]`. `install()` / `update()` run `docker pull`, write metadata JSON, and create an executable wrapper that runs `docker run ...`. Expects image refs as install args, typically via overrides on a `Binary`. It writes a local wrapper script for the binary and executes it via `docker run ...`; the binary version is parsed from the image tag, so semver-like tags work best.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch4\u003e🧩 \u003ccode\u003eChromeWebstoreProvider\u003c/code\u003e (\u003ccode\u003echromewebstore\u003c/code\u003e)\u003c/h4\u003e\u003c/summary\u003e\n\nSource: [`abxpkg/binprovider_chromewebstore.py`](./abxpkg/binprovider_chromewebstore.py) • Tests: [`tests/test_chromewebstoreprovider.py`](./tests/test_chromewebstoreprovider.py)\n\n```python\nINSTALLER_BIN = \"node\"\nPATH = \"\"\ninstall_root = $ABXPKG_CHROMEWEBSTORE_ROOT or $ABXPKG_LIB_DIR/chromewebstore\nbin_dir = \u003cinstall_root\u003e/extensions\n```\n\n- Install root: set `install_root` for the extension cache root, and `bin_dir` for the unpacked extension output dir.\n- Auto-switching: none.\n- `dry_run`: shared behavior.\n- Security: `min_release_age` is unsupported and is ignored with a warning if explicitly requested. `postinstall_scripts=False` is supported as a standard kwarg and `ABXPKG_POSTINSTALL_SCRIPTS` hydrates the provider default here, but there is no extra install-time toggle beyond the packaged JS runtime path this provider already uses.\n- Overrides: `install_args` are `[webstore_id, \"--name=\u003cextension_name\u003e\"]`.\n- Notes: the packaged JS runtime under `abxpkg/js/chrome/` is used to download, unpack, and cache the extension, and the resolved binary path is the unpacked `manifest.json`. `no_cache=True` bypasses that metadata cache on the next install/update without deleting the unpacked extension tree.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch4\u003e🎭 \u003ccode\u003ePuppeteerProvider\u003c/code\u003e (\u003ccode\u003epuppeteer\u003c/code\u003e)\u003c/h4\u003e\u003c/summary\u003e\n\nSource: [`abxpkg/binprovider_puppeteer.py`](./abxpkg/binprovider_puppeteer.py) • Tests: [`tests/test_puppeteerprovider.py`](./tests/test_puppeteerprovider.py)\n\n```python\nINSTALLER_BIN = \"puppeteer-browsers\"\nPATH = \"\"\ninstall_root = $ABXPKG_PUPPETEER_ROOT or $ABXPKG_LIB_DIR/puppeteer\nbin_dir = \u003cinstall_root\u003e/bin\n```\n\n- Install root: set `install_root` for the root dir and `bin_dir` for symlinked executables. Downloaded browser artifacts live under `\u003cinstall_root\u003e/cache` when an install root is pinned. Leave it unset for ambient/global mode, where cache ownership stays with the host and `INSTALLER_BINARY` must already be resolvable from the ambient provider set.\n- Auto-switching: bootstraps `@puppeteer/browsers` through `NpmProvider` and then uses that CLI for browser installs.\n- `dry_run`: shared behavior.\n- Security: `min_release_age` is unsupported for browser installs and is ignored with a warning if explicitly requested. `postinstall_scripts=False` is supported for the underlying npm bootstrap path, and `ABXPKG_POSTINSTALL_SCRIPTS` hydrates the provider default here.\n- Overrides: `install_args` are passed through to `@puppeteer/browsers install ...`, with the provider appending `--path=\u003ccache_dir\u003e`. Installing `puppeteer-browsers` itself is treated as the CLI bootstrap case, not as a browser target.\n- Notes: installed-browser resolution uses semantic version ordering, not lexicographic string sorting. The provider records `node` and `puppeteer-browsers` as dependency cache entries when they are resolved through upstream providers.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch4\u003e🎬 \u003ccode\u003ePlaywrightProvider\u003c/code\u003e (\u003ccode\u003eplaywright\u003c/code\u003e)\u003c/h4\u003e\u003c/summary\u003e\n\nSource: [`abxpkg/binprovider_playwright.py`](./abxpkg/binprovider_playwright.py) • Tests: [`tests/test_playwrightprovider.py`](./tests/test_playwrightprovider.py)\n\n```python\nINSTALLER_BIN = \"playwright\"\nPATH = \"\"\ninstall_root = None              # when set, doubles as PLAYWRIGHT_BROWSERS_PATH\nbin_dir = \u003cinstall_root\u003e/bin     # symlink dir for resolved browsers\neuid = 0                         # routes exec() through sudo-first-then-fallback\n```\n\n- Install root: set `install_root` to pin both the abxpkg root dir AND `PLAYWRIGHT_BROWSERS_PATH` to the same directory. Leave it unset to let playwright use its own OS-default browsers path (`~/.cache/ms-playwright` on Linux etc.) — in that case abxpkg maintains no symlink dir or npm prefix at all, the `playwright` npm CLI bootstraps against the host's npm default, and `load()` returns the resolved `executablePath()` directly. `bin_dir` overrides the symlink directory when `install_root` is pinned.\n- Auto-switching: bootstraps the `playwright` npm package through `NpmProvider`, then runs `playwright install --with-deps \u003cinstall_args\u003e` against it. Resolves each installed browser's real executable via the `playwright-core` Node.js API (`chromium.executablePath()` etc.) and writes a symlink into `bin_dir` when one is configured.\n- `dry_run`: shared behavior — the install handler short-circuits to a placeholder without touching the host.\n- Privilege handling: `--with-deps` installs system packages and requires root on Linux. ``euid`` defaults to ``0``, which routes every ``exec()`` call through the base ``BinProvider.exec`` sudo-first-then-fallback path — it tries ``sudo -n -- playwright install --with-deps ...`` first on non-root hosts, falls back to running the command directly if sudo fails or isn't available, and merges both stderr outputs into the final error if both attempts fail.\n- Security: `min_release_age` and `postinstall_scripts=False` are unsupported for browser installs and are ignored with a warning if explicitly requested.\n- Overrides: `install_args` are appended onto `playwright install` after `playwright_install_args` (defaults to `[\"--with-deps\"]`) and passed through verbatim — use whatever browser names / flags the `playwright install` CLI accepts (`chromium`, `firefox`, `webkit`, `--no-shell`, `--only-shell`, `--force`, etc.).\n- Notes: `update()` bumps the `playwright` npm package in `install_root` first (via `NpmProvider.update`) so its pinned browser versions refresh, then re-runs `playwright install --force \u003cinstall_args\u003e` to pull any new browser builds. `uninstall()` removes the relevant `\u003cbin_name\u003e-*/` directories from `install_root` alongside the bin-dir symlink, since `playwright uninstall` only drops *unused* browsers on its own. Both `update()` and `uninstall()` leave playwright's OS-default cache untouched when `install_root` is unset.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch4\u003e🛠️ \u003ccode\u003ePyinfraProvider\u003c/code\u003e (\u003ccode\u003epyinfra\u003c/code\u003e)\u003c/h4\u003e\u003c/summary\u003e\n\nSource: [`abxpkg/binprovider_pyinfra.py`](./abxpkg/binprovider_pyinfra.py) • Tests: [`tests/test_pyinfraprovider.py`](./tests/test_pyinfraprovider.py)\n\n```python\nINSTALLER_BIN = \"pyinfra\"\nPATH = os.environ.get(\"PATH\", DEFAULT_PATH)\npyinfra_installer_module = \"auto\"\npyinfra_installer_kwargs = {}\n```\n\n- Install root: **no hermetic prefix support**. It delegates to host package managers through pyinfra operations.\n- Auto-switching: `installer_module=\"auto\"` resolves to `operations.brew.packages` on macOS and `operations.server.packages` on Linux.\n- `dry_run`: shared behavior.\n- Security: `min_release_age` and `postinstall_scripts=False` are unsupported and are ignored with a warning if explicitly requested.\n- Overrides: `install_args` is the package list passed to the selected pyinfra operation.\n- Notes: privilege requirements depend on the underlying package manager and selected module. When pyinfra tries a privileged sudo path and then falls back, both error outputs are preserved if the final attempt also fails.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch4\u003e📘 \u003ccode\u003eAnsibleProvider\u003c/code\u003e (\u003ccode\u003eansible\u003c/code\u003e)\u003c/h4\u003e\u003c/summary\u003e\n\nSource: [`abxpkg/binprovider_ansible.py`](./abxpkg/binprovider_ansible.py) • Tests: [`tests/test_ansibleprovider.py`](./tests/test_ansibleprovider.py)\n\n```python\nINSTALLER_BIN = \"ansible\"\nPATH = os.environ.get(\"PATH\", DEFAULT_PATH)\nansible_installer_module = \"auto\"\nansible_playbook_template = ANSIBLE_INSTALL_PLAYBOOK_TEMPLATE\n```\n\n- Install root: **no hermetic prefix support**. It delegates to the host via `ansible-runner`.\n- Auto-switching: `installer_module=\"auto\"` resolves to `community.general.homebrew` on macOS and `ansible.builtin.package` on Linux.\n- `dry_run`: shared behavior.\n- Security: `min_release_age` and `postinstall_scripts=False` are unsupported and are ignored with a warning if explicitly requested.\n- Overrides: `install_args` becomes the playbook loop input for the chosen Ansible module.\n- Notes: when using the Homebrew module, the provider auto-injects the detected brew search path into module kwargs. Privilege requirements still come from the underlying package manager, and failed sudo attempts are included in the final error if the fallback attempt also fails.\n\n\u003c/details\u003e\n\n### [`Binary`](https://github.com/ArchiveBox/abxpkg/blob/main/abxpkg/binary.py#:~:text=class%20Binary)\n\nRepresents a single binary dependency aka a package (e.g. `wget`, `curl`, `ffmpeg`). Each `Binary` can declare one or more `BinProvider`s it supports, along with per-provider overrides.\n\n`Binary`s implement the following interface:\n- `load()`, `install()`, `update()`, `uninstall()` `-\u003e` `Binary`\n- `binproviders`\n- `binprovider` / `loaded_binprovider`\n- `abspath` / `loaded_abspath`\n- `abspaths` / `loaded_abspaths`\n- `version` / `loaded_version`\n- `sha256` / `loaded_sha256`\n- `mtime` / `loaded_mtime`\n- `euid` / `loaded_euid`\n\n`Binary.install()` and `Binary.update()` return a fresh loaded `Binary`. `Binary.uninstall()` returns a `Binary` with `binprovider`, `abspath`, `version`, `sha256`, `mtime`, and `euid` cleared after removal. `Binary.load()`, `Binary.install()`, and `Binary.update()` all enforce `min_version` consistently. All four lifecycle methods also accept `no_cache=True` to bypass cached/current-state checks.\n\n```python\nfrom abxpkg import Binary, SemVer, env, brew\n\ncurl = Binary(\n    name=\"curl\",\n    min_version=SemVer(\"8.0.0\"),\n    binproviders=[env, brew],\n).install()\n\nprint(curl.binprovider)   # EnvProvider(...) or BrewProvider(...)\nprint(curl.abspath)       # Path('/usr/local/bin/curl')\nprint(curl.version)       # SemVer(8, 4, 0)\nprint(curl.is_valid)      # True\n\ncurl = curl.update()\ncurl = curl.uninstall()\n```\n\nFor reusable `Binary` subclasses with per-provider overrides, see [Advanced Usage](#advanced-usage) above.\n\n### [`SemVer`](https://github.com/ArchiveBox/abxpkg/blob/main/abxpkg/semver.py#:~:text=class%20SemVer)\n\n```python\nfrom abxpkg import SemVer\n\n### Example: Use the SemVer type directly for parsing \u0026 verifying version strings\nSemVer.parse('Google Chrome 124.0.6367.208+beta_234. 234.234.123')  # SemVer(124, 0, 6367)\nSemVer.parse('2024.04.05')                                          # SemVer(2024, 4, 5)\nSemVer.parse('1.9+beta')                                            # SemVer(1, 9, 0)\nstr(SemVer(1, 9, 0))                                                # '1.9.0'\n```\n\u003cbr/\u003e\n\n\u003e These types are all meant to be used library-style to make writing your own apps easier.  \n\u003e e.g. you can use it to build things like [`playwright install --with-deps`](https://playwright.dev/docs/browsers#install-system-dependencies).\n\n\n\u003cbr/\u003e\n\n---\n---\n\n\u003cbr/\u003e\n\u003cbr/\u003e\n\n## Development\n\n`abxpkg` uses `uv` for local development, dependency sync, linting, and tests.\n\n```bash\ngit clone https://github.com/ArchiveBox/abxpkg \u0026\u0026 cd abxpkg\n\n# setup the venv and install packages\nuv sync --all-extras\nsource .venv/bin/activate\n\n# run formatting/lint/type checks\nuv run prek run --all-files\n\n# run the full test suite from tests/\nuv run pytest -sx tests/\n\n# build distributions\nuv build \u0026\u0026 uv publish --username=__token__\n```\n\n- Tests live under [`tests/`](./tests/).\n- Use `uv run pytest -sx tests/test_npmprovider.py` or a specific node like `uv run pytest -sx tests/test_npmprovider.py::TestNpmProvider::test_provider_dry_run_does_not_install_zx` when iterating on one provider.\n\n\u003cbr/\u003e\n\u003cbr/\u003e\n\n\n*Note:* this package used to be called `pydantic-pkgr`, it was renamed to `abxpkg` on 2024-11-12.\n\n\n## Other Packages We Like\n\n- https://github.com/MrThearMan/django-signal-webhooks\n- https://github.com/MrThearMan/django-admin-data-views\n- https://github.com/lazybird/django-solo\n- https://github.com/joshourisman/django-pydantic-settings\n- https://github.com/surenkov/django-pydantic-field\n- https://github.com/jordaneremieff/djantic\n\n[coverage-badge]: https://coveralls.io/repos/github/ArchiveBox/abxpkg/badge.svg?branch=main\n[status-badge]: https://img.shields.io/github/actions/workflow/status/ArchiveBox/abxpkg/test.yml?branch=main\n[pypi-badge]: https://img.shields.io/pypi/v/abxpkg?v=1\n[licence-badge]: https://img.shields.io/github/license/ArchiveBox/abxpkg?v=1\n[repo-badge]: https://img.shields.io/github/last-commit/ArchiveBox/abxpkg?v=1\n[issues-badge]: https://img.shields.io/github/issues-raw/ArchiveBox/abxpkg?v=1\n[version-badge]: https://img.shields.io/pypi/pyversions/abxpkg?v=1\n[downloads-badge]: https://img.shields.io/pypi/dm/abxpkg?v=1\n[django-badge]: https://img.shields.io/pypi/djversions/abxpkg?v=1\n\n[coverage]: https://coveralls.io/github/ArchiveBox/abxpkg?branch=main\n[status]: https://github.com/ArchiveBox/abxpkg/actions/workflows/test.yml\n[pypi]: https://pypi.org/project/abxpkg\n[licence]: https://github.com/ArchiveBox/abxpkg/blob/main/LICENSE\n[repo]: https://github.com/ArchiveBox/abxpkg/commits/main\n[issues]: https://github.com/ArchiveBox/abxpkg/issues\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farchivebox%2Fabxpkg","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farchivebox%2Fabxpkg","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farchivebox%2Fabxpkg/lists"}