Use to read or write Jira / Confluence (create / update / delete / comment / transition issues and pages, run JQL/CQL) on macOS when the Atlassian MCP and API tokens are blocked or unavailable. Drives the user's already-logged-in Safari OR Google Chrome tab via osascript and calls Atlassian's own REST API from inside the authenticated browser session — no API token, no MCP.
How this skill is triggered — by the user, by Claude, or both
Slash command
/atlassian-browser-skills:atlassian-browser-macosThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Some environments block the Atlassian MCP and outbound API-token calls.
Some environments block the Atlassian MCP and outbound API-token calls. But the user's browser still reaches Jira/Confluence fine (that's how they use it). This skill runs Atlassian's own REST API from inside the logged-in browser tab, so requests carry the existing session cookie (incl. SSO). No token, no MCP, no extra login.
Pick the script for the browser the user is logged into:
| browser | script | applescript |
|---|---|---|
| Safari | scripts/atl_safari.sh | safari_atl.applescript (do JavaScript) |
| Chrome | scripts/atl_chrome_mac.sh | chrome_atl.applescript (execute javascript) |
Both do the same thing: build a small fetch() script, base64-encode it, run it
via osascript in the first tab whose URL matches the Atlassian host, then poll
for the async result (neither do JavaScript nor execute javascript can await a
promise). They use the live logged-in session — no debug port, no separate
profile, no relaunch. You only choose the METHOD + PATH + BODY; see
references/atlassian-rest-cookbook.md (repo root) for every endpoint and payload.
Both browsers block AppleScript-driven JS until the user explicitly opts in.
Until they do, every call returns an inject failed: ... Allow JavaScript from Apple Events ... error. If you see that, stop and have the user do the steps for
their browser, then log into the Atlassian site (https://<site>.atlassian.net
or the self-hosted host) and leave that tab open.
execute javascript is the
twin of Safari's do JavaScript; this single toggle is the only Chrome-side step.)
Verify with a harmless read — it should return "status":200:
# Safari:
skills/atlassian-browser-macos/scripts/atl_safari.sh GET /rest/api/3/myself
# Chrome:
skills/atlassian-browser-macos/scripts/atl_chrome_mac.sh GET /rest/api/3/myself
The scripts find the target tab by URL substring (ATL_HOST, default
atlassian). Before the first call, establish two things — by asking the
user if you don't already know:
atl_safari.sh,
Chrome → atl_chrome_mac.sh.https://<site>.atlassian.net) matches the
default ATL_HOST — no address needed as long as a logged-in tab is open.
Self-hosted (Server/DC) requires ATL_HOST=<their-host> — if the user hasn't
given the address, ask for it; never guess a hostname.To discover or confirm candidates, you may list open tab URLs (read-only):
osascript -e 'tell application "Safari" to get URL of every tab of every window'
osascript -e 'tell application "Google Chrome" to get URL of every tab of every window'
If several Atlassian-looking tabs match (e.g. two sites), show them and ask the
user which one to use, then set ATL_HOST to that hostname.
# Safari → atl_safari.sh ; Chrome → atl_chrome_mac.sh (identical interface)
SH=skills/atlassian-browser-macos/scripts/atl_safari.sh
# read
"$SH" GET /rest/api/3/myself
"$SH" GET "/rest/api/3/issue/ABC-123?fields=summary,status,assignee"
# search (JQL)
"$SH" POST /rest/api/3/search/jql '{"jql":"project = ABC AND statusCategory != Done","maxResults":20,"fields":["summary","status"]}'
# create issue (Cloud uses ADF for description)
"$SH" POST /rest/api/3/issue '{"fields":{"project":{"key":"ABC"},"issuetype":{"name":"Task"},"summary":"Hello"}}'
# update / comment / transition / delete
"$SH" PUT /rest/api/3/issue/ABC-123 '{"fields":{"summary":"New title"}}'
"$SH" POST /rest/api/3/issue/ABC-123/comment '{"body":{"type":"doc","version":1,"content":[{"type":"paragraph","content":[{"type":"text","text":"hi"}]}]}}'
"$SH" DELETE /rest/api/3/issue/ABC-123
# Confluence (Cloud is under /wiki)
"$SH" GET "/wiki/rest/api/content/123456?expand=body.storage,version"
"$SH" POST /wiki/rest/api/content '{"type":"page","title":"New","space":{"key":"DOCS"},"body":{"storage":{"value":"<p>hi</p>","representation":"storage"}}}'
Big/awkward bodies: pipe via stdin with - as the body arg:
echo '{"jql":"project = ABC"}' | "$SH" POST /rest/api/3/search/jql -
Self-hosted (Server/DC) host: set ATL_HOST to a substring of its URL, and use
/rest/api/2 (Jira) / /rest/api (Confluence):
ATL_HOST=jira.company.com "$SH" GET /rest/api/2/myself
Always JSON: {"status":200,"ok":true,"data":{...}}. Parse it yourself.
ok:false + 401/403 → not logged in / wrong tab.404 → Cloud vs DC base-path mismatch, or bad id/key."Safari is not running" / "Google Chrome is not running" / "no tab whose URL contains ..." → open & log into the site first.Save the response to a file, then parse it with a quoted heredoc ('PYEOF')
so the shell never touches the Python code — no \" escaping needed:
"$SH" GET /rest/api/3/myself > /tmp/atl_resp.json
python3 - /tmp/atl_resp.json <<'PYEOF'
import json, sys
d = json.load(open(sys.argv[1]))
print(f"{d['status']}\t{d['ok']}")
PYEOF
Never pipe into python3 -c '...' with \"-escaped quotes: inside shell single
quotes the backslashes reach Python verbatim and fail with
SyntaxError: unexpected character after line continuation character.
Inside a 'PYEOF' heredoc shell variables do not expand — pass values in
via sys.argv or env instead. For simple field extraction, jq -r '.data.key'
is an even shorter alternative.
GET, JQL/CQL search) need no approval.POST/PUT/DELETE that creates, edits,
deletes, comments, or transitions, state the exact target and get user approval
(per the workspace's external-write policy). Never bulk-delete without explicit
confirmation.Concrete input → output (Jira Cloud; SH = atl_safari.sh or atl_chrome_mac.sh).
Read the current user
$ skills/atlassian-browser-macos/scripts/atl_safari.sh GET /rest/api/3/myself
{"status":200,"ok":true,"data":{"accountId":"5b10...","emailAddress":"[email protected]"}}
Create an issue, then delete it (self-clean demo)
$ "$SH" POST /rest/api/3/issue '{"fields":{"project":{"key":"ABC"},"issuetype":{"name":"Task"},"summary":"demo"}}'
{"status":201,"ok":true,"data":{"id":"10110","key":"ABC-42"}}
$ "$SH" DELETE /rest/api/3/issue/ABC-42
{"status":204,"ok":true,"data":""}
Search with JQL (bounded query required)
$ "$SH" POST /rest/api/3/search/jql '{"jql":"project = ABC ORDER BY created DESC","maxResults":3,"fields":["summary"]}'
{"status":200,"ok":true,"data":{"issues":[{"key":"ABC-7","fields":{"summary":"..."}}]}}
inject failed: ... Allow JavaScript from Apple Events
THEN the one-time toggle is off — stop and do the setup steps; do not retry blindly.status is 401/403 THEN the tab isn't logged in (or it's the wrong
tab) — have the user log into the site, then retry.status is 404 on a valid id THEN the base path is wrong — switch
Cloud /rest/api/3 ↔ DC /rest/api/2 (and /wiki for Confluence Cloud).*.atlassian.net THEN use Cloud paths (ADF bodies, /wiki
for Confluence); ELSE assume Server/DC (plain-text bodies, no /wiki).400 Unbounded JQL THEN add a restriction
(e.g. project = ABC) — search/jql rejects unrestricted queries.POST/PUT/DELETE) THEN state the target and
get approval before running.fetch — DOM selectors break on
every Atlassian UI change.Authorization header. The session cookie already authenticates
the same-origin request; an injected token would be wrong and may be blocked.Allow JavaScript from Apple Events error — it never
succeeds until the user flips the toggle.PUT without
version.number = current + 1 fails with 409.DELETE purges — it trashes first; purge with
?status=trashed.GET …/transitions
first; ids differ per workflow.\"-escaped inline python3 -c — use the
file + 'PYEOF' heredoc pattern from Output.GET the current
body, keep the original nodes untouched, and construct only the replacement
nodes (tables and complex formatting are easy to corrupt otherwise).GET the
issue/page back once and confirm the text round-tripped without mojibake.Automated, CI-safe checks (bash syntax, AppleScript compile, arg validation, generated-JS validity — no browser/Automation needed):
bash skills/atlassian-browser-macos/tests/test_macos.sh
Run in CI by .github/workflows/test-atlassian-browser-macos.yml
on every PR to main and on merge.
Manual checks against a live tab:
# 1) plumbing only — expect a clean error, NO external call:
ATL_HOST=__none__ scripts/atl_safari.sh GET /rest/api/3/myself
# → {"status":0,"ok":false,"error":"no tab whose URL contains '__none__' ..."}
# 2) with a logged-in tab open — expect "status":200:
scripts/atl_safari.sh GET /rest/api/3/myself
scripts/atl_chrome_mac.sh GET /rest/api/3/myself
# 3) full self-clean CRUD (writes — get approval first):
# create → read → update → comment → transition → delete, then GET → expect 404.
# syntax checks:
bash -n scripts/atl_safari.sh scripts/atl_chrome_mac.sh
osacompile -o /tmp/_t.scpt scripts/safari_atl.applescript && rm /tmp/_t.scpt
osacompile -o /tmp/_t.scpt scripts/chrome_atl.applescript && rm /tmp/_t.scpt
decodeURIComponent(escape(atob(…))) (plain atob() mojibaked non-ASCII
bodies); add UTF-8 round-trip test + non-ASCII/ADF anti-patterns.'PYEOF'
heredoc) and the matching anti-pattern / troubleshooting entry.atl_chrome_mac.sh +
chrome_atl.applescript) alongside Safari; both use the live logged-in session.references/troubleshooting.md — error → cause →
fix table for this skill.../../references/atlassian-rest-cookbook.md
— full Jira/Confluence endpoint + payload reference (Cloud & DC).Guides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.
npx claudepluginhub baekchangjoon/atlassian-browser-skills --plugin atlassian-browser-skills