From 3f37df03808de7625567d1a280fff7081bb03d5d Mon Sep 17 00:00:00 2001 From: sofq Date: Tue, 21 Apr 2026 14:56:07 +0700 Subject: [PATCH 1/3] docs: clarify ADF-only comment formatting in agent guides Jira Cloud v3 accepts only ADF for rich content; markdown and wiki markup render as literal text. Agents were defaulting to `workflow comment --text` with markdown syntax and silently getting plain paragraphs. Document the two paths (plain text vs raw ADF), show a realistic multi-node example, and list the common ADF node types so agents can compose rich comments without guessing. --- CLAUDE.md | 33 ++++++++++++++++++++++++-- skill/jira-cli/SKILL.md | 51 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index fb19828..13eb894 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,6 +17,7 @@ jr configure --profile myprofile --delete # remove a profile - **Use `--fields`** to limit Jira response fields: `jr issue get --issueIdOrKey PROJ-1 --fields key,summary,status` - **Use `jr batch`** to run multiple operations in one call - **Use `jr raw`** for any API endpoint not covered by generated commands +- **Jira Cloud v3 uses ADF, not markdown.** Any rich-content field (comment body, issue description) must be an ADF JSON document. Markdown (`**bold**`, `- item`, `# h1`) and wiki markup (`*bold*`, `h1.`) are NOT parsed — they render as literal characters. `jr workflow comment --text` wraps your string in a single ADF paragraph; for anything richer, use `jr issue add-comment --body ` (see examples below). ## Common Operations @@ -43,7 +44,7 @@ jr workflow assign --issue PROJ-123 --to "none" # Transition + optional assign in one call jr workflow move --issue PROJ-123 --to "In Progress" --assign me -# Add comment (plain text, auto-converted to ADF) +# Add comment (plain text only — wrapped in a single ADF paragraph; markdown/wiki syntax is NOT parsed) jr workflow comment --issue PROJ-123 --text "This is done" # Create issue from flags (no raw JSON) @@ -64,9 +65,37 @@ jr issue edit --issueIdOrKey PROJ-123 --body '{"fields":{"summary":"Updated titl # Delete issue jr issue delete --issueIdOrKey PROJ-123 -# Add comment (raw API — prefer `workflow comment` above) +# Add comment (raw ADF — REQUIRED for any rich formatting: headings, lists, code blocks, panels, tables, links, etc.) +# Jira Cloud REST v3 accepts ONLY ADF (JSON). Markdown and wiki markup are NOT parsed — they render as literal text. +# Use `--body @/path/to/file.json` for multi-node documents. jr issue add-comment --issueIdOrKey PROJ-123 --body '{"body":{"type":"doc","version":1,"content":[{"type":"paragraph","content":[{"text":"A comment","type":"text"}]}]}}' +# Rich comment example — headings, bold/italic/code marks, bullet list, code block, info panel +jr issue add-comment --issueIdOrKey PROJ-123 --body '{"body":{"type":"doc","version":1,"content":[ + {"type":"heading","attrs":{"level":2},"content":[{"type":"text","text":"Deploy summary"}]}, + {"type":"paragraph","content":[ + {"type":"text","text":"Shipped "}, + {"type":"text","text":"v1.4.2","marks":[{"type":"strong"}]}, + {"type":"text","text":" at "}, + {"type":"text","text":"14:20 UTC","marks":[{"type":"code"}]}, + {"type":"text","text":"."} + ]}, + {"type":"bulletList","content":[ + {"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"migrations applied"}]}]}, + {"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"smoke tests green"}]}]} + ]}, + {"type":"codeBlock","attrs":{"language":"bash"},"content":[{"type":"text","text":"kubectl rollout status deploy/api"}]}, + {"type":"panel","attrs":{"panelType":"info"},"content":[{"type":"paragraph","content":[{"type":"text","text":"Monitor dashboards for the next hour."}]}]} +]}}' + +# Common ADF node types to know: +# Block: paragraph, heading (level 1-6), bulletList/orderedList + listItem, +# taskList + taskItem (attrs.state: TODO|DONE), blockquote, codeBlock (attrs.language), +# panel (attrs.panelType: info|warning|success|error|note), rule, table + tableRow + tableHeader/tableCell, +# expand (attrs.title) +# Inline: text (with marks: strong, em, strike, underline, code, textColor, link), +# emoji (attrs.shortName), status (attrs.text, attrs.color), mention, hardBreak + # Raw API call (method is positional, not a flag; POST/PUT/PATCH require --body) jr raw GET /rest/api/3/myself jr raw POST /rest/api/3/search/jql --body '{"jql":"project=PROJ"}' diff --git a/skill/jira-cli/SKILL.md b/skill/jira-cli/SKILL.md index 59769b9..3a6312e 100644 --- a/skill/jira-cli/SKILL.md +++ b/skill/jira-cli/SKILL.md @@ -129,11 +129,57 @@ jr workflow assign --issue PROJ-123 --to "none" ``` ### Add a comment + +**Important:** Jira Cloud REST v3 stores rich content as **ADF (Atlassian Document Format)** — a JSON document tree. **Markdown (`**bold**`, `- item`, `# h1`) and wiki markup (`*bold*`, `h1.`) are NOT parsed** by the API; they render as literal characters. There are two paths: + ```bash -# Plain text, auto-converted to ADF +# Plain text — wrapped in a single ADF paragraph (no formatting, no markdown parsing). +# Use this for simple one-line comments. jr workflow comment --issue PROJ-123 --text "This is done" ``` +```bash +# Rich content — write ADF directly. REQUIRED for headings, lists, code blocks, panels, tables, links, colored text, etc. +# Prefer --body @file.json for multi-node documents (shell-escaping large JSON is painful). +jr issue add-comment --issueIdOrKey PROJ-123 --body @comment.adf.json +``` + +**Minimal ADF envelope:** +```json +{"body": {"type": "doc", "version": 1, "content": [ ...nodes... ]}} +``` + +**Rich comment example** (heading + marks + list + code block + panel): +```json +{"body": {"type": "doc", "version": 1, "content": [ + {"type": "heading", "attrs": {"level": 2}, "content": [{"type": "text", "text": "Deploy summary"}]}, + {"type": "paragraph", "content": [ + {"type": "text", "text": "Shipped "}, + {"type": "text", "text": "v1.4.2", "marks": [{"type": "strong"}]}, + {"type": "text", "text": " at "}, + {"type": "text", "text": "14:20 UTC", "marks": [{"type": "code"}]}, + {"type": "text", "text": "."} + ]}, + {"type": "bulletList", "content": [ + {"type": "listItem", "content": [{"type": "paragraph", "content": [{"type": "text", "text": "migrations applied"}]}]}, + {"type": "listItem", "content": [{"type": "paragraph", "content": [{"type": "text", "text": "smoke tests green"}]}]} + ]}, + {"type": "codeBlock", "attrs": {"language": "bash"}, "content": [{"type": "text", "text": "kubectl rollout status deploy/api"}]}, + {"type": "panel", "attrs": {"panelType": "info"}, "content": [{"type": "paragraph", "content": [{"type": "text", "text": "Monitor dashboards for the next hour."}]}]} +]}} +``` + +**Common ADF node types:** + +| Category | Nodes | +|----------|-------| +| Block | `paragraph`, `heading` (`attrs.level` 1–6), `bulletList` / `orderedList` + `listItem`, `taskList` + `taskItem` (`attrs.state`: `TODO`\|`DONE`), `blockquote`, `codeBlock` (`attrs.language`), `panel` (`attrs.panelType`: `info`\|`warning`\|`success`\|`error`\|`note`), `rule`, `table` + `tableRow` + `tableHeader` / `tableCell`, `expand` (`attrs.title`) | +| Inline | `text` with `marks` (`strong`, `em`, `strike`, `underline`, `code`, `textColor` with `attrs.color` hex, `link` with `attrs.href`), `emoji` (`attrs.shortName`), `status` (`attrs.text`, `attrs.color`), `mention` (`attrs.id`), `hardBreak` | + +Note: a few nodes (`status`, `expand`, `taskList`) depend on site edition / feature flags — if they render as blank, the target instance may not support them. + +**Converting markdown → ADF:** there is no built-in flag today. Either (a) hand-author ADF, (b) use an external converter (e.g. the Atlassian `md-to-adf` lib) and pipe the result into `--body @-`, or (c) fall back to `workflow comment` for plain text only. + ### Link issues ```bash # Resolves link type ID automatically @@ -294,7 +340,8 @@ Batch uses `"resource verb"` strings matching `jr schema` output. Hand-written c | `jr workflow assign` | `"workflow assign"` | | `jr workflow move` | `"workflow move"` | | `jr workflow create` | `"workflow create"` | -| `jr workflow comment` | `"workflow comment"` | +| `jr workflow comment` | `"workflow comment"` (plain text only) | +| `jr issue add-comment` | `"issue add-comment"` (raw ADF for rich formatting) | | `jr workflow link` | `"workflow link"` | | `jr workflow log-work` | `"workflow log-work"` | | `jr workflow sprint` | `"workflow sprint"` | From f184d314690856496e104a04bae795723e7f3fae Mon Sep 17 00:00:00 2001 From: sofq Date: Tue, 21 Apr 2026 20:30:17 +0700 Subject: [PATCH 2/3] feat: add Claude Code plugin support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add .claude-plugin/plugin.json so the repo is installable via /plugin marketplace add and /plugin install without manual file copying. Rename skill/ → skills/ to match the Claude Code plugin convention. Update README and skill-setup.md with plugin install as the primary method. --- .claude-plugin/plugin.json | 8 ++++++++ README.md | 13 ++++++++++--- {skill => skills}/jira-cli/SKILL.md | 0 website/guide/skill-setup.md | 29 +++++++++++++++++++---------- 4 files changed, 37 insertions(+), 13 deletions(-) create mode 100644 .claude-plugin/plugin.json rename {skill => skills}/jira-cli/SKILL.md (100%) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..e8ed0e4 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,8 @@ +{ + "name": "jira-cli", + "description": "Agent-friendly Jira CLI with JSON output, semantic exit codes, JQL search, batch operations, and workflow commands.", + "author": { + "name": "sofq" + }, + "homepage": "https://github.com/sofq/jira-cli" +} diff --git a/README.md b/README.md index 7fc5ee1..218087c 100644 --- a/README.md +++ b/README.md @@ -192,10 +192,17 @@ jr raw POST /rest/api/3/search/jql --body '{"jql":"project=PROJ"}' ## 🤖 Agent Integration -### Claude Code skill (included) +### Claude Code plugin (included) ```bash -cp -r skill/jira-cli ~/.claude/skills/ # global install +/plugin marketplace add https://github.com/sofq/jira-cli +/plugin install jira-cli@jira-cli +``` + +Or install the skill manually: + +```bash +cp -r skills/jira-cli ~/.claude/skills/ # global install ``` ### Any agent @@ -208,7 +215,7 @@ Use `jr schema` to discover commands. Use --jq to reduce tokens. ``` > [!NOTE] -> See [`skill/jira-cli/SKILL.md`](skill/jira-cli/SKILL.md) for the full agent integration guide with patterns, error handling, and token optimization strategies. +> See [`skills/jira-cli/SKILL.md`](skills/jira-cli/SKILL.md) for the full agent integration guide with patterns, error handling, and token optimization strategies.

↑ Back to top

diff --git a/skill/jira-cli/SKILL.md b/skills/jira-cli/SKILL.md similarity index 100% rename from skill/jira-cli/SKILL.md rename to skills/jira-cli/SKILL.md diff --git a/website/guide/skill-setup.md b/website/guide/skill-setup.md index ae06a40..d6976be 100644 --- a/website/guide/skill-setup.md +++ b/website/guide/skill-setup.md @@ -7,31 +7,28 @@ The skill follows the [Agent Skills](https://agentskills.io) open standard, supp ## Download the Skill ```bash -curl -sL https://raw.githubusercontent.com/sofq/jira-cli/main/skill/jira-cli/SKILL.md \ +curl -sL https://raw.githubusercontent.com/sofq/jira-cli/main/skills/jira-cli/SKILL.md \ -o SKILL.md ``` Or copy from a local `jr` install: ```bash -cp $(brew --prefix jr)/share/jr/skill/jira-cli/SKILL.md SKILL.md +cp $(brew --prefix jr)/share/jr/skills/jira-cli/SKILL.md SKILL.md ``` ## Setup by Tool ### Claude Code -| Scope | Path | -|-------|------| -| Project (shared via git) | `.claude/skills/jira-cli/SKILL.md` | -| Personal (all projects) | `~/.claude/skills/jira-cli/SKILL.md` | +**Plugin install (recommended)** — installs the skill and auto-updates with new releases: -```bash -mkdir -p .claude/skills/jira-cli -cp SKILL.md .claude/skills/jira-cli/SKILL.md +``` +/plugin marketplace add https://github.com/sofq/jira-cli +/plugin install jira-cli@jira-cli ``` -Grant permission to run `jr`: +Grant permission to run `jr` once after install: ```json // .claude/settings.json @@ -42,6 +39,18 @@ Grant permission to run `jr`: } ``` +**Manual install** — copy the skill file into Claude's skills directory: + +| Scope | Path | +|-------|------| +| Project (shared via git) | `.claude/skills/jira-cli/SKILL.md` | +| Personal (all projects) | `~/.claude/skills/jira-cli/SKILL.md` | + +```bash +mkdir -p .claude/skills/jira-cli +cp SKILL.md .claude/skills/jira-cli/SKILL.md +``` + Claude loads the skill automatically when you mention Jira, or invoke it directly with `/jira-cli`. See [Claude Code skills docs](https://code.claude.com/docs/en/skills) for more details. From da9eb6767b00762367b3bc0bb2d1da87ea86595c Mon Sep 17 00:00:00 2001 From: sofq Date: Tue, 21 Apr 2026 20:44:13 +0700 Subject: [PATCH 3/3] docs(jira-cli skill): restructure SKILL.md with progressive disclosure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Split the 3,251-word monolithic SKILL.md into a tight 1,333-word entry point plus four reference files that load on demand. Trims the description to trigger-only phrasing and keeps the reconsile warning, ADF requirement, error-code table, and common mistakes inline. New references/: - adf.md — ADF envelope, node catalog, marks, worked example - operations.md — per-op detail and agent patterns - batch.md — command-name lookup and args conventions - config.md — auth, profiles, operation policy, audit --- skills/jira-cli/SKILL.md | 622 +++++------------------ skills/jira-cli/references/adf.md | 163 ++++++ skills/jira-cli/references/batch.md | 153 ++++++ skills/jira-cli/references/config.md | 213 ++++++++ skills/jira-cli/references/operations.md | 417 +++++++++++++++ 5 files changed, 1082 insertions(+), 486 deletions(-) create mode 100644 skills/jira-cli/references/adf.md create mode 100644 skills/jira-cli/references/batch.md create mode 100644 skills/jira-cli/references/config.md create mode 100644 skills/jira-cli/references/operations.md diff --git a/skills/jira-cli/SKILL.md b/skills/jira-cli/SKILL.md index 3a6312e..6dafedc 100644 --- a/skills/jira-cli/SKILL.md +++ b/skills/jira-cli/SKILL.md @@ -1,561 +1,211 @@ --- name: jira-cli -description: "Use when the user asks to interact with Jira — issues, projects, sprints, boards, workflows, tickets, JQL searches, or any Jira Cloud operation. Also use when you see `jr` CLI commands in the codebase. Covers setup, discovery, batch ops, templates, and error handling." +description: "Use when the user asks to interact with Jira — issues, projects, sprints, boards, workflows, tickets, JQL searches, or any Jira Cloud operation. Also use when you see `jr` CLI commands in the codebase." --- # jr — Jira CLI for AI Agents -`jr` is a Jira Cloud CLI designed for AI agents. Every command returns structured JSON on stdout, errors as JSON on stderr, and semantic exit codes — so you can parse, branch, and retry reliably. +`jr` is a Jira Cloud CLI built for agents. Every command returns structured JSON on stdout, errors as JSON on stderr, and semantic exit codes — so you can parse, branch, and retry reliably. -**Sections:** [Setup](#setup) · [Discovering Commands](#discovering-commands) · [Common Operations](#common-operations) · [Token Efficiency](#token-efficiency) · [Batch Operations](#batch-operations) · [Error Handling](#error-handling) · [Global Flags](#global-flags) · [Common Agent Patterns](#common-agent-patterns) · [Security](#security) · [Troubleshooting](#troubleshooting) +## Core principles -## Setup +1. **Minimize output.** A raw issue GET returns ~10K tokens. Use `--preset`, `--fields`, or `--jq` — combined they cut that to ~50. +2. **Discover at runtime.** 600+ commands auto-generated from Jira's OpenAPI spec. Don't guess names — run `jr schema`. +3. **Branch on exit codes.** `0`=ok, `2`=auth, `3`=not_found, `4`=validation, `5`=rate_limited, `6`=conflict, `7`=server_error. +4. **Rich content requires ADF.** Jira Cloud v3 does **not** parse markdown or wiki markup — only ADF JSON. See [references/adf.md](references/adf.md). -If `jr` is not configured yet, help the user set it up: +## Setup ```bash -# Check if jr is installed -jr version - -# Configure with Jira Cloud credentials jr configure --base-url https://yoursite.atlassian.net --token YOUR_API_TOKEN --username your@email.com +jr configure --test # verify credentials ``` -**Auth types:** `basic` (default, username + API token), `bearer`, `oauth2` +Env vars: `JR_BASE_URL`, `JR_AUTH_TOKEN`, `JR_AUTH_USER`, `JR_AUTH_TYPE`, `JR_CONFIG_PATH`. Named profiles via `--profile `. -> **Note:** `oauth2` profiles cannot be set up via `jr configure` — they must be configured manually in the config file (`~/.config/jr/config.json`) with `client_id`, `client_secret`, and `token_url`. +Auth failure (exit 2)? Token is likely expired — regenerate at https://id.atlassian.com/manage-profile/security/api-tokens. -**Config resolution order:** CLI flags > environment variables > config file +Full auth, profiles, env vars, policy, and audit docs: [references/config.md](references/config.md). -```bash -# Environment variables (useful for CI/containers) -export JR_BASE_URL=https://yoursite.atlassian.net -export JR_AUTH_TOKEN=your-api-token -export JR_AUTH_TYPE=basic # auth type (basic, bearer, oauth2) -export JR_AUTH_USER=your@email # username for basic auth -export JR_CONFIG_PATH=/path/to/config.json # override config file location - -# Named profiles for multiple Jira instances -jr configure --base-url https://work.atlassian.net --token TOKEN --profile work -jr issue get --profile work --issueIdOrKey PROJ-1 -jr configure --profile work --delete # remove a profile (--profile is required) -``` +## Discovery ```bash -# Validate credentials before saving (or test an existing profile) -jr configure --base-url https://yoursite.atlassian.net --token TOKEN --username your@email.com --test -jr configure --test # test the default profile's saved credentials -jr configure --test --profile work # test a specific profile +jr schema # resource → verbs (start here) +jr schema issue # all operations for a resource +jr schema issue get # full flags for one operation ``` -If you get exit code 2 (auth error), the token is likely expired or wrong. Ask the user to generate a new API token at https://id.atlassian.com/manage-profile/security/api-tokens. +Command names come straight from Jira's OpenAPI spec and can be verbose (e.g. `search-and-reconsile-issues-using-jql`). **"reconsile" is not a typo — do not "fix" it to "reconcile".** -## Discovering Commands +## Token efficiency -`jr` has 600+ commands auto-generated from Jira's OpenAPI spec. You don't need to memorize them — discover at runtime: +Three filters, use all three for max compression: ```bash -jr schema # resource → verbs mapping (default, most useful overview) -jr schema --list # all resource names only (issue, project, search, ...) -jr schema issue # all operations for a resource -jr schema issue get # full schema with all flags for one operation -``` - -Always use `jr schema` to discover the exact command name and flags before running an unfamiliar operation. Command names come directly from Jira's API and can be verbose (e.g., `search-and-reconsile-issues-using-jql`). +# --preset: named field set (agent, detail, triage, board) +jr issue get --issueIdOrKey PROJ-1 --preset agent -`jr schema` (no flags) defaults to the compact resource→verbs mapping, which is the most useful starting point. +# --fields: server-side filter (Jira only returns these fields) +jr issue get --issueIdOrKey PROJ-1 --fields key,summary,status -## Common Operations +# --jq: client-side shaping +jr issue get --issueIdOrKey PROJ-1 --jq '{key, summary: .fields.summary}' -### Get an issue -```bash -jr issue get --issueIdOrKey PROJ-123 +# Combined — ~10K tokens → ~50 +jr issue get --issueIdOrKey PROJ-1 --fields key,summary --jq '{key, summary: .fields.summary}' ``` -### Search issues with JQL -```bash -# Use the search resource (not the deprecated /search endpoint) -# NOTE: "reconsile" is NOT a typo — it matches Jira's API spec. Do not "fix" it to "reconcile". -jr search search-and-reconsile-issues-using-jql \ - --jql "project = PROJ AND status = 'In Progress'" \ - --jq '[.issues[] | {key, summary: .fields.summary, status: .fields.status.name}]' -``` +Preset field sets: -### Create an issue -```bash -# From flags (preferred — no raw JSON needed) -jr workflow create --project PROJ --type Bug --summary "Login broken" --description "Steps to reproduce..." --priority High --labels bug,urgent --assign me +| Preset | Fields | +|--------|--------| +| `agent` | key, summary, status, assignee, type, priority | +| `detail` | `agent` + description, comments, subtasks, links | +| `triage` | key, summary, status, priority, created, updated, reporter | +| `board` | key, summary, status, assignee, sprint, story points, type | -# From raw JSON (for advanced fields) -jr issue create-issue --body '{"fields":{"project":{"key":"PROJ"},"summary":"Bug title","issuetype":{"name":"Bug"}}}' -``` +Also: `--cache 5m` for read-heavy data, `--no-paginate` to skip auto-pagination, `jr preset list` for user-defined presets. -### Edit an issue -```bash -jr issue edit --issueIdOrKey PROJ-123 --body '{"fields":{"summary":"Updated title"}}' -``` +## Common operations -### Delete an issue ```bash -jr issue delete --issueIdOrKey PROJ-123 -``` +# Get +jr issue get --issueIdOrKey PROJ-1 --preset agent -### Transition an issue (by status name) -```bash -# No need to look up transition IDs — jr resolves them automatically -jr workflow transition --issue PROJ-123 --to "Done" -jr workflow transition --issue PROJ-123 --to "In Progress" -``` - -> **Note on `--to` flag:** Multiple commands use `--to` with different semantics: -> - `workflow transition/move --to` = target **status** name (e.g. "Done", "In Progress") -> - `workflow assign --to` = **person** (email, display name, "me", or "none") -> - `workflow sprint --to` = **sprint** name (e.g. "Sprint 5") -> - `workflow link --to` = target **issue** key (e.g. "PROJ-2") - -### Transition + assign in one step -```bash -jr workflow move --issue PROJ-123 --to "In Progress" --assign me -``` - -### Assign an issue -```bash -# "me" resolves to the authenticated user's account ID -jr workflow assign --issue PROJ-123 --to "me" -# Also accepts email or display name -jr workflow assign --issue PROJ-123 --to "john@company.com" -# Unassign -jr workflow assign --issue PROJ-123 --to "none" -``` - -### Add a comment +# Search with JQL (not the deprecated /search endpoint) +jr search search-and-reconsile-issues-using-jql \ + --jql "project = PROJ AND status = 'In Progress'" \ + --jq '[.issues[] | {key, summary: .fields.summary}]' -**Important:** Jira Cloud REST v3 stores rich content as **ADF (Atlassian Document Format)** — a JSON document tree. **Markdown (`**bold**`, `- item`, `# h1`) and wiki markup (`*bold*`, `h1.`) are NOT parsed** by the API; they render as literal characters. There are two paths: +# Create from flags (no raw JSON) +jr workflow create --project PROJ --type Bug --summary "Login broken" --priority High --assign me -```bash -# Plain text — wrapped in a single ADF paragraph (no formatting, no markdown parsing). -# Use this for simple one-line comments. -jr workflow comment --issue PROJ-123 --text "This is done" -``` +# Edit +jr issue edit --issueIdOrKey PROJ-1 --body '{"fields":{"summary":"Updated"}}' -```bash -# Rich content — write ADF directly. REQUIRED for headings, lists, code blocks, panels, tables, links, colored text, etc. -# Prefer --body @file.json for multi-node documents (shell-escaping large JSON is painful). -jr issue add-comment --issueIdOrKey PROJ-123 --body @comment.adf.json -``` +# Delete +jr issue delete --issueIdOrKey PROJ-1 -**Minimal ADF envelope:** -```json -{"body": {"type": "doc", "version": 1, "content": [ ...nodes... ]}} -``` +# Transition (by status name — IDs resolved automatically) +jr workflow transition --issue PROJ-1 --to "Done" -**Rich comment example** (heading + marks + list + code block + panel): -```json -{"body": {"type": "doc", "version": 1, "content": [ - {"type": "heading", "attrs": {"level": 2}, "content": [{"type": "text", "text": "Deploy summary"}]}, - {"type": "paragraph", "content": [ - {"type": "text", "text": "Shipped "}, - {"type": "text", "text": "v1.4.2", "marks": [{"type": "strong"}]}, - {"type": "text", "text": " at "}, - {"type": "text", "text": "14:20 UTC", "marks": [{"type": "code"}]}, - {"type": "text", "text": "."} - ]}, - {"type": "bulletList", "content": [ - {"type": "listItem", "content": [{"type": "paragraph", "content": [{"type": "text", "text": "migrations applied"}]}]}, - {"type": "listItem", "content": [{"type": "paragraph", "content": [{"type": "text", "text": "smoke tests green"}]}]} - ]}, - {"type": "codeBlock", "attrs": {"language": "bash"}, "content": [{"type": "text", "text": "kubectl rollout status deploy/api"}]}, - {"type": "panel", "attrs": {"panelType": "info"}, "content": [{"type": "paragraph", "content": [{"type": "text", "text": "Monitor dashboards for the next hour."}]}]} -]}} -``` +# Transition + assign in one call +jr workflow move --issue PROJ-1 --to "In Progress" --assign me -**Common ADF node types:** +# Assign (also: email, display name, "none") +jr workflow assign --issue PROJ-1 --to me -| Category | Nodes | -|----------|-------| -| Block | `paragraph`, `heading` (`attrs.level` 1–6), `bulletList` / `orderedList` + `listItem`, `taskList` + `taskItem` (`attrs.state`: `TODO`\|`DONE`), `blockquote`, `codeBlock` (`attrs.language`), `panel` (`attrs.panelType`: `info`\|`warning`\|`success`\|`error`\|`note`), `rule`, `table` + `tableRow` + `tableHeader` / `tableCell`, `expand` (`attrs.title`) | -| Inline | `text` with `marks` (`strong`, `em`, `strike`, `underline`, `code`, `textColor` with `attrs.color` hex, `link` with `attrs.href`), `emoji` (`attrs.shortName`), `status` (`attrs.text`, `attrs.color`), `mention` (`attrs.id`), `hardBreak` | +# Plain-text comment (wrapped in a single ADF paragraph; markdown renders literally) +jr workflow comment --issue PROJ-1 --text "LGTM" -Note: a few nodes (`status`, `expand`, `taskList`) depend on site edition / feature flags — if they render as blank, the target instance may not support them. +# Rich comment — requires ADF; see references/adf.md +jr issue add-comment --issueIdOrKey PROJ-1 --body @comment.adf.json -**Converting markdown → ADF:** there is no built-in flag today. Either (a) hand-author ADF, (b) use an external converter (e.g. the Atlassian `md-to-adf` lib) and pipe the result into `--body @-`, or (c) fall back to `workflow comment` for plain text only. - -### Link issues -```bash -# Resolves link type ID automatically +# Link issues (link-type name resolves automatically) jr workflow link --from PROJ-1 --to PROJ-2 --type blocks -``` - -### Log work -```bash -# Human-friendly duration (e.g. 2h, 1d 3h, 45m) -jr workflow log-work --issue PROJ-123 --time "2h 30m" --comment "Debugging" -``` - -### Move to sprint -```bash -# Resolves sprint ID by name automatically -jr workflow sprint --issue PROJ-123 --to "Sprint 5" -``` - -### Watch for changes (NDJSON stream) -```bash -# Poll a JQL query and emit events as NDJSON (one JSON object per line) -jr watch --jql "project = PROJ AND updated > -5m" --interval 30s - -# Watch with a preset for output shaping -jr watch --jql "status changed" --interval 1m --preset triage - -# Watch a single issue -jr watch --issue PROJ-123 --interval 10s - -# Stop after N events -jr watch --jql "project = PROJ" --max-events 10 -``` - -Events: `initial` (first poll), `created`, `updated`, `removed`. - -**Important:** Always use `--max-events` when calling from an automated/agent context — agents cannot send Ctrl-C (SIGINT) to stop the stream. - -### Create issues from templates -```bash -# List available templates (bug-report, story, task, epic, subtask, spike) -jr template list - -# Show a template's variables and fields -jr template show bug-report - -# Create issue from a template (no raw JSON needed) -jr template apply bug-report --project PROJ --var summary="Login broken" --var severity=High --assign me - -# Create a sub-task from template -jr template apply subtask --project PROJ --var summary="Fix auth" --var parent=PROJ-100 - -# Create a user-defined template from an existing issue -jr template create my-template --from PROJ-123 -# Overwrite an existing user-defined template -jr template create my-template --from PROJ-123 --overwrite -``` - -User-defined templates are stored in `~/.config/jr/templates/` as YAML files. - -### Diff / Changelog -```bash -# All changes on an issue -jr diff --issue PROJ-123 +# Log work (human-friendly duration) +jr workflow log-work --issue PROJ-1 --time "2h 30m" --comment "Debugging" -# Changes in last 2 hours -jr diff --issue PROJ-123 --since 2h +# Sprint (by name) +jr workflow sprint --issue PROJ-1 --to "Sprint 5" -# Changes since a specific date -jr diff --issue PROJ-123 --since 2025-01-01 +# Template apply (built-ins: bug-report, story, task, epic, subtask, spike) +jr template apply bug-report --project PROJ --var summary="Login broken" --var severity=High -# Only status changes -jr diff --issue PROJ-123 --field status -``` +# Changelog +jr diff --issue PROJ-1 --since 2h -### List projects -```bash -jr project search --jq '[.values[] | {key, name}]' -``` +# Watch (NDJSON stream — ALWAYS use --max-events in automated contexts) +jr watch --jql "project = PROJ AND updated > -5m" --interval 30s --max-events 20 -### Raw API call (escape hatch) -```bash -# For any endpoint not covered by generated commands +# Raw API escape hatch (POST/PUT/PATCH require --body) jr raw GET /rest/api/3/myself -jr raw POST /rest/api/3/some/endpoint --body '{"key":"value"}' -jr raw POST /rest/api/3/some/endpoint --body @request.json -# Read body from stdin (must use --body - explicitly) -echo '{"key":"value"}' | jr raw POST /rest/api/3/some/endpoint --body - -# Pass query parameters (repeatable) -jr raw GET /rest/api/3/issue --query "fields=summary" --query "expand=changelog" +jr raw POST /rest/api/3/search/jql --body '{"jql":"project=PROJ"}' ``` -**Note:** POST/PUT/PATCH require `--body`. Without it, `jr raw` will error instead of hanging on stdin. Use `--query key=value` (repeatable) to append query parameters to any raw request. - -## Token Efficiency - -Jira responses can be huge (10K+ tokens for a single issue). Always minimize output: - -```bash -# --preset: use a named preset for common field combinations -jr issue get --issueIdOrKey PROJ-123 --preset agent # key, summary, status, assignee, type, priority -jr issue get --issueIdOrKey PROJ-123 --preset detail # above + description, comments, subtasks, links -jr issue get --issueIdOrKey PROJ-123 --preset triage # key, summary, status, priority, created, updated, reporter -jr issue get --issueIdOrKey PROJ-123 --preset board # key, summary, status, assignee, sprint, story points, type - -# List all available presets -jr preset list - -# --fields: tell Jira to return only these fields (server-side filtering) -jr issue get --issueIdOrKey PROJ-123 --fields key,summary,status,assignee - -# --jq: filter the JSON response (client-side filtering) -jr issue get --issueIdOrKey PROJ-123 --jq '{key: .key, summary: .fields.summary}' - -# Combine both for maximum efficiency (~50 tokens vs ~10,000) -jr issue get --issueIdOrKey PROJ-123 --fields key,summary --jq '{key: .key, summary: .fields.summary}' - -# Cache read-heavy data to avoid redundant API calls -jr project search --cache 5m --jq '[.values[].key]' -``` - -**Always use `--preset` or `--fields` + `--jq`.** `--preset` gives you common field sets with zero effort. `--fields` reduces what Jira sends back, `--jq` shapes the output into exactly what you need. - -### Preset field reference - -| Preset | Server-side fields requested | -|--------|------------------------------| -| `agent` | `key,summary,status,assignee,issuetype,priority` | -| `detail` | `key,summary,status,assignee,issuetype,priority,description,comment,subtasks,issuelinks` | -| `triage` | `key,summary,status,priority,created,updated,reporter` | -| `board` | `key,summary,status,assignee,customfield_10020,customfield_10028,issuetype` | - -The response is standard Jira JSON with `--fields` applied — the `fields` object contains only the requested fields. Use `--jq` to further shape the output (e.g., `--jq '{key, summary: .fields.summary}'`). - -User-defined presets can include a `jq` filter; store them in `~/.config/jr/presets.json`. - -## Batch Operations - -When you need multiple Jira calls, use `jr batch` to run them in a single process: - -```bash -echo '[ - {"command": "issue get", "args": {"issueIdOrKey": "PROJ-1"}, "jq": ".key"}, - {"command": "issue get", "args": {"issueIdOrKey": "PROJ-2"}, "jq": ".fields.summary"}, - {"command": "project search", "args": {}, "jq": "[.values[].key]"} -]' | jr batch -``` - -**Batch exit code:** The process exit code is the highest-severity exit code from all operations. If one op returns 0 and another returns 5 (rate_limited), the batch process exits with 5. Check individual `exit_code` fields for per-operation status. - -### Batch command names - -Batch uses `"resource verb"` strings matching `jr schema` output. Hand-written commands use their full resource+verb name: - -| CLI command | Batch `"command"` string | +**Watch for `--to` overloading:** +| Command | `--to` means | |---|---| -| `jr workflow transition` | `"workflow transition"` | -| `jr workflow assign` | `"workflow assign"` | -| `jr workflow move` | `"workflow move"` | -| `jr workflow create` | `"workflow create"` | -| `jr workflow comment` | `"workflow comment"` (plain text only) | -| `jr issue add-comment` | `"issue add-comment"` (raw ADF for rich formatting) | -| `jr workflow link` | `"workflow link"` | -| `jr workflow log-work` | `"workflow log-work"` | -| `jr workflow sprint` | `"workflow sprint"` | -| `jr template apply` | `"template apply"` | -| `jr diff` | `"diff diff"` | -| `jr issue get` | `"issue get"` | - -Note: `"diff diff"` is correct — the resource is `diff` and the verb is `diff`. - -Batch args use the flag names directly (without `--`), and `template apply` uses `name`/`project` plus variable names as direct keys (not `--var key=value`): - -```bash -echo '[ - {"command": "issue get", "args": {"issueIdOrKey": "PROJ-1"}, "jq": ".key"}, - {"command": "workflow transition", "args": {"issue": "PROJ-2", "to": "Done"}}, - {"command": "diff diff", "args": {"issue": "PROJ-3", "since": "2h"}}, - {"command": "template apply", "args": {"name": "bug-report", "project": "PROJ", "summary": "Login broken", "severity": "High"}} -]' | jr batch - -# Or read from a file -jr batch --input ops.json -``` +| `workflow transition` / `move` | status name | +| `workflow assign` | person (email, display name, `me`, `none`) | +| `workflow sprint` | sprint name | +| `workflow link` | target issue key | -## Error Handling +Detailed flags and patterns: [references/operations.md](references/operations.md). -Errors are structured JSON on stderr. Branch on `exit_code` and `error_type`: +## Error handling -| Exit code | error_type | Meaning | What to do | -|-----------|-----------|---------|------------| -| 0 | — | Success | Parse stdout as JSON | -| 1 | `connection_error` | Network/unknown error | Check connectivity, retry | -| 2 | `auth_failed` | Auth failed (401/403) | Check token/credentials | -| 3 | `not_found` | Resource not found (404) | Verify issue key / resource ID | -| 3 | `gone` | Resource gone (410) | Resource was deleted; do not retry | -| 4 | `validation_error` | Bad request (400/422/4xx) | Fix the request payload | -| 4 | `client_error` | Other 4xx errors | Check request parameters | -| 5 | `rate_limited` | Rate limited (429) | Wait `retry_after` seconds, then retry | -| 6 | `conflict` | Conflict (409) | Retry or resolve conflict | -| 7 | `server_error` | Server error (5xx) | Retry with backoff | +Errors are JSON on stderr. Branch on `exit_code`: -Error JSON includes optional `hint` (actionable recovery text) and `retry_after` (integer seconds for rate limits): +| Exit | `error_type` | Meaning | Action | +|------|--------------|---------|--------| +| 0 | — | Success | Parse stdout | +| 1 | `connection_error` | Network/unknown | Retry once | +| 2 | `auth_failed` | 401/403 | Check token — do NOT retry | +| 3 | `not_found` / `gone` | 404/410 | Verify key — do NOT retry | +| 4 | `validation_error` / `client_error` | 4xx | Fix request — do NOT retry | +| 5 | `rate_limited` | 429 | Wait `retry_after` seconds, retry (max 3) | +| 6 | `conflict` | 409 | Resolve or retry | +| 7 | `server_error` | 5xx | Exponential backoff: 1s, 2s, 4s (max 3) | -```json -{ - "error_type": "rate_limited", - "status": 429, - "message": "Rate limit exceeded", - "hint": "You are being rate limited. Wait before retrying.", - "retry_after": 30, - "request": {"method": "GET", "path": "/rest/api/3/search/jql"} -} -``` +Error JSON includes optional `hint` (recovery text) and `retry_after` (seconds): ```json -{ - "error_type": "auth_failed", - "status": 401, - "message": "Unauthorized", - "hint": "Run `jr configure --base-url --token --username ` to authenticate.", - "request": {"method": "GET", "path": "/rest/api/3/issue/BAD-999"} -} -``` - -### Retry pattern for agents - -For rate limits (exit 5) and server errors (exit 7), retry with backoff: - -``` -1. Run the command -2. If exit code is 5 (rate_limited): - - Parse stderr JSON, read "retry_after" (integer seconds) - - Wait that many seconds, then retry (max 3 retries) -3. If exit code is 7 (server_error): - - Retry with exponential backoff: wait 1s, 2s, 4s (max 3 retries) -4. If exit code is 3 (not_found/gone) or 4 (validation_error): - - Do NOT retry — fix the request or report to user -``` - -## Global Flags - -| Flag | Description | -|------|-------------| -| `--preset ` | named output preset (agent, detail, triage, board) | -| `--jq ` | jq filter on response | -| `--fields ` | comma-separated fields to return (GET only) | -| `--cache ` | cache GET responses (e.g. `5m`, `1h`) | -| `--pretty` | pretty-print JSON output | -| `--no-paginate` | disable automatic pagination | -| `--dry-run` | show the request without executing it | -| `--verbose` | log HTTP details to stderr (JSON) | -| `--timeout ` | HTTP request timeout (default 30s) | -| `--profile ` | use a named config profile | -| `--audit` | enable audit logging for this invocation | -| `--audit-file ` | audit log file path (implies --audit) | -| `--base-url ` | override base URL for this invocation | -| `--auth-token ` | override auth token for this invocation | -| `--auth-user ` | override auth username for this invocation | -| `--auth-type ` | override auth type for this invocation | - -## Common Agent Patterns - -### Check-then-act: update if exists, create if not - -```bash -# Try to get the issue; check exit code -jr issue get --issueIdOrKey PROJ-123 --preset agent - -# If exit code 0 → issue exists, update it -jr issue edit --issueIdOrKey PROJ-123 --body '{"fields":{"summary":"Updated"}}' - -# If exit code 3 (not_found) → create it -jr workflow create --project PROJ --type Task --summary "New task" +{"error_type":"rate_limited","status":429,"retry_after":30,"hint":"Wait before retrying."} ``` -### Bulk status transitions +## Batch -Search + batch to move all matching issues: +For any sequence of 3+ ops, use `jr batch` — one process instead of N cold starts: ```bash -# Step 1: Find issues -ISSUES=$(jr search search-and-reconsile-issues-using-jql \ - --jql "project = PROJ AND status = 'In Progress' AND assignee = currentUser()" \ - --jq '[.issues[].key]') - -# Step 2: Build batch payload and execute -# (construct a JSON array of workflow transition ops from the keys) -echo "$ISSUES" | jr raw POST /dev/null --dry-run # use your language to build the array echo '[ - {"command": "workflow transition", "args": {"issue": "PROJ-1", "to": "Done"}}, - {"command": "workflow transition", "args": {"issue": "PROJ-2", "to": "Done"}} + {"command": "issue get", "args": {"issueIdOrKey": "PROJ-1"}, "jq": ".key"}, + {"command": "workflow transition","args": {"issue": "PROJ-2", "to": "Done"}}, + {"command": "template apply", "args": {"name": "bug-report", "project": "PROJ", "summary": "Bug"}} ]' | jr batch ``` -### Create epic with subtasks - -```bash -# Step 1: Create the epic -EPIC=$(jr workflow create --project PROJ --type Epic --summary "Auth redesign" \ - --jq '.key') - -# Step 2: Create subtasks under the epic (use batch for efficiency) -echo "[ - {\"command\": \"workflow create\", \"args\": {\"project\": \"PROJ\", \"type\": \"Subtask\", \"summary\": \"Design auth flow\", \"parent\": \"$EPIC\"}}, - {\"command\": \"workflow create\", \"args\": {\"project\": \"PROJ\", \"type\": \"Subtask\", \"summary\": \"Implement OAuth\", \"parent\": \"$EPIC\"}}, - {\"command\": \"workflow create\", \"args\": {\"project\": \"PROJ\", \"type\": \"Subtask\", \"summary\": \"Write tests\", \"parent\": \"$EPIC\"}} -]" | jr batch -``` - -### Validate before executing - -Use `--dry-run` to preview requests before making API calls: - -```bash -# Preview what would be sent (no API call made) -jr workflow create --project PROJ --type Bug --summary "Test" --dry-run -# Returns: {"method":"POST","url":"...","body":{"fields":{...}}} - -# If the output looks correct, run without --dry-run -jr workflow create --project PROJ --type Bug --summary "Test" -``` - -## Security - -### Operation Policy (per profile) -Restrict which operations a profile can execute: - -```json -{ - "profiles": { - "agent": { - "allowed_operations": ["issue get", "search *", "workflow *"] - }, - "readonly": { - "denied_operations": ["* delete*", "bulk *", "raw *"] - } - } -} -``` - -- Use `allowed_operations` OR `denied_operations`, not both -- Patterns use glob matching: `*` matches any sequence -- `allowed_operations`: implicit deny-all, only matching ops run -- `denied_operations`: implicit allow-all, only matching ops blocked - -### Batch Limits -Default max batch size is 50. Override with `--max-batch N`. - -### Audit Logging -Enable per-profile (`"audit_log": true`) or per-invocation (`--audit`). -Logs to `~/.config/jr/audit.log` (JSONL). Override path with `--audit-file`. - -## Pagination - -`jr` auto-paginates by default — it fetches all pages and merges results into a single response. Use `--no-paginate` if you only need the first page. - -## Troubleshooting - -**"command not found"** — `jr` is not installed. Install via: -```bash -brew install sofq/tap/jr # Homebrew -go install github.com/sofq/jira-cli@latest # Go -``` - -**Exit code 2 (auth)** — Token expired or misconfigured. Test with: -```bash -jr configure --test -``` +- Command strings are `"resource verb"` (e.g. `"diff diff"`, `"workflow transition"`). +- Args use flag names **without** `--`. `template apply` variables are flat keys, not `--var`. +- Process exit code = highest-severity op code. Check per-op `exit_code` fields for individual status. +- Default cap: 50 ops (`--max-batch N`). -**Unknown command** — Command names are auto-generated from Jira's API and can be verbose. Use `jr schema` to find the right name, or use `jr raw` as an escape hatch. +Full command-name table and arg conventions: [references/batch.md](references/batch.md). -**Large responses filling context** — Always use `--preset` or `--fields` + `--jq` to minimize output. +## Global flags (most used) -**"--dry-run"** — Use this to preview what `jr` will send without making the API call. Useful for debugging request bodies. - ---- - -> **Note:** The project root also contains `CLAUDE.md` with a compressed quick-reference for `jr`. If both files are loaded in context, prefer this SKILL.md as the authoritative reference — `CLAUDE.md` is a contributor-oriented summary. +| Flag | Purpose | +|------|---------| +| `--preset ` | Named output preset | +| `--jq ` | jq filter on response | +| `--fields ` | Server-side field filter (GET only) | +| `--cache ` | Cache GET responses (e.g. `5m`, `1h`) | +| `--dry-run` | Show request without executing | +| `--verbose` | Log HTTP details to stderr (JSON) | +| `--profile ` | Named config profile | +| `--timeout ` | HTTP timeout (default 30s) | +| `--no-paginate` | Disable auto-pagination | +| `--pretty` | Pretty-print JSON output | + +## Common mistakes + +- **Writing markdown in comments.** Jira Cloud v3 renders it literally. Use `workflow comment --text` for plain text, or hand-author ADF (see [references/adf.md](references/adf.md)). +- **Omitting `--max-events` on `jr watch`.** Agents can't send Ctrl-C. Without a cap, the stream runs forever. +- **Not filtering responses.** A single unfiltered `issue get` can burn 10K tokens. Always use `--preset` or `--fields` + `--jq`. +- **"Fixing" `reconsile`.** It matches the Jira API spec. Leaving it misspelled is correct. +- **Retrying exit 3 or 4 errors.** Not-found, gone, and validation errors won't become success on retry. Fix the request instead. +- **Looking up transition / link-type / sprint IDs.** `jr` resolves these from names automatically — pass `"Done"`, `"blocks"`, `"Sprint 5"`, never the numeric ID. +- **Running many single invocations.** Each cold-starts the schema parser. Use `jr batch` for 3+ calls. + +## References + +| File | Contents | +|------|----------| +| [references/adf.md](references/adf.md) | Atlassian Document Format: envelope, node catalog, marks, worked examples, site-edition caveats | +| [references/operations.md](references/operations.md) | Per-operation detail (get, search, create, edit, transition, assign, log-work, watch, diff, raw, templates) + agent patterns (check-then-act, bulk transitions, epic+subtasks, retry loops, sync loops) | +| [references/batch.md](references/batch.md) | Batch command-name lookup, args conventions, exit-code rules, size limits | +| [references/config.md](references/config.md) | Auth types, env vars, profiles, operation policy, audit logging, file locations | + +> The project root's `CLAUDE.md` contains a contributor-oriented quick reference for `jr` that overlaps with this skill. If both are in context, treat this SKILL.md + its references as the authoritative guide. diff --git a/skills/jira-cli/references/adf.md b/skills/jira-cli/references/adf.md new file mode 100644 index 0000000..8899a3d --- /dev/null +++ b/skills/jira-cli/references/adf.md @@ -0,0 +1,163 @@ +# ADF — Atlassian Document Format + +Jira Cloud REST v3 stores rich content (comments, issue descriptions, wiki fields) as an **ADF JSON document** — a tree of typed nodes. **Markdown (`**bold**`, `- item`, `# h1`) and wiki markup (`*bold*`, `h1.`) are NOT parsed by the API** — they render as literal characters. For any formatting beyond a single plain-text paragraph, you must hand-author ADF. + +## Envelope + +Every ADF document has this outer shape: + +```json +{ + "body": { + "type": "doc", + "version": 1, + "content": [ + /* block-level nodes go here */ + ] + } +} +``` + +For the `jr issue add-comment` command, the `body` wrapper is part of the request payload: + +```bash +jr issue add-comment --issueIdOrKey PROJ-1 --body @comment.adf.json +``` + +Multi-node documents are painful to shell-escape — prefer `--body @file.json` or `--body -` (stdin). + +## Block-level nodes + +Block nodes live in `doc.content` (and in some container nodes like `listItem`, `panel`, `blockquote`, `tableCell`). + +| Node | Attrs | Notes | +|------|-------|-------| +| `paragraph` | — | Most common block. Contains inline nodes. | +| `heading` | `level` 1–6 | `attrs: {"level": 2}` | +| `bulletList` | — | Contains `listItem` children only. | +| `orderedList` | `order` (optional start #) | Contains `listItem` children only. | +| `listItem` | — | Contains block nodes (usually a `paragraph`). | +| `taskList` | `localId` | Contains `taskItem` children. ⚠ site-edition dependent. | +| `taskItem` | `localId`, `state`: `TODO` \| `DONE` | Contains inline nodes. | +| `blockquote` | — | Contains block nodes. | +| `codeBlock` | `language` (optional: `bash`, `python`, `json`, ...) | Content: single `text` node (no marks). | +| `panel` | `panelType`: `info` \| `warning` \| `success` \| `error` \| `note` | Colored callout box. | +| `rule` | — | Horizontal divider. No content. | +| `table` | `isNumberColumnEnabled`, `layout` | Contains `tableRow` children. | +| `tableRow` | — | Contains `tableHeader` / `tableCell`. | +| `tableHeader` | `colspan`, `rowspan`, `background` | Contains block nodes. | +| `tableCell` | `colspan`, `rowspan`, `background` | Contains block nodes. | +| `expand` | `title` | Collapsible section. ⚠ site-edition dependent. | +| `mediaSingle` | `layout`, `width` | Embedded image/file (requires media ID). | + +## Inline nodes + +Inline nodes live in the `content` array of most block nodes. + +| Node | Attrs | Notes | +|------|-------|-------| +| `text` | — (but takes `marks`) | The workhorse — all visible text. | +| `hardBreak` | — | Line break within a paragraph. | +| `mention` | `id` (accountId), `text` (display) | @mentions a user. | +| `emoji` | `shortName` (e.g. `:smile:`), `id`, `text` | Inline emoji. | +| `status` | `text`, `color`: `neutral` \| `purple` \| `blue` \| `red` \| `yellow` \| `green` | Inline status lozenge. ⚠ site-edition dependent. | +| `date` | `timestamp` (unix ms) | Inline date chip. | +| `inlineCard` | `url` | Smart link (renders URL as card). | + +## Marks (inline formatting on `text`) + +Marks are applied via a `marks` array on a `text` node: + +```json +{"type": "text", "text": "bold code", "marks": [{"type": "strong"}, {"type": "code"}]} +``` + +| Mark | Attrs | Effect | +|------|-------|--------| +| `strong` | — | **bold** | +| `em` | — | *italic* | +| `strike` | — | ~~strikethrough~~ | +| `underline` | — | underline | +| `code` | — | `inline code` | +| `subsup` | `type`: `sub` \| `sup` | subscript/superscript | +| `textColor` | `color` (hex, e.g. `#FF0000`) | colored text | +| `link` | `href`, `title` (optional) | hyperlink | +| `backgroundColor` | `color` (hex) | highlighted background | + +## Worked example — a rich deploy comment + +```json +{"body": {"type": "doc", "version": 1, "content": [ + {"type": "heading", "attrs": {"level": 2}, "content": [ + {"type": "text", "text": "Deploy summary"} + ]}, + {"type": "paragraph", "content": [ + {"type": "text", "text": "Shipped "}, + {"type": "text", "text": "v1.4.2", "marks": [{"type": "strong"}]}, + {"type": "text", "text": " at "}, + {"type": "text", "text": "14:20 UTC", "marks": [{"type": "code"}]}, + {"type": "text", "text": ". See "}, + {"type": "text", "text": "runbook", "marks": [{"type": "link", "attrs": {"href": "https://wiki/runbook"}}]}, + {"type": "text", "text": "."} + ]}, + {"type": "bulletList", "content": [ + {"type": "listItem", "content": [ + {"type": "paragraph", "content": [{"type": "text", "text": "migrations applied"}]} + ]}, + {"type": "listItem", "content": [ + {"type": "paragraph", "content": [{"type": "text", "text": "smoke tests green"}]} + ]} + ]}, + {"type": "codeBlock", "attrs": {"language": "bash"}, "content": [ + {"type": "text", "text": "kubectl rollout status deploy/api"} + ]}, + {"type": "panel", "attrs": {"panelType": "info"}, "content": [ + {"type": "paragraph", "content": [ + {"type": "text", "text": "Monitor dashboards for the next hour."} + ]} + ]} +]}} +``` + +## Common mistakes + +- **Marks on non-text nodes.** `marks` is only valid on `text`. Don't put it on `paragraph`, `heading`, etc. +- **Block nodes inside inline contexts.** You cannot put a `paragraph` inside another `paragraph`. Use `hardBreak` for line breaks within a paragraph. +- **Code block content.** `codeBlock` content must be a single `text` node with no marks — it's already monospace. +- **List items without paragraphs.** `listItem` content should be a `paragraph` wrapping the text, not a bare `text` node. +- **Missing `version: 1`.** The `doc` node requires `"version": 1`. +- **Using markdown and expecting it to render.** It won't. Literally any formatting requires ADF nodes. + +## Site-edition caveats + +Some nodes depend on the instance's edition or feature flags. If they render as blank or literal JSON, the target instance doesn't support them: + +- `status` — requires the status feature +- `expand` — requires collapsible sections +- `taskList` / `taskItem` — requires Jira task support +- `mention` — requires valid `accountId` for the target user +- `emoji` — some custom `shortName`s depend on workspace-installed emoji + +## Markdown → ADF + +There is no built-in flag. Options: + +1. **Hand-author ADF** for one-off rich comments (use the envelope above). +2. **External converter** — e.g. the Atlassian `md-to-adf` JS lib — pipe output into `jr issue add-comment --body -`. +3. **Fall back to plain text.** `jr workflow comment --text "..."` wraps your string in a single ADF paragraph — fine for simple one-liners, but no formatting. + +## When to use plain text vs ADF + +```dot +digraph adf_decision { + "Need formatting?" [shape=diamond]; + "Rich content? (headings, lists, code, panels, tables, links, colors)" [shape=diamond]; + "Use jr workflow comment --text" [shape=box]; + "Write ADF, use jr issue add-comment --body @file" [shape=box]; + + "Need formatting?" -> "Use jr workflow comment --text" [label="no"]; + "Need formatting?" -> "Rich content? (headings, lists, code, panels, tables, links, colors)" [label="yes"]; + "Rich content? (headings, lists, code, panels, tables, links, colors)" -> "Write ADF, use jr issue add-comment --body @file" [label="yes"]; + "Rich content? (headings, lists, code, panels, tables, links, colors)" -> "Use jr workflow comment --text" [label="no"]; +} +``` diff --git a/skills/jira-cli/references/batch.md b/skills/jira-cli/references/batch.md new file mode 100644 index 0000000..3527f15 --- /dev/null +++ b/skills/jira-cli/references/batch.md @@ -0,0 +1,153 @@ +# Batch — running multiple ops in one process + +`jr batch` takes a JSON array of operations on stdin (or `--input file.json`) and runs them in a single process. Use it any time you need 3+ Jira calls in sequence — each standalone invocation pays the cold-start cost of parsing the schema. + +## Shape + +```json +[ + {"command": "issue get", "args": {"issueIdOrKey": "PROJ-1"}, "jq": ".key"}, + {"command": "workflow transition","args": {"issue": "PROJ-2", "to": "Done"}}, + {"command": "project search", "args": {}, "jq": "[.values[].key]"} +] +``` + +Each entry: +- **`command`** — `"resource verb"` string, exactly as it appears in `jr schema`. +- **`args`** — flag names without the `--` prefix, mapped to values. +- **`jq`** *(optional)* — jq filter applied to this op's response. +- **`fields`** *(optional)* — comma-separated Jira field list for GET ops. +- **`preset`** *(optional)* — named preset for this op. + +## Command-name lookup table + +Batch `"command"` strings use the full `resource verb` form. Hand-written (non-generated) commands have their own names: + +| CLI invocation | Batch `"command"` | +|---|---| +| `jr issue get` | `"issue get"` | +| `jr issue edit` | `"issue edit"` | +| `jr issue delete` | `"issue delete"` | +| `jr issue create-issue` | `"issue create-issue"` | +| `jr issue add-comment` | `"issue add-comment"` | +| `jr workflow create` | `"workflow create"` | +| `jr workflow transition` | `"workflow transition"` | +| `jr workflow move` | `"workflow move"` | +| `jr workflow assign` | `"workflow assign"` | +| `jr workflow comment` | `"workflow comment"` (plain text only) | +| `jr workflow link` | `"workflow link"` | +| `jr workflow log-work` | `"workflow log-work"` | +| `jr workflow sprint` | `"workflow sprint"` | +| `jr template apply` | `"template apply"` | +| `jr diff` | `"diff diff"` ← resource is `diff`, verb is `diff` | +| `jr project search` | `"project search"` | +| `jr search search-and-reconsile-issues-using-jql` | `"search search-and-reconsile-issues-using-jql"` | +| `jr raw` | Not supported in batch — use individual invocations. | + +When in doubt, check `jr schema ` for the canonical verb name. + +## Args conventions + +Most commands just take their flag names (stripped of `--`): + +```json +{"command": "workflow transition", "args": {"issue": "PROJ-1", "to": "Done"}} +``` + +### `template apply` — variables are flat keys + +`--var key=value` flags flatten into top-level keys in `args`, alongside `name` / `project`: + +```json +{"command": "template apply", "args": { + "name": "bug-report", + "project": "PROJ", + "summary": "Login broken", + "severity": "High" +}} +``` + +Not `"vars": {...}` — direct keys. + +### `diff diff` — duplicated name is intentional + +```json +{"command": "diff diff", "args": {"issue": "PROJ-1", "since": "2h"}} +``` + +The resource is named `diff` and the verb is also `diff`. + +## Worked example + +```bash +cat <<'EOF' | jr batch +[ + {"command": "issue get", "args": {"issueIdOrKey": "PROJ-1"}, "jq": ".key"}, + {"command": "workflow transition","args": {"issue": "PROJ-2", "to": "Done"}}, + {"command": "diff diff", "args": {"issue": "PROJ-3", "since": "2h"}}, + {"command": "template apply", "args": { + "name": "bug-report", + "project": "PROJ", + "summary": "Login broken", + "severity": "High" + }} +] +EOF + +# From a file +jr batch --input ops.json +``` + +## Exit codes + +The **process exit code is the highest-severity code from any operation.** If op #1 returns 0 and op #2 returns 5 (rate_limited), the process exits with 5. + +Severity ordering (highest first): 7 > 6 > 5 > 4 > 3 > 2 > 1 > 0. + +Per-operation results appear in the output as a JSON array: + +```json +[ + {"exit_code": 0, "output": {...}}, + {"exit_code": 5, "error": {...}}, + {"exit_code": 0, "output": {...}} +] +``` + +Always check individual `exit_code` fields to know which ops succeeded — the process-level exit code tells you "at least one op hit this severity." + +## Size limits + +Default maximum batch size is **50 operations**. Override for a single invocation: + +```bash +jr batch --max-batch 100 --input big-batch.json +``` + +If you need more than a few hundred ops, split into multiple batches and handle partial failure between them. + +## Patterns + +### Generating batch JSON from a list + +Build the array in your language of choice, don't hand-edit: + +```bash +# From a list of keys, generate transition ops +cat keys.txt | jq -R -s 'split("\n") | map(select(length > 0)) | + map({command: "workflow transition", args: {issue: ., to: "Done"}})' | jr batch +``` + +### Parallel-looking, sequential-actually + +`jr batch` runs ops **sequentially**, not in parallel. Order matters — if op #2 depends on op #1's output, that dependency is not automatic. Either: +- Chain via script (capture op #1's output, feed into op #2's args), or +- Accept that batch is about reducing process-startup overhead, not concurrency. + +### Don't batch `jr raw` + +`jr raw` is not supported as a batch command. Call it directly. + +### Dry-run a batch + +There is no batch-level `--dry-run` today — use individual `--dry-run` calls to verify the shape before bundling into a batch. diff --git a/skills/jira-cli/references/config.md b/skills/jira-cli/references/config.md new file mode 100644 index 0000000..6f9307b --- /dev/null +++ b/skills/jira-cli/references/config.md @@ -0,0 +1,213 @@ +# Config, auth, profiles, security + +## Setup + +### Basic auth (username + API token) + +The default. Most Jira Cloud users authenticate this way. + +```bash +jr configure \ + --base-url https://yoursite.atlassian.net \ + --token YOUR_API_TOKEN \ + --username your@email.com +``` + +Generate API tokens at https://id.atlassian.com/manage-profile/security/api-tokens. + +### Bearer auth + +```bash +jr configure --base-url https://... --token YOUR_BEARER_TOKEN --auth-type bearer +``` + +### OAuth2 + +**Cannot be set up via `jr configure`.** Edit `~/.config/jr/config.json` manually: + +```json +{ + "profiles": { + "default": { + "base_url": "https://yoursite.atlassian.net", + "auth": { + "type": "oauth2", + "client_id": "...", + "client_secret": "...", + "token_url": "https://auth.atlassian.com/oauth/token" + } + } + } +} +``` + +### Validate credentials + +```bash +jr configure --test # test the default profile +jr configure --test --profile work # test a specific profile + +# Validate AND save in one step +jr configure --base-url https://... --token ... --username ... --test +``` + +Exit code 2 means auth failed — the token is likely expired or the username is wrong. + +## Config resolution order + +Highest precedence first: + +1. **CLI flags** — `--base-url`, `--auth-token`, `--auth-user`, `--auth-type` +2. **Environment variables** +3. **Config file** — `~/.config/jr/config.json` (default) or `$JR_CONFIG_PATH` + +This means you can override a saved profile for one invocation: + +```bash +jr issue get --issueIdOrKey PROJ-1 --base-url https://other.atlassian.net --auth-token XXX +``` + +## Environment variables + +Useful for CI, containers, and ephemeral environments: + +| Variable | Purpose | +|----------|---------| +| `JR_BASE_URL` | Jira base URL | +| `JR_AUTH_TOKEN` | API token / bearer token | +| `JR_AUTH_USER` | Username for basic auth | +| `JR_AUTH_TYPE` | `basic` (default), `bearer`, `oauth2` | +| `JR_CONFIG_PATH` | Override config file location | + +## Profiles + +Named profiles let you work with multiple Jira instances from one machine. + +```bash +# Create a new profile +jr configure --base-url https://work.atlassian.net --token TOKEN --profile work + +# Use a profile +jr issue get --profile work --issueIdOrKey PROJ-1 + +# Delete a profile (--profile is required) +jr configure --profile work --delete + +# Test a profile +jr configure --test --profile work +``` + +Profiles are stored as keys in `~/.config/jr/config.json`: + +```json +{ + "profiles": { + "default": { "base_url": "...", "auth": {...} }, + "work": { "base_url": "...", "auth": {...} } + } +} +``` + +## Operation policy + +Restrict which operations a profile can execute. Useful for agent profiles that should only be allowed to read, or for read-only service accounts. + +```json +{ + "profiles": { + "agent": { + "base_url": "...", + "auth": {"type": "basic", "token": "..."}, + "allowed_operations": ["issue get", "search *", "workflow *"] + }, + "readonly": { + "base_url": "...", + "auth": {"type": "basic", "token": "..."}, + "denied_operations": ["* delete*", "bulk *", "raw *"] + } + } +} +``` + +### Rules + +- Use `allowed_operations` **or** `denied_operations`, never both on the same profile. +- Patterns use glob matching: `*` matches any sequence of characters. +- `allowed_operations` is **deny-by-default** — only matching ops run. +- `denied_operations` is **allow-by-default** — only matching ops are blocked. + +### Pattern examples + +| Pattern | Matches | +|---------|---------| +| `issue get` | Exactly `issue get` | +| `issue *` | All verbs under `issue` | +| `* delete*` | Any resource's `delete` verbs (`issue delete`, `comment delete`, ...) | +| `workflow *` | All workflow operations | +| `raw *` | All raw API calls | +| `bulk *` | All bulk operations | + +When an operation is blocked, `jr` exits with a policy error before making any HTTP call. + +## Audit logging + +Enable to record every operation (command, args, exit code, timing) for later review. + +### Per-profile + +```json +{ + "profiles": { + "agent": { + "audit_log": true, + "audit_log_path": "/var/log/jr-audit.log" + } + } +} +``` + +### Per-invocation + +```bash +# Enable for this call +jr issue get --issueIdOrKey PROJ-1 --audit + +# Custom audit file (implies --audit) +jr issue get --issueIdOrKey PROJ-1 --audit-file /tmp/jr-session.log +``` + +Default path: `~/.config/jr/audit.log` (JSONL — one event per line). + +Audit entries look like: + +```json +{ + "ts": "2025-01-15T09:12:34.001Z", + "profile": "agent", + "command": "issue get", + "args": {"issueIdOrKey": "PROJ-1"}, + "exit_code": 0, + "duration_ms": 412 +} +``` + +## Batch limits + +Default max batch size is **50 operations**. Override: + +```bash +jr batch --max-batch 100 --input ops.json +``` + +The limit is per-invocation, not a rate limit against Jira — it exists to prevent runaway scripts from making hundreds of API calls unintentionally. + +## File locations + +| File | Purpose | +|------|---------| +| `~/.config/jr/config.json` | Profiles, auth, policy (default) | +| `~/.config/jr/presets.json` | User-defined output presets | +| `~/.config/jr/templates/*.yaml` | User-defined issue templates | +| `~/.config/jr/audit.log` | Default audit log | + +Override the config directory with `JR_CONFIG_PATH`. diff --git a/skills/jira-cli/references/operations.md b/skills/jira-cli/references/operations.md new file mode 100644 index 0000000..dfd7dc4 --- /dev/null +++ b/skills/jira-cli/references/operations.md @@ -0,0 +1,417 @@ +# Operations — detailed reference + +Full examples for every common `jr` operation, plus agent-oriented patterns. The main SKILL.md has one-liners; use this file when you need flag-level detail or when chaining operations. + +## Contents + +- [Reading issues](#reading-issues) — `get`, `search`, `watch`, `diff` +- [Writing issues](#writing-issues) — `create`, `edit`, `delete` +- [Workflow](#workflow) — `transition`, `move`, `assign`, `link`, `log-work`, `sprint` +- [Comments](#comments) — plain text and ADF +- [Templates](#templates) — apply, show, create +- [Projects and metadata](#projects-and-metadata) +- [Raw API](#raw-api) +- [Agent patterns](#agent-patterns) + +--- + +## Reading issues + +### Get one issue + +```bash +# Full response (~10K tokens — avoid without filtering) +jr issue get --issueIdOrKey PROJ-123 + +# With preset (recommended default) +jr issue get --issueIdOrKey PROJ-123 --preset agent + +# With server-side field filter +jr issue get --issueIdOrKey PROJ-123 --fields key,summary,status,assignee + +# Shape output with jq +jr issue get --issueIdOrKey PROJ-123 --jq '{key, summary: .fields.summary, status: .fields.status.name}' + +# Expand changelog / renderedFields / etc. +jr issue get --issueIdOrKey PROJ-123 --expand changelog,renderedFields +``` + +### Search with JQL + +```bash +# Use search-and-reconsile-issues-using-jql (NOT the deprecated /search). +# "reconsile" is NOT a typo — it matches the Jira API spec. Do not "fix" it. +jr search search-and-reconsile-issues-using-jql \ + --jql "project = PROJ AND status = 'In Progress' AND assignee = currentUser()" \ + --fields "key,summary,status,assignee" \ + --jq '[.issues[] | {key, summary: .fields.summary, status: .fields.status.name}]' + +# Limit results +jr search search-and-reconsile-issues-using-jql --jql "..." --maxResults 50 + +# Next page (use nextPageToken from prior response) +jr search search-and-reconsile-issues-using-jql --jql "..." --nextPageToken "tok_abc" +``` + +### Watch (NDJSON stream) + +```bash +# Poll a JQL query and emit events as one JSON object per line. +# ALWAYS use --max-events in automated contexts — agents cannot send SIGINT. +jr watch --jql "project = PROJ AND updated > -5m" --interval 30s --max-events 20 + +# Watch a single issue +jr watch --issue PROJ-123 --interval 10s --max-events 5 + +# With preset shaping +jr watch --jql "status changed" --interval 1m --preset triage --max-events 10 +``` + +Event types: `initial` (first poll), `created`, `updated`, `removed`. + +### Diff / Changelog + +```bash +# Full changelog +jr diff --issue PROJ-123 + +# Time-bounded +jr diff --issue PROJ-123 --since 2h +jr diff --issue PROJ-123 --since 2025-01-01 + +# Single field only +jr diff --issue PROJ-123 --field status +jr diff --issue PROJ-123 --field assignee --since 1d +``` + +--- + +## Writing issues + +### Create (from flags — no raw JSON) + +```bash +jr workflow create \ + --project PROJ \ + --type Bug \ + --summary "Login broken on Safari" \ + --description "Steps to reproduce: ..." \ + --priority High \ + --labels bug,urgent \ + --assign me \ + --parent PROJ-100 # for subtasks/child issues +``` + +### Create (raw JSON — for custom fields) + +```bash +jr issue create-issue --body '{ + "fields": { + "project": {"key": "PROJ"}, + "summary": "Bug title", + "issuetype": {"name": "Bug"}, + "customfield_10020": 5 + } +}' +``` + +### Edit + +```bash +# Simple field update +jr issue edit --issueIdOrKey PROJ-123 --body '{"fields":{"summary":"Updated title"}}' + +# Multiple fields +jr issue edit --issueIdOrKey PROJ-123 --body '{ + "fields": { + "summary": "New title", + "priority": {"name": "High"}, + "labels": ["bug", "p1"] + } +}' + +# Edit returns {} for 204 responses (success with no body) +``` + +### Delete + +```bash +jr issue delete --issueIdOrKey PROJ-123 +``` + +--- + +## Workflow + +### Transition + +```bash +# By status name (jr resolves transition ID automatically) +jr workflow transition --issue PROJ-123 --to "Done" +jr workflow transition --issue PROJ-123 --to "In Progress" +``` + +### Transition + assign in one step + +```bash +jr workflow move --issue PROJ-123 --to "In Progress" --assign me +``` + +### Assign + +```bash +# "me" resolves to the authenticated user +jr workflow assign --issue PROJ-123 --to me + +# Email or display name +jr workflow assign --issue PROJ-123 --to "john@company.com" +jr workflow assign --issue PROJ-123 --to "John Smith" + +# Unassign +jr workflow assign --issue PROJ-123 --to none +``` + +### Link issues + +```bash +# Link type name resolves to ID automatically +jr workflow link --from PROJ-1 --to PROJ-2 --type blocks +jr workflow link --from PROJ-1 --to PROJ-2 --type "is blocked by" +jr workflow link --from PROJ-1 --to PROJ-2 --type "relates to" +``` + +### Log work + +```bash +# Human-friendly durations: 2h, 1d 3h, 45m, 1w +jr workflow log-work --issue PROJ-123 --time "2h 30m" --comment "Debugging auth flow" + +# With start date +jr workflow log-work --issue PROJ-123 --time "1h" --started "2025-01-15T09:00:00.000+0000" +``` + +### Sprint + +```bash +# Move to sprint by name (jr resolves sprint ID) +jr workflow sprint --issue PROJ-123 --to "Sprint 5" +``` + +--- + +## Comments + +### Plain text (one-line) + +```bash +# Wrapped in a single ADF paragraph. No formatting — markdown renders literally. +jr workflow comment --issue PROJ-123 --text "LGTM, merging now." +``` + +### Rich content (ADF) + +For anything with formatting — headings, lists, code blocks, panels, tables, links, colored text — you must hand-author ADF. See [adf.md](adf.md) for the full node catalog. + +```bash +# Minimal ADF comment +jr issue add-comment --issueIdOrKey PROJ-123 --body '{ + "body": { + "type": "doc", + "version": 1, + "content": [ + {"type": "paragraph", "content": [{"type": "text", "text": "A comment"}]} + ] + } +}' + +# Multi-node documents — use a file, shell-escaping large JSON is painful +jr issue add-comment --issueIdOrKey PROJ-123 --body @comment.adf.json + +# Or from stdin +cat comment.adf.json | jr issue add-comment --issueIdOrKey PROJ-123 --body - +``` + +--- + +## Templates + +Templates are pre-defined issue shapes with variable substitution. Built-in templates: `bug-report`, `story`, `task`, `epic`, `subtask`, `spike`. User-defined templates live in `~/.config/jr/templates/` as YAML. + +```bash +# List all templates +jr template list + +# Show a template's variables and fields +jr template show bug-report + +# Apply a template (variables via --var key=value) +jr template apply bug-report \ + --project PROJ \ + --var summary="Login broken" \ + --var severity=High \ + --assign me + +# Subtask under a parent +jr template apply subtask \ + --project PROJ \ + --var summary="Fix auth check" \ + --var parent=PROJ-100 + +# Create a user-defined template from scratch +jr template create my-template + +# Or from an existing issue (captures its fields as the template) +jr template create my-template --from PROJ-123 + +# Overwrite existing +jr template create my-template --from PROJ-123 --overwrite +``` + +--- + +## Projects and metadata + +```bash +# List projects (returns a paginated Jira response — .values has the rows) +jr project search --jq '[.values[] | {key, name}]' + +# Cache project list for 5 minutes +jr project search --cache 5m --jq '[.values[].key]' + +# Get a specific project +jr project get --projectIdOrKey PROJ + +# List issue types for a project +jr issuetypes get --jq '[.[] | {name, subtask}]' + +# List transitions available for an issue +jr transitions get --issueIdOrKey PROJ-123 --jq '[.transitions[] | {id, name, to: .to.name}]' +``` + +--- + +## Raw API + +Escape hatch for endpoints not covered by generated commands. + +```bash +# GET +jr raw GET /rest/api/3/myself + +# POST with body +jr raw POST /rest/api/3/some/endpoint --body '{"key":"value"}' + +# POST with body from file +jr raw POST /rest/api/3/some/endpoint --body @request.json + +# POST with body from stdin (must pass --body - explicitly) +echo '{"key":"value"}' | jr raw POST /rest/api/3/some/endpoint --body - + +# Query parameters (repeatable) +jr raw GET /rest/api/3/issue --query "fields=summary" --query "expand=changelog" + +# DELETE +jr raw DELETE /rest/api/3/issue/PROJ-123 +``` + +**Gotchas:** +- POST/PUT/PATCH **require** `--body`. Without it, `jr raw` errors (won't hang on stdin). +- Method is positional, not a flag: `jr raw GET /path`, not `jr raw --method GET`. + +--- + +## Agent patterns + +### Check-then-act: update if exists, create if not + +```bash +# Exit code 0 → exists; exit 3 → not found +if jr issue get --issueIdOrKey PROJ-123 --preset agent > /dev/null 2>&1; then + jr issue edit --issueIdOrKey PROJ-123 --body '{"fields":{"summary":"Updated"}}' +else + jr workflow create --project PROJ --type Task --summary "New task" +fi +``` + +### Bulk status transitions + +Search + batch is 5–10x faster than N individual calls: + +```bash +# Step 1: find issues +KEYS=$(jr search search-and-reconsile-issues-using-jql \ + --jql "project = PROJ AND status = 'In Progress' AND assignee = currentUser()" \ + --jq '[.issues[].key] | join(" ")') + +# Step 2: build a batch array and execute +# (in practice, generate the JSON with your script/language) +echo '[ + {"command": "workflow transition", "args": {"issue": "PROJ-1", "to": "Done"}}, + {"command": "workflow transition", "args": {"issue": "PROJ-2", "to": "Done"}}, + {"command": "workflow transition", "args": {"issue": "PROJ-3", "to": "Done"}} +]' | jr batch +``` + +### Create epic with subtasks + +```bash +# Step 1: create the epic, capture its key +EPIC=$(jr workflow create --project PROJ --type Epic --summary "Auth redesign" --jq -r '.key') + +# Step 2: batch-create subtasks under it +cat < err.json) + CODE=$? + case $CODE in + 0) echo "$OUT"; break ;; + 5) sleep "$(jq -r .retry_after err.json)" ;; + 7) sleep "$((2 ** (attempt - 1)))" ;; + *) cat err.json; exit $CODE ;; + esac +done +``` + +### Sync loop (watch-and-act) + +```bash +# Poll a JQL query, route events to handlers, stop after N events. +jr watch --jql "project = PROJ AND status changed" --interval 30s --max-events 100 \ + | while read -r line; do + EVENT=$(echo "$line" | jq -r .event) + KEY=$(echo "$line" | jq -r .issue.key) + case "$EVENT" in + created) echo "new issue $KEY" ;; + updated) echo "changed $KEY" ;; + removed) echo "deleted $KEY" ;; + esac + done +```