A simplified Rust implementation of aw-notify that matches the Python version's behavior while providing Rust's performance and safety benefits.
This is a streamlined rewrite that consolidates functionality into a single file (~750 lines) similar to the Python version, while maintaining:
- β Identical behavior to the Python implementation
- β Type safety and memory safety of Rust
- β Zero runtime overhead with native compilation
- β Simple architecture that's easy to understand and maintain
- Time Summaries: Get daily and hourly summaries of your most-used categories
- Threshold Alerts: Receive notifications when you reach specific time thresholds
- Server Monitoring: Get notified if the ActivityWatch server goes down
- New Day Greetings: Start your day with a greeting showing the current date
- Cross-platform: Native desktop notifications on macOS, Linux, and Windows
- Smart Caching: 60-second TTL cache reduces server requests (matches Python's
@cache_ttl)
- Rust toolchain (1.70+)
- ActivityWatch server running (default: localhost:5600)
# Clone the repository
git clone https://github/0xbrayo/aw-notify-rs.git
cd aw-notify-rs
# Build the application
cargo build --release
# The binary will be available at target/release/aw-notify# Start with default settings
./target/release/aw-notify start
# Start in testing mode (connects to port 5666)
./target/release/aw-notify --testing start
# Start with custom port
./target/release/aw-notify --port 5678 start
# Enable verbose logging
./target/release/aw-notify --verbose start# Send summary of today's activity (top-level categories only)
./target/release/aw-notify checkin
# Send detailed summary with all category levels (parent and leaf categories)
./target/release/aw-notify checkin-detailed
# Send summary in testing mode
./target/release/aw-notify --testing checkinActivityWatch notification service
Usage: aw-notify [OPTIONS] [COMMAND]
Commands:
start Start the notification service
checkin Send a summary notification (top-level categories)
checkin-detailed Send a detailed summary with all category levels
help Print this message or the help of the given subcommand(s)
Options:
-v, --verbose Verbose logging
--testing Testing mode (port 5666)
--port <PORT> Port to connect to ActivityWatch server
-h, --help Print help
-V, --version Print version
aw-notify-rs supports three different category aggregation modes for analyzing your time:
-
None (
CategoryAggregation::None)- Returns full category hierarchy as-is
- Example:
"Work > Programming > Rust"stays as"Work > Programming > Rust" - Use case: When you need detailed, granular category information
-
TopLevelOnly (
CategoryAggregation::TopLevelOnly)- Aggregates all subcategories into their top-level parent
- Example:
"Work > Programming > Rust"becomes just"Work" - Use case: High-level overview of time spent across major categories
- Command:
./target/release/aw-notify checkin
-
AllLevels (
CategoryAggregation::AllLevels)- Aggregates by all category levels (both parent and leaf categories)
- Example:
"Work > Programming > Rust"creates entries for:"Work"(total time including all subcategories)"Work > Programming"(total time including all its subcategories)"Work > Programming > Rust"(leaf category time)
- Use case: Comprehensive analysis showing both overview and details
- Command:
./target/release/aw-notify checkin-detailed
The aggregation mode is controlled through the CategoryAggregation enum:
// Get detailed hierarchy
let cat_time = get_time(None, CategoryAggregation::None)?;
// Get top-level summary only
let cat_time = get_time(None, CategoryAggregation::TopLevelOnly)?;
// Get all levels (parent + leaf categories)
let cat_time = get_time(None, CategoryAggregation::AllLevels)?;Given these ActivityWatch categories with times:
"Work > Programming > Rust": 30 minutes"Work > Programming > Python": 20 minutes"Work > Meetings": 15 minutes"Personal > Reading": 10 minutes
None returns:
Work > Programming > Rust: 30m
Work > Programming > Python: 20m
Work > Meetings: 15m
Personal > Reading: 10m
TopLevelOnly returns:
Work: 65m (30 + 20 + 15)
Personal: 10m
AllLevels returns:
Work: 65m (total)
Work > Programming: 50m (30 + 20)
Work > Programming > Rust: 30m
Work > Programming > Python: 20m
Work > Meetings: 15m
Personal: 10m
Personal > Reading: 10m
This implementation uses a simplified architecture that mirrors the Python version:
- One main.rs file (~750 lines) containing all functionality
- Global state using
once_cell::Lazy(matches Python's globals) - Simple daemon threads for background tasks (matches Python's threading)
- CategoryAlert: Tracks time thresholds (exact match to Python class)
- Caching: TTL cache with 60-second expiration (matches Python's
@cache_ttl) - Notifications: macOS terminal-notifier β notify-rust fallback (matches Python)
- Query System: Canonical events queries (identical to Python)
- Threshold monitoring: Checks category time limits every 10 seconds
- Hourly checkins: Sends summaries at the top of each hour (if active)
- New day notifications: Greets user when they first become active each day
- Server monitoring: Alerts when ActivityWatch server goes up/down
aw-notify-rs supports flexible configuration through a TOML configuration file. By default, it looks for a configuration file at ~/.config/aw-notify/config.toml.
The default configuration file location varies by operating system:
- Linux:
~/.config/aw-notify/config.toml - macOS:
~/Library/Application Support/aw-notify/config.toml - Windows:
%APPDATA%\aw-notify\config.toml
When you first run aw-notify-rs, it will automatically create a default configuration file if one doesn't exist. The configuration file will be created with sensible defaults that match the original hardcoded behavior.
# First run will create the default config automatically
./target/release/aw-notify startThe configuration file supports the following options:
hourly_checkins: Enable/disable hourly activity summaries (default: true)new_day_greetings: Enable/disable new day greeting notifications (default: true)server_monitoring: Enable/disable ActivityWatch server monitoring alerts (default: true)
Configure custom category alerts using the [[alerts]] sections:
[[alerts]]
category = "Programming" # Category name (must match ActivityWatch)
label = "π» Programming" # Display label with optional emoji
thresholds_minutes = [30, 60, 120, 180, 240] # Alert thresholds in minutes
positive = true # true = "Goal reached!", false = "Time spent" warningFor users who want to minimize distractions and track specific work activities:
hourly_checkins = false # Disable hourly interruptions
new_day_greetings = true
server_monitoring = true
# Track overall programming time
[[alerts]]
category = "Work > Programming"
label = "π» Programming"
thresholds_minutes = [60, 120, 180, 240]
positive = true # Celebrate coding achievements
# Track specific languages/projects
[[alerts]]
category = "Work > Programming > Rust"
label = "π¦ Rust Development"
thresholds_minutes = [30, 60, 90, 120]
positive = true
[[alerts]]
category = "Work > Programming > Python"
label = "π Python Development"
thresholds_minutes = [30, 60, 90, 120]
positive = true
# Limit meeting time
[[alerts]]
category = "Work > Meetings"
label = "π
Meetings"
thresholds_minutes = [30, 60, 120]
positive = false
# Limit distractions
[[alerts]]
category = "Social Media"
label = "π± Social Media"
thresholds_minutes = [15, 30] # Early warnings for social media
positive = false
[[alerts]]
category = "YouTube"
label = "πΊ YouTube"
thresholds_minutes = [20, 45] # Limit video consumption
positive = falseFor users who prefer simpler, high-level tracking:
hourly_checkins = false # Disable hourly interruptions
new_day_greetings = true
server_monitoring = true
[[alerts]]
category = "Programming"
label = "π» Programming"
thresholds_minutes = [30, 60, 120, 180, 240]
positive = true # Celebrate coding achievements
[[alerts]]
category = "Social Media"
label = "π± Social Media"
thresholds_minutes = [15, 30] # Early warnings for social media
positive = false
[[alerts]]
category = "YouTube"
label = "πΊ YouTube"
thresholds_minutes = [20, 45] # Limit video consumption
positive = falseFor users who want gentle reminders without being too restrictive:
hourly_checkins = true
new_day_greetings = true
server_monitoring = true
[[alerts]]
category = "Work"
label = "πΌ Work"
thresholds_minutes = [60, 120, 180, 240]
positive = true
[[alerts]]
category = "Reading"
label = "π Reading"
thresholds_minutes = [30, 60, 90]
positive = true
[[alerts]]
category = "All"
label = "Total Activity"
thresholds_minutes = [480] # 8-hour daily reminder only
positive = falseFor users who only want essential notifications:
hourly_checkins = false
new_day_greetings = false
server_monitoring = true
[[alerts]]
category = "All"
label = "Daily Activity"
thresholds_minutes = [360, 480, 600] # 6h, 8h, 10h warnings
positive = falseIf no configuration file is found, the application uses these default alerts:
- All activities: 1h, 2h, 4h, 6h, 8h notifications
- Twitter: 15min, 30min, 1h warnings
- YouTube: 15min, 30min, 1h warnings
- Work: 15min, 30min, 1h, 2h, 4h achievements (shown as "Goal reached!")
- README.md - Main documentation (this file)
- config.example.toml - Example configuration file
- Threshold alerts: "Time spent" or "Goal reached!" when limits hit
- Hourly summaries: Top categories every hour (when active)
- Daily summaries: "Time today" and "Time yesterday" reports
- New day greetings: Welcome message with current date
- Server status: Alerts when ActivityWatch server connectivity changes
- Category Names: Must match exactly what ActivityWatch reports. Check your ActivityWatch dashboard to see available categories.
- Positive vs. Negative Alerts: Use
positive = truefor activities you want to encourage (shows "Goal reached!"), andpositive = falsefor activities you want to limit (shows "Time spent"). - Threshold Strategy: Start with longer thresholds and adjust based on your habits. Too many notifications can become counterproductive.
- Emoji in Labels: Use emoji in labels to make notifications more visually distinctive and easier to identify at a glance.
- Testing Configuration: Use
--output-onlyflag to test your configuration without desktop notifications:./target/release/aw-notify --output-only start - Editing Configuration: After the initial run, you can edit the generated configuration file to customize your alerts and preferences.
- 100% behavioral compatibility with Python version
- Identical queries and time calculations
- Same notification logic and message formatting
- Matching cache behavior and error handling
# Development build
cargo build
# Release build (optimized)
cargo build --release
# Check for errors
cargo check
# Run with logging
RUST_LOG=debug cargo run -- --verbose checkinIf the service can't connect to ActivityWatch:
- Ensure ActivityWatch server is running
- Check the correct port (default: 5600, testing: 5666)
- Verify server accessibility:
curl http://localhost:5600/api/0/info
This project is licensed under the Mozilla Public License 2.0 (MPL-2.0), the same as the ActivityWatch project.
This is a simplified rewrite of the original aw-notify Python implementation by Erik BjΓ€reholt and the ActivityWatch team, designed to match its behavior exactly while providing Rust's performance and safety benefits.