Check for and install updates via AppUpdater - #10
Merged
Merged
Conversation
Adds AppUpdater 4.1.2 (pinned exact, Package.resolved committed) and an UpdateManager wrapping it, following the same pattern as OpenLens and OpenDefendrWatchr: checks GitHub Releases once a day (waking hourly), downloads and validates a found update in the background, and only ever installs on an explicit user action. GitHubAttestationPolicy is deliberately not required: the notarization broker builds the release in its own repository, so there's no provenance from trsdn/OpenPromptr for AppUpdater to check against. A teleprompter must not restart mid-talk, so UpdateFlow.installUpdate refuses while AppModel.isRunning is true (with an explanatory alert) instead of just disabling a button — both the status-item menu and the app's new Update command menu route through it, so there is one place that decides whether an install may proceed. Automatic checks are opt-out via a toggle; installing itself is never automatic regardless. build-app.sh now also copies AppUpdater_AppUpdater.bundle (the TUF trust roots AppUpdater needs at runtime) into Contents/Resources, so local dev builds have a working updater too, not just broker-built releases. This is OpenPromptr's first third-party dependency. The broker-side half (updating the stale profile, adding the dependency lock, a new assemble_openpromptr build step) is trsdn/macos-notarization-broker#49. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XBbvDgF84mTXMJMcioTX4U
AppUpdater's GitHub Releases check is the app's first network access, so the "no network communication" claims in SECURITY.md and the Pages site's Privacy section were no longer accurate. Adds a README section on how the check works, the opt-out toggle, and why installing is blocked while output is running; a third-party licenses note for AppUpdater (Unlicense) and its own Version dependency (Apache-2.0); and updates AGENTS.md's forbidden-operations list to reflect that a dependency and a network connection now exist deliberately, with tighter limits (don't add another; don't install mid-session). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XBbvDgF84mTXMJMcioTX4U
An independent code review of this branch found that isRunning goes false the instant a capture failure starts an automatic-recovery retry (AppModel sets isRunning = false while desiredOutput stays true), even though that retry is still trying to restore the same session. installUpdate() and offerInstall() gated on isRunning alone, so an install could fire mid-recovery — exactly the "restart mid-talk" scenario this feature is supposed to prevent. Both now gate on model.canStop (desiredOutput || isRunning || isBusy), which covers that window. Also wires up UpdateManager.dismiss()/hasPreparedUpdate, which had no caller: adds a persistent "Later" item next to "Install Update and Restart…" in both the status-item menu and the Update command menu, and disables "Check for Updates…" while an update is already prepared, matching OpenDefendrWatchr's reference behavior. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XBbvDgF84mTXMJMcioTX4U
This was referenced Sep 17, 2026
trsdn
added a commit
that referenced
this pull request
Sep 17, 2026
Covers everything merged since 1.1.0: the local HTTP API (#12), the AppUpdater integration (#10), the version-from-tag/About panel work, and the color-only status fix. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XBbvDgF84mTXMJMcioTX4U
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the app-side half of issue #7. The broker-side half (updating the stale profile, adding the dependency lock, a new
assemble_openpromptrbuild step) is trsdn/macos-notarization-broker#49.Package.resolvedcommitted) and anUpdateManagerwrapping it, following the same pattern as OpenLens/OpenDefendrWatchr: checks GitHub Releases once a day (waking hourly), downloads and validates a found update in the background, only ever installs on an explicit user action.GitHubAttestationPolicyis deliberately not required — the broker builds the release in its own repository, so there's no provenance fromtrsdn/OpenPromptrto check against.UpdateFlow.installUpdaterefuses whileAppModel.canStopis true (covers running, desired-but-recovering, and busy states — not justisRunning) and explains why, rather than just disabling a button. Both the status-item menu and a new Update command menu route through the sameUpdateFlow, so there's one place deciding whether an install may proceed.build-app.shnow also copiesAppUpdater_AppUpdater.bundle(AppUpdater's TUF trust roots) intoContents/Resources, so local dev builds have a working updater too.SECURITY.md, the Pages site's Privacy section, andAGENTS.mdaccordingly, since they previously claimed "no network communication" / "no dependencies" as a guarantee. Added a README "Checking for updates" section and a third-party licenses note (AppUpdater: Unlicense, its own Version dependency: Apache-2.0).An independent
/code-reviewpass on this branch found that gating onisRunningalone was wrong — it goes false the instant a capture failure starts an automatic-recovery retry, even though that retry is still trying to restore the same session, which would have let an install fire mid-recovery. Fixed by gating oncanStopinstead. The review also flaggedUpdateManager.dismiss()/hasPreparedUpdateas unreachable; both are now wired up (a persistent "Later" menu item, and disabling "Check for Updates…" while an update is already prepared, matching OpenDefendrWatchr's reference behavior).Test plan
swift buildandswift testclean (39 tests)swift format lint --strict --recursive Sources Tests Package.swiftclean./build-app.shlocally: builds, signs, verifies;AppUpdater_AppUpdater.bundlepresent inContents/Resources;--versioncorrect/code-reviewpass on this branch; both findings it surfaced are fixed hereBuild and tests,App bundle)🤖 Generated with Claude Code
https://claude.ai/code/session_01XBbvDgF84mTXMJMcioTX4U