A native macOS front end for rsync: a visual editor for 100+ flags, live progress, launchd scheduling, multi-source / multi-destination sync, delta reports, built-in backup insights, a desktop widget, a loopback API — and an optional AI assistant (NovaAIKit) that turns plain English into a reviewed rsync command.
| Feature | Description |
|---|---|
| 100+ rsync flags | Tabbed visual editor across 14 categories (Basic, Transfer, Preserve, Filters, Comparison, Bandwidth, Output, SSH, Ownership, Backup, Logging, Network, I/O, Checksum) |
| Real-time progress | Animated progress circle with speed, ETA, file count and the current file |
| Multi-source / multi-destination | Fan-out (1:N), fan-in (N:1) and full mesh (N:N) with parallel or sequential execution |
| launchd scheduling | Hourly, daily, weekly, monthly or custom schedules that run even when the app is closed |
| SSH remote sync | Public-key auth, connection testing, key path validation |
| iCloud Drive sync | One-click iCloud destination with automatic .icloud placeholder exclusion |
| Describe it in English (AI, optional) | Type an intent; AI — or the built-in phrase matcher when AI is off — proposes an rsync command. It is validated against a strict flag allow-list, shown for review, and only pre-fills the builder. Never auto-executed |
| One AI feature set (NovaAIKit) | AI on/off, backend picker (Auto, Ollama, MLX, TinyLLM, TinyChat, OpenWebUI, Nova Gateway, OpenRouter, OpenAI), model picker, editable URLs, Auto failover with timeouts and retries, status indicator, Keychain-only keys (https only), output validation |
| Backup insights (built-in) | Error diagnosis, change summary, anomaly detection, smart scheduling, storage prediction, exclusion suggestions, health scoring, recovery assistant, sensitive-file detection — rule-based, run locally, no AI needed |
| Delta reporting | Files added / modified / deleted with byte-level statistics after every run |
| Job dependencies | Chain jobs; CryptoKit change detection skips unchanged sources |
| Pre/post sync scripts | Absolute-path scripts with JOB_NAME, JOB_STATUS, FILES_TRANSFERRED |
| Dry run | Preview every change before running |
| Desktop widget | WidgetKit (Small / Medium / Large): health score, last and next sync, recent activity |
| Menu bar | Status item with quick job access |
| Nova API server | Loopback HTTP API on port 37424 for automation |
graph TD
subgraph UI["SwiftUI"]
CV[ContentView] --> JL[JobListView]
CV --> JD[JobDetailView]
CV --> JE[JobEditorView]
JE --> RAS["RsyncAssistSection<br/>(Describe it in English)"]
JE -->|"Browse / iCloud / typed path"| SDP["SyncJob.setDestinationPath(id:path:)"]
CV --> SP[SyncProgressView]
CV --> AIV[AIInsightsView]
CV --> HT[JobHistoryTabView]
ST[SettingsView] --> AIS_UI["Settings › AI<br/>NovaAISettingsView"]
MB[MenuBarManager] --> CV
end
subgraph AI["AI (optional) — NovaAIKit 1.0.0"]
RAS --> VM[RsyncAssistViewModel]
VM -->|"generate(request, validate:, fallback:)"| APPAI["AppAI.shared (NovaAI)"]
VM -->|fallback| HEU["RsyncIntentHeuristics<br/>(built-in phrase matcher)"]
APPAI -->|validator| VAL["parseRsyncSuggestion<br/>flag allow-list"]
APPAI --> BACK["Ollama · MLX · TinyLLM · TinyChat · OpenWebUI<br/>Nova Gateway · OpenRouter · OpenAI"]
APPAI --> KC["Keychain<br/>com.jkoch.rsyncgui.*"]
RAS --> IND[NovaAIStatusIndicator]
end
subgraph Services["Services"]
JM[JobManager] --> RE[RsyncExecutor]
JM --> AES[AdvancedExecutionService]
JM --> SM[ScheduleManager]
RE -->|"Process.arguments (no shell)"| RSYNC["/usr/bin/rsync"]
AES -->|parallel / sequential| RE
SM --> LA["~/Library/LaunchAgents"]
INS["AIInsightsService<br/>(rule-based)"]
WDS[WidgetDataSync] -->|App Group| WK[Widget]
end
subgraph Data["Persistence"]
JOBS[jobs.json]
HIST[ExecutionHistory]
DR[DeltaReport]
end
API["Nova API :37424 (loopback)"] --> JM
UI --> Services
JM --> JOBS
RE --> HIST
RE --> DR
HIST --> INS
HIST --> WDS
INS --> AIV
AI is optional and off-switchable. It powers one feature — Describe it in English in the Job Editor's Basic tab — through NovaAIKit 1.0.0, the shared AI layer used by all of Jordan's macOS apps.
| Toggle | Settings › AI › Use AI. Off = nothing is sent anywhere; the built-in phrase matcher answers. |
| Backends | Auto (failover in order: Ollama, MLX, TinyChat, TinyLLM, OpenWebUI, Nova Gateway, OpenRouter, OpenAI), or pin one. Model picker and editable URL per backend, "Test connection". |
| Keys | Only in the macOS Keychain, service com.jkoch.rsyncgui.<backend>, account apiKey. Shown as "stored", never as text. Keys are only ever sent over https. |
| Status | NovaAIStatusIndicator next to the assistant: green online, orange unreachable, grey off. Click to re-check. |
| Reliability | Per-request timeouts, bounded retries with backoff, Auto failover across reachable backends. |
| Validation | Every AI reply must parse as one clean rsync command (no ; ` |
| Source label | Each suggestion shows where it came from: "AI · Ollama (qwen3:8b)" or "Built-in". |
Fallback per feature
| Feature | AI off / unreachable / reply rejected |
|---|---|
| Describe it in English | RsyncIntentHeuristics maps phrases to flags (mirror / delete extras → --delete, preview / dry run → --dry-run, skip video / music / caches / node_modules / .git → --exclude=…, remote / ssh → -z, only new → --ignore-existing, newer → --update, …) on top of -a, using the job's known source and destination. |
| Backup insights | Always rule-based; never use AI. |
sequenceDiagram
participant U as User
participant VM as RsyncAssistViewModel
participant AI as AppAI (NovaAIKit)
participant B as Backend (Auto failover)
participant V as parseRsyncSuggestion
participant H as RsyncIntentHeuristics
U->>VM: "mirror Photos to the NAS, skip videos"
VM->>AI: generate(prompt, validate, fallback)
alt AI off
AI->>H: fallback()
else AI on
AI->>B: chat (timeouts, retries, failover)
alt no backend reachable
AI->>H: fallback()
else reply
AI->>V: validate(reply)
alt clean rsync command
V-->>AI: RsyncCommand
else rejected
AI->>B: one corrective retry
AI->>H: fallback() if still rejected
end
end
end
AI-->>VM: value + source ("AI · …" / "Built-in")
VM-->>U: review card — "Apply flags to builder" (never runs)
Upgrading from 1.7.x: on first launch the old settings and keys are migrated once — the OpenAI key from
com.digitalnoise.RsyncGUI and the OpenRouter key from com.jordankoch.rsyncgui.openrouter move to
com.jkoch.rsyncgui.* (legacy items deleted after a verified write), the old Ollama / Nova Gateway URLs carry over,
and secrets of the removed Google Cloud, Azure, AWS and IBM Watson backends are deleted. Those backends are no
longer supported.
sequenceDiagram
participant User
participant UI as SwiftUI
participant JM as JobManager
participant AES as AdvancedExecutionService
participant RE as RsyncExecutor
participant RS as /usr/bin/rsync
participant INS as AIInsightsService
participant WK as Widget
User->>UI: Run job
UI->>JM: executeJob(id)
JM->>JM: Check dependencies (CryptoKit hash)
alt Multi-destination
JM->>AES: fan-out / fan-in / mesh
AES->>RE: parallel or sequential runs
else Single destination
JM->>RE: execute(job)
end
RE->>RE: Build arguments, run pre-sync script
RE->>RS: Process.run(arguments)
loop stdout
RS-->>RE: progress
RE-->>UI: SyncProgressView (speed, ETA, file)
end
RS-->>RE: exit code + stats
RE->>RE: DeltaReport, post-sync script
RE->>JM: ExecutionHistory entry
JM->>INS: anomalies, health score (rule-based)
JM->>WK: App Group sync
JM-->>UI: done
- Download
RsyncGUI-v1.8.0-build20.dmgfrom Releases. - Drag RsyncGUI onto Applications and open it from there.
Local builds are ad hoc signed. If macOS says the developer cannot be verified: Control-click the app › Open, or
System Settings › Privacy & Security › Open Anyway, or
xattr -dr com.apple.quarantine /Applications/RsyncGUI.app. Developer-ID-signed, notarized releases are produced
with scripts/release.sh — see RELEASE.md.
| Requirement | Minimum |
|---|---|
| macOS | 14.0 (Sonoma) |
| Architecture | Apple Silicon and Intel |
| rsync | The one bundled with macOS, or Homebrew's |
| AI (optional) | Any one of: Ollama, MLX server, TinyLLM, TinyChat, OpenWebUI, Nova Gateway, OpenRouter key, OpenAI key |
Requires Xcode 16 or later. The only package dependency is NovaAIKit (resolved automatically by Xcode / SwiftPM).
git clone git@github.com:kochj23/RsyncGUI.git
cd RsyncGUI
xcodebuild -project RsyncGUI.xcodeproj -scheme RsyncGUI -configuration Release build # zero warningsxcodebuild test -project RsyncGUI.xcodeproj -scheme RsyncGUI -destination 'platform=macOS'479 tests across 19 classes, all passing. AI tests use a stub URLProtocol and an in-memory key store — no real
network, no real Keychain.
| Test class | Tests | Category |
|---|---|---|
| RsyncOptionsTests | 57 | Unit — argument generation, archive mode, filter sanitization, Codable |
| ProgressParsingTests | 38 | Unit — speed / time / bytes parsing, final stats |
| RsyncSuggestionTests | 30 | Security / Unit / Functional — validator injection rejection, prompt builder, AI-off path |
| SyncJobTests | 29 | Unit — job model, sync modes, Codable |
| NovaAPITests | 29 | Functional — API routing and payloads |
| FrameTests | 28 | Frame — view and model instantiation |
| PathValidationTests | 26 | Security — tilde expansion, iCloud, SMB/USB, traversal |
| SecurityTests | 25 | Security — credential exposure, sanitization, binary resolution |
| DestinationEditingTests | 25 | All seven — destination path editing (issue #4) regression lock |
| ScheduleConfigTests | 24 | Unit — plist generation, XML injection prevention |
| CommandBuilderTests | 24 | Unit — delta report filenames, parallel file-splitting strategies |
| CommandInjectionTests | 23 | Security — SSH host/user validation, shell escaping |
| AIKitAdoptionTests | 21 | Security 3, Performance 2, Retry 2, Unit 5, Integration 2, Functional 3, Frame 3, + legacy-code scan — NovaAIKit contract |
| WidgetDataTests | 18 | Unit — widget data, App Group |
| FunctionalFlowTests | 18 | Functional — end-to-end job flows |
| ExecutionHistoryTests | 17 | Unit — history entries |
| DeltaReportTests | 17 | Unit — itemize parsing |
| IntegrationTests | 15 | Integration — real rsync against temp dirs |
| DependencyCheckTests | 15 | Unit — job dependencies |
RsyncGUI/
├── RsyncGUI/
│ ├── RsyncGUIApp.swift App entry, Settings scene
│ ├── NovaAPIServer.swift Loopback HTTP API (37424)
│ ├── AI/AppAI.swift The app's one NovaAI instance + one-time legacy migration
│ ├── Design/ModernDesign.swift
│ ├── Models/ SyncJob, RsyncOptions, ScheduleConfig, ExecutionHistory, DeltaReport, …
│ ├── Services/
│ │ ├── JobManager.swift, RsyncExecutor.swift, AdvancedExecutionService.swift, ScheduleManager.swift
│ │ ├── RsyncSuggestionService.swift Prompt builder, validator, built-in phrase matcher
│ │ ├── AIInsightsService.swift Rule-based backup insights
│ │ ├── MenuBarManager.swift, WidgetDataSync.swift
│ └── Views/ ContentView, JobEditorView, LLMAssistViews (assistant + Settings › AI), …
├── RsyncGUI Widget/ WidgetKit extension
├── RsyncGUITests/ 479 tests
├── scripts/release.sh Developer ID archive → DMG → notarize
└── RsyncGUI.xcodeproj
Port 37424 (127.0.0.1 loopback only). No authentication required.
| Method | Path | Description |
|---|---|---|
GET |
/api/status |
App status, version, job count, uptime |
GET |
/api/ping |
Health check |
GET |
/api/jobs |
List all sync jobs |
GET |
/api/jobs/:id |
Single job detail |
POST |
/api/jobs/:id/run |
Execute a job |
POST |
/api/jobs/:id/dryrun |
Dry-run a job |
GET |
/api/history |
Recent execution history |
GET |
/api/jobs/:id/history |
History for a specific job |
curl -s http://127.0.0.1:37424/api/status | python3 -m json.tool
curl -X POST http://127.0.0.1:37424/api/jobs/<uuid>/runSee CHANGELOG.md. 1.8.0: AI migrated to NovaAIKit 1.0.0, built-in fallback for the assistant, Keychain migration, Google/Azure/AWS/IBM backends removed, zero-warning Release build.
MIT License — Copyright (c) 2026 Jordan Koch. See LICENSE.
Written by Jordan Koch