A command-line interface for interacting with Jira.
This project uses UV for dependency management.
pip install cac-jira
cac-jira supports two authentication methods: Basic Auth (default) and Personal Access Tokens (PAT).
On first-run, you’ll be prompted for a Jira API token; generate one here. This will be stored in your system credential store (e.g. Keychain on Mac OS) in an item called cac-jira.
Some Jira Server/Data Center instances have disabled HTTP Basic Authentication; these require Personal Access Tokens, which use Bearer token authentication.
Note: PAT authentication is only supported on Jira Server/Data Center. Jira Cloud does not support PATs — use Basic Auth with an API token instead.
To use PAT authentication, set auth_method: pat in your config file. On the next run you’ll be prompted for your PAT, which is stored in your system credential store. username is optional when using PAT authentication.
server: https://your-jira-instance.example.com
project: YOUR_PROJECT_KEY
auth_method: pat
On first-run, you’ll be prompted for your server, username (basic auth only), and default project, and a configuration file will be generated at ~/.config/cac_jira/config.yaml.
server: https://your-jira-instance.atlassian.net
project: YOUR_PROJECT_KEY # Optional default project
username: your.email@example.com
auth_method: basic # or 'pat' for Personal Access Token
The Jira CLI follows a command-action pattern for all operations:
jira <command> <action> [options]
--verbose: Enable debug output (includes a traceback for unexpected errors)--output [table|json]: Control output format (default table)--help: Show command help
Commands exit 0 on success and non-zero on failure (invalid input, a
not-found issue/project, or a Jira API error), so they compose safely in
scripts. --help and argument parsing work offline and do not require
credentials — the Jira connection is only established when a command runs.
List issues in a project:
jira issue list --project PROJ
List only issues assigned to you (and optionally include completed ones):
jira issue list --project PROJ --mine
jira issue list --project PROJ --done # include issues that are resolved
Create a new issue:
jira issue create --project PROJ --type Task --title "Fix login bug" --description "Users can't log in"
Create a new issue of a type that requires custom fields:
#
# This assumes the name of the custom fields is "Custom Field One" and "Custom Field Two";
# the field name will be swapped to lower-case, and spaces replaced with underscores
#
jira issue create --project PROJ --type Custom\ Issue\ Type --title "Issue Title" --description "Issue description" \
--field custom_field_one custom_field_value \
--field custom_field_two custom_field_value
Create and assign to yourself:
jira issue create --project PROJ --type Bug --title "Server crash" --assign
Create and immediately start work:
jira issue create --project PROJ --type Story --title "Add login feature" --begin
Add an issue to an epic:
jira issue create --project PROJ --type Task --title "Subtask" --epic PROJ-100
Label an issue:
jira issue label --issue ISSUE_KEY --labels label1,label2
Transition an issue:
jira issue begin --issue ISSUE_KEY # Start work
jira issue block --issue ISSUE_KEY # Mark as blocked
jira issue close --issue ISSUE_KEY # Mark as complete
Delete an issue (prompts for confirmation; pass --force to skip it, e.g. in scripts):
jira issue delete --issue ISSUE_KEY
jira issue delete --issue ISSUE_KEY --force
List all projects:
jira project list
Filter projects by name or key (case-insensitive, partial match):
jira project list --name "Core"
jira project list --key COR
Show a single project by its key:
jira project show PROJ
Update an issue’s title or description:
jira issue update --issue ISSUE_KEY --title "New issue title" --description "new issue description"
Add a comment to an issue:
jira issue comment --issue ISSUE_KEY --comment "This is a comment."
List all issue IDs matching a label:
jira issue list --output json | jq -r '.[] | select(.Labels | contains("production")) | .ID'
jira supports tab-completion of commands, actions, and options via
argcomplete.
The recommended approach is per-command registration. Add the appropriate line to your shell startup file:
# bash (~/.bashrc) or zsh (~/.zshrc)
eval "$(register-python-argcomplete jira)"
Then restart your shell (or source the file). Tab-completion works
immediately:
jira <TAB> # -> issue project
jira issue <TAB> # -> assign attach begin ... show update
jira issue show --<TAB> # -> --issue --output --project --verbose
# Install dependencies including dev dependencies
uv sync
# Activate the venv
source .venv/bin/activate
# Run tests
uv run pytest
cac_jira/__init__.py - Module init: the CONFIG/JIRA_CLIENT globals and
the main console-script entry point (main = make_main("cac_jira", "jira", ...))cac_jira/commands/ - Command implementations (auto-discovered at runtime)
issue/ - Issue-related commandsproject/ - Project-related commandscac_jira/core/client.py - Thin wrapper around the jira Python clientCommand discovery, argument parsing, shell completion, and dispatch are all
provided by the shared runner in cac-core
(cac_core.cli.run / make_main); this project only supplies the commands/
tree and its Jira client.
{Command}{Action} naming convention (e.g. commands/issue/create.py →
IssueCreate).define_arguments() and execute() methods.execute() contains the command’s logic and returns an exit code (0/None
for success, non-zero for validation failures). It does not need to wrap Jira
calls in try/except: the shared run() template (from cac-core) catches
errors and maps them to a non-zero exit code, and JiraCommand.handle_exception
renders JIRAErrors using their human-readable Jira message.