🏭 Factory, not a class — createJiraClient(options) returns a plain object of methods; no new, no inheritance, nothing to extend.
🔌 ~45 methods, one client — issues, comments, worklogs, watchers, links, projects, agile boards/sprints/epics, people search, and instance metadata (fields, priorities, statuses, labels, filters, dashboards, server info, permissions) all come off the same object.
🧼 Zero environment coupling — no process.env reads, no process.exit. Every credential and setting comes in through the options object, so the same client works identically in a CLI, an MCP server, a TUI, or a test.
🧪 Injectable fetch — defaults to globalThis.fetch, but a caller can pass a fake for tests or a wrapped one for logging/retries.
📝 ADF ⇄ Markdown, both directions — adfToMarkdown and markdownToAdf convert Jira's Atlassian Document Format to and from plain Markdown, without ever touching ANSI or HTML.
📎 Attachment origin tracking — locateAttachments maps each attachment back to where it was actually referenced (issue body, description, or a specific comment), something Jira's API never says directly.
⚠️ Typed API errors — jiraApiError / isJiraApiError give callers a narrowed error shape instead of a bare thrown string.
📦 Zero runtime dependencies — pure fetch-based client using only Node/Web platform APIs (Buffer, fetch).
baseUrl accepts a bare host (myorg.atlassian.net) as readily as a full URL — normalizeBaseUrl fills in https:// when it's missing, since a bare host is the common shape a config value or env var arrives in.
Converts an Atlassian Document Format node tree to Markdown. Handles headings, lists, tables, code blocks, panels, mentions, emoji, and media references.
markdownToAdf(text)
Converts Markdown back to ADF — paragraphs, fenced code, headings, lists, links, and code spans. Deliberately partial: richer formatting is better authored in Jira directly.
Both stop at Markdown rather than emitting ANSI or HTML — rendering is the caller's job, so a TUI, a pager, and a --json consumer all get the same text.
Maps an issue's attachments back to where they were referenced — the issue body, the description, or a specific comment — since Jira's API never states this directly.
isTextual(attachment)
Mime/extension sniffing for attachments that are safe to render as text.
downloadAttachment(client, id, fetchImpl?)
Downloads attachment bytes, handling Jira's redirect-to-media-host flow without forwarding the Authorization header cross-origin.
Consumed today by @kud/jira-cli, which extracted this package's logic from its own src/api/ layer so a second surface — an MCP server, a TUI — could consume the same client without going through the CLI.