Skip to content
Conduit

skill

jira-agent-cli

A non-interactive CLI for Jira designed for AI agents and automation

AndersSpringborg/jira-agent-clicommunitylistedunreviewed

Set up

Skills are installed by placing their folder where your agent loads skills.

The download comes from github.com/AndersSpringborg/jira-agent-cli itself, not from our server, so what you get is exactly what its author published.

Install instructions come from the README

Skills do not use an MCP config. Follow the steps in the README below.

README

synced Oct 26, 2026

jira-cli

A non-interactive CLI for Jira built for AI agents, CI, and automation. All output is machine-readable JSON by default, so it drops straight into jq pipelines, shell scripts, and LLM tool calling.

jira search jql "assignee = currentUser() AND status != Done" | jq -r '.[].key'

Why use this

  • JSON by default, clean streams. Results go to stdout, all diagnostics to stderr, and failures return a non-zero exit code. jq, rg, and wc work without choking on error blobs, and scripts check $? instead of parsing strings.
  • Auth stays out of band. You log in once; the token lives in the OS keychain and is never written to disk or exposed to an agent. Execution inherits an already-authenticated environment.
  • Sudo-protected boundary. sudo jira boundary set --label safe limits every request to issues you allow. Agents can read the boundary but can't widen it without your password. See Boundary and context.
  • Inspectable, scoped state. jira context set work --project PROJ --profile default saves project and authentication defaults together; jira context use work switches scope explicitly.
  • Lean output. Project only the fields you need with repeatable -F customfield_x flags, keeping responses (and token usage) small.
  • No daemon, no custom protocol. It's a plain binary. Every action is one replayable command line — debug by copying it from your shell history and running it again.
  • Server-side audit. jira me audit --date YYYY-MM-DD reconstructs what actually changed on a given day from Jira's own changelog, independent of local state.

See What makes this an agent CLI for the longer rationale.

Why not the existing jira-cli?

This project is inspired by ankitpokhrel/jira-cli — a feature-rich interactive Jira command line with a full TUI (tables, keyboard navigation, prompts). Think of it as the k9s of Jira: visual and built for humans at a terminal.

This takes the opposite approach — the kubectl of Jira: non-interactive, scriptable, and built for agents and automation. No TUI, no prompts, just structured output machines can parse. Want a great interactive experience? Use that one. Want to wire Jira into an AI agent, CI pipeline, or shell script? Use this.

Install

npm (recommended)

npm install -g @888aaen/jira-cli

Installs the jira binary for macOS (arm64, x64), Linux (x64, arm64), and Windows (x64).

Build from source

Prerequisites: Go 1.25+

git clone https://github.com/AndersSpringborg/jira-agent-cli.git
cd jira-agent-cli
sudo make install      # builds and installs to /usr/local/bin/jira
sudo make uninstall    # to remove

Quick Start (AI agent)

Give an agent Jira access in two commands:

npm install -g @888aaen/jira-cli
npx skills@latest add AndersSpringborg/jira-agent-cli

The first installs the jira binary. The second installs the jira-cli skill into ~/.claude/skills/jira-cli/, so any Claude Code agent on the machine learns to drive it — it checks for an existing session, guides you through login if needed, and picks the right command for each request.

Quick Start (manual)

1. Authenticate

Create an API token at https://id.atlassian.com/manage-profile/security/api-tokens, then:

jira auth login --server https://your-org.atlassian.net \
  --email you@example.com --token YOUR_API_TOKEN

The token is stored in the OS keychain — never written to disk.

Jira Cloud and Server/Data Center. Cloud profiles (*.atlassian.net) use basic auth + REST v3 + ADF bodies. Server/Data Center profiles use PAT/bearer auth + REST v2 + wiki/plain text bodies.

2. Verify and set defaults

jira ping
jira context set work --project PROJ --board-id 42 --profile default
jira context use work                  # switch project and authentication profile together
jira context list                      # inspect all saved contexts

A per-command --format flag always overrides the context default. See Output Formats.

3. Use it

jira issue list                                          # issues in your project
jira issue view PROJ-123                                  # includes attachment metadata
jira issue attachment download PROJ-123 10042 -o /tmp/screenshot.png
jira issue create -p PROJ -s "Fix login bug" -t Bug
jira search jql "project = PROJ AND status = 'In Progress'"
jira issue list | jq '.[].key'
jira issue ready                       # unresolved work with no unresolved blockers
jira issue graph | jq '{ready, cycles}'
jira issue graph-pretty                # visual dependency check for humans

Output Formats

FlagDescription
--format jsonMachine-readable JSON (default)
--format markdownStructured markdown, fewer tokens for LLMs

Set a persistent default on a named context with jira context set work --display markdown; --format always overrides it.

Debugging Jira responses

Use the global --debug flag to print the raw Jira HTTP response to stderr while keeping normal command output on stdout:

jira ping --debug 2>jira-debug.log
jira issue view PROJ-123 --debug 2>jira-debug.log

The debug log includes the request method and URL, response status, response headers, and unparsed response body. This is useful when an on-prem proxy or login page returns HTML and the command fails with an error such as invalid character '<'. Authentication request headers are never printed, and Set-Cookie response headers are redacted. Response bodies can still contain sensitive Jira data, so review the file before sharing it.

Writing to Jira

Every mutation is a single command with explicit flags, so the transcript line is exactly what changed. Write commands print the result as JSON and exit non-zero on failure, so you can chain them with && and check the exit code.

Issue descriptions and comment bodies are written in Markdown (CommonMark): headings, bullet/ordered lists, fenced code blocks, links, bold/italic. The CLI converts it to the format the server expects — ADF on Jira Cloud, wiki markup on Server/Data Center — so you never hand-write {code} or h3. wiki syntax.

# Create (prints structured JSON with .key; add --raw for the full Jira response)
jira issue create -p PROJ -s "Fix login bug" -t Bug -b "Steps to reproduce..."

# Edit fields, including custom fields by id (-F is repeatable)
jira issue edit PROJ-123 -s "New summary" -l backend -F customfield_10145="value"

# Transition through the workflow
jira issue move PROJ-123 "In Progress"
jira issue move PROJ-123 Done --resolution Fixed --comment "Shipped in v1.2"

# Assign and comment ('me' for yourself, 'x' to unassign; otherwise pass an account ID,
# username, or email resolvable by `jira user search`)
jira issue assign PROJ-123 me
jira issue comment add PROJ-123 "Investigated -- root cause was a stale cache."

# Link, inspect dependencies, clone, delete
jira issue link PROJ-123 PROJ-456 "blocks"
jira issue ready                       # highest-leverage ready work first
jira issue graph                       # nodes, blocker -> blocked edges, ready, blocked, cycles
jira issue graph-pretty                # connected human-readable dependency view
jira issue clone PROJ-123 -s "Follow-up: ..."
jira issue delete PROJ-123

# Sprint management
jira sprint add 42 PROJ-123 PROJ-456

Capture a created key and act on it in the same script:

key=$(jira issue create -p PROJ -s "Automated task" -t Task | jq -r '.key')
jira issue move "$key" "In Progress" && jira issue assign "$key" me

Run jira issue <verb> --help for the full flag set on any command.

Inspecting attachments

jira issue get PROJ-123 (alias of view) includes attachment metadata by default, including each attachment's ID, filename, media type, size, and Jira content URL. JSON keeps Jira's original attachment objects; markdown renders a dedicated attachments table. If you explicitly narrow fields with --field or --fields, include attachment yourself.

When attachments exist, normal issue output also includes a structured follow-up hint (rendered as Next steps at the bottom in markdown):

"nextSteps": [
  {
    "action": "downloadAttachment",
    "command": "jira issue attachment download PROJ-123 ATTACHMENT_ID --output PATH"
  }
]

Replace ATTACHMENT_ID with an ID from fields.attachment and PATH with the local destination. The same workflow and examples are available offline through jira issue get --help and jira issue attachment download --help.

Download binary content to a file before asking an LLM or another local tool to inspect it:

# IDs are unambiguous and preferred for automation
jira issue attachment download PROJ-123 10042 --output /tmp/screenshot.png

# An exact filename also works when only one attachment has that name
jira issue attachment download PROJ-123 screenshot.png -o /tmp/screenshot.png

The command never prints binary data to stdout. It downloads through the authenticated Jira client, writes through a temporary file so failures do not leave a partial destination, and returns structured metadata including the local absolute path. Cross-origin redirects do not receive Jira credentials.

Commands

CommandDescription
jira apiRaw access to any Jira REST endpoint; --list to discover endpoints
jira authLogin, logout, status, whoami
jira boundaryShow, or with sudo change, which issues the CLI may access
jira configManage profiles (init, list, show, set, use, delete)
jira contextSet default filters (project, board, labels, etc.)
jira issueFull issue lifecycle plus dependency analysis (ready, graph)
jira boardList boards, view board issues
jira sprintList, start, close sprints; add issues
jira projectList and view projects
jira searchJQL and full-text search
jira userSearch and get users
jira meShow current user; me audit for daily activity
jira mineList issues assigned to you
jira openOpen project or issue in browser
jira pingCheck connectivity to Jira

Run jira <command> --help for details on any command. Failed commands also print the failing command's complete help text to stderr, so agents can recover without a second discovery call.

Raw API access

jira api is the escape hatch when no predefined command covers what you need — raw SQL for Jira. It sends any request with the active profile's authentication and prints the response body verbatim (pipe to jq):

# GET is the default; full paths pass through verbatim (platform, agile, plugins)
jira api /rest/agile/1.0/board/42/backlog
jira api "/rest/api/3/issue/PROJ-1?expand=changelog,renderedFields" | jq .changelog

# shorthand paths get the platform prefix for your instance flavor:
# /rest/api/3 on Jira Cloud, /rest/api/2 on Jira Server/Data Center
jira api issue/PROJ-1

# writes: -d implies POST; @file and - (stdin) work too; -X and -H as in curl
jira api issue -d '{"fields":{...}}'
echo '{"issues":["PROJ-1"]}' | jira api /rest/agile/1.0/sprint/7/issue -d -

jira api --list [filter...] browses the API surface itself — endpoint catalogs generated from Atlassian's official OpenAPI specs are embedded in the binary, so it works offline and without authentication. The catalog matches your instance flavor (Cloud v3 vs Server/DC v2), deduced from the active profile:

jira api --list sprint            # every sprint-related endpoint
jira api --list "POST worklog"    # filter terms match method, path, and description

Failed requests print Jira's error body (that's the useful part) and exit non-zero.

Resolving issue dependencies

Jira's dependency links form a directed graph from blocker to blocked issue. By default every link type described as "blocks" or "depends on" is followed (for example Blocks or a custom Depend type); an issue is blocked by the issue it depends on. The dependency commands use the active project/context filters and accept the same scope flags (--project, --status, --assignee, --type, --epic, --label, and --max). Status accepts a comma-separated set, for example --status "Define,To Do,Backlog". Use --link-type NAME (repeatable) to follow only specific link types.

jira issue ready --project PROJ
jira issue graph --project PROJ | jq '.edges'
jira issue graph --project PROJ --format markdown
jira issue graph-pretty --project PROJ

issue ready returns unresolved issues with no unresolved blockers, ordered by how much unresolved work they directly unblock. A blocker is resolved only when its Jira resolution field is set. issue graph returns a stable object containing nodes, directed edges, ready, blocked, and cycles. Linked blockers outside the selected scope remain in the graph with inScope: false.

issue graph-pretty builds the complete graph before rendering it as deterministic plain text. It condenses cycles, assigns dependency-depth layers, and draws each connected component from top to bottom. Continuous lines make branches and shared downstream joins visible without duplicating subtrees. Compact issue keys keep larger graphs readable; use issue graph when summaries or full node metadata are needed. graph-pretty always emits plain text regardless of --format.

Dependency graph (blocker ──▶ blocked)
Legend: ● ready  ○ blocked  ✓ resolved  ◇ external  ↻ cycle

Component 1
          ● PROJ-1
              │
       ┌──────┴──────┐
       ▼             ▼
   ○ PROJ-2      ○ PROJ-3
       │             │
       └──────┬──────┘
              ▼
          ○ PROJ-4

Configuration

Config lives at ~/.config/jira-cli/config.yml. You normally don't edit it by hand — use the jira config and jira context commands.

Profiles

Manage multiple Jira instances:

jira config init --profile work --base-url https://work.atlassian.net
jira auth login --profile work --server https://work.atlassian.net \
  --email you@work.com --token YOUR_TOKEN
jira config use work                 # switch default
jira issue list --profile work       # use a profile for one command

Boundary and context

There are two layers of scope:

Boundary (jira boundary)Context (jira context)
PurposeWhat the agent is allowed to see and changeWhat the agent is working on
Example--label safe--epic CER-33
Changed byThe user, with sudo (Administrator on Windows)Anyone, no sudo
Stored in/etc/jira-cli/boundaries/<user>.yml, root-owned (%ProgramData%\jira-cli\boundaries\ on Windows)~/.config/jira-cli/config.yml
Can be stepped out ofNeverYes: flags and raw JQL can query outside the context, but only inside the boundary
sudo jira boundary set --project CER --label safe   # user, once
jira boundary show                                  # anyone
jira context set work --project CER --epic CER-33   # agent, no sudo
jira search jql "project = CER"                     # only returns issues labelled "safe"
jira issue view CER-99                              # fails if CER-99 is not "safe"

An issue is inside the boundary when its project is one of the --project values (if any are given) and it carries every --label. While a boundary is active:

  • Searches stay inside it. Every search (issue list, search jql, mine, me audit, board and sprint issues, dependency graphs) is rewritten to (<boundary>) AND (<your JQL>). JQL that tries to break out (unbalanced parentheses or quotes) is rejected. Results that fall outside the boundary anyway are dropped.
  • Issue keys outside it fail. Any command that names an issue (view, edit, move, assign, comment, link, delete, clone, sprint add, attachments) fails if the issue is outside the boundary, before anything is sent.
  • Linked issues outside it are hidden. Links, subtasks and parents that are outside the boundary are removed from results, so their summaries don't leak.
  • New issues stay inside it. New and cloned issues must be in an allowed project and automatically get the boundary labels. Edits can't remove a boundary label.
  • Projects outside it are hidden. project list and project view only show allowed projects.
  • jira api is refused. Raw requests can't be confined to the boundary. A user running the CLI with sudo isn't confined.
  • Tampering fails closed. If the boundary file or its folders aren't owned by root (Administrators/SYSTEM on Windows), or others can write to them, every command refuses to run.

Message for AI agents

If an agent tries to change the boundary without sudo, the command fails and prints:

changing the jira boundary requires sudo (Administrator on Windows)

The boundary limits which Jira issues AI agents can see and change, so only the user may change it.
AI agents: Ask the user to run this command themselves. Never run it yourself with sudo,
and never try to bypass or weaken this protection (editing boundary files, changing file
permissions or ownership, or reusing cached sudo credentials).

Ask the user to run this command:

  sudo jira boundary set --label safe

Requests outside the boundary fail with ... is outside the jira boundary. AI agents: do not try to work around it; if you need access, ask the user.

AI agents: never run sudo jira boundary … yourself, and never try to bypass the boundary. Show the user the exact command and ask them to run it.

Caveats. The boundary is enforced by this CLI, not by Jira. For a hard guarantee, give the agent a Jira account whose permissions match the boundary. It is also only as strong as your sudo setup: if sudo doesn't ask for a password (a NOPASSWD rule, or a cached credential the agent's shell can reuse), the agent can get through. Consider Defaults timestamp_timeout=0 in sudoers. Board names and sprint metadata (not their issues), users, and me are not restricted.

Environment Variables

These override config file values and are useful in CI/automation:

VariableDescription
JIRA_BASE_URLJira server URL
JIRA_TOKENAPI token (bypasses OS keychain)
JIRA_EMAILUser email
JIRA_AUTH_TYPEAuth type: basic or pat
JIRABOT_PROFILEProfile name to use

What makes this an agent CLI

Out-of-band authentication

Authentication and execution are decoupled. A human or CI process runs jira auth login once; the token is stored in the OS keychain, never on disk. An agent never sees, requests, or routes the credential — it inherits an already-authenticated environment, and access is revoked system-side without any model trust.

Inspectable state and blast-radius control

jira context set work --project PROJ --board-id 42 --profile default writes project filters and the authentication profile to a named context. jira context use work makes that context active, while jira context list keeps every saved scope inspectable. An explicit per-command --profile still overrides the context's authentication profile. Every request, whatever the context or profile, stays inside the sudo-protected boundary.

Built for the Unix pipe

The CLI leans on standard tools (jq, rg, grep, wc) instead of a bespoke processing engine. stdout carries only the result; diagnostics, warnings, and errors go to stderr, so pipelines never choke on error output. Failed calls return a non-zero exit code, so success/failure is read from $? rather than parsed from text.

jira search jql "assignee = currentUser()" | jq -r '.[].key'
jira issue list | jq '[.[] | select(.status.name=="Done")] | length'
jira issue view CER-1 --raw | rg -o '"customfield_\d+"'

Non-mutating discovery and field projection

Read paths (search jql, search text, issue list, mine, issue view, attachment downloads) do not mutate Jira and return consistent, keyed JSON (key, fields.*). issue view includes attachment metadata by default, and issue attachment download materializes selected binary content at an explicit local path for inspection. Custom-field projection keeps responses small so an agent retrieves only what it needs via repeatable --field/-F flags; issue view also keeps --fields as a comma-separated alias.

CLI over MCP

Rather than running a Model Context Protocol server, this is a standard binary: no long-lived daemon, no open socket, no persistent state between executions. Every action is the exact execution string, so debugging is just re-running the command from shell history, and agents reuse shell operators (&&, ||, |, >) instead of learning a custom RPC protocol.

Dual-layer auditing

Verification rests on hard records, not transcripts. Shell history is a replayable record of what was attempted — every input is a command-line argument, not hidden state. jira me audit --date YYYY-MM-DD queries Jira's server-side changelog to reconstruct what actually mutated on a given day for the authenticated user, independent of local terminal state.

↑↓ navigate openesc close