Some casks (claude and others) version themselves as
"user-facing-version,build-id" — the hash after the comma only
feeds brew's own download URL and was never meant for display,
but showed up verbatim in the Outdated menu. displayVersion() now
truncates at the first comma for both the installed and target
version shown in the label; brew commands are unaffected since
they operate on the package name, never this string.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
It only appeared when both formulae and casks were outdated; with a
single kind it vanished entirely. Now it shows whenever anything is
outdated, with the empty kind greyed out (autoenablesItems is off —
AppKit would otherwise re-enable the items from the responder chain
on every menu open).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
RunningApps maps installed casks to their .app bundles via the
Caskroom copy (no brew process needed) and checks them against
NSWorkspace's running applications. Two settings in a new "Running
apps" section: auto-upgrade skips running casks (on by default —
that flow has no dialog to warn anyone), manual bulk upgrades can opt
in. The auto-upgrade filter runs before the trigger decision so a
permanently running app cannot start an upgrade cycle that would skip
it anyway. The confirmation dialog now names the apps actually
running instead of a blanket warning, and stays silent when none are.
Single-package upgrades from the Outdated menu always proceed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
brew's batch upgrade --formula errors out on the first broken formula
(no bottle for this macOS, deprecated, …), blocking every other
pending formula — and the failure only showed in the logs, so the
menu silently fell back to "N outdated" as if nothing had run.
Formulae now upgrade individually like casks already did, failures
of any kind (including a failed reinstall fallback) are collected,
and the status line reports them once after the closing refetch.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The ⌥ purge alternate carries the same key equivalent as Cleanup —
alternate pairing requires it — so ⌥C triggers the purge variant.
The shortcut list in the Tips window is updated accordingly.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Only fetchOutdated ever touched the menu bar icon, so during an
upgrade it kept showing "updates available". Every long-running
operation now sets the updating icon when it starts; paths that end
without a refetch (cleanup, failed refresh) restore the icon of the
last known state via a new cachedStateIcon() helper. Single-package
upgrades also gained a status line, they showed nothing before.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Lists the ⌥ cleanup variant, menu keyboard shortcuts, selective
upgrades, the self-updating cask reinstall behavior and the Logs
reflex. Content is a plain array in TipsWindowController — adding a
tip is one entry. No pbxproj change needed: the project uses Xcode 16
synchronized groups, so new files in BrewBar/ are picked up.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Self-updating apps (Zen, Chrome, …) drift from what brew installed;
brew then refuses to upgrade the cask in place, prints a warning and
does nothing, so the package stayed outdated forever. Detect the
warning and run the fix brew itself suggests (reinstall --cask
--force). Single-package upgrades now also route casks through
upgrade --cask instead of a bare brew upgrade.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
"Upgrade All" becomes "Upgrade formulae & casks", and an "Upgrade
only…" submenu offers Formulae/Casks with live counts — shown only
when both kinds are outdated, since with a single kind the main item
already upgrades exactly what there is. All three actions share one
parametrized upgrade(formulae:casks:) flow; the confirmation dialog
lists only the selected packages and drops the running-app warning
when no casks are involved.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resolved by combining both sides in upgradeSingle (two-param runBrew
closure from fix/offline-refresh + cleanup hook from feature/cleanup)
and adapting the cleanup branch's runBrew calls to the new signature.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A Cleanup menu item runs brew cleanup; holding ⌥ swaps it for a purge
variant (cleanup --prune=all, which also drops cached downloads of
current versions). A new opt-out-by-default setting interposes a plain
cleanup between the end of any upgrade path and its closing refetch.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
runBrew now reports the process exit status to its completion handler.
refreshAll and fetchOutdated stop on failure and show "Check failed —
see Logs": NWPathMonitor cannot see a network that is up but broken
(captive portal, dead DNS, git host down), while the exit code of the
brew command itself catches every failure mode. Upgrade commands keep
ignoring the flag on purpose — a failed upgrade leaves the package in
the outdated list at the next fetch, so the final state stays correct.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
brew outdated never touches the network: offline, a refresh silently
succeeded on stale local tap data and reported "all up to date". An
NWPathMonitor now gates refreshAll — offline shows a dedicated status
instead, and every offline→online transition triggers a refresh, which
covers the launch refresh, scheduled refreshes skipped during an
outage, and clearing the offline status when the connection returns.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the premature 1.0 with 0.1 to signal beta status. Document the
0.x version scheme and the brew-never-downgrades caveat in RELEASING.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Recent Homebrew removed the --no-quarantine option entirely, so the
install docs and cask now instruct clearing the quarantine attribute
with xattr after install/upgrade. The cask prints it as a caveat at
install time.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
15.6 was just the Xcode default from project creation, not a real
requirement — the newest API in use is SMAppService (macOS 13). The
compiler enforces the audit: everything builds and tests pass at 13.0.
Release zip rebuilt; cask gets the new sha256 and a :ventura floor.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The repo doubles as a Homebrew tap: brew tap accepts an explicit git
URL, so Casks/brewbar.rb here is enough — no separate homebrew-* repo.
The cask downloads the zipped Release build from the matching Forgejo
release (v<version>) and verifies it against the pinned sha256. dist/
(local release artifacts) is gitignored.
Install:
brew tap maxsoch/brewbar https://forgejo.socheleau.fr/maxsoch/BrewBar.git
brew install --cask --no-quarantine maxsoch/brewbar/brewbar
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
requestAuthorization only shows the system prompt once ever; when
permission is already denied it silently returns false and the checkbox
just snapped off with no explanation. toggleNotify now checks the
authorization status first: already-authorized enables directly,
not-determined triggers the system prompt (errors surfaced in an
alert), and denied shows an alert with an Open System Settings button
pointing at the Notifications pane.
Also adds a UNUserNotificationCenterDelegate so banners are shown even
while BrewBar is the active app — otherwise macOS suppresses them,
which reads as "notifications don't work" right after enabling.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
runBrew dispatched onto the global concurrent queue, so overlapping
operations (refresh timer firing mid-upgrade, quick manual actions)
ran multiple brew processes at once — each able to summon its own sudo
password dialog, hence duplicate password windows. A private serial
queue makes every brew command wait for the previous one, so a second
dialog cannot appear while one is open. Log output no longer
interleaves either.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Monospaced font and semantic colors (dark-mode correct), visible
vertical scroller (was missing), text inset, centered window with a
proper title. Autoscroll now only follows the tail when already at the
bottom instead of yanking the scroll position on every log line.
Also sets isReleasedWhenClosed = false, fixing a latent crash on
close-then-reopen (the settings window already had this guard).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Accessory apps never appear in the Cmd-Tab switcher and macOS does not
activate them when they open windows — the log window opened behind
other apps (settings only worked via a manual activate call).
presentWindow(of:) now switches the activation policy to .regular
while a BrewBar window is open, giving a Dock icon and a Cmd-Tab entry;
a willClose observer drops back to .accessory once the last window is
gone. Used by both showLogs and showSettings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Batched cask upgrades give no per-package hook, so the password dialog
could only echo the whole command. upgradeCasksSequentially chains one
"upgrade --cask <name>" per outdated cask (from the typed cache), each
with an askpass message naming that cask; the status line follows along
("Upgrading tailscale-app..."). Used by both Upgrade All and
auto-upgrade; formulae stay batched since they don't need sudo.
Explicitly named casks upgrade even when self-updating, so the
per-cask commands need no --greedy. Casks that turn outdated only
after the preceding brew update surface in the final refetch.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
runBrew gains an optional askpassMessage; the dialog reads "BrewBar
needs your administrator password to <message>." and defaults to naming
the brew command. Groundwork for per-cask upgrade prompts that name the
app instead of the command.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The askpass script is now rewritten before each brew invocation with
the current command baked into the dialog text ("...to run: brew
upgrade tailscale-app") instead of a generic message written once at
launch. The command text is whitelisted to characters that cannot
break out of the shell/AppleScript quoting it is embedded in.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fetchOutdated returned before refreshing the UI when auto-upgrade
triggered, so the menu showed stale state for the whole upgrade. Now
the fetched list lands in the menu, status line and menu bar count
first; runAutoUpgrade then overwrites the status with its own message.
Notification stays after the post-upgrade refetch — leftovers only.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fetchOutdated now uses --json and the typed parser, so cachedOutdated
knows formulae from casks. The auto-upgrade trigger checks each kind
against its own toggle: an outdated cask no longer starts a pointless
"upgrade --formula" run (previously any outdated package triggered
whichever auto-upgrades were enabled). runAutoUpgrade takes explicit
formulae/casks flags instead of re-reading settings.
Bonus from the version info now available: outdated menu items and the
Upgrade All confirmation show "name installed → current" instead of
bare names. The legacy line parser and its tests are removed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
parseOutdatedJSON decodes brew outdated --json into OutdatedPackages,
which separates formulae from casks — the plain-text output is just
names with no type information. Codable structs mirror the JSON shape,
with convertFromSnakeCase mapping installed_versions to Swift naming.
Undecodable input (brew error text, empty output) parses as .none.
The legacy line parser stays until callers switch over next commit.
Covered by 4 new unit tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add a note under the greedy checkbox spelling out what self-updating
casks are and that the setting affects both the outdated listing and
upgrades (Upgrade All and auto-upgrade). The indented-gray-note layout
is extracted into a noteRow helper, now shared with the cask warning.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
--greedy exists on both brew commands: outdated --greedy lists
self-updating casks, upgrade --greedy actually upgrades them (plain
upgrade skips them, per brew's own help). The listing and auto-upgrade
paths had the flag but manual Upgrade All didn't, so greedy-listed
casks would survive Upgrade All and reappear as outdated forever.
Single-package upgrades don't need it: brew upgrades an explicitly
named cask even when it self-updates.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New "Ask for confirmation before Upgrade All" checkbox; when disabled,
Upgrade All runs immediately. Auto-upgrade is unaffected either way —
it never prompts.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>