Tired of Jira's slow UI? Drive it from your terminal with `jr`
If your day looks like open Jira → wait for the SPA to boot → hunt for the ticket → click into the status dropdown → wait again, you already know the tax. Most of what we actually do to a ticket — move it, comment on it, assign it — is two seconds of intent buried under ten seconds of web app. jira-sh is a tiny bash CLI that skips all of it. One command, jr, talking straight to the Jira Cloud REST API.
jr move PROJ-123 "In Review"
jr comment PROJ-123 "Deployed to staging"
jr view PROJ-123
No Electron, no browser, no waiting. It's a single bash script plus curl and python3 (standard library only for the core commands).
Setup in four lines
git clone https://github.com/shukebeta/jira-sh ~/Projects/jira-sh
bash ~/Projects/jira-sh/install.sh # adds a source line to ~/.bashrc
# put these in ~/.bashrc
export JIRA_BASE=https://yourcompany.atlassian.net
export [email protected]
export JIRA_TOKEN=your-api-token
source ~/.bashrc
The token is an Atlassian API token, not your password. Create one in 30 seconds here: https://id.atlassian.com/manage-profile/security/api-tokens — click Create API token, give it a label, copy the string. Jira Cloud authenticates with HTTP Basic over email:token, which is why both JIRA_EMAIL and JIRA_TOKEN are required.
Type less: bare numbers and branch-derived tickets
Two ergonomics that matter once you live in this thing.
Bare ticket numbers. Set JIRA_PROJECT_PREFIXES (pipe-separated, e.g. MT|DOS) and you can drop the prefix:
export JIRA_PROJECT_PREFIXES="MT|DOS"
jr view 123 # resolves to MT-123 (or probes each prefix if ambiguous)
Branch-derived tickets. Pass a bare ., @, or nothing at all, and jr pulls the ticket key out of your current git branch name:
git switch -c feature/MT-456-add-export
jr view # → MT-456, read straight from the branch
jr move . "In Progress"
So the ticket you're coding on is always the ticket jr acts on, with zero typing.
The command surface
move <TICKET> <STATUS> Transition a ticket; moving to "In Progress" claims it for you
comment <TICKET> <TEXT> Add a comment
view [TICKET] Dump fields + rendered description (the task + acceptance criteria)
transitions <TICKET> List the transitions available right now
assign <TICKET> [NAME] Fuzzy-assign by name/email; omit NAME to take it yourself
users <TICKET> [query] List assignable users for the ticket
resolve [--force] [TICKET] Move to review and fill the review template from the branch's PR
merge [--force] [TICKET] Merge the approved PR, advance the ticket, fill the Merge Results template
help Show all of the above
A few things worth calling out:
move matches on the destination status, not the transition name. You say where you want the ticket to be ("In Review", "Done"), and jr fuzzy-matches that against the available transitions. You don't have to memorize that the transition to "In Review" is internally called "Submit for review" or whatever your workflow named it.
move to "In Progress" claims the ticket. Starting work assigns the ticket to you if it's unassigned, and refuses (rather than silently stomping) if it's someone else's. Moving to other statuses warns you before touching a ticket that isn't yours.
view is the one AI agents like most. It renders the description ADF — including acceptance criteria — into plain text in the terminal, which is usually all you need to understand a task without the web app at all. You can easily achieve this: simply throw the ticket number to your AI agent and it will start working out the ticket for you.
The opinionated part: resolve and merge
resolve and merge are tailored to a specific team workflow — they expect particular status names (Merge, Test in Main) and fill in "review template" / "Merge Results" comments that mirror that team's process. merge even reaches into GitHub via the gh CLI to merge the approved PR (squash if it's yours, plain merge otherwise) before advancing the Jira ticket.
If you fork this, treat those two as a worked example rather than something that'll run as-is: swap the status names for yours and adjust the comment templates. The generic core — move, comment, view, transitions, assign, users — works against any Jira Cloud instance untouched.
Extra dependencies (only for resolve)
The core commands need just bash + curl + python3. resolve additionally wants:
gh, authenticated for the repo owner, to read the PR.mistune(pip install mistune) to render the PR's Markdown description into Jira's ADF format.
That's the whole thing. It's small on purpose — 若非必要,勿增实体 (Don't add things if unnecessary). If you spend your day shuttling tickets through statuses and the Jira UI's latency is a paper cut you feel a hundred times a week, this turns it into a keystroke.