Skip to main content

Agent-first CLI to publish Markdown to Confluence

Project description

confpub

Agent-first CLI to publish Markdown to Confluence.

Publish one file or an entire documentation tree — from the terminal, a CI pipeline, or an LLM agent. Every command returns structured JSON. Every error has a stable code. One call to confpub guide gives an agent everything it needs to drive the tool zero-shot.

Installation

Run directly with uvx (no install needed):

uvx confpub-cli --help

Or install permanently:

uv tool install confpub-cli   # recommended
pip install confpub-cli        # alternative

Once installed, the command is available as both confpub and confpub-cli.


Quick Start

Publish a single file

export CONFPUB_URL=https://yourorg.atlassian.net/wiki
export CONFPUB_TOKEN=your-api-token
export CONFPUB_USER=you@example.com

confpub page publish README.md --space DEV --parent "Engineering"

Publish a documentation tree

Create a confpub.yaml manifest:

schema_version: "1.0"
space: DEV
parent: "Engineering"

pages:
  - title: "Architecture Overview"
    file: docs/architecture.md
    assets:
      - docs/diagrams/*.png
    children:
      - title: "API Reference"
        file: docs/api.md
      - title: "Deployment Guide"
        file: docs/deploy.md

Then run the transactional workflow:

confpub plan create   --manifest confpub.yaml        # Plan (no writes)
confpub plan validate --plan confpub-plan.json       # Check for drift
confpub plan apply    --plan confpub-plan.json       # Apply to Confluence
confpub plan verify   --assertions verify.json       # Assert post-conditions

Or preview first with --dry-run:

confpub plan apply --plan confpub-plan.json --dry-run

Features

  • Structured JSON output — every command returns the same envelope shape on stdout
  • Transactional workflow — plan → validate → apply → verify with fingerprint-based conflict detection
  • Markdown → Confluence — code blocks become code macros, > [!NOTE] becomes Info panels, tables stay tables, task lists, math, definition lists, footnotes, panels, expand/collapse, page layouts, and {macro} syntax for Status, TOC, Jira, Anchor, Children, and more
  • Asset handling — images are uploaded as attachments and URLs are rewritten automatically
  • Idempotent — a lockfile tracks page IDs so re-publishing updates in place
  • Agent-readyconfpub guide returns the full CLI schema; LLM=true suppresses interactive behavior
  • Cloud + Server — works with Confluence Cloud (*.atlassian.net) and Server/Data Center

Commands

All commands follow a noun verb pattern. Verbs telegraph mutation intent.

Command Mutates Description
confpub guide No Machine-readable CLI schema
confpub search No Search Confluence content using CQL
confpub page list No List pages in a space
confpub page inspect No Detailed view of one page
confpub page publish Yes Publish a single Markdown file
confpub page pull No Pull Confluence pages to local Markdown
confpub page delete Yes Delete a page (supports --cascade)
confpub space list No List accessible spaces
confpub attachment list No List attachments on a page
confpub attachment upload Yes Upload a file as an attachment
confpub plan create No Generate a plan artifact from a manifest
confpub plan validate No Check a plan against current state
confpub plan apply Yes Execute a plan (supports --dry-run)
confpub plan verify No Assert post-conditions hold
confpub auth inspect No Show credential status
confpub config set Yes Write a config value
confpub config inspect No Show current config

Structured Envelope

Every command — success or failure — returns this exact shape on stdout:

{
  "schema_version": "1.0",
  "request_id": "req_20260228_143000_7f3a",
  "ok": true,
  "command": "page.publish",
  "target": {
    "space": "DEV",
    "title": "Architecture Overview"
  },
  "result": { "..." : "..." },
  "warnings": [],
  "errors": [],
  "metrics": {
    "duration_ms": 842
  }
}

On failure, ok is false, result is null, and errors contains structured error objects:

{
  "ok": false,
  "errors": [
    {
      "code": "ERR_CONFLICT_FINGERPRINT",
      "message": "Page was modified externally since plan was created",
      "retryable": false,
      "suggested_action": "fix_input",
      "details": {
        "page_id": "123456",
        "plan_fingerprint": "sha256:abc123",
        "current_fingerprint": "sha256:def456"
      }
    }
  ]
}

Invariants:

  • stdout is exclusively JSON — one object, no preamble, no epilogue
  • errors and warnings are always arrays (possibly empty)
  • result is always present (null on failure)
  • stderr gets progress events, diagnostics, and debug logs

Pulling Pages

Pull Confluence pages back to local Markdown files:

# Pull a single page by title
confpub page pull --space DEV --title "Architecture Overview" --output docs/

# Pull a single page by ID
confpub page pull --page-id 123456 --output docs/

# Pull a page and all its children recursively
confpub page pull --space DEV --title "Engineering" --recursive --output docs/

# Generate a manifest even for a single page
confpub page pull --page-id 123456 --manifest --output docs/

Pull flags

Flag Description
--space Confluence space key
--title Page title
--page-id Confluence page ID (alternative to --space + --title)
--output / -o Output directory (default: .)
--recursive / -r Pull child pages recursively
--force Overwrite existing local files
--layout flat (default) or nested directory structure
--no-attachments Skip downloading attachments
--manifest Generate confpub.yaml manifest

Recursive pulls automatically generate a confpub.yaml manifest and a confpub.lock lockfile for round-tripping back to Confluence.


Searching

Search Confluence content using CQL (Confluence Query Language):

# Search by CQL
confpub search --cql 'label = "api-docs"'

# Filter by space and type
confpub search --space DEV --type page --limit 10

# Combine CQL with filters
confpub search --space DEV --cql 'title ~ "deploy"'

Exit Codes

Code Meaning Action
0 Success
10 Validation error Fix input, do not retry
20 Auth / permission Re-authenticate or escalate
40 Conflict Re-plan, do not blindly retry
50 I/O error Retry with backoff
90 Internal error File a bug

Error Codes

Stable across versions. An agent can branch on these without parsing messages.

ERR_VALIDATION_REQUIRED          Missing required argument
ERR_VALIDATION_MANIFEST          Manifest fails schema validation
ERR_VALIDATION_MARKDOWN          Unparseable Markdown
ERR_VALIDATION_ASSET_MISSING     Referenced image not found on disk
ERR_VALIDATION_NOT_FOUND         Page or resource not found
ERR_VALIDATION_SPACE_MISMATCH    Space key mismatch between manifest and target

ERR_AUTH_REQUIRED                No credentials configured
ERR_AUTH_EXPIRED                 Token has expired
ERR_AUTH_FORBIDDEN               Lacks permission to write

ERR_CONFLICT_FINGERPRINT         Page changed since plan was created
ERR_CONFLICT_LOCK                Another confpub process holds the lock
ERR_CONFLICT_PAGE_EXISTS         Title exists with unexpected ID
ERR_CONFLICT_FILE_EXISTS         Local file already exists (pull)

ERR_IO_FILE_NOT_FOUND            Source file missing
ERR_IO_CONNECTION                Confluence unreachable
ERR_IO_TIMEOUT                   Request timed out

ERR_INTERNAL_CONVERTER           Markdown → Confluence conversion crashed
ERR_INTERNAL_REVERSE_CONVERTER   Confluence → Markdown conversion crashed
ERR_INTERNAL_SDK                 Unexpected API response

Authentication

Credentials are resolved in this order (highest precedence first):

CLI flags     →  --token / --user
Env vars      →  CONFPUB_TOKEN / CONFPUB_USER / CONFPUB_URL
Config file   →  ~/.config/confpub/config.json
OS keychain   →  via keyring

Cloud vs Server is auto-detected from the URL: *.atlassian.net uses token + email auth; everything else uses PAT.

# Check current auth status
confpub auth inspect

# Set config values
confpub config set base_url https://yourorg.atlassian.net/wiki
confpub config set user you@example.com

When LLM=true or stdin is non-interactive, confpub never prompts — it returns a structured ERR_AUTH_REQUIRED error instead.


Markdown Conversion

confpub converts Markdown to Confluence Storage Format (and back via page pull):

Markdown Confluence Output
# Heading <h1>Heading</h1>
**bold** <strong>bold</strong>
`code` <code>code</code>
Fenced code block <ac:structured-macro ac:name="code"> with language param
> [!NOTE] Confluence Info macro
> [!WARNING] Confluence Warning macro
> [!TIP] Confluence Tip macro
![img](photo.png) Upload attachment + <ac:image> reference
Tables Standard XHTML <table>
~~strikethrough~~ <del>strikethrough</del>
- [ ] task / - [x] done <ac:task-list> with task status
$E=mc^2$ LaTeX math macro (inline)
$$...$$ LaTeX math macro (block)
Term + : Definition <dl><dt><dd> definition list
[^1] footnotes Superscript links + numbered list
::: panel Title Confluence Panel macro
::: expand Title Confluence Expand macro
:::: layout two-equal Confluence page layout
---yaml--- front matter Silently stripped
{status:Done|colour=Green} Confluence Status lozenge
{toc} Table of Contents macro
{anchor:name} Anchor macro
{children} Children Display macro
{jira:PROJECT-123} Jira issue link/table
{recently-updated} Recently Updated macro
{excerpt-include:Page} Excerpt Include macro
{include:Page} Include Page macro
::: excerpt Excerpt macro (body)

Manifest Format

schema_version: "1.0"
space: DEV
parent: "Architecture Notes"

confluence:
  base_url: https://yourorg.atlassian.net/wiki
  auth:
    type: token  # Credentials via CONFPUB_TOKEN + CONFPUB_USER

conflict_strategy: fail     # fail | overwrite | skip
on_removal: leave           # leave | delete
version_comment: "Published by confpub @ {timestamp}"

labels:
  - architecture
  - auto-published

assertions:
  - type: page.exists
    title: "Overview"
  - type: page.parent
    title: "Components"
    expected_parent: "Overview"

pages:
  - title: "Overview"
    file: overview.md

  - title: "Component Design"
    file: components/design.md
    assets:
      - components/diagrams/*.png
    children:
      - title: "API Reference"
        file: components/api.md

Lockfile

After the first successful apply, confpub writes confpub.lock alongside the manifest. Commit this to version control — it maps page titles to Confluence page IDs for idempotent re-publishing.

{
  "schema_version": "1.0",
  "last_updated": "2026-02-28T14:35:00Z",
  "pages": {
    "Overview":          { "page_id": "123456", "version": 5 },
    "Component Design":  { "page_id": "123457", "version": 1 },
    "API Reference":     { "page_id": "123458", "version": 1 }
  }
}

Agent Integration

An LLM agent can drive confpub entirely from one bootstrap call:

# Step 1: Learn the CLI
confpub guide

# Step 2: Check credentials
confpub auth inspect

# Step 3: Explore
confpub space list
confpub page list --space DEV

# Step 4: Publish
confpub page publish doc.md --space DEV --parent "Docs" --dry-run
confpub page publish doc.md --space DEV --parent "Docs"

The guide command returns the complete schema — all commands with flags, all error codes with exit codes and retry hints, auth precedence, and concurrency rules:

confpub guide                          # Full schema
confpub guide --section auth           # Just auth info
confpub guide --section error_codes    # Just error codes
confpub guide --section commands       # Just commands

Environment variables for agents

Variable Effect
LLM=true Suppress interactive prompts; return structured errors instead
CONFPUB_TOKEN API token
CONFPUB_USER Email / username
CONFPUB_URL Confluence base URL

Development

# Clone and install with dev dependencies
git clone https://github.com/ThomasRohde/confpub-cli.git
cd confpub-cli
uv pip install -e ".[dev]"

# Run tests
pytest tests/ -v

# Run with coverage
pytest tests/ -v --cov=confpub

Releasing

Version is defined in confpub/__init__.py. Use uvx hatch to bump it:

uvx hatch version patch    # 0.2.1 → 0.2.2
uvx hatch version minor    # 0.2.1 → 0.3.0
uvx hatch version major    # 0.2.1 → 1.0.0

Then commit and push to main — GitHub Actions will publish to PyPI automatically.

Project Structure

confpub/
├── cli.py                # Typer app, commands, envelope wrapping
├── envelope.py           # Pydantic envelope model
├── errors.py             # Error codes, exit codes, ConfpubError
├── output.py             # TOON / LLM=true / isatty logic
├── config.py             # Credential precedence
├── confluence.py         # atlassian-python-api wrapper
├── converter.py          # Markdown → Confluence Storage Format
├── reverse_converter.py  # Confluence Storage Format → Markdown
├── manifest.py           # Manifest + plan artifact models
├── lockfile.py           # confpub.lock persistence
├── assets.py             # Asset discovery, upload, URL rewriting
├── planner.py            # plan.create
├── validator.py          # plan.validate
├── applier.py            # plan.apply
├── verifier.py           # plan.verify
├── publish.py            # page.publish shortcut
├── puller.py             # page.pull workflow
└── guide.py              # Machine-readable CLI schema

Technology Stack

Concern Choice
CLI framework Typer
Confluence API atlassian-python-api
Markdown parsing markdown-it-py
HTML → Markdown markdownify + BeautifulSoup4
Validation Pydantic v2
JSON serialization orjson
Credentials keyring + env vars

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

confpub_cli-1.7.1.tar.gz (198.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

confpub_cli-1.7.1-py3-none-any.whl (69.1 kB view details)

Uploaded Python 3

File details

Details for the file confpub_cli-1.7.1.tar.gz.

File metadata

  • Download URL: confpub_cli-1.7.1.tar.gz
  • Upload date:
  • Size: 198.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for confpub_cli-1.7.1.tar.gz
Algorithm Hash digest
SHA256 929e7e45bb98ef075368290e264506c67bd1f239e43b387e713c74d1dfdcc3b8
MD5 ed870e00f7c82cbc302c81693f2a751a
BLAKE2b-256 04d12da1490c98b7d89d7ecda6e57954fbd84bcbc52081bddf25b3ec4bdac62f

See more details on using hashes here.

Provenance

The following attestation bundles were made for confpub_cli-1.7.1.tar.gz:

Publisher: publish.yml on ThomasRohde/confpub-cli

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file confpub_cli-1.7.1-py3-none-any.whl.

File metadata

  • Download URL: confpub_cli-1.7.1-py3-none-any.whl
  • Upload date:
  • Size: 69.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for confpub_cli-1.7.1-py3-none-any.whl
Algorithm Hash digest
SHA256 35e4257a3c1680cf4e782f27f2eafdec129a016025e8e863cd9aeedfde12aab6
MD5 ef542e0bdbb4cabc559cfb0f17eccf7c
BLAKE2b-256 d234198e9fcc022a6ae4d9955d9eb3ad1d5de9107bb35efb3e83bbf3d9c141d9

See more details on using hashes here.

Provenance

The following attestation bundles were made for confpub_cli-1.7.1-py3-none-any.whl:

Publisher: publish.yml on ThomasRohde/confpub-cli

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page