agentsclimarketplace

Confluence publisher

Skill eugenelim/agent-ready-repo/packs/atlassian/.apm/skills/confluence-publisher

Publish content to a Confluence page (Atlassian Cloud or Server/Data Center) by creating a new page or updating an existing one. Accepts Markdown (default), raw Confluence storage XHTML, or plain text. Resolves the target by page ID, URL, frontmatter `confluence_id`, or space + title lookup. Handles optimistic-locking 409s with one retry. Use when the user wants to push a report, design doc, or other content to a Confluence page they have access to.From its SKILL.md

Install
npx -y skills add eugenelim/agent-ready-repo --skill confluence-publisher

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 15 stars15 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

11.7 KB, ~2.9k tokens by cl100k_base, as published. Nobody here has run it

Confluence Publisher

Publish a single page to Confluence — create or update — from Markdown, storage XHTML, or plain text. Companion to confluence-crawler: same credentials namespace, same flavor support, opposite direction.

Instructions

You are a Confluence publishing agent. Authentication, REST mechanics, optimistic-locking retries, and the Markdown→storage conversion live in scripts/. Do not re-implement any of that; invoke the script with the right flags and report the result.

Flavor support

Same as the crawler:

  • Atlassian Cloud (*.atlassian.net) — Basic auth with email + API token. Base URL must include /wiki.
  • Confluence Server / Data Center — Bearer auth with a Personal Access Token.

Flavor is auto-detected from the base URL; override via CONFLUENCE_FLAVOR=cloud|server if needed.

Configuration location

Credentials are resolved by the build-projected credentials_shim.load_credentials through Tier 1 (env) → Tier 2 (OS keyring) → Tier 3 dotfile. The dotfile lives at ~/.agentbundle/credentials.env. The declared schema is at references/creds-schema.toml and shares the confluence namespace with confluence-crawler — if either skill has been configured, this one works.

KeyRequiredNotes
CONFLUENCE_BASE_URLyesCloud: https://<site>.atlassian.net/wiki. Server: https://confluence.corp.example.com.
CONFLUENCE_API_TOKENyesCloud API token or Server PAT.
CONFLUENCE_EMAILCloud onlyAtlassian account email.
CONFLUENCE_FLAVORnocloud or server. Auto-detected from URL host.

Populate any tier by running credential-setup skill.

Security rules (non-negotiable)

  • Secrets live only in ~/.agentbundle/credentials.env (mode 0600 on POSIX; DACL-restricted on Windows), the OS keyring, or process environment variables. Never read that file, print it, or echo the token.
  • Never put the token on the command line. The primitive refuses flags like --token / --api-token / --bearer / --pat / --password and exits — do not work around it.
  • If --check reports missing or invalid creds, tell the user to run credential-setup skill themselves. It's interactive — do not run it for them.

Step 1: Verify the environment

python -m pip install -r requirements.txt
python scripts/publish_page.py --check
  • Exit code 0 → authenticated, proceed.
  • Exit code 2 → the user must act (credentials missing/invalid/expired). Tell the user to run credential-setup skill themselves (interactive — they run it, not you). Stop here.
  • Any other non-zero → see When a request fails.

When a request fails

The CLI uses a banded exit-code contract; read the stderr message for the specific cause, then act on the band:

ExitBandWhat to do
0successproceed
1functional error — server 5xx, transport, keychain hard-fail, unexpectedsurface the message to the user; don't loop or retry blindly
2user must act — credentials (401/403), a publish conflict, or a target/input the user must fixfollow the NEED-INPUT: message: re-auth via credential-setup, resolve the conflict, or fix the target — then retry

Tier2HardFailError (OS keyring unavailable) or an unprojected shim surface as exit 1 with a message naming the cause.

Step 2: Decide how to identify the target page

In order of robustness — use whichever the user gave:

  1. By page ID or URL (preferred). --page-id 12345 or --url https://acme.atlassian.net/wiki/spaces/ENG/pages/12345/Some+Title. The page ID is parsed out of the URL. Idempotent.
  2. By frontmatter — if the input file was produced by confluence-crawler it carries confluence_id (and optionally version, space_key) in YAML frontmatter. --from-frontmatter reads it. This is the round-trip case (crawl → edit → publish back).
  3. By space + title--space ENG --title "My Page" [--parent-id 999]. Looks up by title; if found, updates; if not, creates. Title lookups are fragile (titles change); prefer modes 1 and 2 when an ID is available.

If none of these are supplied, the script exits 2 and asks which. Do not guess.

Step 3: Publish

Pick the form that matches the user's request:

# Update an existing page by ID, from Markdown:
python scripts/publish_page.py --page-id 12345 --input report.md

# Same, but from a Confluence URL:
python scripts/publish_page.py --url 'https://acme.atlassian.net/wiki/spaces/ENG/pages/12345/Foo' --input report.md

# Round-trip case — the markdown came from confluence-crawler:
python scripts/publish_page.py --from-frontmatter --input crawled/eng-handbook.md

# Lookup-then-upsert by title:
python scripts/publish_page.py --space ENG --title "Q2 Report" --parent-id 999 --input report.md

# Plain text body (one paragraph per line):
python scripts/publish_page.py --page-id 12345 --input - --input-format text   # stdin

# Already-rendered storage XHTML:
python scripts/publish_page.py --page-id 12345 --input snippet.xhtml --input-format storage

# Dry-run — print what would be sent, do not call write APIs:
python scripts/publish_page.py --page-id 12345 --input report.md --dry-run

Flags:

FlagMeaning
--checkVerify credentials and connectivity, then exit.
--page-id IDUpdate this page (preferred).
--url URLParse page ID from a Confluence URL.
--from-frontmatterRead confluence_id (and optional version) from input file's YAML frontmatter.
--space KEY --title TITLELookup-then-upsert by title. --parent-id ID optional.
--input PATH or -Source file (or - for stdin). Required.
--input-formatmarkdown (default), storage, text.
--version-comment TEXTRecorded on the new page version. Defaults to a generic message.
--attach PATH (repeatable)Upload file as a page attachment; Markdown image refs whose target filename matches an attachment get rewritten to <ac:image>.
--label LABEL (repeatable)Apply labels after publish.
--dry-runPrint the rendered storage XHTML and planned operation; no writes.
--insecureDisable TLS verification (Server/DC w/ self-signed). User-requested only.
--verboseDebug logging.

Step 4: Interpret the output

On success the script prints:

OK: <create|update> page 12345 (version 8) — https://acme.atlassian.net/wiki/spaces/ENG/pages/12345/Foo

On a 409 (someone else edited between read and write) the script re-reads the page once and retries with the new version number. If the second attempt still conflicts, it surfaces the error — tell the user a human edited concurrently and ask them to re-run.

Behavior notes

  • Update vs create. --page-id/--url always updates; never creates a new page at a specific ID. --from-frontmatter updates the page named in the frontmatter. --space + --title updates if a page with that title exists in the space, otherwise creates one (under --parent-id if given, otherwise at the space root).
  • Title. On update, the title is taken from --title if given, the first # H1 of the markdown if not (markdown input only), and the existing page title as a final fallback. On create, --title is required (or the first H1 if --input-format markdown). Heads-up: for markdown input, the H1 overrides the existing page title even on a routine re-publish — if you don't want a rename, pass --title explicitly or strip the H1.
  • Attachment ordering. On an update of an existing page, attachments upload before the body update so <ac:image> references resolve immediately. On a create, attachments upload after the page is created (the page must exist first); the body's image refs render broken for the subsecond gap between create and the attachment uploads. Failure semantics are not symmetric: if an update's attachment uploads partly succeed and then raise, the body update is skipped — the page still shows the prior body but now has the new attachments orphaned on it; re-running is idempotent because Confluence dedupes attachment uploads by filename. On create, an attachment failure after a successful create leaves the page in place with the body referencing un-uploaded files.
  • Version comment. Recorded on the new version; helps reviewers see why an agent edited. Default: Published by confluence-publisher.
  • Markdown conversion. Renders CommonMark via markdown-it-py, then post-processes to storage XHTML. The macro round-trip mirrors confluence-crawler's allowlist: info / warning / note / tip / panel / expand / code. Bold-leadin admonitions (**Note:** …, **Tip:** …, **Warning:** …, **Info:** …, **Important:** …) become the matching macro. Other Markdown is rendered as standard XHTML elements Confluence accepts.
  • Attachments. --attach uploads each file as a page attachment. After upload, Markdown image references in the input whose target filename matches an attached filename are rewritten to <ac:image><ri:attachment ri:filename="…"/></ac:image>. Files not matched are uploaded anyway (the user might link them by other means).
  • Labels. Applied after the page write; failure to apply labels is reported but does not roll back the page write.
  • Mermaid / PlantUML. Out of scope. Run the mermaid-renderer skill first to pre-render fenced ```mermaid blocks to PNGs, then pass those PNGs via --attach to this skill.

Don't

  • Don't read ~/.agentbundle/credentials.env from skill body.
  • Don't print or log the token.
  • Don't run credential-setup skill non-interactively or pipe the token into it.
  • Don't write your own REST calls to Confluence — extend the scripts and surface the gap to the user if a flag is missing.
  • Don't auto-resolve a title collision by appending suffixes — surface the ambiguity (the script does this) and ask which page to update.
  • Don't assume --insecure is safe to add by default; only when the user explicitly accepts it.
  • Don't pass --force to bypass a 409 — there is no such flag. Concurrent edits need human attention.

Edge cases

  • Page moved between spaces between when the user got the URL and when you publish: the page ID still resolves; the publish targets the page in its current space.
  • Title collision in lookup mode: if GET /rest/api/content?spaceKey=X&title=Y returns more than one result (rare but possible across page states), the script exits 2 with the list of IDs. Ask the user which to target via --page-id.
  • Frontmatter without confluence_id: the script exits 2 and asks for one of the other identification flags.
  • Storage-format input with invalid XHTML: the API returns 400; the script surfaces the error message. Don't try to fix it client-side — ask the user.
  • Network failure mid-publish. Reads (the version probe) are retried by the client. Writes are not — a failed PUT/POST means the page is in its prior state; re-run.
  • Large pages. Confluence soft-caps storage at ~5 MB. Beyond that, break the content into linked sub-pages; this skill doesn't do that for you.

What ships with it: 10 files

58.7 KB alongside SKILL.md, 5 of them executable

evals/

references/

scripts/

Keep looking

Skills are one crate of 326,834. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.