macnotifier の実装ガイド。
macos-notify-mcp の MacOSNotifyMCP/main.swift を参考にする。
-
NSApplication ライフサイクル
NSApplication.shared.setActivationPolicy(.accessory)で Dock 非表示NSApplication.shared.run()でイベントループ開始- クリック処理後に
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5)で遅延終了
-
UNUserNotificationCenter
requestAuthorization(options: [.alert, .sound])で権限要求trigger: nilで即時配信UNMutableNotificationContent.userInfoにクリック時のメタデータを格納
-
デリゲート
UNUserNotificationCenterDelegate.didReceiveでクリックハンドリングresponse.notification.request.content.userInfoからメタデータ取得
-
ビルド
swiftc -oで単一ファイルコンパイルcodesign --force --deep --sign -で ad-hoc 署名
- MCP サーバー部分は不要。純粋な CLI ツールとして実装する
- tmux 連携は不要。汎用的な
-activateと-executeに置き換える - コマンドライン引数パーサーは手動実装(外部依存を入れない)
UNUserNotificationCenter は .app バンドル内のバイナリからしか動作しない。Info.plist に CFBundleIdentifier が必要。
<key>CFBundleIdentifier</key>
<string>sh.send.macnotifier</string>
<key>LSUIElement</key>
<true/>
<key>NSUserNotificationAlertStyle</key>
<string>alert</string>LSUIElement: true— Dock に表示しないNSUserNotificationAlertStyle: alert— バナーではなくアラートスタイル(ユーザーが明示的に閉じるまで表示)
open -n macnotifier.app --args -m "message" で起動する。-n フラグで複数インスタンスを許可。
CLI ラッパースクリプトがこの open -n 呼び出しをラップする。
Sources/main.swiftを作成NSApplication+UNUserNotificationCenterで基本的な通知送信-t(title) と-m(message) の引数パースscripts/build.shで.appバンドルビルド- 動作確認:
open -n macnotifier.app --args -m "test"
UNUserNotificationCenterDelegate.didReceiveを実装-e(execute):Process()+/bin/sh -cでコマンド実行-a(activate):NSWorkspace.shared.open(URL)またはosascriptでアプリアクティブ化- 両方指定時: execute → activate の順で実行
- 処理後に
NSApplication.shared.terminate(nil)で自己終了
--sound—UNNotificationSound(named:)でカスタム通知音--icon—UNNotificationAttachmentで PNG アイコン添付-h(help) — ヘルプ表示
bin/macnotifierCLI ラッパースクリプト(open -nをラップ、シンボリックリンク対応)scripts/build.sh完成(Info.plist 生成、コンパイル、署名、アイコン組み込み)- アプリアイコン追加(吹き出し、モノクロ、黄金比グリッド)
外部依存なしで手動パース。参考実装と同じパターン:
var i = 1
let args = CommandLine.arguments
while i < args.count {
switch args[i] {
case "-t", "--title":
i += 1; title = args[i]
case "-m", "--message":
i += 1; message = args[i]
// ...
}
i += 1
}let task = Process()
task.executableURL = URL(fileURLWithPath: "/bin/sh")
task.arguments = ["-c", command]
try task.run()
// waitUntilExit() は不要(バックグラウンドで実行させる)バンドル ID からアプリをアクティブ化:
if let url = NSWorkspace.shared.urlForApplication(
withBundleIdentifier: bundleId
) {
NSWorkspace.shared.open(url)
}scripts/build.sh が行うこと:
.appバンドルのディレクトリ構造を作成Info.plistを生成(CFBundleIconFile含む)swiftcでコンパイル- アイコンがあれば
Resources/にコピー codesign --force --deep --sign -で ad-hoc 署名
./scripts/build.shで.appバンドルがビルドできること./scripts/test.shで全テストがパスすること./bin/macnotifier -m "test"で通知が表示されること./bin/macnotifier -m "test" -e "echo clicked > /tmp/test"でクリック時にコマンド実行./bin/macnotifier -m "test" -a com.github.wez.weztermでクリック時に WezTerm がアクティブ化- 通知クリック後にプロセスが終了すること