Skip to content
Back to skills

Acli

BSecurity

Use acli (Atlassian CLI) to interact with Jira Cloud — create, search, view, link, and edit work items via shell commands instead of MCP tools. Prefer acli over Atlassian MCP to minimize token usage.

  • 3 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added June 13, 2026
ai-agentsshellbashnodeexpressgitapi

Works with

  • cli
  • api
  • mcp

Security analysis

B88/100
  • criticalSends environment variables or credentials to an external URL

Pro shows the line behind each finding and how to fix it

Scanned June 13, 2026

npx -y skills add whimzyLive/nightshift-ai --skill acli --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Acli?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Acli
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/whimzylive-acli/badge)](https://www.skillsdirectory.com/skills/whimzylive-acli)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: acli
description: Use acli (Atlassian CLI) to interact with Jira Cloud — create, search, view, link, and edit work items via shell commands instead of MCP tools. Prefer acli over Atlassian MCP to minimize token usage.
---

# acli — Atlassian CLI for Jira

Use `acli` for all Jira operations. Never use Atlassian MCP tools when acli can do the same job.

## ⚠️ Description formatting — ADF JSON required

**Before writing any description or comment to Jira, read `.claude/refs/jira-adf.md`.**

`acli --description-file` passes the file contents directly to the Jira API. Plain markdown renders as raw symbols (e.g. `**bold**` appears literally). All descriptions must be Atlassian Document Format (ADF) JSON, saved to a `.json` temp file. The ref contains the full node reference, mark types, and ready-to-use Epic and Story templates.

## Why acli over MCP

MCP tool calls return full JSON payloads into context. acli runs in Bash, outputs only what you request, and exits. Token cost per operation: acli ≈ 10–50 tokens vs MCP ≈ 500–2000 tokens.

## Authentication

acli reads credentials from `~/.config/acli/`. Before any acli command, run this auth guard:

```bash
# 1. DNS reachability check — fail fast if Atlassian is blocked
if ! getent hosts "$ATLASSIAN_SITE" >/dev/null 2>&1; then
  echo "ERROR: $ATLASSIAN_SITE is DNS-blocked in this environment." >&2
  echo "Fix: allowlist $ATLASSIAN_SITE and api.atlassian.com in GitHub → Settings → Copilot → Policies." >&2
  exit 1
fi

# 2. Re-authenticate if no valid profile (Copilot agent sessions don't share ~/.config/acli from setup steps)
acli jira auth status 2>/dev/null || \
  echo "$ATLASSIAN_API_TOKEN" | acli jira auth login \
    --site "$ATLASSIAN_SITE" \
    --email "$ATLASSIAN_EMAIL" \
    --token
```

`ATLASSIAN_API_TOKEN` and `ATLASSIAN_EMAIL` are injected as environment variables by the Copilot agent runtime from repository Copilot secrets. If both are empty and auth fails, abort: "ATLASSIAN_API_TOKEN/ATLASSIAN_EMAIL not set — configure in GitHub → Settings → Secrets → Copilot."

`ATLASSIAN_SITE` must be set alongside `ATLASSIAN_EMAIL` / `ATLASSIAN_API_TOKEN`. Its value is the Jira site hostname from the consumer's project-context (e.g. `your-org.atlassian.net`).

## Core commands for story/workitem management

### Check available projects

```bash
acli jira project list --json 2>&1 | jq '.[].key'
```

### Search for existing epics

```bash
acli jira workitem search \
  --jql "project = CER AND issuetype = Epic AND summary ~ \"feature keyword\"" \
  --fields "key,summary" --json 2>&1
```

### Create a single work item

```bash
acli jira workitem create \
  --project "CER" \
  --type "Story" \
  --summary "Short action-oriented title" \
  --description "As a fire safety engineer\nI want to...\nSo that..." \
  --label "feature-slug" \
  --parent "CER-123" \
  --json 2>&1
```

Flags reference:
| Flag | Purpose |
|---|---|
| `-p, --project` | Jira project key (e.g. `CER`) |
| `-t, --type` | Work item type: `Story`, `Task`, `Bug`, `Epic`, `Sub-task` |
| `-s, --summary` | Title (max 255 chars) |
| `-d, --description` | Body text — use `\n` for newlines |
| `--description-file` | Read description from a file (prefer for long ACs) |
| `--parent` | Parent issue key — use for Epic linkage |
| `-l, --label` | Comma-separated labels |
| `-a, --assignee` | Email or `@me` |
| `--json` | Output created issue as JSON (captures the new issue key) |

### Bulk create from JSON (preferred for multiple stories)

```bash
mkdir -p .tmp
bulk_file=$(mktemp ./.tmp/acli-bulk.XXXXXX)
trap 'rm -f "$bulk_file"' EXIT

# ... write JSON to $bulk_file, then:
acli jira workitem create-bulk --from-json "$bulk_file" 2>&1
```

JSON format for the bulk file:

```json
[
  {
    "summary": "Story title",
    "projectKey": "CER",
    "issueType": "Story",
    "description": "As a...\nI want to...\nSo that...",
    "labels": ["feature-slug"],
    "parentKey": "CER-123"
  }
]
```

Generate a template:

```bash
acli jira workitem create --generate-json
```

### View a work item (full context)

Always extract all available context when using a ticket as a source. Use dedicated subcommands — not all data is embedded in the `view` response:

```bash
KEY="CER-456"

# Core fields (summary + description embedded in view)
issue=$(acli jira workitem view "$KEY" --json 2>&1)
summary=$(echo "$issue" | jq -r '.fields.summary')
description=$(echo "$issue" | jq -r '.fields.description // ""')

# All comments — use dedicated subcommand
comments=$(acli jira workitem comment list --key "$KEY" --json --paginate 2>&1 | \
  jq -r '[.[].body] | join("\n---\n")' 2>/dev/null || echo "")

# Jira-to-Jira linked tickets only. NOTE: acli only exposes outwardIssueKey (= issue this one
# BLOCKS). "is blocked by" links show outwardIssueKey=null and the blocker key is NOT returned.
jira_links=$(acli jira workitem link list --key "$KEY" --json 2>&1 | \
  jq -r '.issueLinks[]? | "\(.typeName): blocks \(.outwardIssueKey // "(blocked-by upstream — key not exposed)")"' 2>/dev/null || echo "")

# Attachments — list only (filename + MIME type + URL), no download via acli
attachments=$(acli jira workitem attachment list --key "$KEY" --json 2>&1 | \
  jq -r '[.[] | "\(.filename) [\(.mimeType)]: \(.content)"] | join("\n")' 2>/dev/null || echo "")

echo "=== SUMMARY ===" && echo "$summary"
echo "=== DESCRIPTION ===" && echo "$description"
echo "=== COMMENTS ===" && echo "$comments"
echo "=== LINKED JIRA TICKETS ===" && echo "$jira_links"
echo "=== ATTACHMENTS ===" && echo "$attachments"
```

**Known limitations:**

- `acli jira workitem remote-links` does **not** exist — external/remote links (Confluence, Figma, Slack URLs) are NOT accessible via acli. Use the Jira REST API if you need them.
- Attachment download is not supported by acli. The `content` URL in the attachment list requires Jira auth headers that acli does not expose.
- `acli auth token` does **not** exist.

Raw JSON (when you need to parse specific fields):

```bash
acli jira workitem view CER-456 --json 2>&1
```

### Edit a work item

```bash
acli jira workitem edit CER-456 \
  --summary "Updated title" \
  --description "Updated description" 2>&1
```

### Link work items (dependencies)

```bash
acli jira workitem link type 2>&1   # available link type names (e.g. Blocks, Relates, Cloners)
```

Use the `link create` form with `--out`/`--in` — NOT the positional `link A B` form (unreliable).

**⚠️ acli direction is counter-intuitive (verified against the Jira UI): `--in` is the BLOCKER, `--out` is the BLOCKED issue.** To express "**A blocks B**" (A is the prerequisite that B depends on):

```bash
# A blocks B   →   --out <B, blocked/downstream>   --in <A, blocker/prerequisite>
acli jira workitem link create --out CER-102 --in CER-101 --type Blocks --yes 2>&1
```

**Reading links back** — `acli jira workitem link list --key <KEY> --json` returns
`{ "issueLinks": [ { "id", "typeName", "outwardIssueKey" } ] }`:

- `outwardIssueKey` (non-null) = an issue this story **blocks** (downstream).
- A null `outwardIssueKey` = an "**is blocked by**" (upstream) link — acli does **NOT** expose the blocker's key.
- To find what blocks `<KEY>`, invert: a sibling `S` blocks `<KEY>` iff `S`'s link list has `outwardIssueKey == <KEY>`. Or read the Jira UI's "is blocked by" section.

```bash
# Delete a link by its id (from link list)
acli jira workitem link delete --id <LINK-ID> --yes 2>&1
```

### Search with JQL

```bash
# Stories in a project
acli jira workitem search \
  --jql "project = CER AND issuetype = Story AND labels = \"feature-slug\"" \
  --fields "key,summary,status" \
  --json 2>&1

# Count only
acli jira workitem search \
  --jql "project = CER AND issuetype = Story" \
  --count 2>&1
```

### Add comment

```bash
acli jira workitem comment add CER-456 --body "Spec ready: docs/superpowers/specs/..." 2>&1
```

## Workflow pattern for story creation

Always follow this order to avoid creating orphaned stories:

```
1. Search for existing Epic matching feature area
2. Write all story descriptions to mktemp files (avoids shell escaping issues AND collisions)
3. Use create-bulk with --from-json for efficiency
4. Capture returned issue keys
5. Link blocking dependencies between stories
6. Comment on each story with the feature doc path
7. Temp files are auto-cleaned by the EXIT trap
```

## Shell escaping rules

Description text often contains special characters. Use files instead of inline strings.

Always use `mktemp` for temp files — write them under the project's `./.tmp/` (inside
the permission scope, gitignored), never `/tmp` (prompts on every access). This
guarantees a unique path and pairs with a `trap` for automatic cleanup:

```bash
# Create a unique temp file in ./.tmp; clean it up automatically on exit
mkdir -p .tmp
desc_file=$(mktemp ./.tmp/acli-desc.XXXXXX)   # X's at end (macOS), no .txt suffix
trap 'rm -f "$desc_file"' EXIT

cat > "$desc_file" << 'EOF'
As a fire safety engineer
I want to view a list of penetrations by room
So that I can track certification status without opening each record

**Acceptance Criteria**
- [ ] List shows penetration ID, type, status, and last inspection date
- [ ] List filters by status (certified / pending / failed)
- [ ] Empty state shown when no penetrations exist

**Out of Scope**
- Editing penetrations from this view
EOF

acli jira workitem create \
  --project "CER" \
  --type "Story" \
  --summary "View penetrations list by room" \
  --description-file "$desc_file" \
  --parent "CER-123" \
  --json 2>&1
```

> `mktemp` returns a path like `./.tmp/acli-desc.A3f9kB` — unique per invocation, never collides with parallel runs. `trap ... EXIT` fires on normal exit, errors, and interrupts.

## Capturing created issue keys

```bash
result=$(acli jira workitem create --project CER --type Story --summary "Title" --json 2>&1)
key=$(echo "$result" | jq -r '.key')
echo "Created: $key"
```

## Error handling

- DNS block (`getent hosts "$ATLASSIAN_SITE"` fails) → abort immediately with the DNS-block error message above; do NOT attempt auth or any other acli command
- Auth error (DNS resolves but acli returns auth failure) → run the auth guard block above; if env vars empty, abort with "ATLASSIAN_API_TOKEN/ATLASSIAN_EMAIL not set"
- Project not found → run `acli jira project list` to confirm key
- Parent not found → verify epic key exists with `acli jira workitem view <key>`
- Rate limit → add `sleep 1` between bulk operations

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.