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 - 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-ready —
confpub guidereturns the full CLI schema;LLM=truesuppresses 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:
stdoutis exclusively JSON — one object, no preamble, no epilogueerrorsandwarningsare always arrays (possibly empty)resultis always present (nullon failure)stderrgets 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 |
 |
Upload attachment + <ac:image> reference |
| Tables | Standard XHTML <table> |
~~strikethrough~~ |
<del>strikethrough</del> |
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file confpub_cli-1.4.3.tar.gz.
File metadata
- Download URL: confpub_cli-1.4.3.tar.gz
- Upload date:
- Size: 178.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c198f0fcb6a79cd989a946904b28f4b9efb07429b4293ede11769cdfacca7c45
|
|
| MD5 |
02c80a5e37081d6aea654084e4b645ed
|
|
| BLAKE2b-256 |
8c37806778cca5498a37777ed52491efcb3945a715226fc3857073800b34c11f
|
Provenance
The following attestation bundles were made for confpub_cli-1.4.3.tar.gz:
Publisher:
publish.yml on ThomasRohde/confpub-cli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
confpub_cli-1.4.3.tar.gz -
Subject digest:
c198f0fcb6a79cd989a946904b28f4b9efb07429b4293ede11769cdfacca7c45 - Sigstore transparency entry: 1014393243
- Sigstore integration time:
-
Permalink:
ThomasRohde/confpub-cli@86cb2e7f2d01311569d3be0eebd2573f3e221679 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/ThomasRohde
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@86cb2e7f2d01311569d3be0eebd2573f3e221679 -
Trigger Event:
push
-
Statement type:
File details
Details for the file confpub_cli-1.4.3-py3-none-any.whl.
File metadata
- Download URL: confpub_cli-1.4.3-py3-none-any.whl
- Upload date:
- Size: 57.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e037d87702436d4936114f47a70ea2e8888da2224a3cbc84882afa5b0b5c6054
|
|
| MD5 |
c27a166cbfa15bd248681b8877340e71
|
|
| BLAKE2b-256 |
bf05800f9755c02098c72dca700126e2f36b59548c678c16632f084aefbe2eb4
|
Provenance
The following attestation bundles were made for confpub_cli-1.4.3-py3-none-any.whl:
Publisher:
publish.yml on ThomasRohde/confpub-cli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
confpub_cli-1.4.3-py3-none-any.whl -
Subject digest:
e037d87702436d4936114f47a70ea2e8888da2224a3cbc84882afa5b0b5c6054 - Sigstore transparency entry: 1014393329
- Sigstore integration time:
-
Permalink:
ThomasRohde/confpub-cli@86cb2e7f2d01311569d3be0eebd2573f3e221679 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/ThomasRohde
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@86cb2e7f2d01311569d3be0eebd2573f3e221679 -
Trigger Event:
push
-
Statement type: