README · ARCHITECTURE · DEVELOPMENT · PRIVACY · SECURITY
Last updated: 2026-02-02
SaneClick is a macOS app + Finder Sync extension that adds curated and custom actions to the Finder right-click menu. The host app manages scripts and settings; the extension renders menus and triggers execution.
- No cloud sync or analytics for script data.
- No background daemon beyond the Finder Sync extension.
- No direct modification of Finder state outside the Finder Sync API.
- Host app: SwiftUI app that manages scripts, categories, and updates.
- Finder Sync extension: Builds the context menu and triggers script execution.
- App Group container: Shared storage and IPC between host + extension.
- Sparkle: Update checks via appcast.
- No GitHub DMG: DMGs are hosted on Cloudflare R2, not in GitHub.
- Shared data lives in the App Group container so both targets see the same source of truth.
- Extension is read-only on script data; host app owns edits and broadcasts changes.
- File-based IPC (with locking) is favored over direct cross-process calls.
- Fail safe: if App Group is unavailable, fall back to Application Support.
| Component | Responsibility | Key Files |
|---|---|---|
| ScriptStore | Load/save scripts + categories; notifies extension on change | SaneClick/Services/ScriptStore.swift |
| ScriptExecutor | Executes bash/AppleScript/Automator workflows | SaneClick/Services/ScriptExecutor.swift |
| UpdateService | Sparkle updater wrapper | SaneClick/Services/UpdateService.swift |
| FinderSync | Finder context menu + execution trigger | SaneClickExtension/FinderSync.swift |
| Script models | Script types, filters, categories | SaneClick/Models/* |
- Scripts:
scripts.jsonin App Group containerM78L6FXD48.group.com.saneclick.app. Fallback:~/Library/Application Support/SaneClick/scripts.json. - Categories:
categories.jsonin the same container (same fallback path). - Execution requests:
pending_execution.json+.execution.lockin App Group container. - Backups: ScriptStore writes
*.backup.jsonand*.corrupted.jsonwhen needed.
- User edits scripts in the host app.
- ScriptStore writes
scripts.jsonand postscom.saneclick.scriptsChanged. - Finder Sync extension rebuilds the menu on next
menu(for:)call.
- Finder Sync builds the menu from
scripts.jsonand current selection. - User clicks an item; extension writes
pending_execution.json. - Extension posts
com.saneclick.executeScriptvia DistributedNotificationCenter. - Host app ScriptExecutor reads request, locks, validates, and executes.
- Result is posted to UI via
ScriptExecutor.executionCompletedNotification.
stateDiagram-v2
[*] --> Idle
Idle --> MenuRequested: Finder asks for menu
MenuRequested --> ScriptsLoaded: read scripts.json
ScriptsLoaded --> MenuRendered: filter + build menu
MenuRendered --> Idle
MenuRequested --> EmptyMenu: no scripts or read failure
EmptyMenu --> Idle
| State | Meaning | Entry | Exit |
|---|---|---|---|
| Idle | Waiting for Finder request | default | menu(for:) |
| MenuRequested | Finder asks for context menu | menu(for:) | scripts loaded |
| ScriptsLoaded | Scripts loaded and filtered | loadScripts() | menu rendered |
| MenuRendered | Menu returned to Finder | menu(for:) return | idle |
| EmptyMenu | Fallback menu (settings only) | load failure | idle |
stateDiagram-v2
[*] --> Waiting
Waiting --> RequestWritten: extension writes file
RequestWritten --> Notified: distributed notification
Notified --> Processing: host reads + locks
Processing --> Running: execute script
Running --> ResultPosted: notify UI
ResultPosted --> Waiting
Processing --> Ignored: stale/duplicate request
Ignored --> Waiting
| State | Meaning | Entry | Exit |
|---|---|---|---|
| Waiting | No pending request | default | pending_execution.json |
| RequestWritten | Request file exists | extension write | notification |
| Notified | Host is signaled | notification | host read |
| Processing | Lock + validate request | ScriptExecutor | run/ignore |
| Running | Script running (bash/osascript/automator) | execute() | result |
| ResultPosted | UI notified | post notification | waiting |
| Ignored | Duplicate or expired request | validation | waiting |
- Finder Sync extension must be enabled in System Settings.
- Script execution is local-only; no telemetry.
- Update checks use Sparkle; only version metadata is transmitted.
- Single source of truth:
.saneprocessin the project root. - Build/test:
./scripts/SaneMaster.rb verify(no raw xcodebuild). - Release:
./scripts/SaneMaster.rb release(delegates to SaneProcessrelease.sh). - DMGs: uploaded to Cloudflare R2 (not committed to GitHub).
- Appcast: Sparkle reads
SUFeedURLfromSaneClick/Info.plist(saneclick.com).
- Unit tests in
Tests/. - Use
./scripts/SaneMaster.rb verifyfor build + tests.
- Finder Sync menu caching can make updates appear stale if script change signals fail.
- App Group container access is required for consistent host/extension behavior.
- Script execution relies on system tools (
/bin/bash,/usr/bin/osascript,/usr/bin/automator).