One‑shot git sync
hanif sync updates main, rebases your branch, and prunes merged branches — the choreography you do ten times a day, in one breath.
Hanif CLI bundles the chores that slow your day — syncing, branching, squashing, versioning, multi-account git setup, env vars, and SVG conversion — into short, friendly commands that just work.
curl -fsSL https://raw.githubusercontent.com/hanif-mianjee/hanif-cli-tools/main/install.sh | bash
Every command was born from a real, repeated annoyance. No clever‑for‑clever‑sake; just the shortcuts a senior engineer would write — and then never share.
hanif sync updates main, rebases your branch, and prunes merged branches — the choreography you do ten times a day, in one breath.
hanif nf add user auth becomes feature/add_user_auth. Ticket numbers are detected; names get sanitized; no quotes required.
Pick the commit to squash into from a numbered list. Hanif preserves every squashed hash in the final message — so review history stays auditable.
See squash workflow →hanif bv minor bumps version everywhere — package.json, Cargo.toml, pyproject.toml — tags it, and stages an RC. hanif bv release promotes.
hanif env set API_KEY=… writes to a managed file and wires your shell once. Secrets are masked in list; values are shell‑quoted, never eval‑ed.
hanif gsetup work generates an SSH key, writes a per‑profile gitconfig, and adds an includeIf so git auto‑switches identity by directory.
hanif svg convert icon.svg 16,32,64 produces every size you need. hanif svg chrome emits a complete extension icon set.
hanif gi .env appends to .gitignore AND removes the file from git tracking — without deleting it from disk.
A daily background check whispers when a new version is out. hanif self-update upgrades in place — no package manager required.
Each command below shows the syntax, options, and at least one realistic scenario you'll recognize from your own workflow. Click Copy on any block to drop it into your terminal.
sync, up, upall, clean, rb, pull, st, amendYou return after the weekend. Your team merged 14 PRs. You have a feature branch in flight. Instead of fetching, switching, pulling, switching back, and rebasing by hand:
$ hanif sync → Updating main… ✓ main is up to date with origin/main → Rebasing feature/checkout-v2 onto main ✓ Rebase complete — 7 commits replayed → Cleaning merged branches… ✓ Deleted 4 stale branches: feature/login-bug, feature/spinner, …
hanif nf sanitizes your description, lower‑cases it, extracts ticket IDs, and enforces a 60‑char ceiling so your CI doesn't choke on absurd refs.
$ hanif nf add user authentication ✓ Switched to a new branch feature/add_user_authentication $ hanif nf "OM-755: fix login bug on Safari" ✓ Switched to a new branch feature/om-755_fix_login_bug_on_safari $ hanif nf JIRA-123 quick patch ✓ Switched to a new branch feature/jira-123_quick_patch
Working a release branch alongside main? hanif upall walks every local branch, fast‑forwards what it can, and reports any branch that diverged so you can rebase it intentionally.
$ hanif upall ✓ main updated (3 commits) ✓ release/2025-q2 updated (12 commits) ⚠ feature/billing diverged — rebase manually with hanif rb main
You forgot to git add one file. hanif amend stages everything and amends with the existing message. Pass a string to override.
$ hanif amend # keep the existing message $ hanif amend "fix: handle null user in middleware"
hanif squash — interactive commit squashingYou've been WIP‑committing all afternoon. Reviewers want one clean commit. hanif squash 9 shows the last 9 commits; pick the one to squash into and (optionally) supply a custom message.
$ hanif squash 9 1) c1d2e3f fix tests 2) a3b4c5d more tweaks 3) 9f8e7d6 WIP refactor ... 6) 1a6c6d8 Third commit ← squash into this one Custom message? (blank to use commit's own message): OM-1200 Major refactor ✓ Squashed 5 commits.
The resulting commit body keeps every original hash for full traceability:
OM-1200 Major refactor
* c1d2e3f fix tests
* a3b4c5d more tweaks
* 9f8e7d6 WIP refactor
* ef3798f Fourth commit
* a524b8f Fifth commit
Just two stray fixup commits to fold in? Skip the count — the default is the last 20 commits, more than enough for nearly any feature branch.
$ hanif squash
Skip the picker entirely. Pass the commit hash and Hanif squashes everything from that commit through HEAD into one — same custom-message prompt, no scrolling through a list.
$ hanif squash 1a6c6d8
Pass two hashes — <older> <newer> — and Hanif squashes the inclusive range, then cherry-picks every commit that came after <newer> back on top. Requires a clean working tree and a checked-out branch; the original HEAD is printed so you can recover via git reset --hard if anything goes wrong.
$ hanif squash 1a6c6d8 ef3798f
hanif bv — semantic versioning with RC workflowA polyglot repo with package.json, Cargo.toml, and a __init__.py:
$ hanif bv init ✓ Detected: Node.js · Rust · Python (poetry) ✓ Wrote .bumpversion.cfg with current_version = 0.4.2 $ hanif bv minor → 0.4.2 → 0.5.0-rc0 ✓ Updated package.json, Cargo.toml, src/myapp/__init__.py ✓ Tagged v0.5.0-rc0 Push tag now? [Y/n]
Bake the release candidate, fix bugs, bump rc as needed, and promote when QA signs off.
$ hanif bv rc # 0.5.0-rc0 → 0.5.0-rc1 $ hanif bv rc # 0.5.0-rc1 → 0.5.0-rc2 $ hanif bv release # 0.5.0-rc2 → 0.5.0 ✓ tagged & ready
$ hanif bv patch # 0.5.0 → 0.5.1-rc0 → iterate or release
Tag conflicts? Hanif detects them and asks before clobbering. Pre‑flight verification runs before any file is rewritten so you never end up with a half‑bumped repo.
hanif env — persistent environment variables, the safe wayeval‑ed.$ hanif env set OPENAI_API_KEY=sk-abc123… First run — append source line to ~/.zshrc? [Y/n] y ✓ Saved. Open a new shell or run eval $(hanif env source)
$ hanif env list ┌──────────────────┬───────────────────────┐ │ KEY │ VALUE │ ├──────────────────┼───────────────────────┤ │ DATABASE_URL │ postgres://…/db │ │ OPENAI_API_KEY │ sk-••••••••••••••••• │ │ STRIPE_SECRET │ sk_live_••••••••••••• │ └──────────────────┴───────────────────────┘
Anything matching *TOKEN*, *SECRET*, *PASSWORD*, *KEY*, or *API* is masked. Reveal a single value with hanif env get OPENAI_API_KEY.
Your env file lives at ~/.hanif/env.sh (or ~/.hanif/env.fish for fish users). Copy it across, run hanif env path to confirm wiring, and you're done.
$ hanif env path env file : ~/.hanif/env.sh profile : ~/.zshrc ✓ wired
hanif gsetup — multi‑account git in one shotincludeIf so git auto‑switches identity by directory.Run it twice — once per identity:
$ hanif gsetup work Repos directory? [~/code/work] Name? [Hanif Mianjee] Email? hanif@acme.com ✓ Generated ~/.ssh/id_ed25519_work ✓ Wrote ~/.gitconfig-work ✓ Appended includeIf block to ~/.gitconfig → Add this public key to GitHub / Azure DevOps: ssh-ed25519 AAAA…work $ hanif gsetup personal # same flow, ~/code/personal
Anything cloned under ~/code/work/ automatically uses your work name, email, and SSH key — no per‑repo configuration, no core.sshCommand incantations.
$ cd ~/code/work $ git clone git@github.com:acme/billing.git $ cd billing $ git config user.email hanif@acme.com # ✓ correct identity, automatically
Re‑running hanif gsetup work updates the existing includeIf block in place — no duplicates, ever.
hanif gi — ignore & untrack in one step.gitignore and runs git rm --cached for you. Files stay on disk; only the index entry is removed..env by mistake$ hanif gi .env ✓ Added .env to .gitignore ✓ Removed from git index (file kept on disk) → Don't forget to commit: git commit -m "chore: ignore .env"
$ hanif gi node_modules/ $ hanif gi "*.log" $ hanif gi .DS_Store
Already in .gitignore? Hanif notices and skips. No .gitignore file? It creates one.
hanif svg — convert SVG to PNG at every size you needrsvg-convert, Inkscape, or ImageMagick — uses whichever is available.$ hanif svg convert icon.svg 16,32,64,128,256 ✓ Wrote icon-16.png icon-32.png icon-64.png icon-128.png icon-256.png
$ hanif svg chrome icon.svg ✓ Wrote icon-16.png icon-32.png icon-48.png icon-128.png to ./icons/
$ hanif svg convert logo.svg 100,200,400 --prefix brand -o dist/
hanif self-update & hanif help$ hanif self-update → Current: 0.4.0 Latest: 1.0.0 ✓ Updated successfully. Run hanif help to see what's new. $ hanif help squash $ hanif bv --help
Hanif CLI is opinionated where it matters and out of your way everywhere else. Here's what makes it stick.
No Node, Python, or runtime to install. Each command is a tiny shell function that runs in milliseconds and is easy to audit.
Inputs are validated with strict regexes, values are printf '%q'‑quoted, secrets are masked, and every destructive write is preceded by a .bak.
Macs, Linux servers, GitHub Codespaces, WSL on Windows — Hanif uses portable sed wrappers and tested fallbacks across shells (bash, zsh, fish).
Drop a file in lib/commands/, call register_command, done. The dispatcher auto‑discovers it. No edits to the entry point.
A growing test suite runs on every push. Tests are isolated to a temp HOME so your machine is never touched.
Color, icons, boxes, and aligned tables when your terminal supports them — clean ASCII when it doesn't (or when NO_COLOR is set). Pipe‑safe by design.
Pick the path that matches your taste. All paths give you the same hanif binary on your $PATH.
curl -fsSL https://raw.githubusercontent.com/hanif-mianjee/hanif-cli-tools/main/install.sh | bash
Installs to ~/.hanif-cli and links hanif into ~/.local/bin. The script is short and human‑readable — feel free to read it on GitHub before piping.
$ git clone https://github.com/hanif-mianjee/hanif-cli-tools.git ~/.hanif-cli $ ln -s ~/.hanif-cli/bin/hanif ~/.local/bin/hanif $ hanif version
Make sure ~/.local/bin is on your PATH. Add export PATH="$HOME/.local/bin:$PATH" to your shell profile if it isn't.
$ hanif version hanif 1.0.0 $ hanif help
Don't see hanif? Open a new terminal so your PATH is reloaded, or run source ~/.zshrc (or your equivalent).
$ rm -rf ~/.hanif-cli ~/.local/bin/hanif # optional: remove ~/.hanif/ if you used hanif env
Real reactions from people who type git commands hundreds of times a week.
"hanif sync alone saves me ten keystrokes and one context switch every hour. The smart branch names mean I stopped quoting things in zsh forever."
"The interactive squash is what I'd build if I had time to build it. The fact that it preserves every original commit hash in the message is *chef's kiss*."
"I have four GitHub accounts. hanif gsetup replaced two pages of notes I'd been copy‑pasting for years. Just works."
curl into bash?install.sh on GitHub or use the manual install above.$HOME (~/.hanif-cli + a symlink in ~/.local/bin). Uninstall is rm -rf.hanif env appends a single source line to your shell profile after a "y/N" prompt; hanif gsetup appends an includeIf block to ~/.gitconfig wrapped in clear markers (and backs the file up first).hanif self-update. A daily background check whispers when there's a new release; you can disable it by setting HANIF_SKIP_UPDATE_CHECK=1.lib/commands/your-cmd.sh that calls register_command …, and the dispatcher will pick it up automatically. See CONTRIBUTING.md.Stop typing the same git incantations. Start shipping.