Cairnquire macOS Sync
A macOS menubar application for syncing and uploading files to a Cairnquire server.
Features
- Tray Icon:
tray.circle.fillas the default menubar icon with upload state animations - Drop on Menubar: Drag files directly onto the menubar icon — it bounces, then uploads
- Upload Progress: Icon switches to
tray.circle(outline) while uploading - Menubar Popover: Click the icon to see a beautiful status panel with quick actions
- Auto Sync: Watch a local folder and automatically sync markdown files to the server
- Drag & Drop Upload: Drop files onto the upload zone to upload and copy the URL to clipboard
- Quick Upload: Upload files via the menubar menu or popover
- Clipboard Notes: Create new notes from clipboard content
- Upload Index: View all uploaded files with timestamps
- Custom Icons: Choose from 11 different SF Symbols for the menubar icon
- CLI Tool: Command-line interface for one-off uploads and note creation
Requirements
- macOS 13.0+
- Xcode 15.0+ (for building)
- A running Cairnquire server
Building
Menubar App
cd apps/macos-sync
swift build
swift run CairnquireSync
Or open in Xcode:
open Package.swift
CLI Tool
cd apps/macos-sync
swift build --product cairnquire-cli
swift run cairnquire-cli --help
You can run the built executable directly after building:
.build/debug/cairnquire-cli help
Setup
- Start your Cairnquire server
- Create an API token from the web UI at
/account - Open the app settings (click the menubar icon → Settings)
- Enter your server URL and API token
- Optionally configure a sync folder and default upload folder
Usage
Menubar App
Left-click the icon to open a beautiful status popover showing:
- Live sync status with color indicator
- Last sync time
- Quick action buttons (Sync, Upload, Note, Drop Zone)
- Recent uploads preview
- Settings and server links
Right-click the icon for the traditional menu with all options.
- Drop on Icon: Drag files directly onto the menubar icon — it bounces, then uploads
- Sync Now: Manually trigger a sync
- Upload File: Select files to upload
- Create Note from Clipboard: Create a new markdown note from clipboard text
- Drop Zone: Open a drag-and-drop upload window
- View Uploads: See all uploaded files with timestamps
- Change Icon: Pick from 11 SF Symbols in Settings (default:
tray.circle.fill)
CLI Tool
Configure the shared app and CLI settings first. Use the port where your server is actually listening:
cd apps/macos-sync
swift run cairnquire-cli config \
--server http://localhost:9000 \
--token dev \
--folder "$HOME/Documents/Cairnquire" \
--upload-folder Inbox
Run one bidirectional sync:
swift run cairnquire-cli sync
Start the menubar app to keep watching the configured folder when auto-sync is enabled:
swift run CairnquireSync
Other CLI commands:
# Upload a file
swift run cairnquire-cli upload ~/Documents/report.pdf
# Create a note from clipboard
swift run cairnquire-cli note --clipboard --title="Meeting Notes"
# Create a note from stdin
echo "Hello World" | swift run cairnquire-cli note -
# Create a note from arguments
swift run cairnquire-cli note "This is a note body"
# Show command-specific documentation
swift run cairnquire-cli help upload
swift run cairnquire-cli help sync
Readable uploads (.md, .markdown, .txt, .html, and .htm) are published as rendered documents. If the same inbox filename already exists, the app asks whether to keep both files, replace the existing document, or cancel. If identical attachment bytes were already uploaded, the app asks whether to copy the existing URL or cancel.
Configuration
Configuration is stored in:
- Settings:
~/Library/Preferences/com.cairnquire.sync.plist - API Token: macOS Keychain
- Sync state:
~/Library/Application Support/CairnquireSync/
SwiftPM debug executables are ad-hoc signed. macOS may ask for Keychain access again after rebuilding because the replacement executable does not have a stable Developer ID identity. A packaged, consistently signed app avoids that development-time prompt. Updating settings preserves the existing Keychain item instead of recreating it.
Architecture
Sources/
├── CairnquireSync/ # Shared library
│ ├── API/
│ │ ├── Client.swift # HTTP client for Cairnquire API
│ │ └── Models.swift # API response models
│ ├── Sync/
│ │ ├── SyncEngine.swift # Bidirectional sync engine
│ │ └── FolderWatcher.swift # FSEvents-based folder watcher
│ ├── Upload/
│ │ └── UploadManager.swift # File upload handling
│ ├── Settings/
│ │ └── Config.swift # Configuration and keychain storage
│ └── UI/
│ ├── MenuBarController.swift # Main controller
│ ├── StatusPopoverView.swift # Menubar popover UI
│ ├── SettingsView.swift # Settings UI
│ ├── DropZoneView.swift # Drag & drop UI
│ └── UploadsListView.swift # Upload index UI
├── CairnquireSyncApp/ # Menubar app
│ ├── App.swift # App entry point
│ └── StatusItemView.swift # Custom status item view with drag & drop
└── cairnquire-cli/ # CLI tool
└── main.swift # CLI entry point
Dev Mode
When running the server locally with CAIRNQUIRE_DEV_MODE=1, you can use the hardcoded API key dev instead of generating a real API token.
Server
CAIRNQUIRE_DEV_MODE=1 mise run server-run
Or set in your config:
{
"devMode": true
}
macOS App
The app automatically detects localhost servers and defaults to the dev token. No configuration needed when running locally.
CLI
cairnquire-cli config --server http://localhost:8080 --token dev
cairnquire-cli upload ~/Documents/report.pdf
API Gaps
See .project/macos-app-api-gaps.md for documented API endpoints that need server-side implementation.
Readable uploads (.md, .markdown, .txt, .html, and .htm) are published as rendered documents. Other supported files remain attachments. The upload index returns the canonical URL for either kind.