Early Alpha (v0.0.1) -- This project is in early alpha and under active development. It has not been publicly released or reviewed. Use it only on a test vault with no real data. Sync, merge, and conflict resolution have known edge cases that may cause data loss. Always keep a separate backup of any vault you test with. Expect breaking changes between versions.
Sync files to remote storage using provider APIs directly -- no git binary needed. Available as an Obsidian plugin (desktop and mobile) and a standalone CLI. Supports GitHub and S3-compatible storage (AWS S3, Cloudflare R2, MinIO, Backblaze B2).
- API-only transport -- uses provider REST APIs directly (GitHub, S3-compatible), no native dependencies
- Cross-platform -- Obsidian plugin works identically on Desktop (Electron), iOS, and Android
- Three-way text merge -- concurrent edits to the same file are merged automatically using diff-match-patch
- Incremental sync -- mtime pre-filtering and snapshot-based change detection keep syncs fast
- Binary file support -- images and attachments sync with configurable conflict resolution (newest, local, or remote wins)
- Automatic sync -- debounced vault events trigger sync; polling on desktop, visibility events on mobile
- Configurable ignore patterns -- glob-based rules to exclude files from sync
The project codename is "Pylon Sync". The Obsidian plugin appears as "Pylon Sync" in the community plugins list. The npm scope is
@pylon-sync.
This is a monorepo with five packages:
| Package | Description |
|---|---|
@pylon-sync/core |
Platform-agnostic sync engine, reconciler, scanner, types |
@pylon-sync/provider-github |
GitHub provider using git-tree comparison (no metadata files in repo) |
@pylon-sync/provider-s3 |
S3-compatible storage provider (AWS S3, Cloudflare R2, MinIO, Backblaze B2) |
@pylon-sync/cli |
CLI companion -- sync directories from the terminal |
pylon-sync |
Obsidian plugin with vault integration and settings UI |
The CLI companion syncs any directory to GitHub using the same engine as the Obsidian plugin.
The CLI is not yet published to npm. To use it, clone the repository and run directly:
cd packages/cli && npx tsx src/main.ts init --repo owner/repo --token ghp_... [--branch main]| Command | Description |
|---|---|
pylon init --repo owner/repo --token TOKEN [--branch main] |
Initialize sync in current directory |
pylon sync [--full-scan] |
Run one sync cycle |
pylon status |
Show local changes since last sync |
The token can also be set via GITHUB_TOKEN environment variable.
- Install BRAT from the Obsidian community plugins
- Open BRAT settings and click "Add Beta Plugin"
- Enter
magarcia/pylon-sync - BRAT will install the plugin and keep it updated automatically
- Download
main.js,manifest.json, andstyles.cssfrom the latest release - Create the plugin directory:
<your-vault>/.obsidian/plugins/pylon-sync/ - Copy the three files into that directory
- Open Obsidian Settings > Community Plugins > enable "Pylon Sync"
- Go to github.com/settings/tokens?type=beta (fine-grained tokens)
- Click "Generate new token"
- Name it something like "Obsidian Vault Sync"
- Set the expiration as needed
- Under Repository access, select the specific repository you want to sync to
- Under Permissions > Repository permissions, set Contents to Read and write
- Click "Generate token" and copy it
- Open Obsidian Settings > Pylon Sync
- Paste your token into the GitHub Token field
- Enter your repository in
owner/repoformat (e.g.,magarcia/my-vault) - Set the branch (default:
main) - Enable "Auto sync" if you want automatic syncing
The settings tab includes a Verify button to test your token, a Browse dropdown to select from your repositories, and a Load branches dropdown to pick a branch. If the repository does not exist yet, the plugin auto-creates it as a private repo on first sync.
The plugin syncs immediately on first setup, pushing your vault to the repo (or pulling from it if the repo already has content). The first sync uses a ZIP archive download for fast bulk retrieval of existing files.
The sync engine uses a snapshot-based model with provider-specific remote change detection. The engine communicates with remote storage through a pluggable Provider interface, decoupling it from any specific backend. The GitHub provider uses git tree comparison via the Git Data API; the S3 provider uses object listing with ETag comparison. Each sync cycle:
- Scan -- detect local changes by comparing file hashes against the last-known snapshot
- Fetch -- detect remote changes by comparing git trees between the last-synced commit and current HEAD. If HEAD hasn't moved, there are no remote changes. Otherwise the engine fetches both trees and diffs them to find added, modified, and deleted files.
- Reconcile -- produce a set of mutations by merging local and remote change sets. Text files use three-way merge (base content fetched on-demand from git); binary files use last-modified-wins
- Push -- send file changes to GitHub via the Git Data API (blobs, trees, commits, refs)
- Apply -- write remote changes to the local filesystem
- Update snapshot -- persist the new snapshot and cursor for the next cycle
Because the GitHub provider compares git trees directly, external modifications to the repository are automatically detected. GitHub Actions, the web editor, pull requests, or any other tool that creates commits will be picked up on the next sync cycle. The S3 provider similarly detects external changes by comparing object ETags against its stored manifest.
All provider communication goes through a pluggable HttpClient abstraction. The Obsidian plugin uses requestUrl (which handles HTTPS on every platform and bypasses CORS); the CLI uses standard fetch.
| Setting | Default | Description |
|---|---|---|
| GitHub Token | -- | Personal access token with Contents read/write scope. Stored securely via SecretStorage (see Security). |
| Repository | -- | Target repo in owner/repo format. |
| Branch | main |
Branch to sync with. |
| Auto sync | On | Sync automatically on file changes. |
| Poll interval | 5m | How often to check for remote changes (desktop only). |
| Debounce delay | 30s | Wait time after last edit before syncing. |
| Sync .obsidian/ settings | Off | Include .obsidian/ config files. Excludes workspace.json, workspace-mobile.json, and cache/ regardless. |
| Ignore patterns | -- | Glob patterns, one per line. Matched files are excluded from sync. |
| Binary conflict resolution | newest | How to resolve concurrent binary edits: newest, local, or remote. |
| Full scan interval | 50 | Run a full hash scan (ignoring mtime) every N syncs. Catches files with stale timestamps. |
| Commit message | vault: sync |
Message used for commits pushed to GitHub. |
The Obsidian plugin works identically on all platforms Obsidian supports:
- Desktop (macOS, Windows, Linux) -- uses polling interval for periodic sync
- iOS -- syncs on app open and before backgrounding via visibility change events
- Android -- same as iOS
No platform-specific code or native dependencies.
The GitHub token is stored securely using Obsidian's SecretStorage API (OS keychain on desktop) with Obsidian's internal local storage as a fallback for older versions. The token is never written to data.json or any file in the vault.
- Token is stored outside the vault filesystem -- cloud sync (iCloud, Dropbox) cannot access it
- Obsidian Sync does not sync the token
- Git-based tools cannot commit the token
- Use a fine-grained token scoped to a single repository with only Contents read/write permission
- Set an expiration date on your token
The CLI stores the token in the OS keychain via cross-keychain (macOS Keychain, Windows Credential Manager, Linux libsecret). The token is never written to the config file.
A typical sync cycle uses ~7 GitHub API requests (for text-only changes). With the default 5-minute poll interval, that is roughly 84 requests per hour -- well under the 5,000 requests/hour limit for authenticated users.
If you hit a rate limit, the plugin displays a notice with the reset time and backs off automatically.
| Command | Description |
|---|---|
| Sync with GitHub | Trigger an immediate sync |
| Force full scan | Sync with a full hash scan, ignoring mtime pre-filtering |
| Reset sync data | Clear snapshot and cursor to start fresh |
All three are available from the Command Palette. The ribbon icon (refresh arrow) triggers an immediate sync -- it animates while a sync is in progress. During sync, a progress notice shows the current step (scanning, fetching, reconciling, etc.). The status bar displays per-file sync indicators: checkmark for synced, dot for modified, plus for new.
- Node.js >= 20
- pnpm >= 9
pnpm install# Run all tests
pnpm -r test
# Type check all packages
pnpm -r run typecheck
# Build Obsidian plugin
cd packages/obsidian-plugin && npm run build
# Run specific package tests
cd packages/core && npx vitest run
cd packages/provider-github && npx vitest run
cd packages/provider-s3 && npx vitest run
cd packages/cli && npx vitest runTo test changes in Obsidian:
-
Build the plugin in watch mode:
cd packages/obsidian-plugin && npm run dev
-
Create a symlink from your vault to the build output:
ln -s /path/to/pylon-sync/packages/obsidian-plugin /path/to/vault/.obsidian/plugins/pylon-sync
-
In Obsidian, enable the plugin and open the Developer Console (Ctrl+Shift+I) for logs.
-
Changes to any package are picked up automatically by esbuild's watch mode.
MIT