[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-83979":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":15,"stars7d":16,"stars30d":16,"stars90d":15,"forks30d":15,"starsTrendScore":16,"compositeScore":17,"rankGlobal":10,"rankLanguage":10,"license":18,"archived":19,"fork":19,"defaultBranch":20,"hasWiki":21,"hasPages":19,"topics":22,"createdAt":10,"pushedAt":10,"updatedAt":26,"readmeContent":27,"aiSummary":10,"trendingCount":15,"starSnapshotCount":15,"syncStatus":16,"lastSyncTime":28,"discoverSource":29},83979,"ohbin","prostomarkeloff\u002Fohbin","prostomarkeloff","Declarative GitHub-release binaries for uv projects — declare a tool in pyproject, ohbin run downloads, verifies, caches, and execs it.","",null,"Python",56,1,52,0,2,0.9,"MIT License",false,"main",true,[5,23,24,25],"python","uv","uv-python","2026-06-12 02:04:36","# ohbin\n\n[![Python 3.11+](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fpython-3.11+-blue.svg)](https:\u002F\u002Fwww.python.org\u002Fdownloads\u002F)\n[![License: MIT](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FLicense-MIT-yellow.svg)](https:\u002F\u002Fopensource.org\u002Flicenses\u002FMIT)\n\nohbin runs the binaries your project needs but can't `pip install`. You know the ones:\n`ripgrep` ([or… can you?](https:\u002F\u002Fpypi.org\u002Fproject\u002Fripgrep\u002F)), [`find-dup-defs`](https:\u002F\u002Fgithub.com\u002Fprostomarkeloff\u002Ffind-dup-defs), [`oasdiff`](https:\u002F\u002Fgithub.com\u002Foasdiff\u002Foasdiff), some linter\nwritten in Rust that only ships as a GitHub release. uv\ninstalls Python packages, and those aren't Python packages, so normally you're stuck either\ntelling everyone to install them by hand and watching the versions drift, or writing a little\ndownload-and-verify wrapper package and copying it into every repo.\n\nWith ohbin you just write the tool down in your `pyproject.toml`. The first time you run it,\nohbin downloads it, checks it against a SHA256 you pinned, caches it, and runs it. One\ndev-dependency, as many tools as you want.\n\nIt's a small thing on purpose, built for people who already live in uv. Your binaries get\npinned right next to your Python deps, in the same file, and run through the same flow.\n\nWhat it gives you:\n\n* binaries pinned to a version, pulled from GitHub releases\n* a SHA256 per platform, checked before anything gets unpacked\n* one dev-dependency, however many tools you declare\n* a per-host cache that's safe to hit from parallel CI\n* mostly stdlib (it shells out to `gh` and `openssl` only for the private-gist part)\n\n## Installation\n\nIt's a dev dependency, so with uv:\n\n```sh\nuv add --dev git+https:\u002F\u002Fgithub.com\u002Fprostomarkeloff\u002Fohbin.git\n```\n\n## How to?\n\nSay you want ripgrep. Point ohbin at the repo:\n\n```sh\nuv run ohbin add BurntSushi\u002Fripgrep --version 14.1.1 --name rg --binary rg\n```\n\nThis goes and looks at the release, finds the right asset for each platform, pins the SHA256s,\nand writes a little table into your `pyproject.toml`. Your comments and formatting stay where\nthey are:\n\n```toml\n[tool.ohbin.tools.rg]\nrepo = \"BurntSushi\u002Fripgrep\"\nversion = \"14.1.1\"\nbinary = \"rg\"\n# add writes one [..assets.\u003Cos>-\u003Carch>] table per platform under here, checksums and all\n```\n\n`--name` is what you'll type when it's different from the repo name (ripgrep becomes rg), and\n`--binary` is the actual executable inside the archive. If add guesses an asset wrong, don't\nfight it, the table is the source of truth, just fix the line.\n\nThen run it:\n\n```sh\nuv run ohbin run rg -- --files     # first time: downloads, checks, caches, runs\nuv run ohbin run rg -- TODO src\u002F   # after that it just runs\n```\n\nohbin hands the process straight over with execv, so the tool itself gets stdin, stdout,\nsignals and the exit code, exactly like you'd run it yourself. In a Makefile I usually hide\nthe prefix behind a variable:\n\n```make\nRG := uv run ohbin run rg --\nsearch:; $(RG) TODO src\u002F\n```\n\n`ohbin which fd` prints the cached path (and downloads it first if it has to), and `ohbin list`\nshows what you've declared.\n\n### Private binaries\n\nSometimes the binary isn't on a public release page. Maybe you built it yourself and you don't\nwant it in a repo at all. ohbin can ship it through a secret gist instead, encrypted with a\npassword:\n\n```sh\nuv run ohbin publish-gist .\u002Fdist\u002Fmytool --password \"$PW\"\n```\n\nThat gzips it, encrypts it, and drops one gist file per platform plus a small index. Run it\nfrom each platform's own machine, passing `--gist \u003Cid>` to add to the same gist. After that\nit's just another tool:\n\n```sh\nuv run ohbin add-gist https:\u002F\u002Fgist.github.com\u002Fyou\u002Fab12… --name mytool\nuv run ohbin run --password \"$PW\" mytool -- --help\n```\n\nWhy a gist and not a private repo? Because a gist isn't tied to a repo, and that's the whole\npoint. You don't commit the binary anywhere, you don't hand out repo access and tokens to\neveryone who needs it, you just give them a link and a password. The link is unlisted and the\nbytes are AES-256-CBC, so a leaked link on its own is nothing without the password. The\npassword goes to openssl over a file descriptor, never on the command line. To take access\naway, delete the gist or change the password.\n\n### From Python\n\nIf you want the path instead of running the thing:\n\n```python\nfrom ohbin import ensure\n\npath = ensure(\"rg\")   # a Path, downloaded and checked the first time\n```\n\nIt finds your `pyproject.toml` by walking up from wherever you are. Set `OHBIN_PYPROJECT` if\nyou need to point it at a specific file, like in CI.\n\n## How it works\n\nNothing clever. On `ohbin run rg`, it reads the rg table, works out your os and arch, and looks\nfor `~\u002F.cache\u002Fohbin\u002Frg\u002F14.1.1\u002Frg`. If it's there, it runs it. If not, it downloads under a lock\n(so two parallel runs don't race), checks the SHA256 before unpacking anything, extracts, and\nruns. The version is in the cache path, so bumping it is just a fresh download that doesn't step\non the old one. Downloads retry with backoff, and a real 404 doesn't get mistaken for a flaky\nnetwork.\n\n## Limitations\n\nPOSIX only for now, the locking uses `fcntl` so it won't even import on Windows. add\nauto-resolves four platforms (linux and macOS, x86_64 and arm64); anything else (windows, musl,\nriscv) you add to the table by hand and the engine runs it fine. Asset matching is just looking\nat the os\u002Farch words in the filename and preferring `.tar.gz`, so a weird naming scheme might\ncost you a one-line fix.\n\n## Development\n\n```sh\ngit clone https:\u002F\u002Fgithub.com\u002Fprostomarkeloff\u002Fohbin\ncd ohbin && uv sync\n\nmake lint-heavy   # ruff format + check + pyright\nmake test-full    # the network-free test suite\n```\n\n## License\n\nMIT, see [LICENSE](LICENSE).\n\nMade with 📦 by [prostomarkeloff](https:\u002F\u002Fgithub.com\u002Fprostomarkeloff) and contributors.\n","2026-06-11 04:11:57","CREATED_QUERY"]