Terminal CLI for Jira issues and Confluence knowledge bases, compatible with Bun and Node.js. It lists and fetches issues and pages, exports them to Markdown with YAML frontmatter, applies edited Markdown back to create or update them, and downloads whole Jira projects or Confluence spaces (issues, comments, changelog, pages, labels and attachments) through a resumable, rate-limited job queue.
It is the Atlassian sibling of youtrack-cli and follows the same principles: plain ESM JavaScript, zero runtime dependencies, and Markdown as the editable transport format.
- Bun or Node.js 18.17 or later (Node 22.5+ or Bun enable the SQLite queue backend; older runtimes use a JSONL journal).
- An Atlassian Cloud site (
https://<site>.atlassian.net) and an API token.
From a local checkout:
bun install # or: npm installThe install script links the jira-cli and jr binaries globally for your user when you work from a local checkout (it is skipped for global installs). To relink manually:
bun link # or: npm linkInstall directly from the repository:
bun install -g github:digiogithub/jira-cli
# or
npm install -g git+https://github.com/digiogithub/jira-cli.gitVerify:
jr --helpjr setup --url https://acme.atlassian.net --email you@example.com --token <api-token>
jr config # shows the resolved configuration (never prints the token)
jr whoami # checks the credentialsConfluence is assumed to live at <url>/wiki; override it with --confluence-url.
The configuration is saved (with 0600 permissions) at:
- Linux/macOS:
~/.config/jira-cli/config.yamlor$XDG_CONFIG_HOME/jira-cli/config.yaml - Windows:
%APPDATA%\jira-cli\config.yaml - or the path in
JIRA_CONFIG
Environment variables override the file:
JIRA_URL=https://acme.atlassian.net JIRA_EMAIL=you@example.com JIRA_TOKEN=... jr issues list| Variable | Meaning |
|---|---|
JIRA_URL |
Site URL. |
JIRA_EMAIL |
Account email. With it, requests use Basic auth (Cloud API token). |
JIRA_TOKEN |
API token. Without an email it is sent as a Bearer personal access token. |
CONFLUENCE_URL |
Confluence base URL, defaults to <JIRA_URL>/wiki. |
JIRA_CONFIG |
Alternative config file path. |
jr projects --query web
jr issues list # assigned to you, most recently updated first
jr issues list --project PRJ --limit 50
jr issues list --jql "project = PRJ AND status = 'In Progress'" --json
jr issues get PRJ-123 --comments
jr issues export PRJ-123 --dependencies --comments --out ./issues
jr issues apply ./issues/PRJ-123.md
jr issues apply ./new-bug.md --dry-run
jr issues comment PRJ-123 --text "Deployed in **v1.2**"
jr issues transition PRJ-123 # list available transitions
jr issues transition PRJ-123 "In Review"issues export --dependencies also exports linked issues, sub-tasks and the parent.
---
kind: issue
id: "10123"
key: PRJ-123
summary: Login fails with SSO
project:
key: PRJ
name: Platform
issuetype: Bug
status: In Progress
priority: High
assignee:
accountId: 5b10ac8d82e05b22cc7d4ef5
displayName: Ana
labels:
- auth
components:
- web
fields:
customfield_10016: 5
---
# PRJ-123 Login fails with SSO
Description in **Markdown**.jr issues apply:
- creates the issue when there is no
key(needsprojectandissuetype) and writes the newkey/idback into the file; - updates the issue otherwise, sending only the fields that changed compared to the live issue;
- changes the workflow status through the matching transition when
statusdiffers; - sends the
fieldsmap verbatim, which gives access to any custom field (customfield_*), andassignee: nullunassigns; - ignores read-only metadata (
reporter,created,links,customFields, ...) and the comments section rendered by--comments.
jr kb spaces
jr kb list --space ENG # pages of a space
jr kb list --query "onboarding" --space ENG # full-text search (CQL)
jr kb list --cql "label = howto ORDER BY created DESC"
jr kb get 123456
jr kb export 123456 --children --out ./kb # page and all its descendants
jr kb apply ./kb/123456-Onboarding.md --message "Update steps"---
kind: page
id: "123456"
title: Onboarding
spaceKey: ENG
spaceId: "98765"
parentId: "123000"
status: current
version: 7
labels:
- howto
---
# Onboarding
Page body in **Markdown**.jr kb apply creates the page when there is no id (needs spaceId, spaceKey or --space; parentId is optional) and updates it otherwise using version + 1. If the page changed remotely after the export (remote version greater than the file's version), the update is refused unless --force is given. After a successful apply the new id/version are written back into the file, so it can be edited and applied again.
Jira Cloud (REST v3) and Confluence Cloud (REST v2) store rich text as Atlassian Document Format (ADF). jira-cli converts it to and from Markdown:
| ADF | Markdown |
|---|---|
| headings, paragraphs, bold, italic, strike, inline code, links | CommonMark |
| bullet/ordered lists, code blocks, quotes, rules, hard breaks | CommonMark |
| simple tables (header row, one paragraph per cell) | GFM tables |
| task lists | - [ ] / - [x] |
| info/note/warning/success/error panels | > [!INFO] alert blockquotes |
| expand sections | <details><summary>Title</summary> ... </details> |
| external images |  |
| underline | <u>text</u> |
| user mentions | [@Name](mention:<accountId>) |
| smart links (inline cards) | <https://...> |
| anything else (uploaded media, status lozenges, dates, macros, colored text, merged cells, ...) | preserved losslessly as <!--adf:{...}--> comments |
Keep the <!--adf:...--> comments in place when editing and those elements survive the round trip.
jr project export PRJ # Jira project into ./PRJ
jr project export PRJ --space ENG --out ./dump # project plus a Confluence space
jr space export ENG --out ./ENG # only the Confluence space
jr project status --out ./dump # inspect progress (safe while running)<out>/
export.json manifest: options, counts, queue stats, timestamps
.jr-export/
queue.db | queue.jsonl persistent job queue
queue.lock single-coordinator lock
run.json current run number
issues/
PRJ-123/
issue.md Markdown export (with comments)
issue.json raw API payload (all fields + field names)
comments.json
changelog.json full field history
assets/<attachmentId>__<filename>
pages/
123456/
page.md Markdown export
page.json raw API payload plus labels
comments.json footer comments
assets/<attachmentId>__<title>
Every remote call is scheduled through an embedded, file-backed job queue in <out>/.jr-export (SQLite when the runtime provides bun:sqlite or node:sqlite, a JSONL journal otherwise).
- Interrupting an export (Ctrl-C) and rerunning it with the same
--outresumes it; finished jobs are not repeated. - Rerunning a completed export starts a new run that re-indexes the project/space and picks up new and updated content; attachments already on disk with the expected size are skipped.
- Only one process may coordinate a directory. A lock left by a dead process is taken over automatically;
--forcebreaks a lock on purpose and--freshdiscards the queue and starts over. - Failed jobs are retried with exponential backoff; remaining failures are printed and the command exits with status 1.
- Jira's
/search/jqlpaging has known reports of tokens looping back to the first page; the indexer detects it and stops.
| Flag | Default | Meaning |
|---|---|---|
--out <dir> |
./<key> |
Output directory. |
--space <key> |
none | (project export) Also export this Confluence space. |
--jql <jql> |
none | (project export) Extra JQL filter, e.g. type = Bug. |
--since <date> |
none | Only issues updated since this date (2024-01-31). |
--limit <n> |
none | Stop after indexing this many issues. |
--rate <req/s> |
5 |
Maximum requests per second (fractions allowed). |
--concurrency <n> |
4 |
In-process workers consuming the queue. |
--no-attachments |
off | Skip binary attachments. |
--no-comments |
off | Skip issue and page comments. |
--no-changelog |
off | Skip the issue changelog. |
--fresh |
off | Discard the previous queue state. |
--force |
off | Take over an existing coordinator lock. |
--json |
off | Print only the JSON summary on stdout. |
Requests from the Jira and Confluence clients share one token bucket and are retried on 429 and 5xx, honouring Retry-After.
- Jira Cloud platform REST API v3:
/rest/api/3/search/jql,/issue,/issue/{key}/comment,/issue/{key}/changelog,/issue/{key}/transitions,/project/search,/myself,/attachment/content/{id}. - Confluence Cloud REST API v2:
/wiki/api/v2/spaces,/spaces/{id}/pages,/pages/{id}(body-format=atlas_doc_format),/pages/{id}/children,/labels,/attachments,/footer-comments; CQL search through v1/wiki/rest/api/search.
See docs/implementation-plan.md for the research notes and design decisions. Jira/Confluence Data Center support is planned but not implemented yet.
npm run check # syntax check of every source file + offline test suite
bun test # the same suite under BunSee AGENTS.md for the repository conventions.