about summary refs log tree commit diff stats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--modules/by-name/mp/mpdpopm/module.nix3
-rw-r--r--modules/home.legacy/conf/beets/plugins/inline/default.nix2
-rw-r--r--pkgs/by-name/mp/mpdpopm/Cargo.lock1221
-rw-r--r--pkgs/by-name/mp/mpdpopm/Cargo.toml1
-rw-r--r--pkgs/by-name/mp/mpdpopm/config.lsp1
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm/cli.rs233
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm/main.rs (renamed from pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm.rs)421
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/config.rs127
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/dj/algorithms.rs206
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/dj/mod.rs2
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/lib.rs12
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/messanges/mod.rs40
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/playcounts.rs53
13 files changed, 1884 insertions, 438 deletions
diff --git a/modules/by-name/mp/mpdpopm/module.nix b/modules/by-name/mp/mpdpopm/module.nix
index 3524554c..82943fe8 100644
--- a/modules/by-name/mp/mpdpopm/module.nix
+++ b/modules/by-name/mp/mpdpopm/module.nix
@@ -42,6 +42,9 @@ in {
         path = config.home-manager.users.soispha.home.sessionVariables.MPD_HOST;
       };
 
+      # Automatically start in DJ mode.
+      mode = "Dj";
+
       local_music_dir = config.soispha.services.mpd.directories.music;
     };
 
diff --git a/modules/home.legacy/conf/beets/plugins/inline/default.nix b/modules/home.legacy/conf/beets/plugins/inline/default.nix
index bf476c9f..8dccdc20 100644
--- a/modules/home.legacy/conf/beets/plugins/inline/default.nix
+++ b/modules/home.legacy/conf/beets/plugins/inline/default.nix
@@ -11,6 +11,6 @@
   programs.beets.settings.item_fields = {
     primary_artist =
       # python
-      ''albumartists[0]'';
+      ''dict(enumerate(albumartists)).get(0, artist)'';
   };
 }
diff --git a/pkgs/by-name/mp/mpdpopm/Cargo.lock b/pkgs/by-name/mp/mpdpopm/Cargo.lock
index a3cfaf4f..43c2b034 100644
--- a/pkgs/by-name/mp/mpdpopm/Cargo.lock
+++ b/pkgs/by-name/mp/mpdpopm/Cargo.lock
@@ -12,6 +12,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
+[[package]]
 name = "android_system_properties"
 version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -93,7 +99,16 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "atomic"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340"
+dependencies = [
+ "bytemuck",
 ]
 
 [[package]]
@@ -103,22 +118,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
 
 [[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "bit-set"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
+dependencies = [
+ "bit-vec 0.6.3",
+]
+
+[[package]]
 name = "bit-set"
 version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
 dependencies = [
- "bit-vec",
+ "bit-vec 0.8.0",
 ]
 
 [[package]]
 name = "bit-vec"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
+
+[[package]]
+name = "bit-vec"
 version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
 
 [[package]]
 name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
 version = "2.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
@@ -145,12 +187,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
 
 [[package]]
+name = "bytemuck"
+version = "1.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
+
+[[package]]
 name = "bytes"
 version = "1.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
 
 [[package]]
+name = "castaway"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
+dependencies = [
+ "rustversion",
+]
+
+[[package]]
 name = "cc"
 version = "1.2.56"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -167,6 +224,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
 
 [[package]]
+name = "cfg_aliases"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+
+[[package]]
 name = "chrono"
 version = "0.4.43"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -210,7 +273,7 @@ dependencies = [
  "heck",
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.115",
 ]
 
 [[package]]
@@ -226,6 +289,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
 
 [[package]]
+name = "compact_str"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a"
+dependencies = [
+ "castaway",
+ "cfg-if",
+ "itoa",
+ "rustversion",
+ "ryu",
+ "static_assertions",
+]
+
+[[package]]
+name = "convert_case"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
 name = "core-foundation-sys"
 version = "0.8.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -241,6 +327,33 @@ dependencies = [
 ]
 
 [[package]]
+name = "crossterm"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
+dependencies = [
+ "bitflags 2.11.0",
+ "crossterm_winapi",
+ "derive_more",
+ "document-features",
+ "mio",
+ "parking_lot",
+ "rustix",
+ "signal-hook",
+ "signal-hook-mio",
+ "winapi",
+]
+
+[[package]]
+name = "crossterm_winapi"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
 name = "crypto-common"
 version = "0.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -251,6 +364,87 @@ dependencies = [
 ]
 
 [[package]]
+name = "csscolorparser"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf"
+dependencies = [
+ "lab",
+ "phf",
+]
+
+[[package]]
+name = "darling"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
+dependencies = [
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "deltae"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4"
+
+[[package]]
+name = "deranged"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "derive_more"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
+dependencies = [
+ "derive_more-impl",
+]
+
+[[package]]
+name = "derive_more-impl"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
+dependencies = [
+ "convert_case",
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "syn 2.0.115",
+]
+
+[[package]]
 name = "digest"
 version = "0.10.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -261,6 +455,15 @@ dependencies = [
 ]
 
 [[package]]
+name = "document-features"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
+dependencies = [
+ "litrs",
+]
+
+[[package]]
 name = "either"
 version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -292,18 +495,78 @@ dependencies = [
 ]
 
 [[package]]
+name = "euclid"
+version = "0.22.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df61bf483e837f88d5c2291dcf55c67be7e676b3a51acc48db3a7b163b91ed63"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "fancy-regex"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2"
+dependencies = [
+ "bit-set 0.5.3",
+ "regex",
+]
+
+[[package]]
+name = "filedescriptor"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d"
+dependencies = [
+ "libc",
+ "thiserror 1.0.69",
+ "winapi",
+]
+
+[[package]]
 name = "find-msvc-tools"
 version = "0.1.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
 
 [[package]]
+name = "finl_unicode"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5"
+
+[[package]]
+name = "fixedbitset"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
+
+[[package]]
 name = "fixedbitset"
 version = "0.5.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
 
 [[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foldhash"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+
+[[package]]
+name = "foldhash"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
+
+[[package]]
 name = "futures"
 version = "0.3.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -359,7 +622,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.115",
 ]
 
 [[package]]
@@ -415,10 +678,37 @@ dependencies = [
 ]
 
 [[package]]
+name = "getrandom"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasip2",
+ "wasip3",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.15.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
+dependencies = [
+ "foldhash 0.1.5",
+]
+
+[[package]]
 name = "hashbrown"
 version = "0.16.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
+dependencies = [
+ "allocator-api2",
+ "equivalent",
+ "foldhash 0.2.0",
+]
 
 [[package]]
 name = "heck"
@@ -427,6 +717,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
 
 [[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
 name = "iana-time-zone"
 version = "0.1.65"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -451,13 +747,49 @@ dependencies = [
 ]
 
 [[package]]
+name = "id-arena"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
 name = "indexmap"
 version = "2.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
 dependencies = [
  "equivalent",
- "hashbrown",
+ "hashbrown 0.16.1",
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "indoc"
+version = "2.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
+dependencies = [
+ "rustversion",
+]
+
+[[package]]
+name = "instability"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "357b7205c6cd18dd2c86ed312d1e70add149aea98e7ef72b9fdf0270e555c11d"
+dependencies = [
+ "darling",
+ "indoc",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
 ]
 
 [[package]]
@@ -492,6 +824,17 @@ dependencies = [
 ]
 
 [[package]]
+name = "kasuari"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fe90c1150662e858c7d5f945089b7517b0a80d8bf7ba4b1b5ffc984e7230a5b"
+dependencies = [
+ "hashbrown 0.16.1",
+ "portable-atomic",
+ "thiserror 2.0.18",
+]
+
+[[package]]
 name = "keccak"
 version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -501,13 +844,19 @@ dependencies = [
 ]
 
 [[package]]
+name = "lab"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f"
+
+[[package]]
 name = "lalrpop"
 version = "0.22.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501"
 dependencies = [
  "ascii-canvas",
- "bit-set",
+ "bit-set 0.8.0",
  "ena",
  "itertools",
  "lalrpop-util",
@@ -539,12 +888,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
 
 [[package]]
+name = "leb128fmt"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
+
+[[package]]
 name = "libc"
 version = "0.2.182"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
 
 [[package]]
+name = "line-clipping"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f4de44e98ddbf09375cbf4d17714d18f39195f4f4894e8524501726fd9a8a4a"
+dependencies = [
+ "bitflags 2.11.0",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
+
+[[package]]
+name = "litrs"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
+
+[[package]]
 name = "lock_api"
 version = "0.4.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -560,6 +936,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
 
 [[package]]
+name = "lru"
+version = "0.16.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593"
+dependencies = [
+ "hashbrown 0.16.1",
+]
+
+[[package]]
+name = "mac_address"
+version = "1.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303"
+dependencies = [
+ "nix",
+ "winapi",
+]
+
+[[package]]
 name = "matchers"
 version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -575,12 +970,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
 
 [[package]]
+name = "memmem"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15"
+
+[[package]]
+name = "memoffset"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
 name = "mio"
 version = "1.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
 dependencies = [
  "libc",
+ "log",
  "wasi",
  "windows-sys 0.61.2",
 ]
@@ -601,7 +1018,8 @@ dependencies = [
  "lazy_static",
  "os_str_bytes",
  "pin-project",
- "rand",
+ "rand 0.9.2",
+ "ratatui",
  "regex",
  "serde",
  "serde_json",
@@ -619,6 +1037,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
 
 [[package]]
+name = "nix"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
+dependencies = [
+ "bitflags 2.11.0",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+ "memoffset",
+]
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
 name = "nu-ansi-term"
 version = "0.50.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -628,6 +1069,23 @@ dependencies = [
 ]
 
 [[package]]
+name = "num-conv"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
+
+[[package]]
+name = "num-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
 name = "num-traits"
 version = "0.2.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -637,6 +1095,15 @@ dependencies = [
 ]
 
 [[package]]
+name = "num_threads"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
+dependencies = [
+ "libc",
+]
+
+[[package]]
 name = "once_cell"
 version = "1.21.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -649,6 +1116,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
 
 [[package]]
+name = "ordered-float"
+version = "4.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
 name = "os_str_bytes"
 version = "7.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -681,16 +1157,102 @@ dependencies = [
 ]
 
 [[package]]
+name = "pest"
+version = "2.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662"
+dependencies = [
+ "memchr",
+ "ucd-trie",
+]
+
+[[package]]
+name = "pest_derive"
+version = "2.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77"
+dependencies = [
+ "pest",
+ "pest_generator",
+]
+
+[[package]]
+name = "pest_generator"
+version = "2.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f"
+dependencies = [
+ "pest",
+ "pest_meta",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "pest_meta"
+version = "2.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220"
+dependencies = [
+ "pest",
+ "sha2",
+]
+
+[[package]]
 name = "petgraph"
 version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772"
 dependencies = [
- "fixedbitset",
+ "fixedbitset 0.5.7",
  "indexmap",
 ]
 
 [[package]]
+name = "phf"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
+dependencies = [
+ "phf_macros",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
+dependencies = [
+ "phf_shared",
+ "rand 0.8.5",
+]
+
+[[package]]
+name = "phf_macros"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
 name = "phf_shared"
 version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -722,7 +1284,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.115",
 ]
 
 [[package]]
@@ -738,6 +1300,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
 
 [[package]]
+name = "portable-atomic"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
 name = "ppv-lite86"
 version = "0.2.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -753,6 +1327,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
 
 [[package]]
+name = "prettyplease"
+version = "0.2.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
+dependencies = [
+ "proc-macro2",
+ "syn 2.0.115",
+]
+
+[[package]]
 name = "proc-macro2"
 version = "1.0.106"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -778,12 +1362,21 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
 
 [[package]]
 name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand"
 version = "0.9.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
 dependencies = [
  "rand_chacha",
- "rand_core",
+ "rand_core 0.9.5",
 ]
 
 [[package]]
@@ -793,16 +1386,107 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
 dependencies = [
  "ppv-lite86",
- "rand_core",
+ "rand_core 0.9.5",
 ]
 
 [[package]]
 name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+
+[[package]]
+name = "rand_core"
 version = "0.9.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
 dependencies = [
- "getrandom",
+ "getrandom 0.3.4",
+]
+
+[[package]]
+name = "ratatui"
+version = "0.30.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc"
+dependencies = [
+ "instability",
+ "ratatui-core",
+ "ratatui-crossterm",
+ "ratatui-macros",
+ "ratatui-termwiz",
+ "ratatui-widgets",
+]
+
+[[package]]
+name = "ratatui-core"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293"
+dependencies = [
+ "bitflags 2.11.0",
+ "compact_str",
+ "hashbrown 0.16.1",
+ "indoc",
+ "itertools",
+ "kasuari",
+ "lru",
+ "strum",
+ "thiserror 2.0.18",
+ "unicode-segmentation",
+ "unicode-truncate",
+ "unicode-width",
+]
+
+[[package]]
+name = "ratatui-crossterm"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "577c9b9f652b4c121fb25c6a391dd06406d3b092ba68827e6d2f09550edc54b3"
+dependencies = [
+ "cfg-if",
+ "crossterm",
+ "instability",
+ "ratatui-core",
+]
+
+[[package]]
+name = "ratatui-macros"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7f1342a13e83e4bb9d0b793d0ea762be633f9582048c892ae9041ef39c936f4"
+dependencies = [
+ "ratatui-core",
+ "ratatui-widgets",
+]
+
+[[package]]
+name = "ratatui-termwiz"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f76fe0bd0ed4295f0321b1676732e2454024c15a35d01904ddb315afd3d545c"
+dependencies = [
+ "ratatui-core",
+ "termwiz",
+]
+
+[[package]]
+name = "ratatui-widgets"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db"
+dependencies = [
+ "bitflags 2.11.0",
+ "hashbrown 0.16.1",
+ "indoc",
+ "instability",
+ "itertools",
+ "line-clipping",
+ "ratatui-core",
+ "strum",
+ "time",
+ "unicode-segmentation",
+ "unicode-width",
 ]
 
 [[package]]
@@ -811,7 +1495,7 @@ version = "0.5.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
 dependencies = [
- "bitflags",
+ "bitflags 2.11.0",
 ]
 
 [[package]]
@@ -844,12 +1528,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c"
 
 [[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustix"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
+dependencies = [
+ "bitflags 2.11.0",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
 name = "rustversion"
 version = "1.0.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
 
 [[package]]
+name = "ryu"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
+
+[[package]]
 name = "same-file"
 version = "1.0.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -865,6 +1577,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 
 [[package]]
+name = "semver"
+version = "1.0.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
+
+[[package]]
 name = "serde"
 version = "1.0.228"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -891,7 +1609,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.115",
 ]
 
 [[package]]
@@ -917,6 +1635,17 @@ dependencies = [
 ]
 
 [[package]]
+name = "sha2"
+version = "0.10.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
 name = "sha3"
 version = "0.10.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -942,6 +1671,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
 
 [[package]]
+name = "signal-hook"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
+dependencies = [
+ "libc",
+ "signal-hook-registry",
+]
+
+[[package]]
+name = "signal-hook-mio"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc"
+dependencies = [
+ "libc",
+ "mio",
+ "signal-hook",
+]
+
+[[package]]
 name = "signal-hook-registry"
 version = "1.4.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -980,6 +1730,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
 name = "string_cache"
 version = "0.8.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -998,6 +1754,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
 
 [[package]]
+name = "strum"
+version = "0.27.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
+dependencies = [
+ "strum_macros",
+]
+
+[[package]]
+name = "strum_macros"
+version = "0.27.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
 name = "syn"
 version = "2.0.115"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1018,6 +1806,109 @@ dependencies = [
 ]
 
 [[package]]
+name = "terminfo"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662"
+dependencies = [
+ "fnv",
+ "nom",
+ "phf",
+ "phf_codegen",
+]
+
+[[package]]
+name = "termios"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "termwiz"
+version = "0.23.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7"
+dependencies = [
+ "anyhow",
+ "base64",
+ "bitflags 2.11.0",
+ "fancy-regex",
+ "filedescriptor",
+ "finl_unicode",
+ "fixedbitset 0.4.2",
+ "hex",
+ "lazy_static",
+ "libc",
+ "log",
+ "memmem",
+ "nix",
+ "num-derive",
+ "num-traits",
+ "ordered-float",
+ "pest",
+ "pest_derive",
+ "phf",
+ "sha2",
+ "signal-hook",
+ "siphasher",
+ "terminfo",
+ "termios",
+ "thiserror 1.0.69",
+ "ucd-trie",
+ "unicode-segmentation",
+ "vtparse",
+ "wezterm-bidi",
+ "wezterm-blob-leases",
+ "wezterm-color-types",
+ "wezterm-dynamic",
+ "wezterm-input-types",
+ "winapi",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
+dependencies = [
+ "thiserror-impl 2.0.18",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
 name = "thread_local"
 version = "1.1.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1027,6 +1918,27 @@ dependencies = [
 ]
 
 [[package]]
+name = "time"
+version = "0.3.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
+dependencies = [
+ "deranged",
+ "libc",
+ "num-conv",
+ "num_threads",
+ "powerfmt",
+ "serde_core",
+ "time-core",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
+
+[[package]]
 name = "tokio"
 version = "1.49.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1050,7 +1962,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.115",
 ]
 
 [[package]]
@@ -1111,7 +2023,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.115",
 ]
 
 [[package]]
@@ -1160,12 +2072,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
 
 [[package]]
+name = "ucd-trie"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
+
+[[package]]
 name = "unicode-ident"
 version = "1.0.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e"
 
 [[package]]
+name = "unicode-segmentation"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
+
+[[package]]
+name = "unicode-truncate"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5"
+dependencies = [
+ "itertools",
+ "unicode-segmentation",
+ "unicode-width",
+]
+
+[[package]]
+name = "unicode-width"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
+
+[[package]]
 name = "unicode-xid"
 version = "0.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1178,6 +2119,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
 
 [[package]]
+name = "uuid"
+version = "1.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb"
+dependencies = [
+ "atomic",
+ "getrandom 0.4.1",
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
 name = "valuable"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1190,6 +2143,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
 
 [[package]]
+name = "vtparse"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d9b2acfb050df409c972a37d3b8e08cdea3bddb0c09db9d53137e504cfabed0"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
 name = "walkdir"
 version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1215,6 +2177,15 @@ dependencies = [
 ]
 
 [[package]]
+name = "wasip3"
+version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
 name = "wasm-bindgen"
 version = "0.2.108"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1246,7 +2217,7 @@ dependencies = [
  "bumpalo",
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.115",
  "wasm-bindgen-shared",
 ]
 
@@ -1260,6 +2231,128 @@ dependencies = [
 ]
 
 [[package]]
+name = "wasm-encoder"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
+dependencies = [
+ "leb128fmt",
+ "wasmparser",
+]
+
+[[package]]
+name = "wasm-metadata"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
+dependencies = [
+ "anyhow",
+ "indexmap",
+ "wasm-encoder",
+ "wasmparser",
+]
+
+[[package]]
+name = "wasmparser"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
+dependencies = [
+ "bitflags 2.11.0",
+ "hashbrown 0.15.5",
+ "indexmap",
+ "semver",
+]
+
+[[package]]
+name = "wezterm-bidi"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c0a6e355560527dd2d1cf7890652f4f09bb3433b6aadade4c9b5ed76de5f3ec"
+dependencies = [
+ "log",
+ "wezterm-dynamic",
+]
+
+[[package]]
+name = "wezterm-blob-leases"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "692daff6d93d94e29e4114544ef6d5c942a7ed998b37abdc19b17136ea428eb7"
+dependencies = [
+ "getrandom 0.3.4",
+ "mac_address",
+ "sha2",
+ "thiserror 1.0.69",
+ "uuid",
+]
+
+[[package]]
+name = "wezterm-color-types"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7de81ef35c9010270d63772bebef2f2d6d1f2d20a983d27505ac850b8c4b4296"
+dependencies = [
+ "csscolorparser",
+ "deltae",
+ "lazy_static",
+ "wezterm-dynamic",
+]
+
+[[package]]
+name = "wezterm-dynamic"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f2ab60e120fd6eaa68d9567f3226e876684639d22a4219b313ff69ec0ccd5ac"
+dependencies = [
+ "log",
+ "ordered-float",
+ "strsim",
+ "thiserror 1.0.69",
+ "wezterm-dynamic-derive",
+]
+
+[[package]]
+name = "wezterm-dynamic-derive"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46c0cf2d539c645b448eaffec9ec494b8b19bd5077d9e58cb1ae7efece8d575b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "wezterm-input-types"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7012add459f951456ec9d6c7e6fc340b1ce15d6fc9629f8c42853412c029e57e"
+dependencies = [
+ "bitflags 1.3.2",
+ "euclid",
+ "lazy_static",
+ "serde",
+ "wezterm-dynamic",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
 name = "winapi-util"
 version = "0.1.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1269,6 +2362,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
 name = "windows-core"
 version = "0.62.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1289,7 +2388,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.115",
 ]
 
 [[package]]
@@ -1300,7 +2399,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.115",
 ]
 
 [[package]]
@@ -1421,6 +2520,88 @@ name = "wit-bindgen"
 version = "0.51.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
+dependencies = [
+ "wit-bindgen-rust-macro",
+]
+
+[[package]]
+name = "wit-bindgen-core"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
+dependencies = [
+ "anyhow",
+ "heck",
+ "wit-parser",
+]
+
+[[package]]
+name = "wit-bindgen-rust"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
+dependencies = [
+ "anyhow",
+ "heck",
+ "indexmap",
+ "prettyplease",
+ "syn 2.0.115",
+ "wasm-metadata",
+ "wit-bindgen-core",
+ "wit-component",
+]
+
+[[package]]
+name = "wit-bindgen-rust-macro"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
+dependencies = [
+ "anyhow",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+ "wit-bindgen-core",
+ "wit-bindgen-rust",
+]
+
+[[package]]
+name = "wit-component"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
+dependencies = [
+ "anyhow",
+ "bitflags 2.11.0",
+ "indexmap",
+ "log",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "wasm-encoder",
+ "wasm-metadata",
+ "wasmparser",
+ "wit-parser",
+]
+
+[[package]]
+name = "wit-parser"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
+dependencies = [
+ "anyhow",
+ "id-arena",
+ "indexmap",
+ "log",
+ "semver",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "unicode-xid",
+ "wasmparser",
+]
 
 [[package]]
 name = "zerocopy"
@@ -1439,7 +2620,7 @@ checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.115",
 ]
 
 [[package]]
diff --git a/pkgs/by-name/mp/mpdpopm/Cargo.toml b/pkgs/by-name/mp/mpdpopm/Cargo.toml
index 18c571d1..b1448e0b 100644
--- a/pkgs/by-name/mp/mpdpopm/Cargo.toml
+++ b/pkgs/by-name/mp/mpdpopm/Cargo.toml
@@ -44,3 +44,4 @@ tracing-subscriber = { version = "0.3.22", features = ["env-filter"]}
 anyhow = "1.0.101"
 shlex = "1.3.0"
 rand = "0.9.2"
+ratatui = "0.30.0"
diff --git a/pkgs/by-name/mp/mpdpopm/config.lsp b/pkgs/by-name/mp/mpdpopm/config.lsp
index 0e9b587d..d3471a05 100644
--- a/pkgs/by-name/mp/mpdpopm/config.lsp
+++ b/pkgs/by-name/mp/mpdpopm/config.lsp
@@ -4,6 +4,7 @@
       "path": "/run/user/1000/mpd/socket"
     }
   },
+  "mode": "Dj",
   "local_music_dir": "/home/soispha/media/music/beets",
   "log": "/home/soispha/.local/share/mpdpopm/log",
   "version": "1"
diff --git a/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm/cli.rs b/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm/cli.rs
new file mode 100644
index 00000000..c20bf3fa
--- /dev/null
+++ b/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm/cli.rs
@@ -0,0 +1,233 @@
+use clap::{Parser, Subcommand};
+use std::path::PathBuf;
+
+/// `mppopmd' client
+#[derive(Parser)]
+pub(crate) struct Args {
+    /// path to configuration file
+    #[arg(short, long)]
+    pub(crate) config: Option<PathBuf>,
+
+    /// enable verbose logging
+    #[arg(short, long)]
+    pub(crate) verbose: bool,
+
+    /// enable debug loggin (implies --verbose)
+    #[arg(short, long)]
+    pub(crate) debug: bool,
+
+    #[command(subcommand)]
+    pub(crate) command: SubCommand,
+}
+
+#[derive(Subcommand)]
+pub(crate) enum RatingCommand {
+    /// retrieve the rating for one or more tracks
+    ///
+    /// With no arguments, retrieve the rating of the current song & print it
+    /// on stdout. With one argument, retrieve that track's rating & print it
+    /// on stdout. With multiple arguments, print their ratings on stdout, one
+    /// per line, prefixed by the track name.
+    ///
+    /// Ratings are expressed as an integer between -128 & 128, exclusive, with
+    /// the convention that 0 denotes "un-rated".
+    #[clap(verbatim_doc_comment)]
+    Get {
+        /// Always show the song URI, even when there is only one track
+        #[arg(short, long)]
+        with_uri: bool,
+
+        tracks: Option<Vec<String>>,
+    },
+
+    /// set the rating for one track
+    ///
+    /// With one argument, set the rating of the current song to that argument.
+    /// With a second argument, rate that song at the first argument. Ratings
+    /// may be expressed a an integer between 0 & 255, inclusive.
+    #[clap(verbatim_doc_comment)]
+    Set { rating: i8, track: Option<String> },
+
+    /// increment the rating for one track
+    ///
+    /// With one argument, increment the rating of the current song.
+    /// With a second argument, rate that song at the first argument.
+    #[clap(verbatim_doc_comment)]
+    Inc { track: Option<String> },
+
+    /// decrement the rating for one track
+    ///
+    /// With one argument, decrement the rating of the current song.
+    /// With a second argument, rate that song at the first argument.
+    #[clap(verbatim_doc_comment)]
+    Decr { track: Option<String> },
+}
+
+#[derive(Subcommand)]
+pub(crate) enum PlayCountCommand {
+    /// retrieve the play count for one or more tracks
+    ///
+    /// With no arguments, retrieve the play count of the current song & print it
+    /// on stdout. With one argument, retrieve that track's play count & print it
+    /// on stdout. With multiple arguments, print their play counts on stdout, one
+    /// per line, prefixed by the track name.
+    #[clap(verbatim_doc_comment)]
+    Get {
+        /// Always show the song URI, even when there is only one track
+        #[arg(short, long)]
+        with_uri: bool,
+
+        tracks: Option<Vec<String>>,
+    },
+
+    /// set the play count for one track
+    ///
+    /// With one argument, set the play count of the current song to that argument. With a
+    /// second argument, set the play count for that song to the first.
+    #[clap(verbatim_doc_comment)]
+    Set {
+        play_count: usize,
+        track: Option<String>,
+    },
+}
+
+#[derive(Subcommand)]
+pub(crate) enum LastPlayedCommand {
+    /// retrieve the last played timestamp for one or more tracks
+    ///
+    /// With no arguments, retrieve the last played timestamp of the current
+    /// song & print it on stdout. With one argument, retrieve that track's
+    /// last played time & print it on stdout. With multiple arguments, print
+    /// their last played times on stdout, one per line, prefixed by the track
+    /// name.
+    ///
+    /// The last played timestamp is expressed in seconds since Unix epoch.
+    #[clap(verbatim_doc_comment)]
+    Get {
+        /// Always show the song URI, even when there is only one track
+        #[arg(short, long)]
+        with_uri: bool,
+
+        tracks: Option<Vec<String>>,
+    },
+
+    /// set the last played timestamp for one track
+    ///
+    /// With one argument, set the last played time of the current song. With two
+    /// arguments, set the last played time for the second argument to the first.
+    /// The last played timestamp is expressed in seconds since Unix epoch.
+    #[clap(verbatim_doc_comment)]
+    Set {
+        last_played: u64,
+        track: Option<String>,
+    },
+}
+
+#[derive(Subcommand)]
+pub(crate) enum PlaylistsCommand {
+    /// retrieve the list of stored playlists
+    #[clap(verbatim_doc_comment)]
+    Get {},
+}
+
+#[derive(Subcommand)]
+pub(crate) enum DjCommand {
+    /// Activate the automatic DJ mode on the mpdpopmd daemon.
+    ///
+    /// In this mode, the daemon will automatically add new tracks to the playlist based on a
+    /// recommendation algorithm.
+    #[clap(verbatim_doc_comment)]
+    Start {
+        /// The chance to select a "positive" track
+        #[arg(long, default_value_t = 0.65)]
+        positive_chance: f64,
+
+        /// The chance to select a "neutral" track
+        #[arg(long, default_value_t = 0.5)]
+        neutral_chance: f64,
+
+        /// The chance to select a "negative" track
+        #[arg(long, default_value_t = 0.2)]
+        negative_chance: f64,
+    },
+
+    /// Deactivate the automatic DJ mode on the mpdpopmd daemon.
+    ///
+    /// In this mode, the daemon will automatically add new tracks to the playlist based on a
+    /// recommendation algorithm.
+    #[clap(verbatim_doc_comment)]
+    Stop {},
+}
+
+#[derive(Subcommand)]
+pub(crate) enum SubCommand {
+    /// Change details about rating.
+    Rating {
+        #[command(subcommand)]
+        command: RatingCommand,
+    },
+
+    /// Change details about play count.
+    PlayCount {
+        #[command(subcommand)]
+        command: PlayCountCommand,
+    },
+
+    /// Change details about last played date.
+    LastPlayed {
+        #[command(subcommand)]
+        command: LastPlayedCommand,
+    },
+
+    /// Change details about generated playlists.
+    Playlists {
+        #[command(subcommand)]
+        command: PlaylistsCommand,
+    },
+
+    /// search for songs matching matching a filter and add them to the queue
+    ///
+    /// This command extends the MPD command `searchadd' (which will search the MPD database) to allow
+    /// searches on attributes managed by mpdpopm: rating, playcount & last played time.
+    ///
+    /// The MPD `searchadd' <https://www.musicpd.org/doc/html/protocol.html#command-searchadd> will search
+    /// the MPD database for songs that match a given filter & add them to the play queue. The filter syntax
+    /// is documented here <https://www.musicpd.org/doc/html/protocol.html#filter-syntax>.
+    ///
+    /// This command adds three new terms on which you can filter: rating, playcount & lastplayed. Each is
+    /// expressed as an unsigned integer, with zero interpreted as "not set". For instance:
+    ///
+    ///     mppopm searchadd "(rating > 2)"
+    ///
+    /// Will add all songs in the library with a rating sticker > 2 to the play queue.
+    ///
+    /// mppopm also introduces OR clauses (MPD only supports AND), so that:
+    ///
+    ///     mppopm searchadd "((rating > 2) AND (artist =~ \"pogues\"))"
+    ///
+    /// will add all songs whose artist tag matches the regexp "pogues" with a rating greater than
+    /// 2.
+    #[clap(verbatim_doc_comment)]
+    Searchadd {
+        filter: String,
+
+        /// Respect the casing, when performing the filter evaluation.
+        #[arg(short, long, default_value_t = false)]
+        case_sensitive: bool,
+    },
+
+    /// Modify the automatic DJ mode on the mpdpopmd daemon.
+    ///
+    /// In this mode, the daemon will automatically add new tracks to the playlist based on a
+    /// recommendation algorithm.
+    Dj {
+        #[command(subcommand)]
+        command: DjCommand,
+    },
+
+    /// Show general stats about your music collection.
+    ///
+    /// This includes favorite artist, songs and also the negative ones.
+    Stats {
+    }
+}
diff --git a/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm.rs b/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm/main.rs
index faa651bf..42f01873 100644
--- a/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm.rs
+++ b/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm/main.rs
@@ -26,21 +26,38 @@
 //! along the lines of [mpdcron](https://alip.github.io/mpdcron)). `mppopm` is a command-line client
 //! for `mppopmd`. Run `mppopm --help` for detailed usage.
 
+use std::{collections::HashMap, io::stdout};
+
+use clap::Parser;
 use mpdpopm::{
     clients::{Client, PlayerStatus},
     config::{self, Config},
+    dj::algorithms::Discovery,
     filters::ExpressionParser,
     filters_ast::{FilterStickerNames, evaluate},
     messanges::COMMAND_CHANNEL,
-    storage::{last_played, play_count, rating},
+    storage::{last_played, play_count, rating, skip_count},
 };
 
 use anyhow::{Context, Result, anyhow, bail};
-use clap::{Parser, Subcommand};
+use ratatui::{
+    Terminal, TerminalOptions, Viewport,
+    crossterm::style::Stylize,
+    layout::HorizontalAlignment,
+    prelude::CrosstermBackend,
+    style::{Color, Style},
+    text::Line,
+    widgets::{Bar, BarChart, BarGroup, Block, Borders},
+};
 use tracing::{debug, info, level_filters::LevelFilter, trace};
 use tracing_subscriber::{EnvFilter, Registry, layer::SubscriberExt};
 
-use std::path::PathBuf;
+use crate::cli::{
+    Args, DjCommand, LastPlayedCommand, PlayCountCommand, PlaylistsCommand, RatingCommand,
+    SubCommand,
+};
+
+mod cli;
 
 /// Map `tracks' argument(s) to a Vec of String containing one or more mpd URIs
 ///
@@ -296,219 +313,6 @@ async fn searchadd(client: &mut Client, filter: &str, case_sensitive: bool) -> R
     }
 }
 
-/// `mppopmd' client
-#[derive(Parser)]
-struct Args {
-    /// path to configuration file
-    #[arg(short, long)]
-    config: Option<PathBuf>,
-
-    /// enable verbose logging
-    #[arg(short, long)]
-    verbose: bool,
-
-    /// enable debug loggin (implies --verbose)
-    #[arg(short, long)]
-    debug: bool,
-
-    #[command(subcommand)]
-    command: SubCommand,
-}
-
-#[derive(Subcommand)]
-enum RatingCommand {
-    /// retrieve the rating for one or more tracks
-    ///
-    /// With no arguments, retrieve the rating of the current song & print it
-    /// on stdout. With one argument, retrieve that track's rating & print it
-    /// on stdout. With multiple arguments, print their ratings on stdout, one
-    /// per line, prefixed by the track name.
-    ///
-    /// Ratings are expressed as an integer between -128 & 128, exclusive, with
-    /// the convention that 0 denotes "un-rated".
-    #[clap(verbatim_doc_comment)]
-    Get {
-        /// Always show the song URI, even when there is only one track
-        #[arg(short, long)]
-        with_uri: bool,
-
-        tracks: Option<Vec<String>>,
-    },
-
-    /// set the rating for one track
-    ///
-    /// With one argument, set the rating of the current song to that argument.
-    /// With a second argument, rate that song at the first argument. Ratings
-    /// may be expressed a an integer between 0 & 255, inclusive.
-    #[clap(verbatim_doc_comment)]
-    Set { rating: i8, track: Option<String> },
-
-    /// increment the rating for one track
-    ///
-    /// With one argument, increment the rating of the current song.
-    /// With a second argument, rate that song at the first argument.
-    #[clap(verbatim_doc_comment)]
-    Inc { track: Option<String> },
-
-    /// decrement the rating for one track
-    ///
-    /// With one argument, decrement the rating of the current song.
-    /// With a second argument, rate that song at the first argument.
-    #[clap(verbatim_doc_comment)]
-    Decr { track: Option<String> },
-}
-
-#[derive(Subcommand)]
-enum PlayCountCommand {
-    /// retrieve the play count for one or more tracks
-    ///
-    /// With no arguments, retrieve the play count of the current song & print it
-    /// on stdout. With one argument, retrieve that track's play count & print it
-    /// on stdout. With multiple arguments, print their play counts on stdout, one
-    /// per line, prefixed by the track name.
-    #[clap(verbatim_doc_comment)]
-    Get {
-        /// Always show the song URI, even when there is only one track
-        #[arg(short, long)]
-        with_uri: bool,
-
-        tracks: Option<Vec<String>>,
-    },
-
-    /// set the play count for one track
-    ///
-    /// With one argument, set the play count of the current song to that argument. With a
-    /// second argument, set the play count for that song to the first.
-    #[clap(verbatim_doc_comment)]
-    Set {
-        play_count: usize,
-        track: Option<String>,
-    },
-}
-
-#[derive(Subcommand)]
-enum LastPlayedCommand {
-    /// retrieve the last played timestamp for one or more tracks
-    ///
-    /// With no arguments, retrieve the last played timestamp of the current
-    /// song & print it on stdout. With one argument, retrieve that track's
-    /// last played time & print it on stdout. With multiple arguments, print
-    /// their last played times on stdout, one per line, prefixed by the track
-    /// name.
-    ///
-    /// The last played timestamp is expressed in seconds since Unix epoch.
-    #[clap(verbatim_doc_comment)]
-    Get {
-        /// Always show the song URI, even when there is only one track
-        #[arg(short, long)]
-        with_uri: bool,
-
-        tracks: Option<Vec<String>>,
-    },
-
-    /// set the last played timestamp for one track
-    ///
-    /// With one argument, set the last played time of the current song. With two
-    /// arguments, set the last played time for the second argument to the first.
-    /// The last played timestamp is expressed in seconds since Unix epoch.
-    #[clap(verbatim_doc_comment)]
-    Set {
-        last_played: u64,
-        track: Option<String>,
-    },
-}
-
-#[derive(Subcommand)]
-enum PlaylistsCommand {
-    /// retrieve the list of stored playlists
-    #[clap(verbatim_doc_comment)]
-    Get {},
-}
-
-#[derive(Subcommand)]
-enum DjCommand {
-    /// Activate the automatic DJ mode on the mpdpopmd daemon.
-    ///
-    /// In this mode, the daemon will automatically add new tracks to the playlist based on a
-    /// recommendation algorithm.
-    #[clap(verbatim_doc_comment)]
-    Start {},
-
-    /// Deactivate the automatic DJ mode on the mpdpopmd daemon.
-    ///
-    /// In this mode, the daemon will automatically add new tracks to the playlist based on a
-    /// recommendation algorithm.
-    #[clap(verbatim_doc_comment)]
-    Stop {},
-}
-
-#[derive(Subcommand)]
-enum SubCommand {
-    /// Change details about rating.
-    Rating {
-        #[command(subcommand)]
-        command: RatingCommand,
-    },
-
-    /// Change details about play count.
-    PlayCount {
-        #[command(subcommand)]
-        command: PlayCountCommand,
-    },
-
-    /// Change details about last played date.
-    LastPlayed {
-        #[command(subcommand)]
-        command: LastPlayedCommand,
-    },
-
-    /// Change details about generated playlists.
-    Playlists {
-        #[command(subcommand)]
-        command: PlaylistsCommand,
-    },
-
-    /// search for songs matching matching a filter and add them to the queue
-    ///
-    /// This command extends the MPD command `searchadd' (which will search the MPD database) to allow
-    /// searches on attributes managed by mpdpopm: rating, playcount & last played time.
-    ///
-    /// The MPD `searchadd' <https://www.musicpd.org/doc/html/protocol.html#command-searchadd> will search
-    /// the MPD database for songs that match a given filter & add them to the play queue. The filter syntax
-    /// is documented here <https://www.musicpd.org/doc/html/protocol.html#filter-syntax>.
-    ///
-    /// This command adds three new terms on which you can filter: rating, playcount & lastplayed. Each is
-    /// expressed as an unsigned integer, with zero interpreted as "not set". For instance:
-    ///
-    ///     mppopm searchadd "(rating > 2)"
-    ///
-    /// Will add all songs in the library with a rating sticker > 2 to the play queue.
-    ///
-    /// mppopm also introduces OR clauses (MPD only supports AND), so that:
-    ///
-    ///     mppopm searchadd "((rating > 2) AND (artist =~ \"pogues\"))"
-    ///
-    /// will add all songs whose artist tag matches the regexp "pogues" with a rating greater than
-    /// 2.
-    #[clap(verbatim_doc_comment)]
-    Searchadd {
-        filter: String,
-
-        /// Respect the casing, when performing the filter evaluation.
-        #[arg(short, long, default_value_t = false)]
-        case_sensitive: bool,
-    },
-
-    /// Modify the automatic DJ mode on the mpdpopmd daemon.
-    ///
-    /// In this mode, the daemon will automatically add new tracks to the playlist based on a
-    /// recommendation algorithm.
-    Dj {
-        #[command(subcommand)]
-        command: DjCommand,
-    },
-}
-
 #[tokio::main]
 async fn main() -> Result<()> {
     let args = Args::parse();
@@ -597,8 +401,191 @@ async fn main() -> Result<()> {
             case_sensitive,
         } => searchadd(&mut client, &filter, case_sensitive).await,
         SubCommand::Dj { command } => match command {
-            DjCommand::Start {} => client.send_message(COMMAND_CHANNEL, "dj start").await,
+            DjCommand::Start {
+                positive_chance,
+                neutral_chance,
+                negative_chance,
+            } => {
+                client
+                    .send_message(
+                        COMMAND_CHANNEL,
+                        format!(
+                            "dj start \
+                            --positive-chance {positive_chance} \
+                            --neutral-chance {neutral_chance} \
+                            --negative-chance {negative_chance}"
+                        )
+                        .as_str(),
+                    )
+                    .await
+            }
             DjCommand::Stop {} => client.send_message(COMMAND_CHANNEL, "dj stop").await,
         },
+        SubCommand::Stats {} => {
+            struct Rating {
+                play_count: Option<usize>,
+                skip_count: Option<usize>,
+                last_played: Option<u64>,
+                rating: Option<i8>,
+                dj_weight: i64,
+            }
+            fn vertical_bar<'a>(count: i64, amount: usize) -> Bar<'a> {
+                fn amount_style(amount: usize) -> Style {
+                    let green = (255.0 * (1.0 - ((amount as f64) - 50.0) / 40.0)) as u8;
+                    let color = Color::Rgb(255, green, 0);
+
+                    Style::new().fg(color)
+                }
+
+                Bar::default()
+                    .value(amount as u64)
+                    .label(Line::from(count.to_string()))
+                    .style(amount_style(amount))
+                    .value_style(amount_style(amount).reversed())
+            }
+            macro_rules! top_five {
+                ($(@$convert:tt)? mode = $mode:tt, $rating_map:expr, $key:ident, $($other:ident),* $(,)?) => {
+                    let mut vec = $rating_map
+                        .iter()
+                        .filter_map(|(track, rating)| top_five!(@convert $($convert)? rating.$key).map(|v| (track, v, rating)))
+                        .collect::<Vec<_>>();
+                    vec.sort_by_key(|(_, pc, _)| *pc);
+
+                        top_five!(@gen_mode $mode, vec.iter())
+                        .take(5)
+                        .for_each(|(song, play_count, rating)| {
+                            println!(
+                                concat!("  - {}: {}", $(top_five!(@gen_empty $other)),*),
+                                <String as Clone>::clone(&song).bold().blue(),
+                                play_count.to_string().bold().white(),
+                                $(
+                                    rating
+                                        .$other
+                                        .map(|r| format!(" ({}: {r})", stringify!($other)))
+                                        .unwrap_or(String::new()),
+                                )*
+                            )
+                        });
+                };
+                (@gen_mode top, $expr:expr) => {
+                    $expr.rev()
+                };
+                (@gen_mode bottom, $expr:expr) => {
+                    $expr
+                };
+                (@gen_empty $tt:tt) => {
+                    "{}"
+                };
+                (@convert convert_to_option $tt:expr) => {
+                    Some($tt)
+                };
+                (@convert $tt:expr) => {
+                    $tt
+                }
+            }
+            macro_rules! histogram {
+                ($(@$convert:tt)? $rating_map:expr, $key:ident, $title:literal) => {
+                    let backend = CrosstermBackend::new(stdout());
+                    let viewport = Viewport::Inline(20);
+                    let mut terminal =
+                        Terminal::with_options(backend, TerminalOptions { viewport })?;
+
+                    let result = (|| {
+                        terminal.draw(|frame| {
+                            let line_chart = frame.area();
+
+                            let bars: Vec<Bar> = {
+                                let mut map = HashMap::new();
+                                $rating_map
+                                    .values()
+                                    .filter_map(|rating| histogram!(@convert $($convert)? rating.$key))
+                                    .for_each(|dj_weight| {
+                                        map.entry(dj_weight)
+                                            .and_modify(|e| {
+                                                *e += 1;
+                                            })
+                                            .or_insert(1);
+                                    });
+
+                                let mut vec = map.into_iter().collect::<Vec<(_, _)>>();
+                                vec.sort_by_key(|(pc, _)| *pc);
+
+                                vec.into_iter()
+                            }
+                            .map(|(dj_weight, amount)| vertical_bar(dj_weight.try_into().expect("Should be convertible"), amount))
+                            .collect();
+
+                            let title = Line::from($title).centered();
+                            let chart = BarChart::default()
+                                .data(BarGroup::default().bars(&bars))
+                                .block(
+                                    Block::new()
+                                        .title(title)
+                                        .title_alignment(HorizontalAlignment::Left)
+                                        .borders(Borders::all()),
+                                )
+                                .bar_width(5);
+
+                            frame.render_widget(chart, line_chart);
+                        })?;
+
+                        Ok::<_, anyhow::Error>(())
+                    })();
+
+                    ratatui::restore();
+                    println!();
+                    result?;
+                };
+                (@convert convert_to_option $val:expr) => {
+                    Some($val)
+                };
+                (@convert $val:expr) => {
+                    $val
+                };
+            }
+
+            let all = client.get_all_songs().await?;
+
+            let mut rating_map = HashMap::new();
+            for song in &all {
+                let rating = Rating {
+                    play_count: play_count::get(&mut client, song).await?,
+                    skip_count: skip_count::get(&mut client, song).await?,
+                    last_played: last_played::get(&mut client, song).await?,
+                    rating: rating::get(&mut client, song).await?,
+                    dj_weight: Discovery::weight_track(&mut client, song).await?,
+                };
+                rating_map.insert(song, rating);
+            }
+
+            let played_songs = rating_map
+                .values()
+                .filter(|s| s.last_played.is_some())
+                .count();
+
+            println!(
+                "Songs played: {:.2}%",
+                (played_songs as f64 / all.len() as f64) * 100.0
+            );
+
+            histogram!(rating_map, play_count, "Play counts");
+
+            println!("\nMost played songs:");
+            top_five!(mode = top, rating_map, play_count, skip_count, rating);
+
+            println!("\nMost skipped songs:");
+            top_five!(mode = top, rating_map, skip_count, play_count, rating);
+
+            println!("\nTop songs based on dj weight:");
+            top_five!(@convert_to_option mode = top, rating_map, dj_weight, rating, play_count, skip_count);
+
+            println!("\nBottom 5 songs based on dj weight:");
+            top_five!(@convert_to_option mode = bottom, rating_map, dj_weight, rating, play_count, skip_count);
+
+            println!();
+            histogram!(@convert_to_option rating_map, dj_weight, "Dj weights");
+
+            Ok(())
+        }
     }
 }
diff --git a/pkgs/by-name/mp/mpdpopm/src/config.rs b/pkgs/by-name/mp/mpdpopm/src/config.rs
index b4fe3c53..8bb5abfb 100644
--- a/pkgs/by-name/mp/mpdpopm/src/config.rs
+++ b/pkgs/by-name/mp/mpdpopm/src/config.rs
@@ -116,6 +116,17 @@ mod test_connection {
     }
 }
 
+/// THe possible start-up mode.
+#[derive(Default, Deserialize, Debug, Serialize)]
+pub enum Mode {
+    #[default]
+    /// Don't do anything special
+    Normal,
+
+    /// Already start the DJ mode on start-up
+    Dj,
+}
+
 /// This is the most recent `mppopmd` configuration struct.
 #[derive(Deserialize, Debug, Serialize)]
 #[serde(default)]
@@ -133,6 +144,9 @@ pub struct Config {
     /// How to connect to mpd
     pub conn: Connection,
 
+    /// The mode to start in
+    pub mode: Mode,
+
     /// The `mpd' root music directory, relative to the host on which *this* daemon is running
     pub local_music_dir: PathBuf,
 
@@ -142,9 +156,6 @@ pub struct Config {
 
     /// The interval, in milliseconds, at which to poll `mpd' for the current state
     pub poll_interval_ms: u64,
-
-    /// Channel to setup for assorted commands-- channel names must satisfy "[-a-zA-Z-9_.:]+"
-    pub commands_chan: String,
 }
 
 impl Default for Config {
@@ -162,7 +173,7 @@ impl Config {
             local_music_dir: [PREFIX, "Music"].iter().collect(),
             played_thresh: 0.6,
             poll_interval_ms: 5000,
-            commands_chan: String::from("unwoundstack.com:commands"),
+            mode: Mode::default(),
         })
     }
 }
@@ -176,111 +187,3 @@ pub fn from_str(text: &str) -> Result<Config> {
     };
     Ok(cfg)
 }
-
-#[cfg(test)]
-mod test {
-    use super::*;
-
-    #[test]
-    #[ignore = "We changed the config format to json"]
-    fn test_from_str() {
-        let cfg = Config::default();
-        assert_eq!(cfg.commands_chan, String::from("unwoundstack.com:commands"));
-
-        assert_eq!(
-            serde_json::to_string(&cfg).unwrap(),
-            format!(
-                r#"((version . "1") (log . "{}/log/mppopmd.log") (conn TCP (host . "localhost") (port . 6600)) (local_music_dir . "{}/Music") (playcount_sticker . "unwoundstack.com:playcount") (lastplayed_sticker . "unwoundstack.com:lastplayed") (played_thresh . 0.6) (poll_interval_ms . 5000) (commands_chan . "unwoundstack.com:commands") (playcount_command . "") (playcount_command_args) (rating_sticker . "unwoundstack.com:rating") (ratings_command . "") (ratings_command_args) (gen_cmds))"#,
-                LOCALSTATEDIR, PREFIX
-            )
-        );
-
-        let cfg: Config = serde_json::from_str(
-            r#"
-((version . "1")
- (log . "/usr/local/var/log/mppopmd.log")
- (conn TCP (host . "localhost") (port . 6600))
- (local_music_dir . "/usr/local/Music")
- (playcount_sticker . "unwoundstack.com:playcount")
- (lastplayed_sticker . "unwoundstack.com:lastplayed")
- (played_thresh . 0.6)
- (poll_interval_ms . 5000)
- (commands_chan . "unwoundstack.com:commands")
- (playcount_command . "")
- (playcount_command_args)
- (rating_sticker . "unwoundstack.com:rating")
- (ratings_command . "")
- (ratings_command_args)
- (gen_cmds))
-"#,
-        )
-        .unwrap();
-        assert_eq!(cfg._version, String::from("1"));
-
-        let cfg: Config = serde_json::from_str(
-            r#"
-((version . "1")
- (log . "/usr/local/var/log/mppopmd.log")
- (conn Local (path . "/home/mgh/var/run/mpd/mpd.sock"))
- (local_music_dir . "/usr/local/Music")
- (playcount_sticker . "unwoundstack.com:playcount")
- (lastplayed_sticker . "unwoundstack.com:lastplayed")
- (played_thresh . 0.6)
- (poll_interval_ms . 5000)
- (commands_chan . "unwoundstack.com:commands")
- (playcount_command . "")
- (playcount_command_args)
- (rating_sticker . "unwoundstack.com:rating")
- (ratings_command . "")
- (ratings_command_args)
- (gen_cmds))
-"#,
-        )
-        .unwrap();
-        assert_eq!(cfg._version, String::from("1"));
-        assert_eq!(
-            cfg.conn,
-            Connection::Local {
-                path: PathBuf::from("/home/mgh/var/run/mpd/mpd.sock")
-            }
-        );
-
-        // Test fallback to "v0" of the config struct
-        let cfg = from_str(r#"
-((log . "/home/mgh/var/log/mppopmd.log")
- (host . "192.168.1.14")
- (port . 6600)
- (local_music_dir . "/space/mp3")
- (playcount_sticker . "unwoundstack.com:playcount")
- (lastplayed_sticker . "unwoundstack.com:lastplayed")
- (played_thresh . 0.6)
- (poll_interval_ms . 5000)
- (playcount_command . "/usr/local/bin/scribbu")
- (playcount_command_args . ("popm" "-v" "-a" "-f" "-o" "sp1ff@pobox.com" "-C" "%playcount" "%full-file"))
- (commands_chan . "unwoundstack.com:commands")
- (rating_sticker . "unwoundstack.com:rating")
- (ratings_command . "/usr/local/bin/scribbu")
- (ratings_command_args . ("popm" "-v" "-a" "-f" "-o" "sp1ff@pobox.com" "-r" "%rating" "%full-file"))
- (gen_cmds .
-	   (((name . "set-genre")
-	     (formal_parameters . (Literal Track))
-	     (default_after . 1)
-	     (cmd . "/usr/local/bin/scribbu")
-	     (args . ("genre" "-a" "-C" "-g" "%1" "%full-file"))
-	     (update . TrackOnly))
-	    ((name . "set-xtag")
-	     (formal_parameters . (Literal Track))
-	     (default_after . 1)
-	     (cmd . "/usr/local/bin/scribbu")
-	     (args . ("xtag" "-A" "-o" "sp1ff@pobox.com" "-T" "%1" "%full-file"))
-	     (update . TrackOnly))
-	    ((name . "merge-xtag")
-	     (formal_parameters . (Literal Track))
-	     (default_after . 1)
-	     (cmd . "/usr/local/bin/scribbu")
-	     (args . ("xtag" "-m" "-o" "sp1ff@pobox.com" "-T" "%1" "%full-file"))
-	     (update . TrackOnly)))))
-"#).unwrap();
-        assert_eq!(cfg.log, PathBuf::from("/home/mgh/var/log/mppopmd.log"));
-    }
-}
diff --git a/pkgs/by-name/mp/mpdpopm/src/dj/algorithms.rs b/pkgs/by-name/mp/mpdpopm/src/dj/algorithms.rs
index 5ddfc7cb..2c3ddad6 100644
--- a/pkgs/by-name/mp/mpdpopm/src/dj/algorithms.rs
+++ b/pkgs/by-name/mp/mpdpopm/src/dj/algorithms.rs
@@ -1,7 +1,10 @@
-use std::collections::HashSet;
+use std::{
+    collections::HashSet,
+    time::{Duration, SystemTime},
+};
 
 use anyhow::{Context, Result};
-use rand::{Rng, distr, seq::SliceRandom};
+use rand::{Rng, distr};
 use tracing::info;
 
 use crate::{clients::Client, storage};
@@ -13,50 +16,66 @@ pub(crate) trait Algorithm {
 /// Generates generic discovery playlist, that fulfills following requirements:
 ///  - Will (eventually) include every not-played song. (So it can be used to rank a library)
 ///  - Returns liked songs more often then not-played or negative songs.
-pub(crate) struct Discovery {
+pub struct Discovery {
     already_done: HashSet<String>,
+    negative_chance: f64,
+    neutral_chance: f64,
+    positive_chance: f64,
 }
 
 impl Algorithm for Discovery {
     async fn next_track(&mut self, client: &mut Client) -> Result<String> {
         macro_rules! take {
-            ($first:expr, $second:expr, $third:expr) => {{
-                $first.pop().map_or_else(
-                    || {
-                        $second.pop().map_or_else(
-                            || {
-                                $third.pop().map_or_else(
-                                    || {
-                                        unreachable!(
-                                            "This means that there are no songs in the libary"
-                                        )
-                                    },
-                                    |val| {
-                                        tracing::info!(
-                                            "Selecting a `{}` track for the next entry in the queue",
-                                            stringify!($third)
-                                        );
-                                        Ok::<_, anyhow::Error>(val)
-                                    },
-                                )
-                            },
-                            |val| {
-                                tracing::info!(
-                                    "Selecting a `{}` track for the next entry in the queue",
-                                    stringify!($second)
-                                );
-                                Ok::<_, anyhow::Error>(val)
-                            },
-                        )
-                    },
-                    |val| {
-                        tracing::info!(
-                            "Selecting a `{}` track for the next entry in the queue",
-                            stringify!($first)
-                        );
-                        Ok::<_, anyhow::Error>(val)
-                    },
-                )
+            ($rng:expr, $from:expr) => {{
+                info!(concat!(
+                    "Trying to select a `",
+                    stringify!($from),
+                    "` track."
+                ));
+
+                assert!(!$from.is_empty());
+
+                let normalized_weights = {
+                    // We normalize the weights here, because negative values don't work for the
+                    // distribution function we use below.
+                    //    "-5" "-3" "1" "6" "19" | +5
+                    // ->  "0"  "2" "6" "11" "24"
+                    let mut weights = $from.iter().map(|(_, w)| *w).collect::<Vec<_>>();
+
+                    weights.sort_by_key(|w| *w);
+
+                    let first = *weights.first().expect(
+                        "the value to exist, because we never run `take!` with an empty vector",
+                    );
+
+                    if first.is_negative() {
+                        weights
+                            .into_iter()
+                            .rev()
+                            .map(|w| w + first.abs())
+                            .collect::<Vec<_>>()
+                    } else {
+                        weights
+                    }
+                };
+
+                let sample = $rng.sample(
+                    distr::weighted::WeightedIndex::new(normalized_weights.iter())
+                        .expect("to be okay, because the weights are normalized"),
+                );
+
+                let output = $from.remove(sample);
+
+                info!(
+                    concat!(
+                        "(",
+                        stringify!($from),
+                        ") Selected `{}` with weight: `{}` (normalized to `{}`)"
+                    ),
+                    output.0, output.1, normalized_weights[sample]
+                );
+
+                Ok::<_, anyhow::Error>(output)
             }};
         }
 
@@ -83,50 +102,74 @@ impl Algorithm for Discovery {
                 base
             };
 
-            let mut positive = vec![];
-            let mut neutral = vec![];
-            let mut negative = vec![];
-
+            let mut sorted_tracks = Vec::with_capacity(tracks.len());
             for track in tracks {
                 let weight = Self::weight_track(client, &track).await?;
 
-                match weight {
-                    1..=i64::MAX => positive.push(track),
-                    0 => neutral.push(track),
-                    i64::MIN..0 => negative.push(track),
-                }
+                sorted_tracks.push((track, weight));
             }
 
-            // Avoid an inherit ordering, that might be returned by the `Client::get_all_songs()` function.
-            positive.shuffle(&mut rng);
-            neutral.shuffle(&mut rng);
-            negative.shuffle(&mut rng);
+            sorted_tracks.sort_by_key(|(_, weight)| *weight);
+
+            let len = sorted_tracks.len() / 3;
+
+            // We split the tracks into three thirds, so that we can also force a pick from e.g.
+            // the lower third (the negative ones).
+            let negative = sorted_tracks.drain(..len).collect::<Vec<_>>();
+            let neutral = sorted_tracks.drain(..len).collect::<Vec<_>>();
+            let positive = sorted_tracks;
+
+            assert_eq!(negative.len(), neutral.len());
 
             (positive, neutral, negative)
         };
 
         let pick = rng.sample(
-            distr::weighted::WeightedIndex::new([0.65, 0.5, 0.2].iter())
-                .expect("to be valid, as hardcoded"),
+            distr::weighted::WeightedIndex::new(
+                [
+                    self.positive_chance,
+                    self.neutral_chance,
+                    self.negative_chance,
+                ]
+                .iter(),
+            )
+            .expect("to be valid, as hardcoded"),
         );
 
         let next = match pick {
-            0 => take!(positive, neutral, negative),
-            1 => take!(neutral, positive, negative),
-            2 => take!(negative, neutral, positive),
+            0 if !positive.is_empty() => take!(rng, positive),
+            1 if !neutral.is_empty() => take!(rng, neutral),
+            2 if !negative.is_empty() => take!(rng, negative),
+            0..=2 => {
+                // We couldn't actually satisfy the request, because we didn't have the required
+                // track. So we just use the first non-empty one.
+                if !positive.is_empty() {
+                    take!(rng, positive)
+                } else if !neutral.is_empty() {
+                    take!(rng, neutral)
+                } else if !negative.is_empty() {
+                    take!(rng, negative)
+                } else {
+                    assert!(positive.is_empty() && neutral.is_empty() && negative.is_empty());
+                    todo!("No songs available to select from, I don't know how to select one.");
+                }
+            }
             _ => unreachable!("These indexes are not possible"),
         }?;
 
-        self.already_done.insert(next.clone());
+        self.already_done.insert(next.0.to_owned());
 
-        Ok(next)
+        Ok(next.0)
     }
 }
 
 impl Discovery {
-    pub(crate) fn new() -> Self {
+    pub(crate) fn new(positive_chance: f64, neutral_chance: f64, negative_chance: f64) -> Self {
         Self {
             already_done: HashSet::new(),
+            positive_chance,
+            neutral_chance,
+            negative_chance,
         }
     }
 
@@ -136,15 +179,50 @@ impl Discovery {
     /// dislikes to a lower number.
     /// Currently, only the rating, skip count and play count are considered. Similarity scores,
     /// fetched from e.g. last.fm should be included in the future.
-    async fn weight_track(client: &mut Client, track: &str) -> Result<i64> {
+    pub async fn weight_track(client: &mut Client, track: &str) -> Result<i64> {
+        let last_played_delta = {
+            let last_played = storage::last_played::get(client, track).await?.unwrap_or(0);
+            let now = SystemTime::now()
+                .duration_since(SystemTime::UNIX_EPOCH)
+                .expect("to be before")
+                .as_secs();
+
+            let played_seconds_ago = now - last_played;
+
+            const HOUR: u64 = Duration::from_hours(1).as_secs();
+            const DAY: u64 = Duration::from_hours(24).as_secs();
+            const MONTH: u64 = Duration::from_hours(24 * 30).as_secs();
+
+            match played_seconds_ago {
+                ..HOUR => {
+                    // it was played in the last hour already
+                    -3
+                }
+                HOUR..DAY => {
+                    // it was not played in the last hour, but in the last day
+                    -2
+                }
+                DAY..MONTH => {
+                    // it was not played in the last day, but in the last month
+                    -1
+                }
+                MONTH.. => {
+                    // it was not played in a month
+                    1
+                }
+            }
+        };
+
         let rating = i32::from(storage::rating::get(client, track).await?.unwrap_or(0));
         let play_count = i32::try_from(storage::play_count::get(client, track).await?.unwrap_or(0))
             .context("`play_count` too big")?;
         let skip_count = i32::try_from(storage::skip_count::get(client, track).await?.unwrap_or(0))
             .context("`skip_count` too big")?;
 
-        let output: f64 =
-            1.0 * f64::from(rating) + 0.3 * f64::from(play_count) + -0.6 * f64::from(skip_count);
+        let output: f64 = 1.0 * f64::from(rating)
+            + 0.3 * f64::from(play_count)
+            + -0.6 * f64::from(skip_count)
+            + 0.65 * f64::from(last_played_delta);
 
         let weight = output.round() as i64;
 
diff --git a/pkgs/by-name/mp/mpdpopm/src/dj/mod.rs b/pkgs/by-name/mp/mpdpopm/src/dj/mod.rs
index a211a571..548ed4f4 100644
--- a/pkgs/by-name/mp/mpdpopm/src/dj/mod.rs
+++ b/pkgs/by-name/mp/mpdpopm/src/dj/mod.rs
@@ -3,7 +3,7 @@ use tracing::info;
 
 use crate::{clients::Client, dj::algorithms::Algorithm};
 
-pub(crate) mod algorithms;
+pub mod algorithms;
 
 pub(crate) struct Dj<A: Algorithm> {
     algo: A,
diff --git a/pkgs/by-name/mp/mpdpopm/src/lib.rs b/pkgs/by-name/mp/mpdpopm/src/lib.rs
index cc2765dc..2394b729 100644
--- a/pkgs/by-name/mp/mpdpopm/src/lib.rs
+++ b/pkgs/by-name/mp/mpdpopm/src/lib.rs
@@ -53,7 +53,7 @@ pub mod filters {
 use crate::{
     clients::{Client, IdleClient, IdleSubSystem},
     config::{Config, Connection},
-    messanges::MessageQueue,
+    messanges::{COMMAND_CHANNEL, MessageQueue},
     playcounts::PlayState,
 };
 
@@ -93,10 +93,10 @@ pub async fn mpdpopm(cfg: Config) -> std::result::Result<(), Error> {
             .context("Failed to connect to TCP idle client")?,
     };
 
-    let mut mqueue = MessageQueue::new();
+    let mut mqueue = MessageQueue::new(cfg.mode);
 
     idle_client
-        .subscribe(&cfg.commands_chan)
+        .subscribe(COMMAND_CHANNEL)
         .await
         .context("Failed to subscribe to idle_client")?;
 
@@ -144,14 +144,14 @@ pub async fn mpdpopm(cfg: Config) -> std::result::Result<(), Error> {
                         Ok(subsys) => {
                             debug!("subsystem {} changed", subsys);
                             if subsys == IdleSubSystem::Player {
-                                state.update(&mut client)
+                                if state.update(&mut client)
                                     .await
-                                    .context("PlayState update failed")?;
-
+                                    .context("PlayState update failed")? {
                                 mqueue
                                     .advance_dj(&mut client)
                                     .await
                                     .context("MessageQueue tick failed")?;
+                                }
                             } else if subsys == IdleSubSystem::Message {
                                 msg_check_needed = true;
                             }
diff --git a/pkgs/by-name/mp/mpdpopm/src/messanges/mod.rs b/pkgs/by-name/mp/mpdpopm/src/messanges/mod.rs
index c5320dd9..7db75672 100644
--- a/pkgs/by-name/mp/mpdpopm/src/messanges/mod.rs
+++ b/pkgs/by-name/mp/mpdpopm/src/messanges/mod.rs
@@ -5,6 +5,7 @@ use tracing::info;
 
 use crate::{
     clients::{Client, IdleClient},
+    config::Mode,
     dj::{Dj, algorithms::Discovery},
 };
 
@@ -26,7 +27,19 @@ enum SubCommand {
 
 #[derive(Subcommand)]
 enum DjCommand {
-    Start {},
+    Start {
+        /// The chance to select a "positive" track
+        #[arg(long)]
+        positive_chance: f64,
+
+        /// The chance to select a "neutral" track
+        #[arg(long)]
+        neutral_chance: f64,
+
+        /// The chance to select a "negative" track
+        #[arg(long)]
+        negative_chance: f64,
+    },
     Stop {},
 }
 
@@ -35,8 +48,17 @@ pub(crate) struct MessageQueue {
 }
 
 impl MessageQueue {
-    pub(crate) fn new() -> Self {
-        Self { dj: None }
+    pub(crate) fn new(mode: Mode) -> Self {
+        match mode {
+            Mode::Normal => Self { dj: None },
+            Mode::Dj => {
+                info!("Dj mode started on launch, as specified in config file");
+
+                Self {
+                    dj: Some(Dj::new(Discovery::new(0.65, 0.5, 0.2))),
+                }
+            }
+        }
     }
 
     pub(crate) async fn advance_dj(&mut self, client: &mut Client) -> Result<()> {
@@ -91,9 +113,17 @@ impl MessageQueue {
 
         match args.command {
             SubCommand::Dj { command } => match command {
-                DjCommand::Start {} => {
+                DjCommand::Start {
+                    positive_chance,
+                    neutral_chance,
+                    negative_chance,
+                } => {
                     info!("Dj started");
-                    self.dj = Some(Dj::new(Discovery::new()));
+                    self.dj = Some(Dj::new(Discovery::new(
+                        positive_chance,
+                        neutral_chance,
+                        negative_chance,
+                    )));
                     self.advance_dj(client).await?;
                 }
                 DjCommand::Stop {} => {
diff --git a/pkgs/by-name/mp/mpdpopm/src/playcounts.rs b/pkgs/by-name/mp/mpdpopm/src/playcounts.rs
index 417b3e7e..8fbee133 100644
--- a/pkgs/by-name/mp/mpdpopm/src/playcounts.rs
+++ b/pkgs/by-name/mp/mpdpopm/src/playcounts.rs
@@ -71,13 +71,16 @@ impl PlayState {
 
     /// Poll the server-- update our status; maybe increment the current track's play count; the
     /// caller must arrange to have this method invoked periodically to keep our state fresh
-    pub async fn update(&mut self, client: &mut Client) -> Result<()> {
+    ///
+    /// Returns whether a song finished between the last call and this one.
+    /// That can be used to add a new song to the queue.
+    pub async fn update(&mut self, client: &mut Client) -> Result<bool> {
         let new_stat = client
             .status()
             .await
             .context("Failed to get client status")?;
 
-        match (&self.last_server_stat, &new_stat) {
+        let previous_song_finished = match (&self.last_server_stat, &new_stat) {
             (PlayerStatus::Play(last), PlayerStatus::Play(curr))
             | (PlayerStatus::Pause(last), PlayerStatus::Play(curr))
             | (PlayerStatus::Play(last), PlayerStatus::Pause(curr))
@@ -93,33 +96,59 @@ impl PlayState {
                     }
 
                     self.have_incr_play_count = false;
+
+                    // We are now playing something else, as such the previous one must have
+                    // finished or was skipped.
+                    true
                 } else if last.elapsed > curr.elapsed
                     && self.have_incr_play_count
                     && curr.elapsed / curr.duration <= 0.1
                 {
                     debug!("Re-play-- resetting PC incremented flag.");
                     self.have_incr_play_count = false;
+
+                    // We are still playing the same song, just skipped at the start again.
+                    // This means that we don't need a new one.
+                    false
+                } else {
+                    // We are still playing the same song, so nothing changed
+                    false
                 }
             }
             (PlayerStatus::Stopped, PlayerStatus::Play(_))
-            | (PlayerStatus::Stopped, PlayerStatus::Pause(_))
-            | (PlayerStatus::Pause(_), PlayerStatus::Stopped)
+            | (PlayerStatus::Stopped, PlayerStatus::Pause(_)) => {
+                self.have_incr_play_count = false;
+
+                // We didn't play anything before and now we play something. This means that we
+                // obviously have something to play and thus don't need to add another song.
+                false
+            }
+            (PlayerStatus::Pause(_), PlayerStatus::Stopped)
             | (PlayerStatus::Play(_), PlayerStatus::Stopped) => {
                 self.have_incr_play_count = false;
+
+                // We played a song before and now we stopped, maybe because we ran out of songs to
+                // play. So we need to add another one.
+                true
+            }
+            (PlayerStatus::Stopped, PlayerStatus::Stopped) => {
+                // We did not play before and we are still not playing, as such nothing really
+                // changed.
+                false
             }
-            (PlayerStatus::Stopped, PlayerStatus::Stopped) => (),
-        }
+        };
 
         match &new_stat {
             PlayerStatus::Play(curr) => {
                 let pct = curr.played_pct();
                 debug!("Updating status: {:.3}% complete.", 100.0 * pct);
+
                 if !self.have_incr_play_count && pct >= self.played_thresh {
                     info!(
-                        "Increment play count for '{}' (songid: {}) at {} played.",
+                        "Increment play count for '{}' (songid: {}) at {:.2}% played.",
                         curr.file.display(),
                         curr.songid,
-                        curr.elapsed / curr.duration
+                        (curr.elapsed / curr.duration) * 100.0
                     );
 
                     let file = curr.file.to_str().ok_or_else(|| {
@@ -150,10 +179,10 @@ impl PlayState {
                         .expect("To exist, as it was skipped");
 
                     info!(
-                        "Marking '{}' (songid: {}) as skipped at {}.",
+                        "Marking '{}' (songid: {}) as skipped at {:.2}%.",
                         last.file.display(),
                         last.songid,
-                        last.elapsed / last.duration
+                        (last.elapsed / last.duration) * 100.0
                     );
 
                     let file = last.file.to_str().ok_or_else(|| {
@@ -168,7 +197,7 @@ impl PlayState {
         };
 
         self.last_server_stat = new_stat;
-        Ok(()) // No need to update the DB
+        Ok(previous_song_finished)
     }
 }
 
@@ -308,6 +337,6 @@ OK
         assert!(check);
 
         ps.update(&mut cli).await.unwrap();
-        ps.update(&mut cli).await.unwrap()
+        ps.update(&mut cli).await.unwrap();
     }
 }