From 9f9073ee404aa024d4e64b5733ccbc691cf9f31f Mon Sep 17 00:00:00 2001 From: Sam Rolfe Date: Tue, 17 Mar 2026 09:57:17 +1100 Subject: [PATCH] Task with alerts to Ntfy --- home/sam/bin/task-due-notify.sh | 98 ++++++++++-- home/sam/config/niri/config.kdl | 9 ++ home/sam/config/waybar/config.jsonc | 101 ++++++++++-- home/sam/config/zsh/local.zsh | 4 + home/sam/home.nix | 235 ++++++++++++++++++++++++++-- 5 files changed, 408 insertions(+), 39 deletions(-) diff --git a/home/sam/bin/task-due-notify.sh b/home/sam/bin/task-due-notify.sh index ccfb20f..f5e4321 100755 --- a/home/sam/bin/task-due-notify.sh +++ b/home/sam/bin/task-due-notify.sh @@ -1,11 +1,91 @@ -#!/usr/bin/env bash +#!/nix/store/x12lw455sq6qy2wcya85d7rb88ybc3df-bash-interactive-5.3p9/bin/bash -# Query tasks due soon (adjust filters as needed, e.g., due.before:now+1h for 1-hour reminders) -DUE_TASKS=$(task due.before:now+1h status:pending export | jq -r '.[] | .description + " (due: " + .due + ")"') +# Simple Taskwarrior Notification Script +# Just checks: if time_until_due <= reminder_time, send notification -if [ -n "$DUE_TASKS" ]; then - while IFS= read -r task; do - notify-send "Task Due Soon" "$task" # Local Mako pop-up - curl -d "Task due soon: $task" https://ntfy.lab.audasmedia.com.au/tasks # Remote ntfy.sh (or swap to your Docker URL like http://YOUR_DOCKER_IP:PORT/task_topic) - done <<< "$DUE_TASKS" -fi +NTFY_URL="https://ntfy.lab.audasmedia.com.au/tasks" +NOW=$(date -u +%s) + +# Auto-add +notify to tasks with due dates or remind tags +task status:pending '( due.any: or tags.has:remindMins or tags.has:remindHours or tags.has:remindDays or tags.has:remindWeeks or tags.has:remindMonths )' -notify export 2>/dev/null | \ + /nix/store/qnaw7i777j52fpgbl5pgmzkq85znp083-jq-1.8.1-bin/bin/jq -r '.[].uuid' 2>/dev/null | \ + while read -r uuid; do + /nix/store/syqlfcifpih00fknzailx8xxn4cnv42d-taskwarrior-3.4.2/bin/task "$uuid" modify +notify 2>/dev/null + done + +# Process tasks with +notify +task status:pending +notify export 2>/dev/null | \ + /nix/store/qnaw7i777j52fpgbl5pgmzkq85znp083-jq-1.8.1-bin/bin/jq -c '.[] | select(.due) | {uuid: .uuid, description: .description, due: .due, tags: .tags}' 2>/dev/null | \ + while read -r task_json; do + + uuid=$(echo "$task_json" | /nix/store/qnaw7i777j52fpgbl5pgmzkq85znp083-jq-1.8.1-bin/bin/jq -r '.uuid') + desc=$(echo "$task_json" | /nix/store/qnaw7i777j52fpgbl5pgmzkq85znp083-jq-1.8.1-bin/bin/jq -r '.description') + due_date=$(echo "$task_json" | /nix/store/qnaw7i777j52fpgbl5pgmzkq85znp083-jq-1.8.1-bin/bin/jq -r '.due') + tags=$(echo "$task_json" | /nix/store/qnaw7i777j52fpgbl5pgmzkq85znp083-jq-1.8.1-bin/bin/jq -r '.tags[]? // empty' 2>/dev/null) + + # Convert due date (UTC) to epoch + formatted_date=$(echo "$due_date" | /nix/store/ryz8kcrm2bxpccllfqlb7qldsfnqp5c2-gnused-4.9/bin/sed 's/T/ /; s/Z$//; s/^\(....\)\(..\)\(..\)/\1-\2-\3/; s/ \(..\)\(..\)\(..\)$/ \1:\2:\3/') + due_epoch=$(/nix/store/i2vmgx46q9hd3z6rigaiman3wl3i2gc4-coreutils-9.9/bin/date -d "$formatted_date UTC" +%s 2>/dev/null || echo 0) + + if [ "$due_epoch" = "0" ]; then + continue + fi + + time_until_due=$((due_epoch - NOW)) + + # Check reminder tags + echo "$tags" | /nix/store/02vv0r262agf9j5n2y1gmbjvdf12zkl0-gnugrep-3.12/bin/grep -E '^remind(Mins|Hours|Days|Weeks|Months)' 2>/dev/null | \ + while read -r remind_tag; do + + # Parse: remindMins2, remindHours1, etc + if [[ "$remind_tag" =~ ^remind(Mins|Hours|Days|Weeks|Months)([0-9]+)$ ]]; then + unit="${BASH_REMATCH[1]}" + num="${BASH_REMATCH[2]}" + + # Convert to seconds + case "$unit" in + Mins) remind_secs=$((num * 60)); time_str="$num minutes" ;; + Hours) remind_secs=$((num * 3600)); time_str="$num hours" ;; + Days) remind_secs=$((num * 86400)); time_str="$num days" ;; + Weeks) remind_secs=$((num * 604800)); time_str="$num weeks" ;; + Months) remind_secs=$((num * 2592000)); time_str="$num months" ;; + esac + + # SIMPLE: If time until due <= reminder time AND still in future, send it + if [ $time_until_due -le $remind_secs ] && [ $time_until_due -gt 0 ]; then + /nix/store/xgdl4gzjzwj7ixs83f17sqppd08lfd03-libnotify-0.8.7/bin/notify-send -u normal "Reminder" "๐Ÿ”” $desc - Due in $time_str" + /nix/store/jqfr3p49g3lch84y45jfzw9fshlv8jyp-curl-8.17.0-bin/bin/curl -s -d "Reminder: $desc - Due in $time_str" "$NTFY_URL" >/dev/null 2>&1 & + /nix/store/syqlfcifpih00fknzailx8xxn4cnv42d-taskwarrior-3.4.2/bin/task "$uuid" modify -"$remind_tag" 2>/dev/null + fi + fi + done + + # Check if task is due (overdue) + if [ $time_until_due -le 0 ] && ! echo "$tags" | /nix/store/02vv0r262agf9j5n2y1gmbjvdf12zkl0-gnugrep-3.12/bin/grep -q "^notified$"; then + /nix/store/xgdl4gzjzwj7ixs83f17sqppd08lfd03-libnotify-0.8.7/bin/notify-send -u critical "Task Due" "โš ๏ธ $desc" + /nix/store/jqfr3p49g3lch84y45jfzw9fshlv8jyp-curl-8.17.0-bin/bin/curl -s -d "Task due: $desc" "$NTFY_URL" >/dev/null 2>&1 & + /nix/store/syqlfcifpih00fknzailx8xxn4cnv42d-taskwarrior-3.4.2/bin/task "$uuid" modify +notified 2>/dev/null + fi + done + +# Cleanup: Remove notification tags from completed/deleted tasks +task '( status:completed or status:deleted )' '( +notify or +notified or tags.has:remindMins or tags.has:remindHours or tags.has:remindDays or tags.has:remindWeeks or tags.has:remindMonths )' export 2>/dev/null | \ + /nix/store/qnaw7i777j52fpgbl5pgmzkq85znp083-jq-1.8.1-bin/bin/jq -r '.[].uuid' 2>/dev/null | \ + while read -r uuid; do + tags=$(/nix/store/syqlfcifpih00fknzailx8xxn4cnv42d-taskwarrior-3.4.2/bin/task "$uuid" export 2>/dev/null | /nix/store/qnaw7i777j52fpgbl5pgmzkq85znp083-jq-1.8.1-bin/bin/jq -r '.[0].tags[]? // empty' 2>/dev/null) + + remove_args="" + echo "$tags" | /nix/store/02vv0r262agf9j5n2y1gmbjvdf12zkl0-gnugrep-3.12/bin/grep -q "^notify$" && remove_args="$remove_args -notify" + echo "$tags" | /nix/store/02vv0r262agf9j5n2y1gmbjvdf12zkl0-gnugrep-3.12/bin/grep -q "^notified$" && remove_args="$remove_args -notified" + + remind_tags=$(echo "$tags" | /nix/store/02vv0r262agf9j5n2y1gmbjvdf12zkl0-gnugrep-3.12/bin/grep "^remind(Mins|Hours|Days|Weeks|Months)" 2>/dev/null || true) + if [ -n "$remind_tags" ]; then + while read -r tag; do + remove_args="$remove_args -$tag" + done <<< "$remind_tags" + fi + + if [ -n "$remove_args" ]; then + /nix/store/syqlfcifpih00fknzailx8xxn4cnv42d-taskwarrior-3.4.2/bin/task "$uuid" modify $remove_args 2>/dev/null + fi + done diff --git a/home/sam/config/niri/config.kdl b/home/sam/config/niri/config.kdl index aa05cff..bcca23d 100644 --- a/home/sam/config/niri/config.kdl +++ b/home/sam/config/niri/config.kdl @@ -76,6 +76,13 @@ input { // https://yalter.github.io/niri/Configuration:-Outputs // Remember to uncomment the node by removing "/-"! +// input { +// "*" allow +// } +// output { +// "*" allow +// } + environment { MOZ_DBUS_REMOTE "1" @@ -748,4 +755,6 @@ binds { // Powers off the monitors. To turn them back on, do any input like // moving the mouse or pressing any other key. Mod+Shift+P { power-off-monitors; } + Mod+N { spawn "swaync-client" "-t"; } + } diff --git a/home/sam/config/waybar/config.jsonc b/home/sam/config/waybar/config.jsonc index 5d46247..43eadf6 100644 --- a/home/sam/config/waybar/config.jsonc +++ b/home/sam/config/waybar/config.jsonc @@ -6,6 +6,8 @@ "modules-left": ["niri/workspaces"], "modules-center": ["niri/window"], "modules-right": [ + "custom/tasks", // <-- ADD THIS LINE + "custom/notification", // <-- ADD THIS "keyboard-state", "cpu", "memory", @@ -15,23 +17,60 @@ "clock" ], - "clock": { "format": "{:%a %d %b %H:%M}" }, +"tray": { + "icon-size": 20, + "spacing": 8 +}, - "cpu": { "format": "๏‹› {usage}%" }, - "memory": { "format": "๓ฐ› {percentage}%" }, - "network": { - "format-wifi": "๏‡ซ {signalStrength}%", - "format-ethernet": "๓ฐˆ€", - "format-disconnected": "๓ฐ–ช" - }, - - "pulseaudio": { - "format": "๏€จ {volume}%", - "format-muted": "๓ฐ–" +"clock": { + "format": "๓ฐฅ” {:%a %d %b %H:%M}", + "on-click": "niri msg action spawn -- gnome-calendar", + "tooltip": true, + "tooltip-format": "{:%Y-%m-%d %H:%M:%S}" +}, + +"cpu": { + "format": "๏‹› {usage}%", + "on-click": "niri msg action spawn -- kitty -e btop", + "tooltip": true +}, + +"memory": { + "format": "๓ฐ› {percentage}%", + "on-click": "niri msg action spawn -- kitty -e btop", + "tooltip": true +}, + +"network": { + "format-wifi": "๓ฐคจ {signalStrength}%", + "format-ethernet": "๓ฐˆ€ Wired", + "format-disconnected": "๓ฐ–ช Offline", + "on-click": "nm-connection-editor", + "on-click-right": "nmcli networking toggle", + "tooltip": true, + "tooltip-format": "{essid}\nLeft-click: Network settings\nRight-click: Toggle networking" +}, + + +"pulseaudio": { + "format": "{icon} {volume}%", + "format-muted": "๓ฐ– Muted", + "format-icons": { + "default": ["๓ฐ•ฟ", "๓ฐ–€", "๓ฐ•พ"], + "headphone": "๓ฐ‹‹", + "headset": "๓ฐ‹Ž" }, + "on-click": "pavucontrol", + "on-click-right": "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle", + "on-scroll-up": "wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.02+ -l 1.0", + "on-scroll-down": "wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.02-", + "tooltip": true, + "tooltip-format": "{icon} {volume}%\nLeft-click: Mixer\nRight-click: Toggle mute\nScroll: Adjust volume" +}, "keyboard-state": { + "device": "/dev/input/event*", "numlock": true, "capslock": true, "format": "{name} {icon}", @@ -39,5 +78,41 @@ "locked": "๏€ฃ", "unlocked": "๏‚œ" } - } + }, + +"custom/tasks": { + "format": "{icon} {text}", + "format-icons": ["๏€บ", "๏‰’"], + "interval": 30, + "exec": "task status:pending count", + "exec-if": "which task", + "on-click": "niri msg action spawn -- kitty -e taskwarrior-tui", + "on-click-right": "niri msg action spawn -- kitty -e task add", + "tooltip": true, + "tooltip-format": "{} pending tasks\nLeft-click: Open TUI\nRight-click: Add task", + "min-length": 3, + "max-length": 20 +}, +"custom/notification": { + "tooltip": false, + "format": "{icon}", + "format-icons": { + "notification": "๓ฐ‚š๏‘„", + "none": "๓ฐ‚š", + "dnd-notification": "๓ฐ‚›๏‘„", + "dnd-none": "๓ฐ‚›" + }, + "return-type": "json", + "exec-if": "which swaync-client", + "exec": "swaync-client -swb", + "on-click": "swaync-client -t -sw", + "on-click-right": "swaync-client -d -sw", + "escape": true +} + + + + + + } diff --git a/home/sam/config/zsh/local.zsh b/home/sam/config/zsh/local.zsh index 41eacfe..256bc31 100644 --- a/home/sam/config/zsh/local.zsh +++ b/home/sam/config/zsh/local.zsh @@ -13,6 +13,10 @@ export EDITOR="nvim" export VISUAL="nvim" +export TICKER_WATCH="^GSPC,^DJI,^IXIC,^AXJO,^AORD,CBA.AX,NAB.AX,WBC.AX,ANZ.AX,BHP.AX,RIO.AX,FMG.AX,NST.AX,^VIX,CL=F,NG=F,ZC=F,KE=F,^TNX,BDRY,BTC-USD,DX-Y.NYB,000001.SS,399001.SZ,^HSI,TIO=F,EURUSD=X,AUDUSD=X,GC=F,SI=F,AAPL,MSFT,GOOG,NVDA,AMZN,META,TSLA" +export TICKER_REFRESH=120 # Refresh rate in seconds + + # This must be at the top for Powerlevel10k Instant Prompt to work. if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" diff --git a/home/sam/home.nix b/home/sam/home.nix index e302be7..4c92cc0 100644 --- a/home/sam/home.nix +++ b/home/sam/home.nix @@ -29,6 +29,159 @@ home.stateVersion = "25.11"; + +services.swaync = { + enable = true; + settings = { + positionX = "right"; + positionY = "top"; + control-center-margin-top = 10; + control-center-margin-right = 10; + control-center-width = 380; + notification-window-width = 400; + notification-icon-size = 64; + notification-body-image-height = 100; + notification-body-image-width = 200; + timeout = 10; + timeout-low = 5; + timeout-critical = 0; + widgets = [ + "notifications" + "mpris" + "volume" + "backlight" + "buttons-grid" + ]; + "widget-config" = { + notifications = { + "hide-on-clear" = false; + "hide-on-action" = true; + }; + mpris = { + "image-size" = 96; + "image-radius" = 12; + }; + volume = { + label = "๓ฐ•พ"; + }; + backlight = { + label = "๓ฐƒž"; + }; + "buttons-grid" = { + actions = [ + { + label = "๓ฐฅ"; + command = "systemctl poweroff"; + } + { + label = "๓ฐœ‰"; + command = "systemctl reboot"; + } + { + label = "๓ฐ"; + command = "swaylock"; + } + ]; + }; + }; + }; +}; + + + +# services.mako = { +# enable = true; +# +# # Essential Wayland integration +# layer = "overlay"; # Required for Niri +# output = ""; # Shows on all outputs +# +# # Design (matches your Dunst preferences) +# font = "JetBrainsMono Nerd Font 10"; +# backgroundColor = "#1e1e2e"; +# borderColor = "#89b4fa"; +# borderSize = 2; +# borderRadius = 5; +# +# # Positioning +# anchor = "top-right"; +# margin = "30,30"; # X,Y from anchor +# width = 300; +# height = 200; +# +# # Behavior +# defaultTimeout = 10000; # ms +# sort = "-time"; # Newest on top-right +# +# settings = { +# "[urgency=high]" = { +# background-color = "#f38ba8"; +# default-timeout = 0; +# }; +# }; +# }; + + +# Deploy the script to ~/.local/bin (Nix handles creation/permissions) + home.file.".local/bin/task-due-notify" = { + source = ./bin/task-due-notify.sh; + executable = true; + }; + +systemd.user.timers.task-due-notify = { + Unit = { + Description = "Timer for Taskwarrior due notifications"; + }; + Timer = { + OnBootSec = "1min"; + OnCalendar = "*:*:0/1"; # Every 5 minutes + Persistent = true; + }; + Install = { + WantedBy = ["timers.target"]; + }; + }; + +# systemd.user.services.task-due-notify = { +# Unit = { +# Description = "Check for due Taskwarrior tasks and notify"; +# }; +# Service = { +# Type = "oneshot"; +# ExecStart = "${pkgs.bash}/bin/bash${config.home.homeDirectory}/.local/bin/task-due-notify"; +# }; +# }; + # Systemd user timer to run the service periodically +# systemd.user.timers.task-due-notify = { +# Unit = { +# Description = "Timer for Taskwarrior due notifications"; +# }; +# Timer = { +# OnBootSec = "1min"; +# OnUnitActiveSec = "1min"; +# AccuracySec = "30s"; +# Persistent = true; +# }; +# Install = { +# WantedBy = ["timers.target"]; +# }; +# }; + +systemd.user.services.task-due-notify = { + Unit = { + Description = "Check for due Taskwarrior tasks and notify"; + }; + Service = { + Type = "oneshot"; + ExecStart = "${pkgs.bash}/bin/bash ${config.home.homeDirectory}/.local/bin/task-due-notify"; + Environment = [ + "PATH=${pkgs.gnugrep}/bin:${pkgs.gnused}/bin:${pkgs.coreutils}/bin:${pkgs.taskwarrior3}/bin:${pkgs.jq}/bin:${pkgs.curl}/bin:${pkgs.libnotify}/bin:/usr/bin:/bin" + "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus" + ]; + }; + }; + + systemd.user.services.kdeconnect-indicator = { Unit = { Description = "KDE Connect indicator"; @@ -140,39 +293,87 @@ dconf.settings."org/gnome/desktop/interface" = { extraPackages = with pkgs; [ tree-sitter gcc gnumake]; }; + + programs.yazi = { + enable = true; + enableZshIntegration = true; + enableBashIntegration = true; + # This sets the command name for the wrapper. + # Many people use 'y' or 'yy' for quick access. + shellWrapperName = "y"; + }; + + home.sessionVariables = { + QT_QPA_PLATFORM = "wayland"; + QT_WAYLAND_DISABLE_WINDOWDECORATION = "1"; + }; + + gtk = { + enable = true; + theme = { + name = "Adwaita-dark"; + package = pkgs.gnome-themes-extra; + }; + iconTheme = { + name = "breeze-dark"; + package = pkgs.kdePackages.breeze-icons; + }; + }; + + qt = { + enable = true; + platformTheme.name = "gtk3"; + style.name = "adwaita-dark"; + }; + + + # --- # Packages (NOW list + a few safe essentials) # --- home.packages = with pkgs; [ + + # Input device support + libinput + seatd + + # Audio controls + pamixer + playerctl + + # Network + networkmanagerapplet + + # System Monitoring + swaynotificationcenter # For notification daemon + + + + libnotify + + kdePackages.dolphin + kdePackages.kio-extras + kdePackages.kdegraphics-thumbnailers + kdePackages.ffmpegthumbs + kdePackages.ark + + adwaita-qt + adwaita-qt6 + kdePackages.breeze-icons + nushell bun pnpm vite - python3Packages.beautifulsoup4 - python3Packages.scrapy - python3Packages.pillow - python3Packages.playwright - - - # Python AI/ML packages - python3Packages.torch - python3Packages.torchvision - python3Packages.opencv4 - # python3Packages.mediapipe # CLI tools # python3Packages.autogpt - vllm # agentops - apache-airflow - python313Packages.langfuse (callPackage ../../pkgs/cao-cli.nix { }) - (callPackage ../../pkgs/agentops.nix { }) - (callPackage ../../pkgs/mediapipe.nix { }) ollama @@ -221,7 +422,7 @@ dconf.settings."org/gnome/desktop/interface" = { zellij tealdeer navi - yazi + # yazi dua jq