jira-download-attachments
Download all attachments and inline media from a Jira ticket
Download all attachments and inline media from a Jira ticket: $ARGUMENTS
Arguments
$1: A Jira URL (e.g.,https://company.atlassian.net/browse/PROJ-123) or a Jira issue key (e.g.,PROJ-123).$2(optional): Output directory. Defaults to/tmp/<issue-key>.
Prerequisites
Use acli for Jira authentication. Before any operations, load the /arriba:acli skill for command syntax reference and verify that acli is authenticated by running acli jira auth status. If not authenticated, tell the user to run acli jira auth login --web and stop.
Steps
1. Parse arguments
Extract the issue key from $1:
- If it is a URL, extract the key from the
/browse/path segment - If it is already a key (e.g.,
PROJ-123), use it directly - Validate the format matches
[A-Z][A-Z0-9]+-[0-9]+
Set the output directory from $2, or default to /tmp/<issue-key>.
2. Extract the site and cloud ID from acli config
yq '.profiles[0].site' ~/.config/acli/global_auth_config.yaml
yq '.profiles[0].cloud_id' ~/.config/acli/global_auth_config.yaml
3. Refresh the OAuth token
Run any lightweight acli command to force a token refresh (the keychain token has a short TTL):
acli jira workitem view <ISSUE-KEY> --fields "summary" --json
4. Extract the OAuth access token from the macOS keychain
The token is stored as a gzipped, base64-encoded JSON blob under the service name acli:
CLOUD_ID=<cloud_id from step 2>
ACCOUNT_ID=$(yq '.profiles[0].account_id' ~/.config/acli/global_auth_config.yaml)
TOKEN_BLOB=$(security find-generic-password -s acli -a "oauth:${CLOUD_ID}:${ACCOUNT_ID}" -w)
ACCESS_TOKEN=$(echo "$TOKEN_BLOB" | sed 's|^go-keyring-base64:||' | base64 -d | gunzip | jq -r '.access_token')
On Linux, the keyring backend may differ. If security is not available, check secret-tool or the Go keyring file at ~/.local/share/keyrings/.
5. Fetch the issue with rendered fields
The rendered HTML maps inline media IDs to real attachment content URLs. Fetch the issue with expand=renderedFields:
curl -sS \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Accept: application/json" \
"https://api.atlassian.com/ex/jira/${CLOUD_ID}/rest/api/3/issue/<ISSUE-KEY>?expand=renderedFields&fields=attachment,description,comment" \
-o /tmp/<ISSUE-KEY>-rendered.json
6. Extract attachment IDs and filenames
Parse the rendered HTML across description and all comments to find every attachment reference. Two patterns appear in the HTML:
- Inline images:
<img src="...attachment/content/<ID>" alt="<filename>"> - File links:
<a href="...attachment/content/<ID>" ... data-attachment-name="<filename>">
Use python to extract them:
import sys, re, json
with open(sys.argv[1]) as f:
data = json.load(f)
rendered = data.get("renderedFields", {})
parts = []
desc = rendered.get("description") or ""
parts.append(desc)
comments = rendered.get("comment", {}).get("comments", [])
for c in comments:
body = c.get("body", "")
if body:
parts.append(body)
html = "\n".join(parts)
attachments = {}
# Images with alt text
for m in re.finditer(r'attachment/content/(\d+)"[^>]*?alt="([^"]+)"', html):
attachments[m.group(1)] = m.group(2)
# File links with data-attachment-name
for m in re.finditer(r'attachment/content/(\d+)"[^>]*?data-attachment-name="([^"]+)"', html):
attachments[m.group(1)] = m.group(2)
# Catch any remaining attachment URLs without a parseable filename
for m in re.finditer(r'attachment/content/(\d+)', html):
if m.group(1) not in attachments:
attachments[m.group(1)] = f"attachment-{m.group(1)}"
for aid, name in sorted(attachments.items()):
print(f"{aid}\t{name}")
If nothing is found, tell the user the ticket has no attachments or inline media.
7. Download each attachment
The Jira attachment content endpoint returns a 303 redirect to a signed media URL. Fetch the redirect URL first, then download from it:
mkdir -p <output-dir>
# For each attachment ID and filename:
REDIRECT_URL=$(curl -sS \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Accept: application/json" \
"https://api.atlassian.com/ex/jira/${CLOUD_ID}/rest/api/3/attachment/content/<ID>" \
-o /dev/null -w "%{redirect_url}")
curl -sS -L -o "<output-dir>/<filename>" "$REDIRECT_URL"
Loop over all attachment IDs from step 6 and download each one.
8. Report results
List the downloaded files with sizes:
ls -lh <output-dir>/
Print a summary: how many files were downloaded, total size, and the output directory path.