docspan
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) → --prefix → DOCSPAN_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
Full reference — every command's arguments, options, and behavior — is generated straight from the CLI's source at docs/commands.md, so it can't go stale here. Build it locally with pip install docspan[docs] && mkdocs serve. Quick summary:
| Command | What it does |
|---|---|
docspan push [FILES]... |
Push local markdown files to remote docs |
docspan pull [FILES]... |
Pull remote documents into local markdown files, with three-way merge |
docspan sync [FILES]... |
Pull then push each mapping — the safe default order |
docspan status |
Show all configured mappings in a table |
docspan map FILE --backend BACKEND |
Create a new remote doc/page and map it to a local file |
docspan migrate-sectioned FILE |
Split a single-file mapping into a sectioned mapping |
docspan style-guide |
Print (or --write into a file) backend authoring guidance |
docspan auth setup BACKEND |
Interactive authentication setup for a backend |
docspan comments respond FILE |
Post Reply:/Resolve: directives from a .comments.md sidecar |
docspan conflicts list |
List files with unresolved merge conflicts |
docspan conflicts resolve FILE --accept STRATEGY |
Resolve a merge conflict (remote, local, or merged) |
docspan config show / config add PREFIX MARKGATE |
View/register projects in the central config |
docspan migrate-xdg --prefix PREFIX |
Move legacy in-repo storage to XDG and register the project |
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_path → ACCOUNT_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 JSONACCOUNT_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_URLATLASSIAN_USER_NAMECONFLUENCE_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.
Metadata
Release files for docspan 0.8.6
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| docspan-0.8.6.tar.gz | 1.2 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| docspan-0.8.6-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.5 MB
Release files / docspan-0.8.6.tar.gz
| Download URL | docspan-0.8.6.tar.gz |
|---|---|
| Size | 1.2 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0353396b5bf71dcc9a9e75578740bf687f6ed03887806778a893b1d4009bdc13
|
|
BLAKE2b-256 checksum How to use checksums |
05ba1f95b51422b58d04800c1a14e346c04667a629f4210de1d208a243b772e7
|
| 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 30, 2026.
Transparency logRelease files / docspan-0.8.6-py3-none-any.whl
| Download URL | docspan-0.8.6-py3-none-any.whl |
|---|---|
| Size | 372.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
790e6a1002899b7a555adf2f3997e6ca2526953e55bcb335e06978734ac01f86
|
|
BLAKE2b-256 checksum How to use checksums |
c6da7c9fa73e3158460efe58e141cbf595568103aa0b517a7db179f3919e458d
|
| 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 30, 2026.
Transparency log