diff --git a/.gitignore b/.gitignore index 9bf2602..a085e89 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,3 @@ Package.resolved .DS_Store *.swp *.swo - -# CLI wrapper (generated by build.sh) -/macnotifier diff --git a/Resources/AppIcon.icns b/Resources/AppIcon.icns new file mode 100644 index 0000000..5c1dd2a Binary files /dev/null and b/Resources/AppIcon.icns differ diff --git a/Resources/icon.svg b/Resources/icon.svg new file mode 100644 index 0000000..e3b7592 --- /dev/null +++ b/Resources/icon.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/bin/macnotifier b/bin/macnotifier new file mode 100755 index 0000000..c4e387b --- /dev/null +++ b/bin/macnotifier @@ -0,0 +1,84 @@ +#!/bin/bash +set -euo pipefail + +# Resolve symlinks so the script works when invoked via symlink (e.g. Homebrew). +SELF="$0" +if [ -L "$SELF" ]; then + TARGET="$(readlink "$SELF")" + case "$TARGET" in + /*) SELF="$TARGET" ;; + *) SELF="$(dirname "$SELF")/$TARGET" ;; + esac +fi +SCRIPT_DIR="$(cd "$(dirname "$SELF")" && pwd)" +APP_BUNDLE="$(cd "$SCRIPT_DIR/.." && pwd)/macnotifier.app" + +# Handle -h/--help directly so output is visible in the terminal. +# open -n does not connect the app's stdout/stderr to the terminal. +for arg in "$@"; do + case "$arg" in + -h|--help) + cat <<'USAGE' +Usage: macnotifier [options] + +Options: + -t, --title Notification title (default: "macnotifier") + -m, --message <message> Notification message (required) + -e, --execute <command> Shell command to execute on click + -a, --activate <id> Bundle ID of app to activate on click + --sound <name> Sound name in ~/Library/Sounds or /System/Library/Sounds (e.g. "Glass") + --icon <path> Path to image file to attach as icon + -h, --help Show this help message +USAGE + exit 0 + ;; + esac +done + +# Validate -m is present with a value before launching the app. +has_message=false +expect_value=false +for arg in "$@"; do + if [ "$expect_value" = true ]; then + has_message=true + break + fi + case "$arg" in + -m|--message) expect_value=true ;; + esac +done + +if [ "$expect_value" = true ] && [ "$has_message" = false ]; then + echo "Error: -m requires a value" >&2 + exit 1 +fi + +if [ "$has_message" = false ]; then + echo "Error: -m (message) is required" >&2 + exit 1 +fi + +if [ ! -d "$APP_BUNDLE" ]; then + echo "Error: $APP_BUNDLE not found (run scripts/build.sh first)" >&2 + exit 1 +fi + +# Convert --icon relative paths to absolute paths. +# open -n changes the working directory, so relative paths would break. +args=() +while [ $# -gt 0 ]; do + if [ "$1" = "--icon" ] && [ $# -ge 2 ]; then + args+=("$1") + shift + case "$1" in + /*) args+=("$1") ;; # absolute path: pass through + ~*) args+=("$1") ;; # tilde path: Swift expands it + *) args+=("$PWD/$1") ;; # relative path: convert to absolute + esac + else + args+=("$1") + fi + shift +done + +open -n "$APP_BUNDLE" --args "${args[@]}" diff --git a/scripts/build.sh b/scripts/build.sh index 7685f64..fadafbd 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -32,6 +32,8 @@ cat > "$APP_BUNDLE/Contents/Info.plist" <<PLIST <string>1.0</string> <key>CFBundlePackageType</key> <string>APPL</string> + <key>CFBundleIconFile</key> + <string>AppIcon</string> <key>LSUIElement</key> <true/> </dict> diff --git a/scripts/test.sh b/scripts/test.sh index ba7e8c5..04d1149 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -84,6 +84,69 @@ run_test_output "-h shows --sound option" 0 "--sound" -h # Test: -h shows --icon option run_test_output "-h shows --icon option" 0 "--icon" -h +# CLI wrapper tests +WRAPPER="$PROJECT_DIR/bin/macnotifier" + +run_wrapper_test() { + local name="$1" + local expected_exit="$2" + local expected_pattern="$3" + shift 3 + + set +e + local output + output=$("$WRAPPER" "$@" 2>&1) + local actual_exit=$? + set -e + + if [ "$actual_exit" -ne "$expected_exit" ]; then + echo "FAIL: $name (expected exit $expected_exit, got $actual_exit)" + failed=$((failed + 1)) + return + fi + + if echo "$output" | grep -qi -- "$expected_pattern"; then + echo "PASS: $name" + passed=$((passed + 1)) + else + echo "FAIL: $name (output missing '$expected_pattern')" + failed=$((failed + 1)) + fi +} + +if [ ! -x "$WRAPPER" ]; then + echo "FAIL: CLI wrapper not found or not executable at $WRAPPER" + failed=$((failed + 1)) +else + echo "PASS: CLI wrapper exists and is executable" + passed=$((passed + 1)) + + # Test: wrapper -h shows usage with "Usage" in output + run_wrapper_test "wrapper -h shows help" 0 "Usage" -h + + # Test: wrapper without -m shows error + run_wrapper_test "wrapper missing -m exits 1" 1 "required" + + # Test: wrapper -m without value shows error + run_wrapper_test "wrapper -m without value exits 1" 1 "requires" -m + + # Test: wrapper works via symlink + SYMLINK_DIR="$(mktemp -d)" + ln -s "$WRAPPER" "$SYMLINK_DIR/macnotifier" + set +e + symlink_output=$("$SYMLINK_DIR/macnotifier" -h 2>&1) + symlink_exit=$? + set -e + rm -rf "$SYMLINK_DIR" + if [ "$symlink_exit" -eq 0 ] && echo "$symlink_output" | grep -qi "Usage"; then + echo "PASS: wrapper works via symlink" + passed=$((passed + 1)) + else + echo "FAIL: wrapper via symlink (exit $symlink_exit)" + failed=$((failed + 1)) + fi +fi + echo "" echo "Results: $passed passed, $failed failed"