# Cairnquire macOS Sync

A macOS menubar application for syncing and uploading files to a Cairnquire server.

## Features

- **Tray Icon**: `tray.circle.fill` as 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
- **New Notes**: Create notes from clipboard content or a terminal `$EDITOR` buffer
- **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

```bash
cd apps/macos-sync
swift build
swift run CairnquireSync
```

For local development with Keychain access, sign the SwiftPM executable with a
stable Apple Development identity and run the signed binary directly:

```bash
cd apps/macos-sync
scripts/sign-dev.sh CairnquireSync
.build/debug/CairnquireSync
```

The script uses the first valid `Apple Development` code-signing identity. To
choose a specific identity, set `CAIRNQUIRE_CODESIGN_IDENTITY` to the certificate
name or SHA-1 hash shown by `security find-identity -v -p codesigning`.

Or open in Xcode:
```bash
open Package.swift
```

### CLI Tool

```bash
cd apps/macos-sync
swift build --product cairnquire-cli
swift run cairnquire-cli --help
```

You can run the built executable directly after building:

```bash
.build/debug/cairnquire-cli help
```

## Setup

1. Start your Cairnquire server
2. Create an API token from the web UI at `/account/tokens/new`
3. Open the app settings (click the menubar icon → Settings)
4. Enter your server URL and API token
5. Optionally configure a sync folder, default upload folder, and New Note behavior

## 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
- **New Note**: Create a markdown note from clipboard text or open Terminal.app with `$EDITOR`, depending on Settings
- **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:

```bash
cd apps/macos-sync
swift run cairnquire-cli config \
  --server http://localhost:9000 \
  --token dev \
  --folder "$HOME/Documents/Cairnquire" \
  --upload-folder Inbox \
  --new-note editor
```

Run one bidirectional sync:

```bash
swift run cairnquire-cli sync
```

Start the menubar app to keep watching the configured folder when auto-sync is enabled:

```bash
swift run CairnquireSync
```

Other CLI commands:

```bash
# 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.

When New Note is set to `Open Terminal $EDITOR`, the menubar action opens a
temporary markdown buffer in Terminal.app. The app uploads the saved buffer
after the editor exits. It uses `$EDITOR`, then `$VISUAL`, and falls back to
`vi`. Configure GUI editor launchers to wait for the buffer to close, such as
`EDITOR="code --wait"`.

## 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

```bash
CAIRNQUIRE_DEV_MODE=1 mise run server-run
```

Or set in your config:

```json
{
  "devMode": true
}
```

### macOS App

The app automatically detects localhost servers and defaults to the `dev` token. No configuration needed when running locally.

### CLI

```bash
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.
