commit - 2da793de0cd4883ac80a6ddb70d38e5392c2bda3
commit + 31da3d0eb44fdab82e620d1cbdd35f31e8f1ee7f
blob - 7cc2a636f4b5d7ffb0f386885857845f94ef092a
blob + 90746acf5ebc4767db1eb0802376063676ab79d4
--- hosts/bbox/overlays/config/magdalena/config.json.nix
+++ hosts/bbox/overlays/config/magdalena/config.json.nix
".qwen",
"saturn"
],
- "fzf_opts": [
- "--highlight-line",
- "--ansi",
- "--layout=reverse",
- "--border=rounded",
- "--color=bg+:#2a1f2e,bg:-1,spinner:#c084b8,hl:#e08060",
- "--color=fg:#c8b8bf,header:#c4607a,info:#7a6e7a,pointer:#c084b8",
- "--color=marker:#e06fad,fg+:#f0dfe5,prompt:#7a9fd4,hl+:#f0a070",
- "--color=border:#3d2a42"
- ],
"favorites": [
"${homeDir}/src",
"${homeDir}/src/nix",
blob - 15a61f61e7aab80997730b2704c127fe08cd7620
blob + 8b2cb2bdfe4df1507f10c19ef239df7fed214a43
--- hosts/impish/default.nix
+++ hosts/impish/default.nix
skopeo
podman
sccache
- podlet
buildah
meson
- butane
- redo-sh
knit
go-task
goreleaser
xorriso
jsonnet-bundler
minisign
+ zf
+ gitleaks
];
lsp = [
gopls
nixd
zls
- clojure-lsp
ols
- npins
jsonnet-language-server
];
golangci-lint
];
- clojure = [
- babashka
- clojure
- clj-kondo
- cljstyle
- leiningen
- ];
-
lisp = [
haunt
gnu-shepherd
- gerbil
chez
ecl
janet
[fnlfmt]
];
- llm = [
- rtk
- snip
- gitingest
- pi-coding-agent
- ollama
- ];
-
gui = [
picard
xnviewmp
emscripten
astroterm
odin
+ npins
+ snip
+ pi-coding-agent
];
}));
};
blob - 07ee7dd6e21834358ba81debb480c57da009916f
blob + 2bca5dbc27fdb99394b361fece0f0ee2b124c29b
Binary files hosts/void/overlays/config/magdalena/config.json.nix and hosts/void/overlays/config/magdalena/config.json.nix differ
blob - 50a5ff03186b05831c1fcde13ea4b77f3a76c903
blob + 09e3303f75f18379849625ab4451acad2d24d43a
Binary files hosts/work/overlays/config/magdalena/config.json.nix and hosts/work/overlays/config/magdalena/config.json.nix differ
blob - 7e34237f3d17734f4f705bfe127f10277491cc9b (mode 644)
blob + 1798d1212ece7f107439e6fbc2ff249861524b15 (mode 755)
--- modules/mixins/dotfiles/bin/khal_notifier
+++ modules/mixins/dotfiles/bin/khal_notifier
-#!/usr/bin/env bb
-;; vim: ft=clojure
+#!/usr/bin/env janet
-(ns khal-notifier
- (:require [clojure.edn :as edn]
- [clojure.string :as str]
- [babashka.process :refer [shell]]
- [babashka.fs :as fs]))
+(defn blank? [s]
+ (or (nil? s) (= "" s)))
-(defn all-day? [event]
- (and (str/blank? (:start-time event))
- (str/blank? (:end-time event))))
+(defn path-join [parts]
+ (string/join parts "/"))
-(defn state-file []
- (or (System/getenv "KHAL_NOTIFIER_STATE_FILE")
- (str (fs/path (or (System/getenv "XDG_CACHE_HOME")
- (fs/path (System/getProperty "user.home") ".cache"))
- "khal_notifier" "notified-all-day.edn"))))
+(defn dir-of [path]
+ (let [parts (string/split "/" path)]
+ (if (<= (length parts) 1)
+ "."
+ (path-join (slice parts 0 -2)))))
-(defn read-notified-all-day []
- (try
- (let [state (edn/read-string (slurp (state-file)))]
- (if (set? state) state #{}))
- (catch Exception _ #{})))
-
-(defn write-notified-all-day [events]
- (let [path (state-file)]
+(defn mkdir-p [path]
+ (when (and (not (blank? path)) (nil? (os/stat path)))
+ (let [parent (dir-of path)]
+ (when (and (not (= parent path)) (not (= parent ".")) (not (blank? parent)))
+ (mkdir-p parent)))
(try
- (fs/create-dirs (fs/parent path))
- (spit path (pr-str events))
- true
- (catch Exception e
- (println (format "[WARN] Could not save all-day notification state: %s"
- (ex-message e)))
- false))))
+ (do (os/mkdir path) true)
+ ([e] false))))
+(defn state-file []
+ (or (os/getenv "KHAL_NOTIFIER_STATE_FILE")
+ (path-join [(or (os/getenv "XDG_CACHE_HOME")
+ (path-join [(or (os/getenv "HOME") "/tmp") ".cache"]))
+ "khal_notifier"
+ "notified-all-day.txt"])))
+
+(defn read-notified []
+ (try
+ (do
+ (def table @{})
+ (each line (string/split "\n" (slurp (state-file)))
+ (when (not (blank? line))
+ (put table line true)))
+ table)
+ ([e] @{})))
+
+(defn write-notified [table]
+ (try
+ (do
+ (mkdir-p (dir-of (state-file)))
+ (spit (state-file) (string/join (keys table) "\n"))
+ true)
+ ([e]
+ (printf "WARN: Could not save all-day notification state: %s" e)
+ false)))
+
(defn event-key [event]
- [(:start-date event)
- (if (str/blank? (:uid event))
- [(:title event) (:organizer event)]
- (:uid event))])
+ (string/join
+ [(or (event :start-date) "")
+ (let [uid (or (event :uid) "")]
+ (if (blank? uid)
+ (string/join [(or (event :title) "") (or (event :organizer) "")] "::")
+ uid))]
+ "|"))
-(defn run-cmd [cmd args & {:keys [timeout] :or {timeout 30000}}]
+(defn run-cmd [cmd & args]
(try
- (if-not (fs/which cmd)
- {:status :error :reason :enoent}
- (let [result (apply shell {:out :string :err :string :timeout timeout} cmd args)]
- {:status :ok :out (:out result) :code (:exit result)}))
- (catch java.util.concurrent.TimeoutException _
- {:status :error :reason :timeout})
- (catch Exception e
- {:status :error :reason e})))
+ (do
+ (file/flush stdout)
+ (def proc (os/spawn [cmd ;args] :p {:out :pipe :err :out}))
+ (def out (or (ev/read (proc :out) :all) ""))
+ (def code (os/proc-wait proc))
+ (os/proc-close proc)
+ {:status :ok :out (string out) :code code})
+ ([e] {:status :error :reason e})))
+(defn cmd-ok? [result]
+ (and (= (result :status) :ok) (zero? (result :code))))
+
(defn sync-pimsync []
- (println "[INFO] Syncing calendars with pimsync")
- (let [result (run-cmd "pimsync" ["sync"] :timeout 60000)]
- (cond
- (and (= :ok (:status result)) (zero? (:code result)))
- (do (println "[INFO] pimsync sync complete") true)
+ (print "INFO: Syncing calendars with pimsync")
+ (def result (run-cmd "pimsync" "sync"))
+ (cond
+ (cmd-ok? result)
+ (do (print "INFO: pimsync sync complete") true)
- (= :ok (:status result))
- (do (println (format "[WARN] pimsync exited with code %d: %s"
- (:code result) (str/trim (or (:out result) ""))))
- false)
+ (= (result :status) :ok)
+ (do
+ (printf "WARN: pimsync exited with code %d: %s"
+ (result :code) (string/trim (or (result :out) "")))
+ false)
- (= :enoent (:reason result)) (do (println "[ERROR] pimsync not found") false)
- (= :timeout (:reason result)) (do (println "[ERROR] pimsync timed out") false)
- :else (do (println (format "[ERROR] pimsync failed: %s" (ex-message (:reason result))))
- false))))
+ (do
+ (printf "ERROR: pimsync failed: %s" (result :reason))
+ false)))
(defn parse-event-line [line]
- (let [parts (str/split line #"\|" 6)
- cnt (count parts)]
+ (let [parts (string/split "|" line)
+ cnt (length parts)
+ at (fn [i] (or (get parts i) ""))]
(case cnt
- 6 {:start-date (nth parts 0) :start-time (nth parts 1)
- :end-time (nth parts 2) :uid (nth parts 3)
- :title (nth parts 4) :organizer (nth parts 5)}
- 4 {:start-time (nth parts 0) :end-time (nth parts 1)
- :title (nth parts 2) :organizer (nth parts 3)}
- 3 {:start-time (nth parts 0) :end-time (nth parts 1)
- :title (nth parts 2) :organizer ""}
- 2 {:start-time (nth parts 0) :end-time ""
- :title (nth parts 1) :organizer ""}
- {:start-time "" :end-time "" :title (nth parts 0) :organizer ""})))
+ 6 {:start-date (at 0) :start-time (at 1) :end-time (at 2)
+ :uid (at 3) :title (at 4) :organizer (at 5)}
+ 4 {:start-date "" :start-time (at 0) :end-time (at 1)
+ :uid "" :title (at 2) :organizer (at 3)}
+ 3 {:start-date "" :start-time (at 0) :end-time (at 1)
+ :uid "" :title (at 2) :organizer ""}
+ 2 {:start-date "" :start-time (at 0) :end-time ""
+ :uid "" :title (at 1) :organizer ""}
+ {:start-date "" :start-time "" :end-time ""
+ :uid "" :title (or (get parts 0) "") :organizer ""})))
(defn get-upcoming-events [lookahead]
- (println (format "[INFO] Checking khal for events in the next %s" lookahead))
- (let [result (run-cmd "khal" ["list" "now" lookahead "--format"
- (str "{start-date}|{start-time}|{end-time}|"
- "{uid}|{title}|{organizer}")])]
- (cond
- (and (= :ok (:status result)) (zero? (:code result)))
- (let [events (->> (:out result)
- str/split-lines
- (map str/trim)
- (remove str/blank?)
- (filter #(str/includes? % "|"))
- (map parse-event-line)
- (remove #(str/blank? (:title %)))
- vec)]
- (println (format "[INFO] Found %d upcoming event(s)" (count events)))
- events)
+ (printf "INFO: Checking khal for events in the next %s" lookahead)
+ (def format-str "{start-date}|{start-time}|{end-time}|{uid}|{title}|{organizer}")
+ (def result (run-cmd "khal" "list" "now" lookahead "--format" format-str))
+ (cond
+ (cmd-ok? result)
+ (do
+ (def lines (string/split "\n" (or (result :out) "")))
+ (def events
+ (filter
+ (fn [e] (not (blank? (e :title))))
+ (map parse-event-line
+ (filter
+ (fn [l] (and (not (blank? (string/trim l))) (string/find "|" l)))
+ lines))))
+ (printf "INFO: Found %d upcoming event(s)" (length events))
+ events)
- (= :ok (:status result))
- (do (println (format "[WARN] khal exited with code %d: %s"
- (:code result) (str/trim (or (:out result) ""))))
- [])
+ (= (result :status) :ok)
+ (do
+ (printf "WARN: khal exited with code %d: %s"
+ (result :code) (string/trim (or (result :out) "")))
+ [])
- (= :enoent (:reason result)) (do (println "[ERROR] khal not found") [])
- (= :timeout (:reason result)) (do (println "[ERROR] khal timed out") [])
- :else (do (println "[ERROR] khal execution failed") []))))
+ (do (print "ERROR: khal execution failed") [])))
-(defn now-time []
- (-> (shell {:out :string} "date" "+%H:%M") :out str/trim))
+(defn now-hhmm []
+ (let [d (os/date nil true)]
+ (string/format "%02d:%02d" (d :hours) (d :minutes))))
(defn event-started? [start end now]
(cond
- (str/blank? start) false
- (str/blank? end) (<= (compare start now) 0)
+ (blank? start) false
+ (blank? end) (<= (compare start now) 0)
(<= (compare start end) 0) (<= (compare start now) 0)
- :else (or (<= (compare start now) 0) (<= (compare now end) 0))))
+ (or (<= (compare start now) 0) (<= (compare now end) 0))))
-(defn filter-events [events]
- (let [now (now-time)]
- (remove #(event-started? (:start-time %) (:end-time %) now) events)))
+(defn all-day? [event]
+ (and (blank? (event :start-time)) (blank? (event :end-time))))
(defn format-event [event]
- (let [time-str (cond
+ (let [start (or (event :start-time) "")
+ end (or (event :end-time) "")
+ org (or (event :organizer) "")
+ time-str (cond
(all-day? event) "All day"
- (str/blank? (:end-time event)) (:start-time event)
- :else (str (:start-time event) " - " (:end-time event)))]
- (if (str/blank? (:organizer event))
+ (blank? end) start
+ (string start " - " end))]
+ (if (blank? org)
time-str
- (str time-str "\nOrganizer: " (:organizer event)))))
+ (string time-str "\nOrganizer: " org))))
(defn send-notification [summary body]
- (println (format "[INFO] Notifying: %s — %s" summary body))
- (let [result (run-cmd "notify-send"
- ["--app-name" "khal_notifier"
- "--urgency" "critical"
- summary body])]
- (cond
- (and (= :ok (:status result)) (zero? (:code result))) true
- (= :ok (:status result))
- (do (println (format "[ERROR] notify-send failed (code %d): %s"
- (:code result) (str/trim (or (:out result) ""))))
- false)
- (= :enoent (:reason result)) (do (println "[ERROR] notify-send not found") false)
- :else (do (println "[ERROR] notify-send failed") false))))
+ (printf "INFO: Notifying: %s - %s" summary body)
+ (def result (run-cmd "notify-send" "--app-name" "khal_notifier"
+ "--urgency" "critical" summary body))
+ (if (cmd-ok? result)
+ true
+ (do
+ (if (= (result :status) :ok)
+ (printf "ERROR: notify-send failed (code %d): %s"
+ (result :code) (string/trim (or (result :out) "")))
+ (print "ERROR: notify-send failed"))
+ false)))
(defn notify-events [events]
- (loop [remaining events
- notified (read-notified-all-day)]
- (when-let [event (first remaining)]
- (let [key (event-key event)]
- (if (and (all-day? event) (contains? notified key))
- (do
- (println (format "[INFO] Already notified: %s — All day" (:title event)))
- (recur (rest remaining) notified))
- (let [sent? (send-notification (:title event) (format-event event))
- newly-notified (if (and sent? (all-day? event))
- (conj notified key)
- notified)]
- (when (not= notified newly-notified)
- (write-notified-all-day newly-notified))
- (recur (rest remaining) newly-notified)))))))
+ (var notified (read-notified))
+ (var dirty false)
+ (each event events
+ (let [key (event-key event)
+ seen (not (nil? (get notified key)))]
+ (if (and (all-day? event) seen)
+ (printf "INFO: Already notified: %s - All day" (event :title))
+ (when (send-notification (event :title) (format-event event))
+ (when (all-day? event)
+ (put notified key true)
+ (set dirty true))))))
+ (when dirty
+ (write-notified notified)))
(defn run-once [lookahead skip-sync]
- (when-not skip-sync
- (when-not (sync-pimsync)
- (println "[WARN] Sync failed — continuing with local calendar data")))
- (let [events (get-upcoming-events lookahead)
- filtered (filter-events events)]
+ (unless skip-sync
+ (unless (sync-pimsync)
+ (print "WARN: Sync failed - continuing with local calendar data")))
+ (let [filtered (filter
+ (fn [e] (not (event-started? (e :start-time) (e :end-time) (now-hhmm))))
+ (get-upcoming-events lookahead))]
(if (empty? filtered)
- (println "[INFO] No upcoming events")
+ (print "INFO: No upcoming events")
(notify-events filtered))))
(defn parse-opts [args]
- (loop [remaining args
- opts {:lookahead "60m" :skip-sync false :help false}]
- (if (empty? remaining)
- opts
- (let [arg (first remaining)]
- (cond
- (= arg "--help") (recur (rest remaining) (assoc opts :help true))
- (= arg "--skip-sync") (recur (rest remaining) (assoc opts :skip-sync true))
- (= arg "--lookahead") (recur (drop 2 remaining) (assoc opts :lookahead (second remaining)))
- :else (recur (rest remaining) opts))))))
+ (var lookahead "60m")
+ (var skip-sync false)
+ (var help false)
+ (var i 0)
+ (while (< i (length args))
+ (let [arg (get args i)]
+ (cond
+ (= arg "--help") (set help true)
+ (= arg "--skip-sync") (set skip-sync true)
+ (= arg "--lookahead")
+ (do
+ (def val (get args (+ i 1)))
+ (if (nil? val)
+ (do (print "ERROR: --lookahead needs a value") (os/exit 2))
+ (set lookahead val))
+ (++ i))))
+ (++ i))
+ {:lookahead lookahead :skip-sync skip-sync :help help})
(defn print-usage []
- (println "Usage: khal_notifier [options]")
- (println)
- (println "Options:")
- (println " --lookahead TIME How far ahead to check (default: 60m)")
- (println " --skip-sync Skip pimsync sync")
- (println " --help Show this help"))
+ (print "Usage: khal_notifier [options]\n\nOptions:\n --lookahead TIME How far ahead to check (default: 60m)\n --skip-sync Skip pimsync sync\n --help Show this help"))
-(defn -main [& args]
- (let [{:keys [lookahead skip-sync help]} (parse-opts args)]
+(defn main [& raw-args]
+ (let [{:lookahead lookahead :skip-sync skip-sync :help help}
+ (parse-opts (slice raw-args 1))]
(if help
- (do (print-usage) (System/exit 0))
+ (do (print-usage) (os/exit 0))
(run-once lookahead skip-sync))))
-
-(apply -main *command-line-args*)
blob - f7e89e6ed912be88257ad76d868a013c8adad5f3
blob + 42f6412011bd44cbec1df9bd99ac1c525abbbf04
--- modules/mixins/dotfiles/bin/plsmenu
+++ modules/mixins/dotfiles/bin/plsmenu
fi
if eval "$("$HOME/bin/amen")" && zsh -c "mpd_add_to_playlist '$playlist'"; then
- notify-send -u normal "mpd" "saved a track to $playlist"
+ notify-send -u normal "mpd" "saved to $playlist"
exit 0
else
- notify-send -u critical "mpd" "could not save a track"
+ notify-send -u critical "mpd" "could not save to $playlist"
exit 1
fi
blob - c1c7580dd8ed8400cef4b0c20b0c254efd88a086
blob + 21edcbdf3e421e6a2c8e818a3b971b6366373d31
--- modules/mixins/dotfiles/config/magdalena/config.json.nix
+++ modules/mixins/dotfiles/config/magdalena/config.json.nix
".nix-data",
"saturn"
],
- "fzf_opts": [
- "--highlight-line",
- "--ansi",
- "--layout=reverse",
- "--border=rounded",
- "--color=bg+:#222222,bg:#101010,spinner:#8a8a8a,hl:#d2d2d2",
- "--color=fg:#999999,header:#a2a2a2,info:#5e5e5e,pointer:#999999",
- "--color=marker:#999999,fg+:#999999,prompt:#999999,hl+:#999999",
- "--color=border:#353535"
- ],
"fav_dirs": [
"${homeDir}/src",
"${homeDir}/src/misc",