gift listens for GitHub events on a port of your own, and answers a push by running a script in a folder — build the site, pull the notes, restart the thing that just changed. Around that server sits a handful of git chores, run from the same command.
A hook is a repository, the branches worth answering, a script, and the folder that script runs in. A delivery arrives and every hook watching that repository and that branch runs. Pick a push and watch it land:
The secret is checked before any of that: a delivery whose signature does not add up is refused. gift create can make the webhook on GitHub for you with gh, and then asks GitHub back whether it is really there — a hook in your file that GitHub never calls is worth being told about rather than assumed to work.
gift servegift restartgift stop · gift statusgift list · gift create · gift deletegift loggift configgift updateThe port also carries a page of its own: the hooks as they are configured, and the last day of deliveries newest first, with what each one's script printed — filling in while it is still printing it.
The same command runs a small set of git chores. gift help lists them, gift run opens a picker — a cursor to move, enter to run, a number key to run that row straight away — and gift <name> skips the picker. Enough of a name will do, and anything after it is passed on to the function itself.
Every git repository under one folder, in a table that keeps itself up to date: which branch each is on, whether anything is uncommitted, how many lines that is, and when it last moved. Pick some rows and the menu is what may be done to them — open one in an editor or an agent, read a diff, fetch, pull, push, branch, merge, rebase, stash, or commit and push the lot. Nested checkouts and submodules get a row of their own.
gift repoA week of pull requests, grouped by the day they were opened. Weeks run Monday to Sunday, and it asks how many weeks back unless you have already said.
gift weeklygit pull --recurse-submodules --autostash in every repository below one folder, however deep it is. --dry-run says what it would pull without pulling it.
gift pullEvery setting is in one file — config.json, beside the code. gift config opens it in your editor. gift's own settings are at the top level and each function's are under functions.<name>:
{
"github_webhook_secret": "…",
"port": 3999,
"functions": {
"repo-master": { "repo_root": "/Users/me/projects" },
"weekly-prs": { "repos": "owner/repo1,owner/repo2", "author": "octocat" }
}
}It is written on first use with the settings worth looking at already in it, at their defaults, so opening it shows what there is to set rather than a blank page. Each one is declared in a config.schema.json next to the code that reads it — where its default, its description and the environment variable it reaches a script as all come from. A value already in the environment wins over the file, and a flag wins over both. The file is git-ignored and written 0600, because the webhook secret is in it.
One line. It takes the latest release, unpacks it into ~/.gift, and runs the setup and install steps from there — so it asks for the public delivery URL as it goes, and leaves the gift command on your PATH. Node 18 or newer, curl and unzip, and nothing else to install first.
curl -fsSL https://raw.githubusercontent.com/lhypds/gift/master/get.sh | bash
GIFT_INSTALL_DIR unpacks it somewhere other than ~/.gift, and GIFT_INSTALL_VERSION=v0.0.1 pins a release rather than taking the newest.
Upgrading is the same line again: config.json, hooks.json and the logs are carried across, so the webhook secret and the hooks you configured survive the swap. A download that fails leaves the install that was working exactly where it was.
~/.gift/uninstall.sh takes the gift command back off again, and deleting ~/.gift removes the rest; from a checkout it is ./uninstall.sh.