Skip to content

Check for and install updates via AppUpdater - #10

Merged
trsdn merged 3 commits into
mainfrom
appupdater-integration
Sep 17, 2026
Merged

trsdn merged 3 commits into
mainfrom
appupdater-integration

Conversation

@trsdn

@trsdn trsdn commented Sep 17, 2026

Copy link
Copy Markdown
Owner

Summary

Implements the app-side half of issue #7. The broker-side half (updating the stale profile, adding the dependency lock, a new assemble_openpromptr build step) is trsdn/macos-notarization-broker#49.

  • Adds AppUpdater 4.1.2 (pinned exact, Package.resolved committed) and an UpdateManager wrapping 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.
  • GitHubAttestationPolicy is deliberately not required — the broker builds the release in its own repository, so there's no provenance from trsdn/OpenPromptr to check against.
  • A teleprompter must not restart mid-talk. UpdateFlow.installUpdate refuses while AppModel.canStop is true (covers running, desired-but-recovering, and busy states — not just isRunning) and explains why, rather than just disabling a button. Both the status-item menu and a new Update command menu route through the same UpdateFlow, so there's one place deciding whether an install may proceed.
  • build-app.sh now also copies AppUpdater_AppUpdater.bundle (AppUpdater's TUF trust roots) into Contents/Resources, so local dev builds have a working updater too.
  • This is OpenPromptr's first third-party dependency and first network access. Updated SECURITY.md, the Pages site's Privacy section, and AGENTS.md accordingly, 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-review pass on this branch found that gating on isRunning alone 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 on canStop instead. The review also flagged UpdateManager.dismiss()/hasPreparedUpdate as 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 build and swift test clean (39 tests)
  • swift format lint --strict --recursive Sources Tests Package.swift clean
  • ./build-app.sh locally: builds, signs, verifies; AppUpdater_AppUpdater.bundle present in Contents/Resources; --version correct
  • Independent /code-review pass on this branch; both findings it surfaced are fixed here
  • CI (Build and tests, App bundle)
  • Merge alongside feat: update the stale teleprompter profile to the OpenPromptr identity macos-notarization-broker#49 before the actual release/update-check behavior can be exercised end to end (no signed release exists yet)

🤖 Generated with Claude Code

https://claude.ai/code/session_01XBbvDgF84mTXMJMcioTX4U

trsdn and others added 3 commits September 17, 2026 10:42
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
@trsdn
trsdn merged commit 6aac0f2 into main Sep 17, 2026
4 checks passed
@trsdn
trsdn deleted the appupdater-integration branch September 17, 2026 09:03
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant