Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ jobs:
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER_ID: ${{ secrets.APPLE_API_ISSUER_ID }}
APPLE_API_PRIVATE_KEY: ${{ secrets.APPLE_API_PRIVATE_KEY }}
SPARKLE_ED_PRIVATE_KEY: ${{ secrets.SPARKLE_ED_PRIVATE_KEY }}
run: |
set -euo pipefail
./script/validate_version.sh "$GITHUB_REF_NAME"
Expand All @@ -46,9 +47,10 @@ jobs:
"$MACOS_SIGNING_IDENTITY" \
"$APPLE_API_KEY_ID" \
"$APPLE_API_ISSUER_ID" \
"$APPLE_API_PRIVATE_KEY"; do
"$APPLE_API_PRIVATE_KEY" \
"$SPARKLE_ED_PRIVATE_KEY"; do
if [[ -z "$value" ]]; then
echo "signed releases require all documented signing and notarization secrets" >&2
echo "signed releases require all documented signing, notarization, and Sparkle secrets" >&2
exit 1
fi
done
Expand Down Expand Up @@ -96,6 +98,7 @@ jobs:
API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
API_ISSUER_ID: ${{ secrets.APPLE_API_ISSUER_ID }}
API_PRIVATE_KEY: ${{ secrets.APPLE_API_PRIVATE_KEY }}
SPARKLE_ED_PRIVATE_KEY: ${{ secrets.SPARKLE_ED_PRIVATE_KEY }}
run: |
set -euo pipefail
if [[ "$RELEASE_MODE" == "unsigned-prerelease" ]]; then
Expand All @@ -114,6 +117,7 @@ jobs:
--api-key-id "$API_KEY_ID" \
--api-issuer-id "$API_ISSUER_ID"
rm -f "$API_KEY_PATH"
./script/generate_appcast.sh

- name: Publish GitHub release
env:
Expand Down Expand Up @@ -141,6 +145,7 @@ jobs:
gh release create "$GITHUB_REF_NAME" \
"$ARCHIVE" \
"$ARCHIVE.sha256" \
"dist/appcast.xml" \
--verify-tag \
--generate-notes \
--title "Agent Notch $VERSION"
Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,30 @@ use [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- In-app updates download and install from GitHub Releases. Check, Download,
then Restart to Update, the same two-step flow as T3 Code. Homebrew marks
the cask as self-updating.

### Changed

- Update checks use the Sparkle appcast instead of opening the GitHub releases
page. Automatic checks default on, run at launch and once a day, and never
download or install until you ask.
- Notch controls show hover and press. Waiting-prompt shortcuts activate after
a click and remain active only while the pointer is over the prompt.
- Primary actions pick black or white text from the system accent so light
yellow, green, and orange fills stay readable.

### Fixed

- Retry after a Sparkle startup failure starts the updater again instead of
doing nothing until relaunch.
- An ineligible update (newer macOS required, and similar Sparkle reasons) no
longer shows as "Up to date".
- Check for Updates from the app menu opens Settings → General so the result
is visible.
- Completed sessions can remain in Activity Center for three or seven days,
but never longer than seven.
- Git worktrees show the main repository as the project, not the worktree
Expand Down
13 changes: 2 additions & 11 deletions Casks/agent-notch.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,10 @@

livecheck do
url :url
regex(/^v?(\d+(?:\.\d+)+)$/i)
strategy :github_releases do |json, regex|
json.map do |release|
next if release["draft"]

match = release["tag_name"]&.match(regex)
next if match.blank?

match[1]
end
end
strategy :github_latest
end

auto_updates true
depends_on macos: :sonoma
depends_on arch: :arm64

Expand Down
15 changes: 15 additions & 0 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 13 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,20 @@ let package = Package(
.executable(name: "AgentsNotch", targets: ["AgentsNotch"]),
.executable(name: "AgentsNotchHook", targets: ["AgentsNotchHook"]),
],
dependencies: [
.package(url: "https://github.com/sparkle-project/Sparkle", from: "2.9.6"),
],
targets: [
.target(
name: "AgentsNotchCore",
swiftSettings: [.swiftLanguageMode(.v6)]
),
.executableTarget(
name: "AgentsNotch",
dependencies: ["AgentsNotchCore"],
dependencies: [
"AgentsNotchCore",
.product(name: "Sparkle", package: "Sparkle"),
],
resources: [.process("Resources")],
swiftSettings: [.swiftLanguageMode(.v6)]
),
Expand All @@ -30,7 +36,12 @@ let package = Package(
),
.testTarget(
name: "AgentsNotchTests",
dependencies: ["AgentsNotchCore", "AgentsNotch", "AgentsNotchHook"],
dependencies: [
"AgentsNotchCore",
"AgentsNotch",
"AgentsNotchHook",
.product(name: "Sparkle", package: "Sparkle"),
],
swiftSettings: [.swiftLanguageMode(.v6)]
),
]
Expand Down
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ brew install --cask aforno/agentnotch/agent-notch
Homebrew 6 does not trust a third-party tap when you add it. The fully
qualified name trusts only this cask.

Later cask bumps land on `main`. Update with `brew update` and
`brew upgrade --cask aforno/agentnotch/agent-notch`.
Later cask bumps land on `main`. Packaged builds also update themselves:
Settings → General → Check for Updates, then Download, then Restart to Update.
Homebrew knows the app self-updates (`auto_updates true`). `brew upgrade --cask`
is still available if you prefer it.

### Manual

Expand Down Expand Up @@ -250,9 +252,10 @@ waiting sessions stay waiting. Other actives enter a short `unknown`
(Reconnecting) grace period until a live hook arrives or the grace expires.

Session history stays on the machine. No analytics, no source upload, no remote
telemetry. If update checking is on, the app makes an HTTPS request to GitHub
Releases at most once a day. Manual checks use the same endpoint. Notifications
are opt-in and delivered by macOS.
telemetry. If update checking is on, the app fetches the Sparkle appcast from
GitHub Releases at launch and at most once a day. Choosing Download fetches
that release ZIP. Sparkle verifies the EdDSA signature and Developer ID before
replacing the app. Notifications are opt-in and delivered by macOS.

## Remove Agent Notch

Expand Down Expand Up @@ -282,8 +285,8 @@ swift test -c release

The ad-hoc package checks release configuration and bundle structure. Do not
distribute it. Maintainers follow [`docs/RELEASING.md`](docs/RELEASING.md) for
Developer ID signing, notarization, stapling, checksums, and GitHub release
automation.
Developer ID signing, notarization, stapling, checksums, Sparkle appcast
signing, and GitHub release automation.

## Security and contributing

Expand Down
1 change: 1 addition & 0 deletions Resources/SparklePublicEDKey
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
E24M1Ba8TfxAO1ELG+M83cczUzxBoP49t60vOlX9gfc=
4 changes: 4 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ It modifies only its own entries in supported provider hook configuration.
source, or send analytics or telemetry. The one Codex exception is a
fail-open 4 MiB tail read used only to decide whether a PermissionRequest
is waiting on a person; missing context stays visible.
- Update checks fetch only the GitHub Releases Sparkle appcast. The ZIP is
downloaded after the user chooses Download. Sparkle verifies the EdDSA
signature against the baked-in public key and the Developer ID signature
before replacing the running app. System profile data is not sent.
- Integration changes are atomic and idempotent, preserve unrelated settings,
preserve restrictive file permissions and symlinked dotfiles, and remove
only Agent Notch entries.
Expand Down
13 changes: 9 additions & 4 deletions Sources/AgentsNotch/App/AgentsNotchApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
"notchEnabled": true,
"showVirtualNotch": false,
"hasCompletedOnboarding": false,
"automaticallyCheckForUpdates": false,
"automaticallyCheckForUpdates": true,
"privacyModeEnabled": false,
"answerFromNotchEnabled": false,
"globalActivityShortcut": GlobalActivityShortcut.off.rawValue,
Expand All @@ -42,7 +42,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
runtime.panelController = panel
runtime.openActivityCenterHandler = { [weak self] in self?.showActivityCenter() }
runtime.openOnboardingHandler = { [weak self] in self?.showOnboarding() }
runtime.openSettingsHandler = { [weak self] in self?.showSettings() }
runtime.openSettingsHandler = { [weak self] pane in self?.showSettings(pane: pane) }
let shortcutController = GlobalActivityShortcutController { [weak self] in
self?.runtime.openActivityCenter()
}
Expand Down Expand Up @@ -91,11 +91,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
controller.show()
}

private func showSettings() {
private func showSettings(pane: SettingsPane? = nil) {
let controller = settingsWindowController ?? SettingsWindowController(runtime: runtime)
controller.onClose = { [weak self] in self?.settingsWindowController = nil }
settingsWindowController = controller
controller.show()
controller.show(pane: pane)
}
}

Expand All @@ -117,6 +117,11 @@ struct AgentsNotchApp: App {
}
.keyboardShortcut(",", modifiers: [.command])
}
CommandGroup(after: .appInfo) {
Button("Check for Updates…") {
appDelegate.runtime.updates.check()
}
}

CommandMenu("Agents") {
Button("Open Activity Center") {
Expand Down
14 changes: 10 additions & 4 deletions Sources/AgentsNotch/App/AppRuntime.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ final class AppRuntime {
private(set) var activitySearchRequest: UInt64 = 0
var openActivityCenterHandler: (() -> Void)?
var openOnboardingHandler: (() -> Void)?
var openSettingsHandler: (() -> Void)?
var openSettingsHandler: ((SettingsPane?) -> Void)?
var updateGlobalShortcutHandler: ((String) -> Void)?
weak var panelController: NotchPanelController?

Expand Down Expand Up @@ -124,6 +124,12 @@ final class AppRuntime {
#if DEBUG
simulator = DebugEventSimulator(activity: activity)
#endif
updates.willInstall = { [weak self] in
self?.stop()
}
updates.presentStatus = { [weak self] in
self?.openSettings(pane: .general)
}
notifications.onOpenSession = { [weak self] sessionID in
self?.presentSession(sessionID)
}
Expand All @@ -138,7 +144,7 @@ final class AppRuntime {
guard await restorePersistedState(generation: generation) else { return }
completeRestoration(generation: generation)
guard await startProviderMonitoring(generation: generation) else { return }
updates.checkAutomaticallyIfNeeded()
updates.start()
}

private func beginStartup() -> Int? {
Expand Down Expand Up @@ -590,8 +596,8 @@ final class AppRuntime {
openOnboardingHandler?()
}

func openSettings() {
openSettingsHandler?()
func openSettings(pane: SettingsPane? = nil) {
openSettingsHandler?(pane)
}

func updateGlobalShortcut(_ rawValue: String) {
Expand Down
Loading
Loading