Documentation Index
Fetch the complete documentation index at: https://mintlify.com/jasonkneen/openclicky/llms.txt
Use this file to discover all available pages before exploring further.
OpenClicky delivers over-the-air updates to directly distributed builds using Sparkle 2. Installed apps poll a signed appcast.xml feed hosted in this repository on GitHub, discover newer releases, download the notarized DMG from GitHub Releases, and install it in the background — all without requiring the user to visit a website or run any manual steps. This update mechanism applies only to direct-distribution builds; a hypothetical future Mac App Store build would receive updates exclusively through Apple’s own delivery pipeline and would not use Sparkle at all.
The following Info.plist keys control update behavior in OpenClicky:
| Key | Behavior |
|---|
SUEnableAutomaticChecks | Enables automatic feed checks on a schedule. |
SUAllowsAutomaticUpdates | Surfaces the option for the user to opt into automatic updates in the Sparkle UI. |
SUAutomaticallyUpdate | Defaults to background download and silent install behavior when the user has opted in. |
SUScheduledCheckInterval | Sets the polling cadence to roughly once per day. |
These keys are set in cursor-buddy/Info.plist. When a direct-distribution build is running and the machine is online, Sparkle checks the feed on launch and on the configured interval, compares sparkle:version (the build number) against the installed CFBundleVersion, and triggers a download if a higher version is found.
Update Feed
| Setting | Value |
|---|
| Feed URL | https://raw.githubusercontent.com/jasonkneen/openclicky/main/appcast.xml |
| Feed file in repo | appcast.xml (repo root) |
| Release asset host | GitHub Releases at https://github.com/jasonkneen/openclicky/releases |
| Public EdDSA key | SUPublicEDKey in cursor-buddy/Info.plist |
| Sparkle Keychain account | openclicky |
Sparkle reads SUFeedURL from Info.plist to locate the appcast. The feed is served over raw GitHub HTTPS so that the content is available to Sparkle without authentication. Pushing a new commit to main that updates appcast.xml is what makes a release visible to installed builds.
Key verification before first release: Before publishing any release, confirm that the Sparkle private key stored under the openclicky account in your Keychain corresponds to the SUPublicEDKey currently in cursor-buddy/Info.plist. If the keys do not match, Sparkle will reject all update signatures and users will never receive updates.Keep the private key outside the repository. Never commit it. If you need to rotate the key, you must first ship a bridge update signed by the old private key that carries the new public key in SUPublicEDKey. Existing installed builds can only trust a new key if they first receive and install that bridge update signed by the key they already trust.
Appcast Structure
The appcast.xml file at the repository root is an RSS 2.0 feed with Sparkle namespace extensions. Each release is represented by one <item> element. Below is the full item template from the project documentation — replace every placeholder with real values after you have a notarized and stapled DMG:
<?xml version="1.0" standalone="yes"?>
<rss xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" version="2.0">
<channel>
<title>OpenClicky Updates</title>
<link>https://github.com/jasonkneen/openclicky</link>
<description>OpenClicky direct-distribution updates.</description>
<language>en</language>
<item>
<title>OpenClicky 1.0.1</title>
<pubDate>Fri, 24 Apr 2026 16:10:08 +0000</pubDate>
<sparkle:version>7</sparkle:version>
<sparkle:shortVersionString>1.0.1</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>14.2</sparkle:minimumSystemVersion>
<enclosure
url="https://github.com/jasonkneen/openclicky/releases/download/v1.0.1/OpenClicky-1.0.1.dmg"
length="12345678"
type="application/octet-stream"
sparkle:edSignature="SIGNATURE_FROM_SPARKLE_SIGN_UPDATE"/>
</item>
</channel>
</rss>
Appcast Field Reference
| Field | Description |
|---|
<title> | Human-readable release name, e.g. OpenClicky 1.0.1. |
<pubDate> | RFC 2822 timestamp in UTC, e.g. Fri, 24 Apr 2026 16:10:08 +0000. |
sparkle:version | The build number (CURRENT_PROJECT_VERSION). Sparkle compares this integer against CFBundleVersion to determine whether an update is available. Must be strictly greater than the installed build. |
sparkle:shortVersionString | The human-readable marketing version (MARKETING_VERSION), e.g. 1.0.1. |
sparkle:minimumSystemVersion | Minimum macOS version required to install the update, e.g. 14.2. |
enclosure url | Direct download URL to the notarized, stapled DMG on GitHub Releases. Must match the uploaded asset exactly. |
enclosure length | Byte size of the DMG file. Sparkle uses this to show download progress. |
enclosure type | Always application/octet-stream for a DMG. |
sparkle:edSignature | EdDSA signature produced by Sparkle’s sign_update tool using the private key for Keychain account openclicky. Must be generated from the final stapled DMG. |
Local Commands
Notarize the DMG
Submit the signed DMG to Apple’s notary service. The keychain profile OpenClickyNotary is created once per machine using xcrun notarytool store-credentials (see Building and Releasing for setup steps):
# Submit and wait for Apple's verdict
xcrun notarytool submit OpenClicky-1.0.1.dmg \
--keychain-profile "OpenClickyNotary" \
--wait
# Staple the notarization ticket into the DMG
xcrun stapler staple OpenClicky-1.0.1.dmg
Generate the Sparkle EdDSA Signature
Run Sparkle’s sign_update against the stapled DMG. The tool reads the private key from the openclicky Keychain account and prints the sparkle:edSignature and length values you need for the appcast <enclosure>:
sign_update --account openclicky OpenClicky-1.0.1.dmg
Get the byte length independently if needed:
stat -f%z OpenClicky-1.0.1.dmg
Always run sign_update after stapling. Stapling changes the file, so a signature produced before stapling will not match the bytes Sparkle downloads and verifies. If you sign before stapling, repeat the signing step on the final stapled file.
Automate with generate-appcast.sh
scripts/generate-appcast.sh handles signature generation, length calculation, and appcast writing in a single step. It automatically locates sign_update in Xcode’s DerivedData for the cursor-buddy project and falls back to any sign_update on PATH:
# Use the GitHub Releases URL inferred from the project version
scripts/generate-appcast.sh dist/OpenClicky-1.0.1-7.dmg
# Supply an explicit download URL
scripts/generate-appcast.sh dist/OpenClicky-1.0.1-7.dmg \
https://github.com/jasonkneen/openclicky/releases/download/v1.0.1/OpenClicky-1.0.1-7.dmg
The script reads MARKETING_VERSION, CURRENT_PROJECT_VERSION, and MACOSX_DEPLOYMENT_TARGET directly from cursor-buddy.xcodeproj/project.pbxproj and writes a complete, valid appcast.xml.
Local OTA Testing
Before publishing to GitHub, you can verify the full Sparkle update loop on your own machine using a local HTTP feed.
Install the Baseline Build
Set the project to a lower test version, archive and export or install a Release build, then copy it to /Applications:
# Point the installed build at a local feed
defaults write com.jkneen.openclicky OpenClickySparkleFeedURLOverride \
-string "http://127.0.0.1:8808/appcast.xml"
The override accepts https, file, or local http feeds. When present, OpenClicky triggers a Sparkle background check immediately on launch.
Build the Update and Serve a Local Feed
Bump the version, build the update DMG, sign it, and use the helper script to generate a local appcast:
scripts/create-local-appcast.sh /path/to/OpenClicky-1.0.1.dmg 1.0.1 101 "SIGNATURE_FROM_SIGN_UPDATE"
That writes /tmp/openclicky-ota/appcast.xml with the higher build number. Serve the folder:
cd /tmp/openclicky-ota
python3 -m http.server 8808 --bind 127.0.0.1
Trigger and Verify the Update
- Launch
/Applications/OpenClicky.app.
- Sparkle reads the local feed override and detects the higher build number.
- Accept or wait for the update flow to complete.
- After relaunch, confirm the displayed app version and build number changed.
Clean Up After Testing
defaults delete com.jkneen.openclicky OpenClickySparkleFeedURLOverride
Pre-Release Checklist
Before committing an updated appcast.xml and pushing to main: