Skip to main content

docspan

PyPI License: MIT

Push and pull markdown to Google Docs and Confluence from a single CLI. docspan provides bidirectional sync with three-way merge conflict detection, structural diff push that preserves comments on unchanged paragraphs, and a simple YAML-based configuration file.

The config file is named markgate.yaml for backward compatibility. docspan.yaml is also recognized — if present in the working directory (and no markgate.yaml is there), docspan reads and writes it instead, so a project can rename its config file at its own pace.


Supported Backends

Backend Push Pull
Google Docs yes yes
Confluence yes yes

Install

pip install docspan

Optional: mermaid diagram rendering (Google Docs backend only). Pushing a ```mermaid fence to Google Docs renders it to a PNG via the official mermaid-cli (mmdc), which wraps Puppeteer/headless Chrome — there is no pure-Python renderer, so this is a Node.js dependency, not a uv/pip one, and isn't declared in pyproject.toml. Install it globally so docspan finds a real binary instead of falling back to npx (which re-fetches on every render):

npm install -g @mermaid-js/mermaid-cli   # tested against 11.x; older 10.x should also work

If Puppeteer's headless Chrome cache gets corrupted (a truncated download, an interrupted npm install), renders fail with an error like Could not find chrome-headless-shell; re-fetch it with:

npx puppeteer browsers install chrome-headless-shell

Without mmdc installed and without network access for npx to fetch it on demand, a mermaid render failure is reported as a push warning, not a crash — see Limitations.


Quick start

1. Create markgate.yaml:

backends:
  google_docs:
    credentials_path: /path/to/service-account.json

mappings:
  - local: docs/design-doc.md
    backend: google_docs
    remote_id: YOUR_GOOGLE_DOC_ID
    direction: both

2. Set up authentication:

docspan auth setup google_docs
# or
docspan auth setup confluence

3. Push and pull:

docspan push                     # push all mappings
docspan pull                     # pull all mappings
docspan status                   # show mapping table

4. Resolve conflicts (if any):

docspan conflicts list
docspan conflicts resolve docs/design-doc.md --accept remote

Configuration (markgate.yaml)

backends:
  google_docs:
    credentials_path: /path/to/service-account.json  # or use env ACCOUNT_A_CREDENTIALS_PATH
  confluence:
    base_url: https://yourorg.atlassian.net
    username: you@example.com
    api_token: your-api-token  # or env CONFLUENCE_API_TOKEN

mappings:
  - local: docs/notes.md
    backend: google_docs
    remote_id: YOUR_GOOGLE_DOC_ID
    direction: both  # push | pull | both
  - local: docs/page.md
    backend: confluence
    remote_id: YOUR_CONFLUENCE_PAGE_ID
    direction: both

Note: markgate.yaml is gitignored by default because it may contain API tokens. Commit a markgate.yaml.example template alongside it.


Central config & XDG storage

By default docspan stores its config, sync state, and credentials under the XDG base directories, and a central config lets you register multiple projects by prefix and run docspan from anywhere.

$XDG_CONFIG_HOME/docspan/config.yaml     # central config (project registry)
$XDG_CONFIG_HOME/docspan/<prefix>/…      # cached OAuth token
$XDG_STATE_HOME/docspan/<prefix>/…       # sync state + base store, per project

Central config (~/.config/docspan/config.yaml):

default_prefix: design-docs
projects:
  design-docs:
    markgate: ~/Documents/design-docs/markgate.yaml

Register and use projects:

docspan config add design-docs ~/Documents/design-docs/markgate.yaml   # register (prefix → markgate.yaml)
docspan config show                                                    # list projects + active resolution
docspan push --prefix design-docs                                      # or DOCSPAN_PREFIX, or default_prefix, or cwd match
docspan migrate-xdg --prefix design-docs                               # move legacy in-repo state to XDG + register

Prefix resolution order: --config PATH (legacy — storage stays beside the file) → --prefixDOCSPAN_PREFIX → cwd inside a registered project → default_prefix. If nothing matches, docspan falls back to a local ./markgate.yaml with beside-the-file storage (fully backward-compatible).


Command Reference

docspan map

docspan map FILE --backend google_docs|confluence [--title TITLE] [--direction push|pull|both]
docspan map FILE --backend google_docs --new-tab-in EXISTING_FILE [--title TITLE]

Create a new remote Google Doc / Confluence page (or, with --new-tab-in, a new tab inside the existing multi-tab Google Doc that EXISTING_FILE is already mapped to) and map FILE to it. --new-tab-in requires --backend google_docs and is mutually exclusive with --tab-id. Pushes FILE's current content immediately after creating the mapping.

docspan push

docspan push [FILES]... [--dry-run] [--config PATH]

Push local markdown files to remote docs. Skips mappings with direction = "pull". Accepts an optional list of local file paths to restrict which mappings are pushed.

docspan pull

docspan pull [FILES]... [--dry-run] [--config PATH]

Pull remote documents into local markdown files with three-way merge. Writes conflict markers to the file if automatic merge fails. For Google Docs, also writes a {file}.comments.md sidecar of the doc's comments (open + resolved, with quoted selections and reply threads) unless pull_comments: false.

docspan status

docspan status [--config PATH]

Display all configured mappings in a table showing local file, backend, remote ID, and direction.

docspan style-guide

docspan style-guide [--backend google_docs|confluence] [--write FILE]

Print backend authoring guidance (e.g. "one image per line on google_docs"). This ships inside the installed package, so re-running it after a docspan upgrade picks up new guidance without hand-copying anything. With --write FILE, embed it as a marked, idempotent block in a file in your own repo (a CLAUDE.md, a style guide doc, etc.) — re-running updates docspan's managed block in place instead of duplicating it.

docspan auth setup

docspan auth setup BACKEND [--config PATH]

Interactive authentication setup. BACKEND is one of google_docs or confluence.

For Google Docs, run it with no flags for a guided flow:

docspan auth setup google_docs

It detects your current state, lets you pick Personal (OAuth) [recommended] or Service account, auto-detects a client_secret.json (scanning ., .markgate/, ~/Downloads) or prompts for the path with validation, runs the browser sign-in, verifies the connection, and offers to persist the choice into markgate.yaml so you never repeat it. In a non-TTY/CI environment it prints manual instructions instead of prompting.

Everything is scriptable — any answer can be supplied as a flag: --oauth / --service-account, --client-secret PATH, --credentials PATH. If a docspan push/pull runs without credentials in an interactive terminal, it offers to run setup inline and then continues.

For Confluence, prompts for base URL, username, and API token, then prints a YAML snippet to add to markgate.yaml.

docspan conflicts list

docspan conflicts list [--config PATH]

Scan all tracked files for unresolved merge conflict markers (<<<<<<< ). Prints a table of conflicted files and conflict block counts.

docspan conflicts resolve

docspan conflicts resolve FILE --accept remote|local|merged [--config PATH]

Resolve a merge conflict in a tracked file.

Strategy Behavior
remote Re-fetch the remote version and overwrite the local file
local Restore the pre-merge local content from the .orig backup
merged Accept the current file contents as the resolved version (conflict markers must be removed first)

Configuration Reference

backends.google_docs

Field Type Default Description
credentials_path string null Path to a Google service account JSON key
oauth_client_secret_path string null Path to an OAuth client secret JSON (Desktop app) for per-user auth
token_path string $XDG_CONFIG_HOME/docspan/google_token.json Where the cached OAuth user token is stored/refreshed (out of the repo)
pull_comments bool true On pull, write a {file}.comments.md sidecar of the doc's comments

Auth resolution order: credentials_pathACCOUNT_A_CREDENTIALS[_PATH] env → per-user OAuth (oauth_client_secret_path, or an already-cached token_path).

Environment variable alternatives (service account):

  • ACCOUNT_A_CREDENTIALS_PATH — path to service account JSON
  • ACCOUNT_A_CREDENTIALS — inline service account JSON string

backends.confluence

Field Type Default Description
base_url string null Confluence base URL, e.g. https://yourorg.atlassian.net
username string null Atlassian account email
api_token string null API token from id.atlassian.com

Environment variable alternatives:

  • CONFLUENCE_BASE_URL
  • ATLASSIAN_USER_NAME
  • CONFLUENCE_API_TOKEN

mappings[]

Field Type Default Required Description
local string yes Relative path to local markdown file
backend string yes "google_docs" or "confluence"
remote_id string yes Google Doc ID or Confluence page ID
direction enum "both" no "push", "pull", or "both"
tab_id string null no Google Docs tab id (e.g. "t.moqlkhpwn82e") to target on a multi-tab doc. Ignored by the Confluence backend. If unset and the doc has more than one tab, push/pull still succeed but report a "warning" naming the tabs found, instead of silently syncing whichever tab is first.
pageless bool null no Reconciles the Google Doc's pageless/pages toggle on every push. true sets PAGELESS, false sets PAGES, null (default) leaves the doc's current mode untouched. Ignored by the Confluence backend.

State Files

docspan generates these files in your project directory after first sync:

File Description
.markgate-state.json Sync state tracking (content hashes, remote versions)
.markgate-base/ Content-addressed store of merge bases
{file}.orig Backup of local file before merge; deleted after conflict resolution
{file}.comments.md Comment sidecar (Google Docs + Confluence); written during pull if comments exist
{file}.mermaid-cache.yaml Google Docs: maps each pushed ​```mermaid fence's rendered-PNG hash back to its source, so a different machine pulling the doc can still restore the fence instead of a bare image link. Written during push if the file has any mermaid fences.

Commit .markgate-state.json, .markgate-base/, and {file}.mermaid-cache.yaml — do not gitignore them. .markgate-state.json/.markgate-base/ need to be shared for three-way merge to work across machines/teammates; {file}.mermaid-cache.yaml is what makes mermaid-fence recovery work across machines at all (Google Docs has no API-writable place to store a diagram's source, only the rendered image — see mermaid_cache_sidecar.py's module docstring for why). {file}.orig and {file}.comments.md are transient/informational and safe to gitignore if you prefer.


Known Limitations


License

MIT. See LICENSE for details.

For contribution guidelines, see CONTRIBUTING.md. For the full change history, see CHANGELOG.md.

Release files for docspan 0.7.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for docspan 0.7.0
File Size Uploaded
docspan-0.7.0.tar.gz 1.1 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for docspan 0.7.0
File Interpreter ABI Platform
docspan-0.7.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.5 MB

Release files / docspan-0.7.0.tar.gz

Download URL docspan-0.7.0.tar.gz
Size 1.1 MB
Tags Source
SHA-256 checksum
How to use checksums
8f0e8d2aca71acb18c691b366696c26a0bf41398c19bf699a8adfddebd343fd5
BLAKE2b-256 checksum
How to use checksums
7856758b2cd6d2cc4216545c29cad0f7aa9cd559f134cf73170a009508bb9bca
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.

Transparency log

Release files / docspan-0.7.0-py3-none-any.whl

Download URL docspan-0.7.0-py3-none-any.whl
Size 361.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b574eea4051c1fccfbc877d7851b448223d05456758c55e0093ce132ca404360
BLAKE2b-256 checksum
How to use checksums
d1f4fce0b4a19197559f43077b4ab2ffb7d6ec8b734c1052746c6f8354e982e6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.7.0 This release

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page