Skip to content

Sync

Bidirectional synchronization between local markdown files and Confluence pages.

  • Authenticated profile with Confluence access (atlcli auth login)
  • Space permission: View for pull, Edit for push operations
  • Initialized sync directory (atlcli wiki docs init)

atlcli provides powerful sync capabilities:

  • Watch: Real-time bidirectional sync with file watching and remote polling
  • Pull: Download Confluence pages as markdown
  • Push: Upload local changes to Confluence
  • Conflict resolution: Three-way merge with automatic and manual resolution

Set up a directory for sync:

Terminal window
atlcli wiki docs init ./docs --space TEAM

Options:

Flag Description
--space Space key (required)
--page-id Sync single page by ID
--ancestor Sync tree under specific page
Terminal window
# Sync entire space
atlcli wiki docs init ./docs --space TEAM
# Sync specific page tree
atlcli wiki docs init ./docs --ancestor 12345
# Sync single page only
atlcli wiki docs init ./docs --page-id 12345

The most powerful sync mode - automatically syncs changes in real-time using file watching and remote polling:

Terminal window
atlcli wiki docs sync ./docs

Watch mode combines two mechanisms for bidirectional sync:

  1. Local file watching: Detects changes to local markdown files instantly
  2. Remote polling: Periodically checks Confluence for remote changes
sequenceDiagram
participant L as Local Files
participant A as atlcli sync
participant C as Confluence
Note over L,C: Bidirectional Sync Flow
L->>A: File changed (watched)
A->>C: Push to remote
C-->>A: OK
loop Every 30s (configurable)
A->>C: Poll for changes
C-->>A: Changed pages
end
A->>L: Pull if changed
Terminal window
atlcli wiki docs sync <dir> [options]
Flag Description
--space <key> Sync entire space
--ancestor <id> Sync page tree under parent ID
--page-id <id> Sync single page
--poll-interval <ms> Remote polling interval, 10086400000 (default: 30000)
--no-poll Disable remote polling (local watch only)
--no-watch Disable file watching (poll only)
--on-conflict <mode> Conflict handling: merge, local, remote
--auto-create Auto-create pages for new local files
--label <label> Only sync pages with this label
--dry-run Report the plan and exit without changing anything
--json JSON line output for scripting

Run atlcli wiki docs sync --help for the same list from the CLI.

Terminal window
atlcli wiki docs sync ./docs --page-id 12345 --dry-run

--dry-run reports the plan and exits. It performs no filesystem writes at all:

  • No pages are pulled; no local file is created, moved or overwritten.
  • Legacy <file>.meta.json / <file>.base sidecars are reported as Would migrate: and left on disk — they are neither migrated nor deleted.
  • No .atlcli/ directory, state store or cache is created, and the target directory itself is not created if it does not exist.
  • The sync daemon is not started, so no watcher and no poller run.

The poller periodically checks Confluence for changes. Efficiency depends on sync scope:

Scope API Calls Best For
--page-id Minimal (1 page) Editing single document
--ancestor Moderate (tree) Working on a section
--space Higher (all pages) Full documentation sync
Terminal window
# Poll single page (most efficient)
atlcli wiki docs sync ./docs --page-id 12345
# Poll page tree
atlcli wiki docs sync ./docs --ancestor 12345
# Poll entire space
atlcli wiki docs sync ./docs --space TEAM

The poller detects three types of remote changes:

Event Description Action
created New page added in scope Pull to local
changed Page content or title updated Pull or merge
deleted Page removed from scope Notify user
Terminal window
# Fast polling (every 10 seconds) - more API calls
atlcli wiki docs sync ./docs --poll-interval 10000
# Slow polling (every 2 minutes) - fewer API calls
atlcli wiki docs sync ./docs --poll-interval 120000
# Disable remote polling entirely
atlcli wiki docs sync ./docs --no-poll

Note: Lower intervals mean faster remote change detection but more API requests. The default (30 seconds) balances responsiveness with API usage.

For instant remote change detection without polling overhead, use webhooks:

Terminal window
atlcli wiki docs sync ./docs \
--webhook-port 8080 \
--webhook-url https://your-server.com:8080/webhook
Flag Description
--webhook-port Local webhook server port
--webhook-url Public URL to register with Confluence

Webhook Events:

The webhook server handles these Confluence events:

Event Description
page_created New page created
page_updated Page content changed
page_removed Page deleted
page_trashed Page moved to trash
page_restored Page restored from trash
page_moved Page moved to different parent

Webhooks vs Polling:

Feature Polling Webhooks
Latency 0-30s (configurable) Instant
Setup None Requires public URL
API usage Regular requests On-demand only
Reliability Always works Requires connectivity
Filtering By scope By page ID and space

For local development, use polling. For production servers with public URLs, webhooks provide better performance.

Create Confluence pages automatically for new local files:

Terminal window
atlcli wiki docs sync ./docs --space TEAM --auto-create

Auto-create triggers in two scenarios:

  1. During initial sync: atlcli creates untracked local files (no frontmatter ID) as new pages
  2. During file watching: New files added while sync is running are automatically created

How it works:

Aspect Behavior
Title From frontmatter title, or filename converted to title case
Parent Space home page (for --space) or ancestor (for --ancestor)
Content Full markdown content converted to Confluence storage format

Example workflow:

Terminal window
# Start sync with auto-create
atlcli wiki docs sync ./docs --space TEAM --auto-create
# In another terminal, create a new file
echo "# My New Feature" > ./docs/my-new-feature.md
# Sync automatically:
# 1. Detects new file
# 2. Creates page "My New Feature" in Confluence
# 3. Updates local file with frontmatter containing page ID

After auto-create, the file is updated with frontmatter:

---
atlcli:
id: "12345678"
title: "My New Feature"
---
# My New Feature

When sync runs, atlcli creates a lock file at .atlcli/.sync.lock. This:

  • Prevents concurrent sync operations
  • Signals to other tools that sync is active
  • Is automatically removed on clean shutdown

Download pages from Confluence to local markdown files:

Terminal window
atlcli wiki docs pull ./docs

The path argument is optional and defaults to the current directory. Pass the same path you used for init (or cd into it and omit the path) so files land where the state expects them. --dir <path> does the same thing as the positional argument and is the spelling to use from scripts.

Options:

Flag Description
--dir <path> Target directory (same as the positional path; use it when running from elsewhere)
--out <path> Legacy alias for --dir
--space Filter by space key
--page-id Pull specific page
--ancestor Pull page tree
--label Filter by label
--force Overwrite local modifications - markdown and attachments
--comments Export comments to sidecar files
--no-attachments Skip downloading attachments
--limit <n> Maximum pages to pull

Local modifications are never overwritten silently

Section titled “Local modifications are never overwritten silently”

Pull compares each file it is about to write against the hash recorded at the last sync. Anything you changed locally is left alone and reported:

Skipping architecture.md (local modifications, use --force)
Skipping architecture.attachments/diagram.png (local modifications, use --force)

This applies to markdown files and to attachments alike. Skipped attachments are counted as attachmentsSkipped in --json output. Use --force to discard the local version and take the remote one.

Terminal window
# Pull using saved scope from init
atlcli wiki docs pull ./docs
# Pull into ./docs from an unrelated working directory
atlcli wiki docs pull --dir ./docs
# Pull pages with specific label
atlcli wiki docs pull ./docs --label api-docs
# Pull with comments exported
atlcli wiki docs pull ./docs --comments
# Force pull (overwrite local changes, attachments included)
atlcli wiki docs pull ./docs --force
# Pull without attachments
atlcli wiki docs pull ./docs --no-attachments

When using --comments, page comments are saved to sidecar files:

docs/
├── architecture.md
├── architecture.comments.json # Comments for architecture.md
└── api-reference.md

The comments file contains both footer comments and inline comments with their full thread hierarchy.

Upload local changes to Confluence:

Terminal window
atlcli wiki docs push ./docs

The path argument is optional. When omitted, push walks up from the current directory to find the nearest .atlcli/ — so from inside your synced tree atlcli wiki docs push and atlcli wiki docs push --page-id 12345 both work without any path.

Options:

Flag Description
--dir <path> Target directory (same as the positional path)
--page-id <id> Push specific page by ID
--validate Validate the files being pushed before pushing
--strict Treat warnings as errors
--json JSON output
Terminal window
# Push all tracked files
atlcli wiki docs push ./docs
# Push single file
atlcli wiki docs push ./docs/page.md
# Push by page ID (from anywhere inside the synced tree)
atlcli wiki docs push --page-id 12345
# Validate before pushing
atlcli wiki docs push ./docs --validate
# Strict validation (fail on warnings)
atlcli wiki docs push ./docs --validate --strict

Pre-push validation checks for:

Check Severity Description
Broken links Error Target file not found
Untracked pages Warning Link to page without ID
Unclosed macros Error :::info without closing :::
Page size Warning Content exceeds 500KB

--validate checks exactly what the push will send: the single file for push <file> or push --page-id <id>, and the collected (ignore-filtered) set for a directory push. Errors in files you are not pushing do not block the push, and --validate works outside an initialized tree too - it validates the named file rather than doing nothing.

Terminal window
# Run validation separately
atlcli wiki docs check ./docs
# From an unrelated working directory
atlcli wiki docs check --dir ./docs
# Strict mode (warnings are errors)
atlcli wiki docs check ./docs --strict
# JSON output for CI/scripts
atlcli wiki docs check ./docs --json

Add a new local file to Confluence tracking:

Terminal window
atlcli wiki docs add <file> [options]
Flag Description
--title <t> Page title (default: from H1 or filename)
--parent <id> Parent page ID
--template <name> Use template for initial content
Terminal window
# Add file with auto-detected title
atlcli wiki docs add ./docs/new-page.md
# Add with specific parent
atlcli wiki docs add ./docs/guide.md --parent 12345
# Add using a template
atlcli wiki docs add ./docs/meeting.md --template meeting-notes

Compare local file with remote Confluence page:

Terminal window
atlcli wiki docs diff <file>

Shows unified diff with colored output:

  • Green: additions (local has, remote doesn’t)
  • Red: deletions (remote has, local doesn’t)
  • Cyan: file headers and hunk markers
Terminal window
# Compare single file
atlcli wiki docs diff ./docs/api-reference.md
# JSON output with statistics
atlcli wiki docs diff ./docs/api-reference.md --json

atlcli detects conflicts when:

  • Local file was modified since last sync
  • Remote page was modified since last sync
Conflict detected: docs/api.md
Local: Modified 2025-01-14 10:30 (version 5 → local changes)
Remote: Modified 2025-01-14 10:35 (version 5 → version 6)

Control how conflicts are handled in sync mode:

Terminal window
atlcli wiki docs sync ./docs --on-conflict <strategy>
Strategy Behavior when the three-way merge fails
merge Write conflict markers into the local file and stop (default)
local Local wins: push the local file over the remote page in one update
remote Remote wins: overwrite the conflicted local file in place; the local edit is discarded and never pushed

All three emit a conflict event first, so --json consumers see which strategy was applied:

{"schemaVersion":"1","type":"conflict","message":"Conflict (1 regions), keeping remote (--on-conflict remote): ./docs/api.md","file":"./docs/api.md","pageId":"12345","details":{"conflictCount":1,"resolution":"remote"}}

With --on-conflict merge, atlcli attempts automatic merging:

  1. Uses the common ancestor (base) version
  2. Computes diffs from both local and remote
  3. Merges non-conflicting changes
  4. Marks conflicting sections with markers

When merge cannot auto-resolve, conflict markers are inserted:

## API Reference
<<<<<<< LOCAL
This endpoint returns user data in JSON format.
=======
This endpoint returns user data. Response format is JSON.
>>>>>>> REMOTE
### Authentication
Terminal window
# Edit file to resolve conflicts manually
vim docs/api.md
# Then resolve with chosen strategy
atlcli wiki docs resolve docs/api.md --accept local
atlcli wiki docs resolve docs/api.md --accept remote
atlcli wiki docs resolve docs/api.md --accept merged # After manual edit

Check sync status of all tracked files:

Terminal window
atlcli wiki docs status ./docs

Output:

Sync status for ./docs:
synced: 12 files
local-modified: 2 files
remote-modified: 1 files
conflict: 1 files
untracked: 3 files
folders: 2
Attachments:
synced: 8
local-modified: 1
conflict: 0
missing: 0
Last sync: 2025-01-14T10:30:00Z
Modified:
api-reference.md (local changes)
getting-started.md (remote changes)
architecture.attachments/diagram.png (attachment local changes)
Conflicts:
troubleshooting.md

The file counters cover markdown only. Attachments are counted separately (they have their own base hashes) and appear in --json output under attachmentStats; individual files show up in modified with the type attachment local changes, or attachment deleted locally when the file is gone.

Options:

Flag Description
--dir <path> Target directory (same as the positional path)
--links Analyze link changes vs stored links
--json JSON output

Create .atlcliignore to exclude files from sync:

Terminal window
# .atlcliignore (gitignore syntax)
# Ignore drafts
drafts/
*.draft.md
# Ignore specific files
internal-notes.md
# Ignore by pattern
temp-*.md
# Negation (include despite earlier rule)
!important-draft.md

Default ignores (always excluded):

  • .atlcli/ - sync state directory
  • *.meta.json - legacy metadata files
  • *.base - base versions for merge
  • .git/ - git directory
  • node_modules/ - node dependencies

Note: Patterns from .gitignore are also respected and merged with .atlcliignore.

After init and pull:

docs/
├── .atlcli/
│ ├── config.json # Sync configuration
│ ├── state.json # Sync state (versions, timestamps)
│ ├── .sync.lock # Lock file (when sync running)
│ └── cache/ # Base versions for 3-way merge
│ └── 12345.md # Base content by page ID
├── getting-started.md
├── api-reference.md
├── api-reference.attachments/
│ └── diagram.png # Attachments for api-reference.md
├── guides/
│ ├── installation.md
│ └── configuration.md
├── .atlcliignore # Ignore patterns
└── .gitignore # Git ignore (also respected)

.atlcli/state.json tracks sync state per page:

{
"lastSync": "2025-01-14T10:00:00Z",
"pages": {
"12345": {
"path": "api-reference.md",
"title": "API Reference",
"version": 5,
"localHash": "abc123",
"remoteHash": "abc123",
"baseHash": "abc123",
"syncState": "synced",
"parentId": "12340",
"ancestors": ["12340", "12300"]
}
}
}

Local files use YAML frontmatter with the atlcli namespace:

---
atlcli:
id: "12345"
title: "API Reference"
---
# API Reference
Content here...

The frontmatter fields:

Field Required Description
id Yes Confluence page ID (set automatically on pull/create)
title No Page title override (defaults to first H1 heading)

See File Format for details.

atlcli maps Confluence page hierarchy to directory structure:

Confluence: Local:
├── Parent Page ├── parent-page.md
│ ├── Child A ├── parent-page/
│ │ └── Grandchild │ ├── child-a.md
│ └── Child B │ ├── child-a/
│ │ └── grandchild.md
│ └── child-b.md

When pages move in Confluence, sync detects this and moves local files to match.

atlcli supports Confluence Cloud folders (introduced September 2024):

Confluence: Local:
├── My Folder (folder) ├── my-folder/
│ ├── Page A │ ├── index.md (type: folder)
│ └── Page B │ ├── page-a.md
│ └── page-b.md

atlcli represents folders as directories with an index.md file containing type: folder in frontmatter. See Folders for full details on folder support and limitations.

Most commands support --json for scripting. Apart from the two streaming commands noted below, --json writes exactly one JSON document to stdout — JSON.parse(stdout) is always safe. Progress notes, skip messages and warnings are suppressed in this mode; the counters in the result document report the same information (a skipped file shows up in results.skipped, not as a log line).

Terminal window
atlcli wiki docs status ./docs --json
{
"schemaVersion": "1",
"dir": "./docs",
"stats": {
"synced": 12,
"localModified": 2,
"remoteModified": 1,
"conflict": 1,
"untracked": 3
},
"lastSync": "2025-01-14T10:00:00Z"
}

sync and watch are the exception: they run until done (or until you stop them) and emit JSON Lines — one complete document per line. Parse these line by line, not as a whole.

Terminal window
atlcli wiki docs sync ./docs --json
{"schemaVersion":"1","type":"status","message":"Starting initial sync..."}
{"schemaVersion":"1","type":"pull","file":"api-reference.md","pageId":"12345","message":"Pulled: API Reference"}
{"schemaVersion":"1","type":"push","file":"guide.md","pageId":"12346","message":"Pushed: Guide"}

A command that exits non-zero reports the reason in an error object. For docs check this is folded into the report document so there is still only one document to parse — see Validation.

  1. Use watch mode for real-time collaboration on documentation
  2. Use labels to sync subsets of pages (--label architecture)
  3. Enable webhooks for production servers with public URLs
  4. Commit to Git - track markdown files in Git for version history
  5. Use .atlcliignore for drafts and local-only content
  6. Run validation before pushing (--validate)

If sync fails due to lock:

Terminal window
# Check if sync is running
ps aux | grep "atlcli wiki docs sync"
# Force remove stale lock (only if no sync is active)
rm ./docs/.atlcli/.sync.lock

If you get stuck in merge conflicts:

Terminal window
# Reset to remote version
atlcli wiki docs pull ./docs --force
# Or force push local version
atlcli wiki docs resolve ./docs/file.md --accept local
atlcli wiki docs push ./docs
Error: You don't have permission to edit page 12345

Check your Confluence permissions for the space/page.

If you see 429 errors, increase poll interval:

Terminal window
atlcli wiki docs sync ./docs --poll-interval 60000
  • File Format - Frontmatter structure and directory conventions
  • Ignore Patterns - Exclude files from sync with .atlcliignore
  • Validation - Pre-push validation rules
  • Attachments - Sync images and file attachments
  • Webhooks - Real-time sync without polling
  • Pages - Direct page operations (create, move, delete)
Jira and Confluence are trademarks of Atlassian Corporation Plc. atlcli is not affiliated with, endorsed by, or sponsored by Atlassian.