cask: upgrade auto_updates casks when bundle version is stale#21882
Conversation
bundle_version initial implementation
There was a problem hiding this comment.
Pull request overview
Adds initial bundle_version support for cask app artifacts to enable “metadata-only” upgrades when an installed app bundle already matches the incoming cask’s version + bundle_version, avoiding unnecessary downloads/reinstalls. This also extends API serialization/loading and introduces an online audit to validate declared bundle_version values against extracted artifacts.
Changes:
- Add
bundle_versionoption toappartifacts, including API JSON serialization/loading and artifact matching logic. - Implement metadata-only upgrade path (and skip prefetch/fetch where applicable) when installed bundle version matches.
- Add
brew audit --onlinechecks and unit tests covering metadata-only upgrade and bundle version auditing.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| Library/Homebrew/cask/artifact/abstract_artifact.rb | Adds default “metadata-upgrade” capability hooks to artifacts. |
| Library/Homebrew/cask/artifact/app.rb | Implements bundle_version option + matching against installed Info.plist. |
| Library/Homebrew/cask/upgrade.rb | Adds metadata-only upgrade flow and fetch filtering for upgrades. |
| Library/Homebrew/cmd/upgrade.rb | Skips prefetching downloads for casks eligible for metadata-only upgrades. |
| Library/Homebrew/cask/installer.rb | Adds write_metadata helper used by metadata-only upgrades. |
| Library/Homebrew/cask/audit.rb | Adds online audit for declared bundle_version vs extracted app bundle version. |
| Library/Homebrew/test/cask/upgrade_spec.rb | Tests metadata-only upgrade behavior and prefetch skipping. |
| Library/Homebrew/test/cask/audit_spec.rb | Tests online audit behavior for bundle version matching/mismatch. |
| Library/Homebrew/test/cask/artifact/app_spec.rb | Tests bundle_version parsing/storage and matching logic on App artifacts. |
| Library/Homebrew/test/cask/cask_loader/from_api_loader_spec.rb | Tests loading bundle_version from JSON and internal JSON API artifacts. |
| Library/Homebrew/test/api/cask/cask_struct_generator_spec.rb | Updates API artifact processing expectations for bundle_version. |
| Library/Homebrew/test/api/cask_spec.rb | Updates expected cask source checksum due to fixture changes. |
| Library/Homebrew/test/support/fixtures/cask/Casks/everything.rb | Adds bundle_version to fixture cask’s app stanza. |
| Library/Homebrew/test/support/fixtures/cask/everything.json | Updates API fixture artifacts to include bundle_version and checksum. |
| Library/Homebrew/test/support/fixtures/cask/everything.internal.json | Updates internal API fixture artifacts to include bundle_version and checksum. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
I think we may also need to add an audit to not allow the use of |
MikeMcQuaid
left a comment
There was a problem hiding this comment.
Thanks for the PR @bevanjkay but I think this needs a different approach.
I think we may also need to add an audit to not allow the use of bundle_version when the download url is unversioned, because the download of a specific version is not guaranteed in that case.
This is revealing because I think, unfortunately, it makes the DSL pointless in that case. The "problem to be solved" also applies when the download URL is unversioned, unless I've missed something.
Basically: we want a way to have brew upgrade --greedy be the default but avoid upgrading things where the bundle version indicates it's already up-to-date. We may still want to update e.g. the caskroom version in that case so we don't keep repeatedly needing to check the same data.
We have a bundle_version attribute in our existing cask JSON API that sniffs this bundle version. That's what's already been used by other tools to do this sort of comparison.
Hope that helps, let me know what you think as I may be totally wrong about some of the above!
Unversioned downloads are always a worse UX because there's no guarantee the version in the cask will match |
This seems better than nothing if we can 👍🏻 |
8e8cdf7 to
38e43de
Compare
|
Thanks for the back and forth here @MikeMcQuaid - I think it's going to take some time to land exactly what the best way forward is here, as I'm discovering some different permutations as they arise. But I think it's better to that with some logic out in the open because I think the discussion is useful, if that's ok? This round removes the One thought that has arisen is that this isn't actually the primary issue. it's nice to know with Your comment in the earlier thread;
made me think that maybe some detection around this idea is a better first goal, and then syncing the Caskroom metadata is secondary. A new goal may be to find a may to detect and develop the criteria for when
In full, to decide between 1 & 2 is not as simple as comparing |
Yeh, of course: much appreciated!
What does this look like, output (and speed) wise compared to the "normal" upgrade?
I guess my thinking is: this change is not so much about deciding whether to upgrade or not but instead having more cases of casks not currently upgraded today without Even if this PR ends up only enabling this for a single cask that's updated relatively often: I think that'll be a net positive and good foundation to build off. Hope that helps! Thanks again @bevanjkay, cracking work. |
38e43de to
f813bcc
Compare
bundle_version initial implementationauto_updates casks when bundle version is stale
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
Library/Homebrew/cmd/upgrade.rb:482
prefetch_outdated_casks!now callsInstall.show_combined_fetch_downloads_headingtwice (lines 475 and 481), which will print duplicate “Fetching/Downloading…” headings duringbrew upgradeprefetch. It also doesprefetch_names&.replace(cask_names)twice; please keep a single, correctly placed update + heading call.
cask_names = outdated_casks.map(&:full_name)
prefetch_names&.replace(cask_names)
Install.show_combined_fetch_downloads_heading(cask_names:) if show_downloads_heading
Install.enqueue_cask_installers(fetchable_cask_installers)
prefetch_names&.replace(cask_names)
prefetch_upgrades&.replace(
outdated_casks.map { |cask| "#{cask.full_name} #{cask.installed_version} -> #{cask.version}" },
)
Install.show_combined_fetch_downloads_heading(cask_names:) if show_downloads_heading
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Converting to draft, because I need to clean some things up here. Quite a diversion from the original PR, but I think it's a productive outcome. Currently, casks marked as For an For now this is still applied only to Cask with a single |
f813bcc to
788dccd
Compare
👏🏻 |
788dccd to
29c8bf3
Compare
|
I have been daily driving this branch since my last push and haven't noticed anything out of the ordinary, other than more casks being upgraded without having to pass |
MikeMcQuaid
left a comment
There was a problem hiding this comment.
Happy to merge when you are, great work here!
|
Let's merge now, it should give a few days before it lands in a release to hopefully pick up anything that needs adjustment. |
|
I don't think this info.plist inspection is working for several of my apps. Homebrew today tries to update 15 casks. Some of these I haven't opened in a while, so their internal auto-updaters haven't run, and they aren't up to date. But some of them such as Aside from the info.plist version inspection issue, I personally prefer the old behavior of letting the app's auto updater handle updates. Because the homebrew process for cask upgrades is iirc to uninstall the current version, and install a new version. Which for some apps, can wipe out it's state/appdata . |
|
This PR #21974 should resolve the
I see this as more of an unfortunate side-effect of the change rather than an actual causation. In saying that, if an application you use has it's state/appdata removed during a |
Thank you for the quick response. I didn't know that was unintended behavior. Thanks for clarifying. I guess I will continue to mention my issues here.. |
|
Is there a way to discern which outdated auto-update casks will be upgraded by For my casks, when I run Homebrew 5.1.11-77-gd430e68 |
…O_UPDATES_CASKS) #542 A recent brew change (Homebrew/brew#21882) made 'brew upgrade' upgrade casks with auto_updates true even without --greedy, force-upgrading self-managing apps (Chrome, Slack, Docker Desktop). Sets HOMEBREW_NO_UPGRADE_AUTO_UPDATES_CASKS=1 in the managed shell init (covers all brew usage) and in the TUI's brew upgrade run, restoring the prior behaviour. Opt back in per run with 'brew upgrade --greedy'. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m]
* feat(tui): scaffold sparkdock terminal hub with tested core packages #542 Adds a Go/bubbletea terminal hub under src/tui following the Elm architecture with a clean split between pure core logic and injected side effects. Core packages are unit-tested: feed (pure parser for the Ansible callback protocol), status (Checker over sparkdock-check-updates and brew, injected command runner), runner (exec ansible-playbook, stream parsed events, cancel, subprocess-scoped become password, injected command builder), and version (git commit/branch of /opt/sparkdock, no semver). The runnable layer wires a page router (app), the launch splash, and the flat grouped status dashboard. The Runner, Password, Log, and sjust pages, the tui Ansible build tag, and the sparkdock dispatch/self-update wiring follow next. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * feat(tui): add runner page with streaming statusline #542 Renders a live run: scrolling content region above a pinned statusline (phase, task, ok/changed/failed, elapsed), fed by the runner backend's event stream. Supports cancel, retry, and opening the captured output. Caches theme styles via theme.Default() and cancels any still-live handle before starting a new run. Reducer is table-tested for content capture, success/failure/cancel state, and the failed-task-count case. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * feat(tui): add masked become-password page #542 Collects the sudo password in a masked textinput and emits Submit/Cancel; the app owns the password lifetime. Clears the input buffer immediately after submit so the secret does not linger. Also migrates the runner viewport off the deprecated LineUp/LineDown to ScrollUp/ScrollDown. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * feat(tui): add copyable run-log page #542 Shows the full captured output in a scrollable view, copies it to the clipboard (pbcopy), and writes it to ~/.cache/sparkdock/last-run.log. Clipboard and file writes are injected for testing. Surfaces copy and write failures in the footer instead of silently claiming success. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * feat(tui): add sjust recipe browser page #542 Lists sjust recipes (injected lister; DefaultLister parses 'sjust --list') and runs the selected one through the shared Runner. parseRecipes skips group-header lines so they are not mistaken for recipes. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * feat(tui): wire app router, run orchestration, and remaining pages #542 Integrates the runner, password, log, and sjust pages into the app router. The app builds the runner.Handle per action, gates sudo actions behind the password page, caches the become password in memory for the session, and re-prompts on a sudo authentication failure. Adds a generic command runner for sjust recipes and an Ansible become-auth-failure detector. The runner page consumes a handed-in handle, gates back/log/retry to finished runs, and emits a back signal so the user cannot leave mid-run and drop events. App orchestration flows are unit-tested with an injected fake runner. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * feat(tui): add sparkdock ansible stdout callback plugin #542 Emits the @@PHASE/@@TASK/@@STAT/@@done marker protocol plus glyph content lines that the terminal hub parses into a streaming run view. Active only when ANSIBLE_STDOUT_CALLBACK=sparkdock is set (the TUI sets it per run), so existing provisioning output is unchanged. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * feat(tui): build sparkdock-tui via ansible tag and add 'sparkdock tui' launcher #542 Adds a 'tui' Ansible tag that mirrors the menubar block: go build of src/tui installed to the Homebrew bin (become: false, no sudo). 'sparkdock tui' launches the hub and, on first use when the binary is absent, builds it via the 'tui' tag (no sudo prompt) before exec'ing it. 'sjust sparkdock-tui-install' rebuilds on demand. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * docs(tui): update README for completed pages and install path #542 Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * fix(tui): create .build dir, surface run errors, add statusline spacing #542 The 'tui' build task failed because 'go build -o .build/...' does not create the output directory (unlike swift's .build); it now mkdir -p .build first. Build failures were also invisible: the build task is failed_when:false, so its stderr was captured but never shown. The follow-up fail task now includes the captured stderr/stdout, and the stdout callback surfaces stderr/stdout for any failed task instead of only the short msg. The runner view also gains a blank line between the last output line and the pinned statusline. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * fix(tui): pass dev_env_dir so runs build from the launch directory #542 The playbook hardcodes dev_env_dir: /opt/sparkdock, so a TUI run against a checkout (SPARKDOCK_ROOT=<repo>) still cd'd to /opt/sparkdock/src/tui and failed. AnsibleBuilder now passes -e dev_env_dir=<abs Dir>, aligning the playbook with the directory the run is launched from; in production (Root=/opt/sparkdock) this equals the default. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * fix(tui): make Upgrade Brew packages run brew upgrade directly #542 The upgrade action now runs 'brew upgrade' via the generic command runner instead of an ansible --tags cask provisioning run, matching what the brew-outdated status reflects. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * feat(tui): replace proxy dashboard with HTTP proxy management actions #542 Removes the no-op 'HTTP-proxy dashboard → opens browser' item. Adds an HTTP proxy group (Status/Start/Stop via spark-http-proxy, Upgrade via sjust http-proxy-install-update) and wires Device info to 'sjust device-info'. All run through the streaming runner view. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * test(tui): use an unplanned action in the unhandled-action test #542 Device info is now wired, so the test uses 'self-update' (still unplanned) to assert unhandled actions no-op back to the dashboard without spawning a real command. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * feat(tui): run under a PTY and answer password prompts interactively #542 Replaces the env/file-based become password with PTY-based prompt detection. Runs now execute under a pseudo-terminal; when the process prompts for a password (ansible --ask-become-pass, or brew/sudo), the runner detects it, the app shows a masked field, and the answer is written straight to the process's PTY. The password is never stored, cached, logged, or placed in env/argv/file. Backing out of the prompt cancels the run; ansible become-auth failures re-run so the prompt reappears. This covers brew casks (e.g. docker-desktop) that prompt mid-upgrade, which previously hung the TUI. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * feat(tui): size child PTY, truncate output, clearer completion, drop sjust runner #542 Sizes the child PTY to the runner view so tty-aware programs (e.g. ayse-get-sm) render to fit instead of overflowing and corrupting the layout, and truncates each output line to the view width as a safety net. The finished statusline now states the result clearly (Completed/Failed/Cancelled). Removes the automatic become-failure re-run loop (failures stay on the runner page with retry). Removes the sjust task runner from Tools (not useful) and wires Device info to ayse-get-sm. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * feat(tui): prime sudo for privileged runs, ansible-only summary, colored footer #542 Privileged runs (provisioning, HTTP-proxy upgrade, brew upgrade) now prime the sudo timestamp up front (sudo -v, answered via the PTY prompt) instead of feeding ansible a become password; ansible's per-task sudo -n and brew's cask sudo reuse the cached credential, so there is one prompt and no 'premature end of stream' become failures. The Summary line and ok/changed/failed counters now render only for ansible runs (plain commands like proxy status or device info no longer show a meaningless '0 ok' tally). Removes the Tools group; Device info moves to a colored 'd' shortcut in the dashboard footer, which now renders claude-code-style colored key hints. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * feat(tui): play a short startup chime on launch #542 Adds an internal/audio package that synthesises a soft two-note chime (E5 to A5) as an in-process WAV and plays it via macOS afplay on launch, the way OpenCode plays a startup sound. Best-effort and non-blocking: fire-and-forget, no UI delay, silent no-op when SPARKDOCK_TUI_NO_AUDIO is set, off macOS, or when afplay is absent. No external audio asset is shipped. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * feat(tui): play opencode sound on logo click #542 Clicking the logo (the splash, or the header on the dashboard) plays a short sound, like OpenCode's logo audio, instead of playing automatically on launch. Enables mouse input and plays asynchronously via afplay; best-effort and silent when disabled (SPARKDOCK_TUI_NO_AUDIO), off macOS, or afplay is absent. The sound is OpenCode's MIT-licensed bip-bop-01.mp3, embedded with attribution (see internal/audio/assets/NOTICE). Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * feat(tui): load status during splash; logo click holds the bootstrap #542 Status now loads in the background during the splash, and the splash hands off to the dashboard only once status is ready and a minimum display time has passed, so the dashboard appears already populated. A maximum timeout still forces the hand-off if the status load is slow, and any key skips. Clicking the logo plays the sound and holds the splash a little longer, so a user can keep clicking to hear it before the app boots. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * refactor(tui): drop splash click-hold timer #542 The status-load wait already gives enough time to click the logo for the sound, so a logo click now just plays the sound. Removes the click-hold state and timer; the splash hands off purely on status-ready plus the minimum display time, with the timeout fallback. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * feat(tui): add system-info panel to the dashboard header #542 Adds an internal/sysinfo package that gathers this machine's hardware (model, serial, chip with CPU/GPU cores, memory free/total, disk free/total, macOS) via the macOS tools sparkdock already uses (system_profiler, sysctl, vm_stat, df, sw_vers) behind an injected command runner, with pure table-tested parsers. The dashboard renders it as a right-hand column beside the status rows, gathered in the background on launch and refreshed with r; the column is width-budgeted and ANSI-truncated so it never overflows the status block, and is omitted on narrow terminals. CPU vs GPU cores are disambiguated by occurrence order (robust on base M-series); memory is shown as free/total. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * fix(tui): route SysInfoMsg to the dashboard so the panel populates #542 The system-info gather completes during the splash (system_profiler is slow), but only StatusMsg was force-routed to the dashboard; SysInfoMsg fell to the active splash page and was dropped, leaving the panel empty. It is now force-routed like StatusMsg. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * feat(tui): one-line model/serial/macOS in sysinfo panel; reports open at top #542 The system-info panel now shows model, serial, and macOS version on a single line (Chip, Memory, Disk remain separate). Plain-command runs (e.g. Device info) now position the runner view at the top on completion so the report reads from the beginning, while ansible runs still follow the tail to the summary. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * fix(tui): disable mouse after splash for copy; pin reports to top; consistent free format #542 Mouse reporting was capturing clicks so the terminal could not do native text selection/copy. Mouse is now enabled only on the splash (for the logo click) and disabled on hand-off to the dashboard, restoring normal selection/copy on the content pages. Plain-command output (e.g. Device info) is pinned to the top throughout the run, not just on completion, so a report reads from the start. Memory and Disk now use the same '<free> free / <total>' format. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * refactor(tui): scroll modes + interactive runner; brew without forced sudo #542 Generalises run presentation: the caller declares a runview.ScrollMode (FollowTail for streaming work, PinTop for one-shot reports like Device info and proxy status) instead of inferring it from whether the run produced stats. Stats/summary stay a runtime content concern, independent of scrolling. The runner view is now interactive while a run is live: typed keys are forwarded to the process PTY (so brew's 'Proceed? [y/n]' and any prompt can be answered), arrows scroll, ctrl+c cancels. Brew upgrade no longer primes sudo up front (formulae need none); a cask that needs sudo prompts on the PTY and is answered via the password page. Removes the now-unused ForSudoCommand. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * refactor(tui): render runs via a content strategy; embed VT terminal for raw programs #542 Splits run rendering into two strategies behind a content interface in the runner view. The runner now emits raw PTY bytes (runner.Handle.Output) and leaves decoding to the view: structuredContent decodes the sparkdock callback (feed.Decoder) into a line list with the phase/task/tally statusline (ansible), while terminalContent feeds the bytes to a charmbracelet/x/vt emulator and renders the live screen, so programs that redraw in place (brew progress bars, docker-compose) display faithfully instead of piling up duplicate lines. Adds feed.Decoder (stateful line buffering, tested). The app declares RenderMode per action (brew/proxy start-stop = Terminal, ansible/reports = Structured) alongside ScrollMode. Input forwarding to the PTY while running is retained for interactive prompts. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * fix(tui): ansible become via --ask-become-pass, answered reactively, never cached #542 Replaces the unreliable sudo-prime wrapper (its tty-bound sudo timestamp did not reach ansible's per-task sudo, causing 'Premature end of stream waiting for become success'). Sudo ansible runs now pass --ask-become-pass, so ansible prompts once and feeds the password to every become task itself, regardless of the tty's sudo timestamp. The prompt is answered reactively on the PTY via the masked password page; the password is never cached, stored, or placed in env/argv. A wrong entry fails the run and is retried with r. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * feat(tui): HTTP proxy upgrade uses the canonical sjust recipe #542 proxy-upgrade now runs 'sjust http-proxy-install-update', which git-updates the http-proxy repository and then re-provisions, instead of only re-running the http-proxy ansible tag (which never pulled the new version). Rendered in terminal mode since the recipe emits default ansible and git output; its nested --ask-become-pass prompt is answered via the password page. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * feat(tui): structured rendering for recipe-driven ansible; \r-aware decoder #542 Gives HTTP proxy upgrade (and any ansible run via a sjust recipe) the structured feed: the recipe now runs with the sparkdock stdout callback env (runner.ForCommandEnv + an absolute callback-plugin dir), so its nested ansible emits @@Markers and renders Structured with phases/glyphs/tally. The feed.Decoder now resolves carriage returns within a line (collapseCR), so single-line progress (git fetch, curl) collapses to its final state instead of piling up — making Structured robust for the whole \r-progress class while the VT emulator stays reserved for multi-line cursor TUIs (brew, docker-compose). Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * feat(tui): brew upgrade with --yes to skip the confirmation prompt #542 Uses brew's official -y/--yes flag so 'Upgrade Brew packages' proceeds without the interactive Proceed? prompt. Casks that need sudo still prompt on the PTY (answered via the password page). Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * fix(brew): stop upgrading auto_updates casks (HOMEBREW_NO_UPGRADE_AUTO_UPDATES_CASKS) #542 A recent brew change (Homebrew/brew#21882) made 'brew upgrade' upgrade casks with auto_updates true even without --greedy, force-upgrading self-managing apps (Chrome, Slack, Docker Desktop). Sets HOMEBREW_NO_UPGRADE_AUTO_UPDATES_CASKS=1 in the managed shell init (covers all brew usage) and in the TUI's brew upgrade run, restoring the prior behaviour. Opt back in per run with 'brew upgrade --greedy'. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * fix(ansible): set HOMEBREW_NO_UPGRADE_AUTO_UPDATES_CASKS on the provisioning play #542 Adds a play-level environment in base.yml so every brew/cask task inherits HOMEBREW_NO_UPGRADE_AUTO_UPDATES_CASKS=1 regardless of how ansible is launched (CI, launchd, menubar), keeping provisioning from upgrading self-updating casks. Covers the provisioning path alongside the shell init and the TUI. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * chore(tui): remove stray vtcheck_main build artifact #542 Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * refactor(tui): apply review fixes — runner robustness, sysinfo sections, scroll, cleanup #542 From a full fresh-eyes review of the branch: guarantee the runner always sends Done (deferred, even on panic) so the UI can't hang, and cap the prompt-detection line buffer so a newline-less stream can't grow it unbounded. Parse CPU vs GPU core counts by section header (Hardware vs Graphics/Displays) instead of ordinal, robust to system_profiler changes. Fix runner-view scrollback (ScrollUp on negative delta; up-arrow/k now scroll up). Collapse a redundant double stats() call. Remove the unused PageSjust id. Set changed_when: false on the tui go build (go's cache makes it a no-op; the install copy reports real change). Also removed a stray committed build artifact earlier. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * refactor(tui): make dashboard action labels consistent #542 Unify the menu vocabulary so each action reads as verb + concrete noun and matches its status row. Rename the skills status row to "AI harness" so it matches the "Sync AI harness" action (one subsystem, one name). Rename "Run full provisioning" to "Provision system" and its run title to "Provisioning system". Rename the HTTP-proxy "Status" action to "Check status" so it is verb-first like its Start/Stop/Upgrade siblings. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * feat(tui): label actions with their CLI equivalent, rebuild on stale sources #542 Rename the full-provisioning action to "Update everything" and show each action's equivalent shell command as its detail (e.g. $ sparkdock, $ brew upgrade, $ spark-http-proxy status), so the TUI teaches the underlying CLI; live status counts stay on the status rows. Hide the not-yet-wired "Update Sparkdock" action so no dead button ships (the amber status dot still signals a stale install; run `sparkdock` to self-update). Export SPARKDOCK_ROOT from the entrypoint so the binary always resolves the right root instead of defaulting to /opt/sparkdock. The `sparkdock tui` launcher now rebuilds the binary when src/tui is newer than the installed binary, not only when it is absent, so a self-update that touched the TUI is picked up before launch. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * ci(tui): add Go build, vet, gofmt, and test workflow #542 Run gofmt check, go vet, go build, and go test for src/tui on pushes and PRs that touch the TUI, mirroring the path-filtered menubar and python workflows. Also gofmt three existing files so the new gofmt gate passes. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * docs(tui): document Go standards and the TUI in AGENTS.md #542 Add a Go Standards section (mandatory gofmt/vet/build/test gate matching the CI workflow, the injected-runner design pattern, and the cross-platform build note) and a Sparkdock TUI section describing the opt-in launch, dashboard, CLI-equivalent action labels, Runner render strategies, the never-cached password handling, how to run against a dev checkout via SPARKDOCK_ROOT, and what is not yet wired. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m] * docs(ansible): correct become/sudo description for --ask-become-pass #542 The play runs unprivileged (become: no) and root work opts in per-task with become: yes; the become password is collected natively via --ask-become-pass (interactive) or --become (CI), populating ansible_become_pass with no ANSIBLE_BECOME_PASS env var. Update the inverted guidance ("omit become: false") that predated the #541 sudo refactor now merged into this branch. Refs: #542 Assisted-by: claude-code/claude-opus-4-8[1m]
Add the HOMEBREW_NO_UPGRADE_AUTO_UPDATES_CASKS environment variable to both zsh and fish templates. This prevents Homebrew from trying to automatically upgrade casks that are configured to handle their own updates (a behavior change introduced in Homebrew/brew PR 21882, see Homebrew/brew#21882).
…ate-apps - mise 2026.7.2's aqua backend layout change broke codex PATH resolution; drop the aqua dependency entirely - codex cask tracks in the Brewfile like everything else - /update-apps now runs brew upgrade --cask: covers auto_updates:false casks plus genuinely stale self-updating apps (brew compares bundle versions since Homebrew/brew#21882)
brew lgtm(style, typechecking and tests) with your changes locally?I used Codex to help plan out and implement the initial version of
bundle_version. I have reviewed and tested the functionality, but anticipate that there will be some edge-cases.Outdated Description
This PR introduces an initial implementation of a
bundle_versionparameter for cask artifacts. In this first iteration, it only applies toappartifacts, which is the primary target for this feature.The goal is to support metadata-only upgrades when an app has already been updated outside Homebrew, for example by an in-app updater. If a newer cask is available in Homebrew, and the installed app bundle already matches the incoming cask’s version and
bundle_version, Homebrew updates its installed cask metadata without fetching or reinstalling the app.Note that this only applies when the version matches, so
brew upgrade <cask>would still run an installation when the installed version is greater than the Homebrew version. A future possible enhancement (out of scope here) could be to handle cases where the installed app is never than the incoming cask version.I’ve kept this PR scoped to the MVP. For broader adoption, we would also want
brew bumpandbrew bump-cask-prsupport to detect and updatebundle_versionautomatically. I intend to work on this as a follow-up, unless there is preference to include it here.Included in this PR;
bundle_versionsupport forappartifactsbundle_versionbrew audit --onlinefunctionality to check thebundle_versionvalues against the artifactFollow-up work:
bundle_versiondetection and update support inbrew bumpandbrew bump-cask-prThis PR introduces a change to the way that we handle upgrades for simple Casks in
homebrew-cask, with the qualifiers that they only have a singleappartifact, and haveauto_updates true. At present these are only ever upgraded withbrew upgradewhen the--greedyor--greedy-auto-updatesflags are passed.The PR introduces a
bundle_versioncheck that looks inside the installed applicationsinfo.plistfile and compares the version numbers with the incoming latest version from the tap. If the incoming version is detected as being newer than the installed version, then we proceed with the upgrade.There is a level of naivety involved in the version matching, as there is a lot of inconsistent between how developers set the
CFBundleVersionandCFShortBundleVersionStringininfo.plistversus how we useversioninhomebrew-cask. This means that we look for a broad range of matches, which may prove to be too loose. In my testing it has seemed to be ok, especially because we still look for an outdated Caskroom file, so we shouldn't ever see repeated upgrades for the same version.