Multi-layered cabin availability monitoring system with native Swift apps for macOS & iOS, CLI, and modular architecture!
- 🚀 Native Swift Performance: High-performance status bar integration
- 🎯 Weekend-First UI: Prioritizes full Friday-Sunday weekends at the top
- 🆕 NEW Status Tracking: Highlights newly available weekends/Saturdays
- 🔗 One-Click Booking: Clickable cabin names open booking pages
- 📊 Smart Icons: Visual status indicators (🏔🆕 = new weekends!)
- 🔔 Smart Notifications: User-friendly permission flow
- ⏰ Automatic Hourly Checks: Background refresh
- ⚙️ Settings Window: Manage cabins with images (⌘,)
- 📱 Native SwiftUI App: Modern iOS experience
- 🆕 NEW Weekend Detection: Highlights newly available weekends with badges
- 🏔 Cabin Images: Beautiful thumbnails from DNT website
- 🔔 Background Refresh: Automatic hourly checks (BGTaskScheduler)
- 🇳🇴 Norwegian Formatting: Dates in Norwegian (e.g., "5 des")
- ⚙️ Rich Settings: Notification preferences, check interval, clear history
- 📳 Haptic Feedback: Tactile feedback for interactions
- 🌓 Dark Mode Optimized: Beautiful in both light and dark modes
- 🎨 Beautiful CLI: Colorful terminal output for scheduled checks
- 📈 Change Detection: Intelligent history-based diffing
- 🏗️ Modern Architecture: UV Workspace + Swift Package Manager
This project combines Python UV Workspace (for core business logic and CLI) with native Swift (for macOS & iOS apps):
DNT-Watcher/
├── swift-toolbar/ # Native Swift menu bar app (macOS) ⭐
│ ├── Package.swift
│ ├── Sources/DNTWatcher/
│ └── build-app.sh
├── DNT-watcher/ # Native SwiftUI app (iOS) 📱
│ ├── DNT-watcher.xcodeproj
│ └── DNT-watcher/
│ ├── CabinListView.swift
│ ├── CabinDetailView.swift
│ ├── SettingsView.swift
│ ├── BackgroundTaskManager.swift
│ └── Models/ (Cabin, AvailabilityHistory)
├── packages/ # Python workspace packages
│ ├── core/ # Business logic (API, analysis, config)
│ ├── notification/ # Cross-platform notification layer (CLI only)
│ ├── cli/ # Terminal application
│ └── toolbar-app/ # Legacy Python toolbar (no notifications)
├── dnt_hytter.yaml # Shared cabin configuration
├── history/ # Shared availability data (macOS)
└── tests/ # Test suite
Location: swift-toolbar/
The native macOS menu bar application with weekend-priority UI:
- AppDelegate.swift: Menu bar integration & UI
- DNTAPIClient.swift: API client
- AvailabilityAnalyzer.swift: Weekend detection & diffing
- ConfigLoader.swift: YAML configuration parsing
- HistoryManager.swift: Change tracking
- NotificationManager.swift: Native macOS notifications
Performance:
- 🚀 20x faster startup (<100ms vs 1-2s)
- 💾 2x lower memory (37MB vs 80MB)
- 📦 Self-contained (no Python runtime needed)
UI Features:
- 🆕 NEW FULL WEEKENDS section (top priority)
- 🆕 NEW SATURDAYS section
- 🏔 ALL WEEKENDS with date ranges
- 🔗 Clickable cabin names → open booking page
- 📊 Smart status icons (🏔🆕, 🏔✨, 🏔✓, 🏔)
Location: DNT-watcher/
The native iOS application built with SwiftUI and SwiftData:
- CabinListView.swift: Main list view with pull-to-refresh
- CabinDetailView.swift: Detailed availability view with weekend sections
- SettingsView.swift: In-app settings with cabin management
- BackgroundTaskManager.swift: BGTaskScheduler integration
- NotificationManager.swift: UNUserNotificationCenter integration
- Models/: SwiftData models (Cabin, AvailabilityHistory)
Key Features:
- 🆕 NEW weekend detection with green badges
- 🏔 Cabin images fetched from DNT website
- 📳 Haptic feedback for interactions
- 🇳🇴 Norwegian date formatting (e.g., "5 des")
- 🔔 Customizable notifications with settings toggle
- ⏰ Background refresh (1h, 2h, 4h, or 6h intervals)
- 🌓 Optimized for dark mode
- 📊 SwiftData persistence for cabins and history
Settings:
- Notification preferences (enable/disable)
- Check interval customization
- Clear history option
- Add/Edit/Delete cabins with images
- Enable/disable individual cabins
Core Package (dnt-core)
- API client for DNT booking system
- Date extraction & weekend detection
- Configuration management
- History persistence
CLI Application (dnt-cli)
- Beautiful colorful terminal output
- Scheduled execution via cron
- Entry point:
uv run dnt-watcher
Notification Package (dnt-notification)
- Cross-platform notification wrapper
- macOS native + fallback for other platforms
- macOS 13.0+ (for Swift menu bar app)
- iOS 17.0+ (for iPhone/iPad app)
- Swift 5.9+ (comes with Xcode Command Line Tools)
- Xcode 15.0+ (for iOS app development)
- Python 3.11+ (for CLI)
- UV package manager - Install:
curl -LsSf https://astral.sh/uv/install.sh | sh
# Clone the repository
git clone https://github.com/hoxmark/DNT-Watcher.git
cd DNT-Watcher
# Sync Python packages (for CLI)
uv sync
# Build Swift menu bar app (macOS)
cd swift-toolbar
./build-app.sh
cd ..
# Open iOS app in Xcode (iOS)
open DNT-watcher/DNT-watcher.xcodeproj
# Build and run on your iPhone/iPad or simulatorEdit dnt_hytter.yaml in the root directory:
dnt_hytter:
- navn: "Stallen"
url: "https://hyttebestilling.dnt.no/hytte/101297"
beskrivelse: "Østmarka – idyllisk ved Røyrivann"
- navn: "Skjennungsvolden"
url: "https://hyttebestilling.dnt.no/hytte/101233402"
beskrivelse: "Nordmarka – flott beliggenhet"
- navn: "Fuglemyrhytta"
url: "https://hyttebestilling.dnt.no/hytte/101209"
beskrivelse: "Nordmarka – moderne DNT-hytte"Launch the native macOS menu bar app:
open swift-toolbar/DNTWatcher.appFeatures:
- Appears as 🏔 icon in your menu bar
- Click to see availability status
- New weekends highlighted at top with 🆕 icon
- Click any cabin name to open booking page
- Automatic hourly checks (runs in background)
- Smart notifications for new weekends/dates (asks permission on first launch)
- Manual "Check Now" button (⌘R)
- "Open at Login" toggle (starts automatically when you log in)
- Settings window to manage cabins with images (⌘,)
- Initial check on launch
Menu Structure:
┌─────────────────────────────────────┐
│ 🆕 NEW FULL WEEKENDS! │ ← Bold header
│ 🎉 Stallen: Nov 15 - Nov 17 │ ← Click to book!
├─────────────────────────────────────┤
│ 🏔 ALL WEEKENDS (3) │
│ 🆕 Stallen: Nov15-17, Jan3-5 │ ← Click to book!
│ Skjennungsvolden: Dec20-22 │ ← Click to book!
├─────────────────────────────────────┤
│ ⏱ Updated: 7:57 AM │
│ 📊 3 weekends • 177 dates │
├─────────────────────────────────────┤
│ 🔄 Check Now ⌘R │
├─────────────────────────────────────┤
│ ✓ Open at Login │ ← Toggle
├─────────────────────────────────────┤
│ Quit DNT Watcher ⌘Q │
└─────────────────────────────────────┘
Status Icons:
- 🏔🆕 = NEW WEEKENDS AVAILABLE! (grab them fast!)
- 🏔✨ = NEW SATURDAYS available
- 🏔✓ = Has weekends (no new ones)
- 🏔⏳ = Checking availability...
- 🏔 = No weekends available
Launch the native iOS app from Xcode or install on your iPhone/iPad:
Features:
- Pull-to-refresh to check availability
- Tap cabin to see detailed weekend view
- Tap gear icon for settings
- Background checks every 1-6 hours (customizable)
- Notifications for new weekends/dates
- Haptic feedback on interactions
Main Views:
Cabin List:
- Shows all enabled cabins with images
- 🆕 NEW badge for cabins with new weekends
- Weekend count and total dates shown
- Pull down to refresh
Cabin Detail:
- 🆕 NEW FULL WEEKENDS section (top, green background)
- 🏔 All Weekends section with Fri-Sun dates
- 📅 All Available Dates in grid layout
- Button to open booking page in Safari
Settings:
- Add/Edit/Delete cabins
- Enable/disable individual cabins
- Toggle notifications on/off
- Customize check interval (1h, 2h, 4h, 6h)
- Clear history option
- Cabin images automatically fetched
Norwegian Formatting:
- Dates shown as "5 des", "20 nov", etc.
- Weekend labels: "Fre - Søn"
Run a single check:
uv run dnt-watcherThe CLI provides colorful output focused on weekend availability:
============================================================
🏔 DNT WATCHER - Cabin Availability Monitor 🏔
============================================================
Monitoring 3 cabin(s)
━━━ Stallen (ID: 101297) ━━━
📊 Total available dates: 64
✓ 2 FULL WEEKEND(S) AVAILABLE:
• 2026-03-14 (Saturday) - Full Fri-Sun weekend
• 2026-09-19 (Saturday) - Full Fri-Sun weekend
📅 Weekday breakdown:
Mon: 16 | Tue: 22 | Wed: 19 | Thu: 7 | Fri: 2 | Sat: 2 | Sun: 16
📆 Range: 2025-11-11 → 2026-10-29
★ NEW FULL WEEKEND(S) AVAILABLE! ★
• 2026-03-14 (Saturday)
============================================================
Option 1: Menu Bar App (Recommended)
# Launch once - stays running in background
open swift-toolbar/DNTWatcher.appThe app automatically checks every hour. You can also use "Check Now" (⌘R) to manually refresh anytime.
Option 2: Scheduled CLI Checks with Cron
# Add to crontab: Check every hour
0 * * * * cd /path/to/DNT-Watcher && uv run dnt-watcher
# Or check only on Saturday mornings
0 8 * * 6 cd /path/to/DNT-Watcher && uv run dnt-watcher- Core Package = Measurement engine (Python business logic)
- Swift Menu Bar App = Dashboard display (native UI, always visible)
- CLI App = Scheduled reporter (hourly terminal checks)
- Notification Layer = Alarm system (critical alerts)
Python Core handles all business logic:
- ✅ API calls
- ✅ Date analysis
- ✅ Weekend detection
- ✅ Configuration loading
- ✅ History management
Swift App provides native UI:
- ✅ Menu bar integration
- ✅ Native performance
- ✅ macOS-native notifications
- ✅ Weekend-priority display
- ✅ Clickable booking links
Run the Python test suite:
# Run all tests
uv run python -m unittest tests/test_core.py -v
# Or use pytest
uv run pytest tests/ -vTest coverage:
- ✅ Cabin ID extraction from URLs
- ✅ API response parsing
- ✅ Weekend detection algorithms
- ✅ Configuration loading
- ✅ Diff comparison logic
Manual Testing:
# Test CLI
uv run dnt-watcher
# Test Swift app
open swift-toolbar/DNTWatcher.app
# Test core package
uv run python -c "from dnt_core import load_cabins; print(load_cabins())"cd swift-toolbar
# Build release version
./build-app.sh
# Or build debug version
swift build
# Run from command line
.build/debug/DNTWatcherAppDelegate
├── ConfigLoader → Loads YAML via Yams
├── DNTAPIClient → Fetches availability
├── AvailabilityAnalyzer → Detects weekends
├── HistoryManager → Tracks changes
└── NotificationManager → Shows alertsEdit dnt_hytter.yaml and add a new entry:
dnt_hytter:
- navn: "New Cabin Name"
url: "https://hyttebestilling.dnt.no/hytte/CABIN_ID"
beskrivelse: "Description here"Both the Swift app and CLI will automatically pick up the new cabin.
Location: swift-toolbar/
Dependencies: Yams (YAML parsing)
Platform: macOS 13.0+
Build: Swift Package Manager
Key Features:
- NSStatusItem integration
- Native UNUserNotificationCenter
- Weekend-first UI with NEW tracking
- Clickable booking links
- Background threading for API calls
Package: dnt-core
Dependencies: requests, pyyaml
Platform: Cross-platform
Exports:
get_availability(cabin_id, from_date, to_date)extract_available_dates(availability)find_available_weekends(dates)load_cabins(config_file)extract_cabin_id(url)
Package: dnt-cli
Dependencies: dnt-core, dnt-notification, colorama
Entry Point: uv run dnt-watcher
Platform: Cross-platform
Endpoint:
GET https://hyttebestilling.dnt.no/api/booking/availability-calendar
Parameters:
cabinId: Cabin ID from booking URLfromDate: Start date (YYYY-MM-DD)toDate: End date (YYYY-MM-DD)
Response:
{
"data": {
"availabilityList": [
{
"date": "YYYY-MM-DDTHH:MM:SS.SSSZ",
"products": [
{"available": 0} // 0=unavailable, 1+=available
]
}
]
}
}Swift for the UI:
- ✅ Native macOS performance (20x faster startup)
- ✅ Proper app bundle with Info.plist
- ✅ Native notifications without hacks
- ✅ Lower memory footprint
- ✅ Self-contained distribution
Python for Business Logic:
- ✅ Rapid development
- ✅ Rich ecosystem (requests, pyyaml)
- ✅ Cross-platform CLI
- ✅ Easy testing
- ✅ Reusable core logic
Make sure you've built the app:
cd swift-toolbar
./build-app.shThe Swift app searches multiple locations for dnt_hytter.yaml:
- Current working directory
- Parent directories (up to 8 levels)
- Bundle resource path
Make sure you run the app from the project root or its parent directory.
First Launch: On first launch, DNT Watcher will request notification permissions using the macOS system prompt. Click "Allow" to receive alerts when:
- New full weekends become available
- New Saturday dates appear
- Any new dates are added
If you allow notifications, you'll receive a welcome notification confirming it works: "DNT Watcher Active 🏔"
Settings Window: You can check and manage notification permissions directly in the Settings window (⌘,):
- Green checkmark = Notifications enabled
- Orange bell = Notifications disabled (click "Open Settings" to enable)
- Blue bell = Not configured (click "Enable" to allow)
System Settings: You can also change notification settings in:
- System Settings → Notifications
- Find "DNTWatcher" in the list
- Toggle "Allow Notifications" on/off
If dependency resolution fails:
rm uv.lock
uv sync- Swift Toolbar Evaluation - Performance analysis & comparison
- Swift V2 Features - Weekend-priority UI details
- CLAUDE.md - Project overview for Claude Code
MIT License - feel free to use and modify!
- Built with Claude Code
- Swift Package Manager for dependency management
- UV Workspace pattern from Astral
- Uses the DNT Hyttebestilling API
- Inspired by the frustration of manually checking cabin availability 😅
Happy cabin hunting! 🏔️⛰️🎿