Commit graph

32 commits

Author SHA1 Message Date
maxsoch 813fc47461 fetch outdated as JSON; auto-upgrade only for matching package kinds
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>
2026-07-06 22:27:37 +02:00
maxsoch 7b3468126a add typed JSON parser for brew outdated
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>
2026-07-06 22:26:12 +02:00
maxsoch 3a5a7ee623 explain the greedy setting in the settings window
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>
2026-07-06 22:18:23 +02:00
maxsoch 741f9572b7 fix: Upgrade All must pass --greedy when greedy listing is on
--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>
2026-07-06 22:16:22 +02:00
maxsoch 0bc18b79b8 make the Upgrade All confirmation dialog optional, on by default
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>
2026-07-06 22:09:26 +02:00
maxsoch 29b9412a34 add optional outdated count next to the menu bar icon
Off by default. The status item button shows both image and title
(imagePosition = .imageLeft), so the icon stays visible with the count
beside it. Cleared when up to date or when the setting is off; toggling
applies immediately from the cached list without refetching.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 22:08:51 +02:00
maxsoch 32d81423ab add update notifications, only when new packages appear
Off by default. Enabling the checkbox requests notification permission
(UNUserNotificationCenter); if denied, the checkbox reverts — the auth
callback arrives on a background queue, so the UI update hops to main.

notifyIfNeeded remembers the last notified set and stays silent while
the outdated list is unchanged, so an hourly refresh doesn't re-ping
about the same packages. The set resets as packages get upgraded.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 22:08:10 +02:00
maxsoch 7c53413e40 add auto-upgrade settings, split into formulae and casks
Two independent toggles, both off by default. When a scheduled fetch
finds outdated packages, runAutoUpgrade runs "upgrade --formula" and/or
"upgrade --cask" (with --greedy if that setting is on), then refetches.

The refetch passes allowAutoUpgrade: false — packages can legitimately
stay outdated after an upgrade (pinned formulae, greedy casks brew
declines), and without the guard that would trigger upgrade → fetch →
upgrade forever. Manual Upgrade All's final fetch gets the same guard.

The casks checkbox carries a warning note in the settings window: a
cask upgrade can close and replace the running app without warning.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 22:06:55 +02:00
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 617e07d53d add launch-at-login setting via SMAppService
Checkbox registers/unregisters SMAppService.mainApp (macOS 13+ API, no
helper app needed). Deliberately not mirrored in UserDefaults: the
service status is the single source of truth, and syncUI reads it live
so changes made in System Settings > Login Items stay in sync. Failures
revert the checkbox and surface the error in an alert.

Note: registration points at the app bundle's current path, so it only
behaves properly once BrewBar runs from a stable location like
/Applications — debug builds may register the build folder copy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 22:05:14 +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 5def46f2cd n/a 2026-07-03 15:41:00 +02:00
Maxence Socheleau 03e121fdae update README 2026-06-30 15:53:48 +00:00
Maxence Socheleau c9b68492e7 addind logo as asset 2026-06-30 17:47:46 +02:00
Maxence Socheleau 28115f4a59 Merge branch 'main' of https://forgejo.socheleau.fr/maxsoch/BrewBar 2026-06-30 17:42:07 +02:00
Maxence Socheleau b7fb754e09 Adding Xcode project; base code available and working 2026-06-30 17:40:51 +02:00
Maxence Socheleau df1c09d9c4 Initial commit 2026-06-30 15:33:33 +00:00
Maxence Socheleau 8a70403d59 Merge branch 'main' of https://forgejo.socheleau.fr/maxsoch/BrewBar 2026-06-30 17:18:40 +02:00
Maxence Socheleau f09aead8af Initial commit 2026-06-30 15:16:27 +00:00
Maxence Socheleau 7bc7a53442 Initial Commit 2026-06-26 11:10:41 +02:00