Commit graph

16 commits

Author SHA1 Message Date
maxsoch bd19bee52e add setting to include self-updating casks (--greedy), on by default
brew outdated normally skips casks that auto-update themselves; with
includeGreedyCasks enabled (the default) BrewBar passes --greedy so they
show up too. Toggling the checkbox refetches immediately since the list
semantics change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 22:05:53 +02:00
maxsoch 98d8cbe2a9 replace interval submenu with a Settings window (frequency)
New SettingsWindowController (programmatic NSWindow, same pattern as
LogWindowController) with a preset popup and a custom-minutes field for
the refresh frequency. Changes apply immediately, per macOS convention.

The ⏱ Refresh Interval submenu, its custom text-field-in-menu view and
the two backing actions are removed in favor of "⚙️ Settings…" (Cmd+,).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 22:04:31 +02:00
maxsoch be49480732 add UserDefaults-backed Settings storage
Settings enum holds all upcoming settings-window preferences. Booleans
default to false (disabled by default); includeGreedyCasks and
confirmBeforeUpgradeAll default to true via register(defaults:), which
provides fallbacks without writing to disk. Launch-at-login is not
stored here — SMAppService will be its source of truth.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 22:03:19 +02:00
maxsoch 9f848be875 hide Upgrade All when there is nothing to upgrade
Same treatment as the Outdated item: upgradeAllItem becomes a property,
starts hidden, and updateOutdatedMenu shows it only when the fetch
returns outdated packages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:40:50 +02:00
maxsoch 15034951fc hide the Outdated menu item when everything is up to date
Promote outdatedItem to a property and toggle isHidden from
updateOutdatedMenu: hidden at launch and whenever a fetch returns no
outdated packages, visible otherwise. The "All up to date" placeholder
inside the submenu is dropped — the status line already says it, and
the item is invisible when the list is empty.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:38:50 +02:00
maxsoch 9ab8af79a4 remove redundant "Refresh List" from the Outdated submenu
The main Refresh action already refetches everything; the submenu
button duplicated it. Its only handler, forceRefreshOutdated, is
removed too as nothing else referenced it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:38:10 +02:00
maxsoch e41d7ac008 fix: exec brew directly instead of interpreting a shell string
runBrew built a command string and ran it through zsh -c, so the shell
interpreted the whole line — any metacharacter in an interpolated
package name (;, $(), backticks) would have been executed. Names come
from brew itself so exploitation was unlikely, but the injection class
is now gone: Process gets an argument array via /usr/bin/env, which
resolves brew on PATH (covering the bare "brew" fallback) and execs it
with no shell in between. Also slightly faster per invocation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:34:28 +02:00
maxsoch 99c243a51c fix: cap logBuffer at 100k chars to prevent unbounded growth
Every brew command appended its full output to logBuffer and nothing
ever trimmed it, so with the hourly refresh timer the buffer grew
forever. Memory aside, each log() call re-renders the entire buffer
into the log window's NSTextView, so logging got slower over time.

Keep only the most recent 100k characters, dropping the oldest content.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:33:32 +02:00
maxsoch d1f4c0cb79 cleanup: remove dead refreshOutdatedList and unused lastOutdatedFetch
refreshOutdatedList (the 5-second cache path) was never called by any
menu item or code path, so the cache logic never ran. lastOutdatedFetch
was only written. cachedOutdated stays: the Upgrade All confirmation
dialog uses it to list pending packages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:30:12 +02:00
maxsoch e47455b3f8 fix: confirm before Upgrade All, warn that running apps may close
brew upgrade also upgrades casks, and replacing a cask quits the running
app (e.g. the browser) with no warning. Upgrade All now shows an NSAlert
listing the outdated packages and warning that running apps may be
closed, with a Cancel option.

NSApp.activate is needed because BrewBar is an accessory app (no Dock
icon) — without it the modal alert can appear behind other windows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:29:23 +02:00
maxsoch e579ac1d56 fix: show a GUI password dialog when brew needs sudo
Brew runs inside a Process with no terminal attached, so any sudo prompt
(cask upgrades, some installers) would hang or fail silently — there was
nowhere to type the password.

Set SUDO_ASKPASS to a small helper script written to Application Support
at launch. When sudo detects no tty, it runs the helper, which shows a
native macOS password dialog via osascript and prints the answer for
sudo to consume. Cancelling the dialog makes sudo fail cleanly instead
of hanging.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:27:45 +02:00
maxsoch 7390d68a33 fix: deliver runBrew completion on the main thread
Completions were invoked on a background queue. Most call sites wrapped
their UI work in DispatchQueue.main.async, but upgradeSingle's completion
calls refreshAll(), which sets statusMenuItem.title directly — an AppKit
mutation off the main thread. It also raced on cachedOutdated /
lastOutdatedFetch (written from background, read from main).

runBrew now always dispatches its completion to the main queue, so every
caller can safely touch UI, and the now-redundant inner main.async hops
are removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:26:14 +02:00
maxsoch 4ab3cc1e70 add unit test target with BrewParser extraction
Extract brew output parsing into BrewParser.swift (pure Swift, no AppKit)
and add BrewBarTests target with 7 passing tests covering parseOutdated
and parseVersion edge cases.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-06 21:17:26 +02:00
maxsoch 6f255380d0 add dev tooling from /init: swiftlint, skills, gitignore; apply swiftformat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-06 21:07:17 +02:00
Maxence Socheleau b7fb754e09 Adding Xcode project; base code available and working 2026-06-30 17:40:51 +02:00
Maxence Socheleau 7bc7a53442 Initial Commit 2026-06-26 11:10:41 +02:00