62e329839625 default tip

Merge
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Wed, 27 Aug 2025 16:19:24 -0400
parents 87ff0514c83b (diff) 93d27886c099 (current diff)
children (none)
branches/tags default tip
files

Changes

--- a/.hgsub	Sun May 18 14:59:11 2025 -0400
+++ b/.hgsub	Wed Aug 27 16:19:24 2025 -0400
@@ -2,6 +2,7 @@
 mercurial/templates              = [hg]ssh://hg.stevelosh.com/repos/mercurial-cli-templates
 vim/bundle/abolish               = [git]https://github.com/tpope/vim-abolish
 vim/bundle/ack                   = [git]https://github.com/mileszs/ack.vim
+vim/bundle/ansible               = [git]https://github.com/pearofducks/ansible-vim
 vim/bundle/badwolf               = [hg]ssh://hg.stevelosh.com/repos/badwolf/
 vim/bundle/boxdraw               = [git]https://github.com/gyim/vim-boxdraw
 vim/bundle/clam                  = [hg]ssh://hg.stevelosh.com/repos/clam.vim/
@@ -18,6 +19,8 @@
 vim/bundle/markdown              = [git]https://github.com/sjl/vim-markdown
 vim/bundle/neoformat             = [git]https://github.com/sbdchd/neoformat
 vim/bundle/nerdtree              = [git]https://github.com/scrooloose/nerdtree
+vim/bundle/nextflow-vim          = [git]https://github.com/sjl/nextflow-vim
+vim/bundle/nvim-r                = [git]https://github.com/jalvesaq/Nvim-R
 vim/bundle/paredit               = [git]https://github.com/kovisoft/paredit
 vim/bundle/pgsql                 = [git]https://github.com/exu/pgsql.vim
 vim/bundle/python-mode           = [git]https://github.com/klen/python-mode
@@ -28,11 +31,13 @@
 vim/bundle/sexp                  = [git]https://github.com/guns/vim-sexp
 vim/bundle/shellcheck            = [git]https://github.com/itspriddle/vim-shellcheck
 vim/bundle/simpylfold            = [git]https://github.com/tmhedberg/SimpylFold
+vim/bundle/singularity-syntax    = [git]https://github.com/rbberger/vim-singularity-syntax
 vim/bundle/splice                = [hg]ssh://hg.stevelosh.com/repos/splice.vim
 vim/bundle/strftimedammit        = [hg]ssh://hg.stevelosh.com/repos/strftimedammit.vim/
 vim/bundle/surround              = [git]https://github.com/tpope/vim-surround
 vim/bundle/swig                  = [git]https://github.com/vim-scripts/SWIG-syntax
 vim/bundle/targets               = [git]https://github.com/wellle/targets.vim
+vim/bundle/ultisnips             = [git]https://github.com/SirVer/ultisnips
 vim/bundle/vim-go                = [git]https://github.com/fatih/vim-go
 vim/bundle/vimtex                = [git]https://github.com/lervag/vimtex
 vim/bundle/vlime                 = [git]https://github.com/sjl/vlime
--- a/.hgsubstate	Sun May 18 14:59:11 2025 -0400
+++ b/.hgsubstate	Wed Aug 27 16:19:24 2025 -0400
@@ -1,24 +1,27 @@
 16aa2def1d5b91f2f38c0bf50a955d28559c7ebe mercurial/hg-prompt
 1fc4a9fbead7e0acc4c828b346f3be2658ec3df9 mercurial/templates
-b6a8b49e2173ba5a1b34d00e68e0ed8addac3ebd vim/bundle/abolish
-a16a9b63eb85cc0960a7f25c54647ac1f99f3360 vim/bundle/ack
-599e1bb1aee98e563132553cf8b7bc32cb402b75 vim/bundle/badwolf
-24f6d94dd03ba0fdc703265fe281f70cf2b45ba6 vim/bundle/boxdraw
+dcbfe065297d31823561ba787f51056c147aa682 vim/bundle/abolish
+36e40f9ec91bdbf6f1adf408522a73a6925c3042 vim/bundle/ack
+3329367d2e5f90d203c8d340c53fa83c60a1ad41 vim/bundle/ansible
+a93aaf7f4247858f5e483dc98001a878a5233761 vim/bundle/badwolf
+b7f789f305b1c5b0b4623585e0f10adb417f2966 vim/bundle/boxdraw
 b542a7bc4d9bc5da8fb12e110fe7975131fb57a4 vim/bundle/clam
-8295187ea1210138c0b171d8e3ec3569936f4c1a vim/bundle/commentary
-c6d1fc5e58d689bfb104ff336aeb89d9ef1b48e2 vim/bundle/ctrlp
+e87cd90dc09c2a203e13af9704bd0ef79303d755 vim/bundle/commentary
+7c972cb19c8544c681ca345c64ec39e04f4651cc vim/bundle/ctrlp
 38487bbec8ba50834e257940b357de03991fa8f9 vim/bundle/delimitmate
 755554bb3c44944f70f4b2048acf0c69261782ac vim/bundle/fugitive
 127d706f2def96876605e6bd5d366c973cb8e406 vim/bundle/gdl
 7fcea1a08423da3012aac87f5224738c85d212a1 vim/bundle/gnupg
 0d57b080f9fae8573c688b6679b31eb1666edc4c vim/bundle/gnuplot
 1d84591fff04caebab75cba2294fc3843f0a4a29 vim/bundle/gundo
-fccd580f5f11d576169ee347907c9fbd77af410a vim/bundle/html5
+485f2cd62162c81e56d8604b4c630f0b5ef69d1f vim/bundle/html5
 dd84369d731bcb8feee0901cbb9b63a2b219bf28 vim/bundle/javascript
 e2d7fcd682a461a3951e8b5067cc8a0083e75e35 vim/bundle/markdown
 964c66fa22500ae7375114342d212d7fe15da341 vim/bundle/neoformat
 9310f91476a94ee9c2f3a587171893743a343e26 vim/bundle/nerdtree
-c76e0987ec45c84103b408691ec0506e7b99cb30 vim/bundle/paredit
+47f6d8508757559fcfa8f49cd93cd5c86ce847f0 vim/bundle/nextflow-vim
+eb97bf7c88480b9f00b6765c09c3886550ed8d43 vim/bundle/nvim-r
+989d1c046bbe36f7d71878636dbe6af8ea3f4f86 vim/bundle/paredit
 1a436f7d875b4ec630da081b041c73264235c7e7 vim/bundle/pgsql
 d241974f40e8d206f9970e51fb0069951862ba35 vim/bundle/python-mode
 eb8baa5428bde10ecc1cb14eed1d6e16f5f24695 vim/bundle/rainbow-parentheses
@@ -28,12 +31,14 @@
 b4398689f7483b01684044ab6b55bf369744c9b3 vim/bundle/sexp
 4346419ac57ef341a15aa39c827c0848f17c6faf vim/bundle/shellcheck
 0459df8a0bbfc8ef1bfd88db889e881626f65914 vim/bundle/simpylfold
+caaba2594df4f1859a448e6cf13c64d956419acd vim/bundle/singularity-syntax
 062b18eebd153c13e6f36577707acb17893cd959 vim/bundle/splice
 26fbdd7d1f1aa5600d2ebf39bbdd292c38aac16e vim/bundle/strftimedammit
 aa1f120ad3a29c27cc41d581cda3751c59343cce vim/bundle/surround
 19c3d966440b6cfe8d74251881a48e961ddb8648 vim/bundle/swig
 f6f2d6618a321f5b0065586a7bc934325fec81ab vim/bundle/targets
+b393ba65386d47664421e1f8b246a87a6e8b218c vim/bundle/ultisnips
 e9d7ff3eb4a369f0cb2069c8f77ae68796bca308 vim/bundle/vim-go
-5d5c71044880443035e07009497962feacb56b20 vim/bundle/vimtex
-54feb567738398ab65d783e096bc84938e7620a0 vim/bundle/vlime
+9df79e15bf035d1cfb32c11fffed38dd7b6a0501 vim/bundle/vimtex
+eee93632fee3b680fe992008b723b0802bb94a60 vim/bundle/vlime
 6876fe38b33732cb124d415ffc4156f16da5e118 vim/bundle/windowswap
--- a/.mbsyncrc	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,81 +0,0 @@
-# tweaked https://raw.githubusercontent.com/bm4cs/dots/Far/.mbsyncrc
-
-IMAPAccount fastmail
-Host imap.fastmail.com
-Port 993
-User steve@stevelosh.com
-PassCmd "pass show mutt/ouroboros/steve@stevelosh.com | head -n 1"
-SSLType IMAPS
-SSLVersion TLSv1.2
-CertificateFile /etc/ssl/certs/ca-certificates.crt
-
-# Remote
-IMAPStore mail-remote
-Account fastmail
-
-# Local
-MaildirStore mail-local
-Subfolders Verbatim
-Path ~/.mail2/sjl/
-Inbox ~/.mail2/sjl/INBOX
-
-# CONNECTIONS SPECIFY LINKS BETWEEN REMOTE AND LOCAL FOLDERS
-# CONNECTIONS ARE SPECIFIED USING PATTERNS, WHICH MATCH REMOTE MAIl
-# FOLDERS. SOME COMMONLY USED PATTERS INCLUDE:
-#
-# 1 "*" TO MATCH EVERYTHING
-# 2 "!DIR" TO EXCLUDE "DIR"
-# 3 "DIR" TO MATCH DIR
-
-Channel mail-inbox
-Far :mail-remote:
-Near :mail-local:
-Patterns INBOX
-Create Near
-Sync All
-Expunge Both
-SyncState *
-
-Channel mail-sent
-Far :mail-remote:
-Near :mail-local:
-Patterns "Sent"
-Create Both
-Sync All
-Expunge Both
-SyncState *
-
-Channel mail-spam
-Far :mail-remote:
-Near :mail-local:
-Patterns "Uncaught Spam"
-Create Near
-Sync All
-Expunge Both
-SyncState *
-
-Channel mail-drafts
-Far :mail-remote:
-Near :mail-local:
-Patterns "Drafts"
-Create Near
-Sync All
-Expunge Both
-SyncState *
-
-Channel mail-archive
-Far :mail-remote:
-Near :mail-local:
-Patterns "Arc*"
-Create Near
-Sync All
-Expunge Both
-SyncState *
-
-Group mail
-Channel mail-inbox
-Channel mail-spam
-Channel mail-drafts
-Channel mail-sent
-Channel mail-archive
-
--- a/Xmodmap	Sun May 18 14:59:11 2025 -0400
+++ b/Xmodmap	Wed Aug 27 16:19:24 2025 -0400
@@ -6,7 +6,11 @@
 clear mod4
 clear mod5
 
+! Laptop lctrl
 keycode 37 = Hyper_L
+! Laptop rctrl
+keycode 105 = Hyper_R
+
 keycode 66 = Control_L
 keycode 133 = Alt_L
 keycode 64 = Super_L
@@ -53,4 +57,4 @@
 add mod1 = Alt_L
 add mod2 = Num_Lock
 add mod4 = Super_L
-add mod5 = Hyper_L
+add mod5 = Hyper_L Hyper_R
--- a/bash_profile	Sun May 18 14:59:11 2025 -0400
+++ b/bash_profile	Wed Aug 27 16:19:24 2025 -0400
@@ -6,6 +6,7 @@
 
 shopt -s expand_aliases
 shopt -s histappend
+shopt -s checkwinsize
 
 # Save multiline commands as a single history entry.
 shopt -s cmdhist
@@ -21,12 +22,12 @@
     eval "$(dircolors -b ~/.dircolors)"
 fi
 
-D=$'\e[37m'
-RED=$'\e[31m'
-GREEN=$'\e[32m'
-ORANGE=$'\e[33m'
-BLUE=$'\e[34m'
-PINK=$'\e[35m'
+D=$'\x01\e[37m\x02'
+RED=$'\x01\e[31m\x02'
+GREEN=$'\x01\e[32m\x02'
+ORANGE=$'\x01\e[33m\x02'
+BLUE=$'\x01\e[34m\x02'
+PINK=$'\x01\e[35m\x02'
 # CYAN=$'\e[36m'
 
 function last_return_value() {
@@ -54,6 +55,10 @@
 alias .....="cd ../../../.."
 alias ......="cd ../../../../.."
 
+function mcd {
+    mkdir "$1" && cd "$1"
+}
+
 alias js='cd ~/scratch'
 
 alias :q=exit
@@ -114,3 +119,5 @@
 
 export NVM_DIR="$HOME/.nvm"
 [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm
+
+export PKG_CONFIG_PATH="/usr/lib/x86_64-linux-gnu/pkgconfig"
--- a/bin/abcl	Sun May 18 14:59:11 2025 -0400
+++ b/bin/abcl	Wed Aug 27 16:19:24 2025 -0400
@@ -1,3 +1,3 @@
 #!/usr/bin/env bash
 
-rlwrap-lisp /usr/local/bin/abcl "$@"
+rlwrap-lisp abcl-raw "$@"
--- a/bin/abcl-raw	Sun May 18 14:59:11 2025 -0400
+++ b/bin/abcl-raw	Wed Aug 27 16:19:24 2025 -0400
@@ -1,4 +1,5 @@
 #!/usr/bin/env bash
 
 set -e
-/usr/local/bin/abcl --noinform "$@"
+
+java -jar /usr/local/bin/abcl.jar --noinform "$@"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/battery	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+function check_battery {
+    # TODO make this less shit
+    BATT="$1"
+    echo "$BATT"
+    cat "$BATT"/type
+
+    if test -e "$BATT"/online; then
+        echo -n "Online: "
+        cat "$BATT"/online
+    fi
+
+    if test -e "$BATT"/capacity; then
+        echo -n "Capacity (%): "
+        cat "$BATT"/capacity
+    fi
+
+    echo
+}
+
+for b in /sys/class/power_supply/*; do
+    check_battery "$b"
+done
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/bht	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+ffplay -v 0 -nodisp -autoexit ~/bin/bht.mp3
--- a/bin/bootstrap.sh	Sun May 18 14:59:11 2025 -0400
+++ b/bin/bootstrap.sh	Wed Aug 27 16:19:24 2025 -0400
@@ -12,11 +12,14 @@
 mkdir -p ~/.local/share
 mkdir -p ~/.config/fish
 mkdir -p ~/.config/nvim
+mkdir -p ~/.config/htop
+mkdir -p ~/.config/mpd
 mkdir -p ~/src/hg
 mkdir -p ~/src/virtualenvs
 mkdir -p ~/bin
 mkdir -p ~/src
 mkdir -p ~/.w3m
+mkdir -p ~/.weechat
 
 ensure_link "src/dotfiles/hgrc" ".hgrc"
 
@@ -44,8 +47,10 @@
 ensure_link "src/dotfiles/gitignore"                   ".gitignore"
 ensure_link "src/dotfiles/gnuplot"                     ".gnuplot"
 ensure_link "src/dotfiles/hgignore"                    ".hgignore"
+ensure_link "src/dotfiles/htoprc"                      ".config/htop/htoprc"
 ensure_link "src/dotfiles/lisprc"                      ".lisprc"
 ensure_link "src/dotfiles/lispwords"                   ".lispwords"
+ensure_link "src/dotfiles/mpd.conf"                    ".config/mpd/mpd.conf"
 ensure_link "src/dotfiles/mutt"                        ".mutt"
 ensure_link "src/dotfiles/mutt/mailcap"                ".mailcap"
 ensure_link "src/dotfiles/mutt/muttrc"                 ".muttrc"
@@ -54,13 +59,13 @@
 ensure_link "src/dotfiles/sbclrc"                      ".sbclrc"
 ensure_link "src/dotfiles/shellcheckrc"                ".shellcheckrc"
 ensure_link "src/dotfiles/sqliterc"                    ".sqliterc"
-ensure_link "src/dotfiles/stumpwmrc"                   ".stumpwmrc"
 ensure_link "src/dotfiles/stumpwm/local-share-stumpwm" ".local/share/stumpwm"
+ensure_link "src/dotfiles/stumpwm/stumpwmrc"           ".stumpwmrc"
 ensure_link "src/dotfiles/vim"                         ".vim"
 ensure_link "src/dotfiles/vim/vimrc"                   ".vimrc"
-ensure_link "src/dotfiles/w3m-keymap"                  ".w3m/keymap"
+ensure_link "src/dotfiles/vim/vimrc-minimal"           ".vimrc-minimal"
 ensure_link "src/dotfiles/w3m-config"                  ".w3m/config"
-ensure_link "src/dotfiles/weechat"                     ".weechat"
+ensure_link "src/dotfiles/w3m-keymap"                  ".w3m/keymap"
 ensure_link "src/dotfiles/xbindkeysrc"                 ".xbindkeysrc"
 ensure_link "src/dotfiles/xsessionrc"                  ".xsessionrc"
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/boxify	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,42 @@
+#!/usr/bin/env python3
+
+import sys
+
+lines = sys.stdin.readlines()
+
+def ch(row, col):
+    if row < 0 or row >= len(lines):
+        return None
+    line = lines[row]
+    if col < 0 or col >= len(line):
+        return None
+    return line[col]
+
+for row, line in enumerate(lines):
+    for col, char in enumerate(line):
+        c = char
+        if char == '-':
+            c = '—'
+        elif char == '|':
+            c = '│'
+        elif char == '+':
+            u = ch(row-1, col) == '|'
+            d = ch(row+1, col) == '|'
+            l = ch(row, col-1) == '-'
+            r = ch(row, col+1) == '-'
+            if u and d and l and r: c = '┼'
+            elif u and d and l and not r: c = '┤'
+            elif u and d and not l and r: c = '├'
+            elif u and not d and l and r: c = '┴'
+            elif not u and d and l and r: c = '┬'
+            elif u and r: c = '└'
+            elif u and l: c = '┘'
+            elif d and l: c = '┐'
+            elif d and r: c = '┌'
+            elif u and d: c = '│'
+            elif l and r: c = '─'
+            else:
+                print(u, d, l, r,)
+                assert False
+
+        sys.stdout.write(c)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/cax	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+chmod a+x "$@"
--- a/bin/cmrm	Sun May 18 14:59:11 2025 -0400
+++ b/bin/cmrm	Wed Aug 27 16:19:24 2025 -0400
@@ -4,4 +4,4 @@
 set -x
 
 rm ~/.ssh/controlmaster/*
-ps auxww | grep 'ssh.*[.]sock.*mux' | f 2 | xargs kill
+ps auxww | grep -P 'ssh.*controlmaster.*mux' | f 2 | xargs kill
--- a/bin/code-to-pdf	Sun May 18 14:59:11 2025 -0400
+++ b/bin/code-to-pdf	Wed Aug 27 16:19:24 2025 -0400
@@ -9,12 +9,11 @@
     --toc \
     --header '%H - $N | | page $% of $= in file $v' \
     --fancy-header=sjl \
-    --font "UbuntuMono-Regular@9" \
-    --header-font "UbuntuMono-Bold@12" \
+    --font "UbuntuMono-Regular@8" \
+    --header-font "UbuntuMono-Bold@10" \
     --title "$TITLE" \
     --baselineskip 3 \
     --line-numbers \
-    --highlight \
     --color \
     --mark-wrapped-lines=arrow \
     --margins=50:50:18:50 \
@@ -25,3 +24,4 @@
 
     # --margins=20:40:30:30 \
     # --landscape \
+    # --highlight \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/cqn	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+/home/sjl/bin/lqn.core --script ~/src/lqn/bin/cqn-sh.lisp "$@"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/csv2tsv	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,20 @@
+#!/usr/bin/env python3
+
+import sys
+import csv
+
+def run_file(f):
+    w = csv.writer(sys.stdout, delimiter='\t')
+    for row in csv.reader(f):
+        w.writerow(row)
+
+def run(paths):
+    for path in paths:
+        if path == '-':
+            run_file(sys.stdin)
+        else:
+            with open(path) as f:
+                run_file(f)
+
+if __name__ == '__main__':
+    run(sys.argv[1:] or ['-'])
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/csvconcat	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+head -n1 "$1"
+tail --quiet -n +2 "$@"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/cux	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+chmod u+x "$@"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/cv	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+column -s, -t "$@"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/cvs	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+csvconcat "$@" | cv
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/date-block	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,19 @@
+#!/usr/bin/env python3
+
+import sys, datetime
+
+
+days = 'mtwhfsu'
+start_date = datetime.datetime.strptime(sys.argv[1] + ' 12:00:00 UTC', '%Y-%m-%d %H:%M:%S %Z')
+
+d = start_date
+
+for _ in range(7 * 8):
+    day = days[d.weekday()]
+    date = d.strftime('%Y-%m-%d')
+
+    if day == 'm':
+        print()
+
+    print(f"{day} {date}")
+    d += datetime.timedelta(days=1)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/descendent-pids	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+pids="$1"
+
+while [ "$pids" ]; do
+    echo $pids
+    pids=$(for p in $pids; do cat /proc/$p/task/$p/children; done)
+done | tr " " "\n"
--- a/bin/disks	Sun May 18 14:59:11 2025 -0400
+++ b/bin/disks	Wed Aug 27 16:19:24 2025 -0400
@@ -12,10 +12,10 @@
 OUT=$(df -h)
 echo "$OUT" | head -1
 echo "$OUT" | head -1 | sed 's/./-/g'
-echo "$OUT" | tail +2 | grep -Pv '^/dev/loop' | grep -Pv tmpfs | sort -k6
+echo "$OUT" | tail +2 | grep -Pv '^/dev/loop' | grep -Pv tmpfs | sort -k6 | grep -P --color '/dev.*\s/$|'
 
 echo
 OUT=$(df -i)
 echo "$OUT" | head -1
 echo "$OUT" | head -1 | sed 's/./-/g'
-echo "$OUT" | tail +2 | grep -Pv '^/dev/loop' | grep -Pv tmpfs | sort -k6
+echo "$OUT" | tail +2 | grep -Pv '^/dev/loop' | grep -Pv tmpfs | sort -k6 | grep -P --color '/dev.*\s/$|'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/f,	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+cut -d, -f "$@"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/fnums	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+# Field Numbers, because I'm sick of manually counting
+
+head -n 1 "$@" | tr -s ',\t' '\n\n' | nl
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/forward-igv	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+slurm_node="$1"
+
+ssh -NAL 60151:localhost:60151 "$slurm_node"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/heic-conv-all	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,44 @@
+#!/bin/bash
+
+# from https://github.com/lokarithm/HandyBashCommands/blob/main/heic-converter/heic-converter.sh
+
+if ! command -v heif-convert &> /dev/null
+then
+    echo "heif-convert COMMAND could not be found."
+    echo "Please install 'libheif-examples' first."
+    echo "To install 'libheif-examples', run the following command:"
+    echo "  sudo apt install libheif-examples"
+    exit
+else
+    fileExtension="jpg"
+
+    while getopts :p flag; do
+        case ${flag} in
+            # -p flag: convert heic files to png format instead
+            p) fileExtension="png"
+            ;;
+        esac
+    done
+
+    start_time=$(date +%s.%3N)
+
+    # look for files in current path that contains ".heic" in a case-insensitive manner
+    for file in $( ls | grep -iF ".heic")
+    do
+        echo "Converting file: $file"
+
+        # file extension of current file
+        currFileExtension=`echo $file | grep -iFo "heic"`
+        sedCommand="s/${currFileExtension}/${fileExtension}/g;s/HEIC/${fileExtension}/g"
+
+        #replace original file name by changing the extension from heic to jpg
+        outputFileName=`echo $file | sed -e $sedCommand`
+        heif-convert $file $outputFileName
+    done
+
+    end_time=$(date +%s.%3N)
+
+    elapsed=$(echo "scale=3; $end_time - $start_time" | bc)
+    echo -e "\nElapsed time: \e[32m$elapsed \e[39mmilliseconds."
+
+fi
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/hg-clone-llp	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+repo="$1"
+
+h clone hsl://"$repo" "$repo"
+cd "$repo"
+llp
--- a/bin/hl	Sun May 18 14:59:11 2025 -0400
+++ b/bin/hl	Wed Aug 27 16:19:24 2025 -0400
@@ -1,3 +1,3 @@
 #!/usr/bin/env bash
 
-less -R
+less -S -R "$@"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/install-figlet-fonts	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+# I'm so sick of doing this shit by hand every time I get a new computer.
+#
+# cd ~/src/
+# git clont https://github.com/xero/figlet-fonts
+
+for f in /home/sjl/src/figlet-fonts/*.fl*; do
+    dest="$(echo "$f" | cut -d/ -f 6 | tr '[:upper:] ' '[:lower:]-')"
+    sudo cp "$f" "/usr/share/figlet/$dest"
+done
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/jabref	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+jabref-bin
+
+scp ~/Sync/papers/sjl.bib "cylinder:bib-backup/sjl.bib.$(nicedate).backup"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/jqn	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+/home/sjl/bin/lqn.core --script ~/src/lqn/bin/jqn-sh.lisp "$@"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/lesss	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+less -S "$@"
--- a/bin/longcat	Sun May 18 14:59:11 2025 -0400
+++ b/bin/longcat	Wed Aug 27 16:19:24 2025 -0400
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import sys
 
@@ -7,25 +7,24 @@
 else:
     n = 20
 
-print r'''
-    /\___/\
-   /       \
-  |  o    o |
-  \     #   |
-   \   _|_ /
-   /       \______
-  / _______ ___   \
-  |_____   \   \__/
+print (r'''
+     /\___/\
+    /       \
+   |  o    o |
+   \     #   |
+    \   _|_ /
+    /       \______
+   / _______ ___   \
+   |_____   \   \__/
    |    \__/
-   |       |
-''',
-print '   |       |\n' * n,
-print r'''   |       |
+   |       |'''),
+print('   |       |\n' * n, end='')
+print(r'''   |       |
    /        \
   /   ____   \
   |  /    \  |
   | |      | |
  /  |      |  \
  \__/      \__/
-'''
+''')
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/lqn	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+/home/sjl/bin/lqn.core --script ~/src/lqn/bin/lqn-sh.lisp "$@"
--- a/bin/mark	Sun May 18 14:59:11 2025 -0400
+++ b/bin/mark	Wed Aug 27 16:19:24 2025 -0400
@@ -4,6 +4,8 @@
 
 EVENT="$1"
 shift
+HOST=$(hostname)
+DIR="$HOME/Sync/marks/$HOST/"
 
-mkdir -p "$HOME/.marks"
-echo "$(date --iso-8601=seconds --utc)" "$@" >> "$HOME/.marks/$EVENT"
+mkdir -p "$DIR"
+echo "$(date --iso-8601=seconds --utc)" "$@" >> "$DIR/$EVENT"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/minimap2-index	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,33 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+# minimap2-index \
+#     /nfs/turbo/boylelab/slosh/reference-genomes/hg38_no_alt/seq/GCA_000001405.15_GRCh38_no_alt_analysis_set.fna \
+#     map-ont/ \
+#     -x map-ont
+reference="$1"
+shift
+output_dir="$1"
+shift
+
+# remaining opts are e.g. -x ont
+
+minimap="/nfs/turbo/boylelab/slosh/software/minimap2-2.30_x64-linux/minimap2"
+
+mkdir -p "$output_dir"
+
+{
+    echo Minimap version: "$("$minimap" --version)"
+
+    echo Indexing command: " "
+    echo \
+    "$minimap" -t 8 -d "$output_dir/index.mmi" "$reference" "$@"
+
+    echo
+    echo Output:
+    "$minimap" -t 8 -d "$output_dir/index.mmi" "$reference" "$@"
+} >> "$output_dir"/README.txt 2>&1
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/mklatex	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+NAME="$1"
+
+pdflatex "$NAME".tex
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/mklatex-bib	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+NAME="$1"
+
+pdflatex "$NAME".tex
+biber "$NAME"
+pdflatex "$NAME".tex
+pdflatex "$NAME".tex
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/mkxelatex	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+NAME="$1"
+
+xelatex "$NAME".tex
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/mkxelatex-bib	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+NAME="$1"
+
+xelatex "$NAME".tex
+bibtex "$NAME"
+xelatex "$NAME".tex
+xelatex "$NAME".tex
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/nicedate	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+date --utc +%Y-%m-%dT%H-%M-%SZ
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/pspg	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+/usr/bin/pspg --vertical-cursor --style 5 "$@"
--- a/bin/remap-hggit-mapfile	Sun May 18 14:59:11 2025 -0400
+++ b/bin/remap-hggit-mapfile	Wed Aug 27 16:19:24 2025 -0400
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 from __future__ import print_function
 import collections
@@ -22,11 +22,11 @@
 
 def hg(*args):
     l = ["hg", "--repository", hg_dir] + list(args)
-    return subprocess.check_output(l).strip()
+    return subprocess.check_output(l).strip().decode('utf-8')
 
 def git(*args):
     l = ["git", "-C", git_dir] + list(args)
-    return subprocess.check_output(l).strip()
+    return subprocess.check_output(l).strip().decode('utf-8')
 
 def find_hg_root_revision():
     rev = hg('log', '-r', 'roots(all())', nodetempl).split()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/rsync-file-pattern	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+# USAGE: rsync-file-pattern PATTERN SOURCE DEST
+#        rsync-file-pattern '*.ipynb' ./ vm:path/to/foo/
+
+# Goal: given a source and dest, sync any files inside the source that match
+# a given pattern.  Rsync's syntax for this is utterly deranged, so I'm making
+# a script so I don't have to search for this every time.
+
+pattern="$1"
+shift
+
+source="$1"
+shift
+
+dest="$1"
+shift
+
+
+rsync -av \
+    --prune-empty-dirs \
+    --include='*/' \
+    --include="$pattern" \
+    --exclude='*' \
+    "${source}" "${dest}" \
+    "$@"
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/sb	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+set -e
+
+sbcl --eval '(ql:quickload :losh :silent t)' --eval '(in-package :losh-user)' "$@"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/st-apl	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+st -f 'BQN386 Unicode:style=Regular:size=12' "$@"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/subtree-cpu-usage	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+x="$1"
+pidstat -h -u -p $(descendent-pids "$x" | tr -s '\n' ',' | sed -e 's/,$//') 5 1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/sv	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+column -s ' ' -t "$@"
--- a/bin/switch-yubikeys	Sun May 18 14:59:11 2025 -0400
+++ b/bin/switch-yubikeys	Wed Aug 27 16:19:24 2025 -0400
@@ -1,6 +1,4 @@
 #!/usr/bin/env bash
 
-set -euo pipefail
-
 grep -rl shadowed-private-key ~/.gnupg/private-keys-v1.d/ | xargs rm
 gpg --card-status
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/t.	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+stumpish terminal-in "$(pwd)"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/temps	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,50 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+function find_names {
+    grep . /sys/class/hwmon/hwmon*/name
+}
+
+function find_temp_numbers {
+    dir="$1"
+    find "/sys/class/hwmon/$dir/" -name 'temp*_input' \
+        | xargs -n1 basename \
+        | tr -dc '0-9\n' \
+        | sort -n
+}
+
+function display_temp {
+    dir="$1"
+    n="$2"
+
+    set +e
+
+    if test -e "/sys/class/hwmon/$dir/temp${n}_label"; then
+        paste \
+            <(cat "/sys/class/hwmon/$dir/temp${n}_label") \
+            <(cat "/sys/class/hwmon/$dir/temp${n}_input" 2>&1)
+    else
+        paste \
+        <(echo '???') \
+        <(cat "/sys/class/hwmon/$dir/temp${n}_input")
+    fi
+
+    set -e
+}
+
+if test -z "${1:-}"; then
+    find_names
+else
+    dir=$(find_names | grep "$1" | cut -d / -f 5)
+
+    find_temp_numbers "$dir" | while read -r n; do
+        display_temp "$dir" "$n"
+    done
+    # paste \
+    #     <(grep . "/sys/class/hwmon/$dir/"temp*_label | sort) \
+    #     <(grep . "/sys/class/hwmon/$dir/"temp*_input | sort) \
+    #     | tr -s ':' ' ' \
+    #     | awk '/Core/ {print $2, $3, $5/1000.0 }' \
+    #     | sort -n -k2
+fi
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/tqn	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+/home/sjl/bin/lqn.core --script ~/src/lqn/bin/tqn-sh.lisp "$@"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/trst	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+tr ' ' $'\t'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/tsv2csv	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,20 @@
+#!/usr/bin/env python3
+
+import sys
+import csv
+
+def run_file(f):
+    w = csv.writer(sys.stdout)
+    for row in csv.reader(f, delimiter='\t'):
+        w.writerow(row)
+
+def run(paths):
+    for path in paths:
+        if path == '-':
+            run_file(sys.stdin)
+        else:
+            with open(path) as f:
+                run_file(f)
+
+if __name__ == '__main__':
+    run(sys.argv[1:] or ['-'])
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/tv	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+column -s $'\t' -t "$@"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/tvs	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+csvconcat "$@" | tv
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/um-pass	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+echo Retrieving password, press yubikey... > /dev/tty
+pass show umich.edu/slosh | head -1 | tr -d '\n'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/unboxify	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,20 @@
+#!/usr/bin/env python3
+
+import sys
+
+for line in sys.stdin:
+    for ch in line:
+        sys.stdout.write({
+            '┤': '+',
+            '├': '+',
+            '┴': '+',
+            '┬': '+',
+            '┼': '+',
+            '└': '+',
+            '┘': '+',
+            '┐': '+',
+            '┌': '+',
+            '│': '|',
+            '—': '-',
+        }.get(ch, ch))
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/watch-latex	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+NAME="$1"
+shift
+
+echo "$NAME".tex "$@" | peat "mklatex $NAME"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/watch-xelatex	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+NAME="$1"
+shift
+
+echo "$NAME".tex "$@" | peat "mkxelatex $NAME"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/watch-xelatex-bib	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+NAME="$1"
+shift
+
+echo "$NAME".tex "$@" | peat "mkxelatex-bib $NAME"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bootstrap.markdown	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,177 @@
+# Bootstrapping a New Machine
+
+Notes for myself for the next time I set up a new laptop or whatever.
+
+First, back up everything if necessary:
+
+* Faster on wired ethernet.
+* Don't forget password-store!
+* Don't forget hidden dotfiles!
+
+Install Debian.  Partition everything, install nothing.
+
+Goal is now to get the dotfiles repo onto the machine and have everything
+required to run `bootstrap.sh`.
+
+Boot into a command line as root (no `sudo` yet):
+
+    apt install sudo
+    usermod -a -G sudo sjl
+    usermod -a -G netdev sjl
+
+Allow non-free packages:
+
+    vim /etc/apt/sources.list
+
+    edit to add non-free to lines, e.g.:
+    deb http://http.us.debian.org/debian bookworm main contrib non-free
+
+    apt update
+
+Install first round of stuff:
+
+    apt install \
+        nmtui sudo make
+        curl wget network-manager
+        fish rlwrap
+        mercurial git
+        xcape
+        pulseaudio alsa-utils
+        libzstd-dev libx11-dev libxft-dev
+        build-essential autoconf pkg-config
+        bzip2 udisks2 lm-sensors htop
+        fonts-ubuntu w3m atool psmisc
+        suckless-tools trash-cli
+        dunst neovim python3-dulwich
+        silversearcher-ag
+        irssi
+
+TODO: `xautolock` is no longer in the Debian repos.  Figure out how to get it.
+
+TODO: Need to disable networkd, remove interface from `/etc/interfaces` and
+restart `NetworkManager` to get that in place.
+
+Set up `.ssh` and keys.
+
+Bootstrap lisp:
+
+    sudo apt install sbcl
+    git clone https://github.com/sbcl/sbcl
+    git checkout sbcl-2.3.6
+    SBCL_MAKE_JOBS=-j8 SBCL_MAKE_PARALLEL=8 sh make.sh --with-core-compression
+    sudo sh install.sh
+    sudo apt remove sbcl
+
+    curl -O https://beta.quicklisp.org/quicklisp.lisp
+    sbcl --load quicklisp.lisp
+    (quicklisp-quickstart:install :path "/home/sjl/.quicklisp/")
+
+    TODO CCL and others
+
+Get dotfiles:
+
+    hg clone ssh://hsl/repos/dotfiles
+    cd dotfiles
+    ./bin/bootstrap.sh
+
+    TODO build hg from scratch to get hg-git working maybe
+    TODO bootstrap script might fuck up the fish config because shit's already created, thanks a lot fish
+
+    git clone https://github.com/sjl/z-fish
+
+`chsh` to `/usr/bin/fish`
+
+Clone repos:
+
+    hg clone hsl://adopt adopt
+    cd adopt
+    llp
+    cd ..
+
+    hg-clone-llp for lisp
+
+    others:
+
+    1am
+    adopt
+    beast
+    bobbin
+    boots
+    cacl
+    chancery
+    cl-d-api
+    cl-digraph
+    cl-losh
+    cl-netpbm
+    cl-pcg
+    conserve
+    d
+    dbvolve
+    docs.stevelosh.com
+    friendly-find
+    jarl
+    peat
+    t
+
+    …etc etc
+
+
+Build st:
+
+    git clone https://github.com/sjl/st
+
+Get a desktop environment up and running:
+
+    sudo apt install xinit xdm
+
+    git clone https://github.com/stumpwm/stumpwm
+
+    (ql:quickload "clx")
+    (ql:quickload "cl-ppcre")
+    (ql:quickload "alexandria")
+    (ql:quickload "xembed")
+
+    ./autogen.sh
+    ./configure
+    make
+    sudo make install
+
+    cd ~/.stumpwm.d/
+    git clone https://github.com/sjl/stumpwm-contrib modules
+    cd modules
+    git co sjl
+
+Set up `pass`:
+
+    git clone sjl@redacted:password-store .password-store
+    sudo apt install pass scdaemon pinentry-gtk2
+    gpg -a --export 5D... > public-key.txt
+    copy over
+    gpg --import < public-key.txt
+
+Pulseaudio crap:
+
+    sudo apt install pulseaudio alsa-utils
+    pulseaudio --start
+    alsamixer
+    (m to unmute, up/down to change vol)
+
+Set up Firefox:
+
+    wget 'https://download.mozilla.org/?product=firefox-latest-ssl&os=linux64&lang=en-US'
+    aunpack fi…
+    mv firefox /opt/
+    cd /opt/
+    chown -R root:root firefox
+    ln -s /opt/firefox/firefox /usr/local/bin/firefox 
+    sudo apt install libgtk-3-0 libdbus-glib-1-2
+    finish the rest of this garbage https://wiki.debian.org/Firefox#Hardware_Video_Acceleration
+
+VLC (installs the world, sigh):
+
+    sudo apt install vlc
+    View > Docked Playlist to unbreak the playlist
+
+UM VPN:
+
+    TODO
--- a/dunstrc	Sun May 18 14:59:11 2025 -0400
+++ b/dunstrc	Wed Aug 27 16:19:24 2025 -0400
@@ -22,6 +22,7 @@
     browser = xdg-open
     frame_width = 1
     frame_color = "#000000"
+    icon_position = off
 
 [urgency_low]
     background = "#ffffff"
--- a/fish/config.fish	Sun May 18 14:59:11 2025 -0400
+++ b/fish/config.fish	Wed Aug 27 16:19:24 2025 -0400
@@ -6,13 +6,14 @@
 function eg; nvim ~/.gitconfig; end
 function eh; nvim ~/.hgrc; end
 function ei; hg -R ~/src/inventory/ pull -u; and nvim ~/src/inventory/inventory.markdown; and hg -R ~/src/inventory/ ci -m 'Update inventory'; and hg -R ~/src/inventory/ push; end
-function el; cd ~/Dropbox/life; nvim .; end
 function em; nvim ~/.mutt/muttrc; end
-function es; pushd ~/src/stumpwm; e ~/.stumpwmrc; popd; end
+function es; pushd ~/src/dotfiles/stumpwm; e stumpwmrc; popd; end
 function ev; nvim ~/.vimrc; end
+function evm; nvim ~/.vimrc-minimal; end
 function eff; nvim ~/.config/fish/functions; end
 function efh; nvim ~/.local/share/fish/fish_history; end # I have visited https://github.com/fish-shell/fish-shell/issues/862 nine thousand times and I'm fucking sick of opening a web browser to figure out where the fuck fish keeps its equivalent of ~/.bash_history
 function essh; nvim ~/.ssh/config; end
+function est; nvim ~/Sync/school/TODO; end
 
 function js; cd ~/scratch; end
 function jd; cd /dump; end
@@ -33,6 +34,10 @@
     end
 end
 
+function mcd --argument dir
+    mkdir "$dir"; and cd "$dir"
+end
+
 alias sin singularity
 alias sm snakemake
 
@@ -46,6 +51,7 @@
 
 abbr --add ai "sudo apt install"
 abbr --add sc "sudo systemctl"
+abbr --add paper --set-cursor=% "cp -t . ~/Sync/papers/files/%"
 
 # }}}
 # Completions {{{
@@ -62,6 +68,7 @@
 complete -c ew -w which
 complete -c h -w hg
 complete -c g -w git
+complete -c hl -w less
 
 # }}}
 # Bind Keys {{{
@@ -108,6 +115,7 @@
 prepend_to_path "$HOME/src/hg"
 prepend_to_path "$HOME/bin"
 prepend_to_path "$HOME/.go/bin"
+prepend_to_path "$HOME/bin/jdk-21.0.2/bin"
 
 set BROWSER open
 
@@ -118,6 +126,8 @@
 set -g -x PAGER 'less -iX'
 # set -g -x _JAVA_OPTIONS "-Djava.awt.headless=true"
 
+set -g -x JAVA_HOME "$HOME/bin/jdk-21.0.2/"
+
 set -g -x GPG_TTY (tty)
 
 # Less Colors for Man Pages
@@ -193,9 +203,23 @@
     end
 end
 
+function hg-managed
+    set --local d "$(pwd)"
+    while test "/" != "$d"
+        if test -d "$d/.hg"
+            return 0
+        end
+        set d (path dirname "$d")
+    end
+
+    return 1
+end
+
 function hg_prompt
     if test -z "$NOVCSPROMPT"
-        hg prompt --angle-brackets $hg_promptstring 2>/dev/null
+        if hg-managed
+            hg prompt --angle-brackets $hg_promptstring 2>/dev/null
+        end
     end
 end
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fish/functions/el.fish	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,8 @@
+function el -d "Edit lab notes"
+    pushd ~/lab
+    ./pull.sh
+    nvim README.markdown
+    hg ci -m 'Update'
+    ./push.sh
+    popd
+end
--- a/fish/functions/eln.fish	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-function ep -d "Edit lab notes"
-    pushd ~/lab
-    hg pull -u
-    nvim README.markdown
-    hg ci -m 'Update'
-    hg push
-    popd
-end
--- a/fish/functions/mutt.fish	Sun May 18 14:59:11 2025 -0400
+++ b/fish/functions/mutt.fish	Wed Aug 27 16:19:24 2025 -0400
@@ -1,5 +1,5 @@
 set -g -x MUTT_BIN (which neomutt)
 
 function mutt
-    bash --login -c "cd ~/Downloads; $MUTT_BIN \$@" custom_mutt $argv
+    bash --login -c "cd ~/downloads; $MUTT_BIN \$@" custom_mutt $argv
 end
--- a/fish/functions/pj.fish	Sun May 18 14:59:11 2025 -0400
+++ b/fish/functions/pj.fish	Wed Aug 27 16:19:24 2025 -0400
@@ -1,9 +1,9 @@
 function pj -d "Prettify JSON"
     if test "$argv[1]" = "-C"
         # no color
-        python -m json.tool
+        python3 -m json.tool
     else
-        python -m json.tool | pygmentize -l json
+        python3 -m json.tool | pygmentize -l json
     end
 end
 
--- a/gitconfig	Sun May 18 14:59:11 2025 -0400
+++ b/gitconfig	Wed Aug 27 16:19:24 2025 -0400
@@ -94,6 +94,7 @@
     fuom = "!sh -c 'git co $(git mainbranch) && git fo && git uo' -"
     fuum = "!sh -c 'git co $(git mainbranch) && git fu && git uu' -"
     rom =  "!sh -c 'git fo && git rebase origin/$(git mainbranch)' -"
+    jom =  "!sh -c 'git join origin/$(git mainbranch)' -"
     recon =  rebase --continue
 
     addremove = !git add . && git add -u
@@ -138,20 +139,28 @@
     path = ~/.gitconfig_local
 
 [credential]
-	helper = store
-	useHttpPath = true
+    helper = store
+    useHttpPath = false
+
+[credential "https://github.com"]
+    username = sjl
 
 [sendemail]
     smtpserver = "/home/sjl/src/dotfiles/bin/msmtp-stevelosh"
 
 [pretty]
     bazel = %C(auto)%H %Cgreen%cd %C(auto)%s %Cred%al %C(auto)%d
+
 [pull]
 	ff = only
+
 [filter "lfs"]
 	clean = git-lfs clean -- %f
 	smudge = git-lfs smudge -- %f
 	process = git-lfs filter-process
 	required = true
+
 [diff "lfs"]
 	textconv = cat
+[init]
+	defaultBranch = main
--- a/gitignore	Sun May 18 14:59:11 2025 -0400
+++ b/gitignore	Wed Aug 27 16:19:24 2025 -0400
@@ -17,5 +17,9 @@
 *.dx64fsl
 .sjl-rsync-exclude
 sjl-jupyter
+sjl-sync-*.sh
+sjl-*-push.sh
+sjl-*-pull.sh
+*.sif
 
 *.waiting
--- a/hgignore	Sun May 18 14:59:11 2025 -0400
+++ b/hgignore	Wed Aug 27 16:19:24 2025 -0400
@@ -8,3 +8,4 @@
 *.dx64fsl
 *.lx64fsl
 .notmylispwords
+.ipynb_checkpoints
--- a/hgrc	Sun May 18 14:59:11 2025 -0400
+++ b/hgrc	Wed Aug 27 16:19:24 2025 -0400
@@ -125,9 +125,11 @@
 
 # Some useful little aliases.
 st = status -SC
+stl = !"$HG" status -SC --color=always | less -SR
 ? = summary
 fdiff = diff -U 10000000000000
 
+
 # Push to the git mirror at the same time.
 pg = !"$HG" push; "$HG" push git
 
@@ -136,10 +138,9 @@
 
 # Commit message shortcuts.
 cm = commit -m
-cus = commit -m 'Update subrepository state'
-cmm = commit -m 'Merge'
-ct = commit -m 'Update TODO' TODO .TODO.done
-cb = commit -m 'Close branch' --close-branch
+cmore  = commit -m 'More'
+cmerge = commit -m 'Merge'
+cus    = commit -m 'Update subrepository state'
 
 # Merge shortcuts.
 mergelocal = !$HG --config ui.merge=internal:local merge $@
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htoprc	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,63 @@
+# Beware! This file is rewritten by htop when settings are changed in the interface.
+# The parser is also very primitive, and not human-friendly.
+htop_version=3.2.2
+config_reader_min_version=3
+fields=0 48 17 18 39 2 46 47 49 1
+hide_kernel_threads=0
+hide_userland_threads=1
+hide_running_in_container=0
+shadow_other_users=0
+show_thread_names=0
+show_program_path=1
+highlight_base_name=1
+highlight_deleted_exe=1
+shadow_distribution_path_prefix=0
+highlight_megabytes=1
+highlight_threads=1
+highlight_changes=1
+highlight_changes_delay_secs=5
+find_comm_in_cmdline=1
+strip_exe_from_cmdline=1
+show_merged_command=0
+header_margin=1
+screen_tabs=1
+detailed_cpu_time=0
+cpu_count_from_one=0
+show_cpu_usage=1
+show_cpu_frequency=0
+show_cpu_temperature=0
+degree_fahrenheit=0
+update_process_names=0
+account_guest_in_cpu_meter=0
+color_scheme=0
+enable_mouse=1
+delay=10
+hide_function_bar=0
+header_layout=two_50_50
+column_meters_0=LeftCPUs2 Memory Swap
+column_meter_modes_0=1 1 1
+column_meters_1=RightCPUs2 Tasks LoadAverage Uptime
+column_meter_modes_1=1 2 2 2
+tree_view=1
+sort_key=46
+tree_sort_key=0
+sort_direction=-1
+tree_sort_direction=1
+tree_view_always_by_pid=1
+all_branches_collapsed=0
+screen:Main=PID USER PRIORITY NICE M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command
+.sort_key=PERCENT_CPU
+.tree_sort_key=PID
+.tree_view=1
+.tree_view_always_by_pid=1
+.sort_direction=-1
+.tree_sort_direction=1
+.all_branches_collapsed=0
+screen:I/O=PID USER IO_PRIORITY IO_RATE IO_READ_RATE IO_WRITE_RATE PERCENT_SWAP_DELAY PERCENT_IO_DELAY Command
+.sort_key=IO_RATE
+.tree_sort_key=PID
+.tree_view=0
+.tree_view_always_by_pid=0
+.sort_direction=-1
+.tree_sort_direction=1
+.all_branches_collapsed=0
--- a/lisp/batchcolor.lisp	Sun May 18 14:59:11 2025 -0400
+++ b/lisp/batchcolor.lisp	Wed Aug 27 16:19:24 2025 -0400
@@ -10,6 +10,7 @@
 ;;;; Configuration ------------------------------------------------------------
 (defparameter *start* 0)
 (defparameter *dark* t)
+(defparameter *multi* t)
 
 
 ;;;; Errors -------------------------------------------------------------------
@@ -67,10 +68,14 @@
 
 (defun find-color (string)
   (gethash string *explicits*
-           (let ((colors (if *dark* *dark-colors* *light-colors*)))
-             (aref colors
-                   (mod (+ (djb2 string) *start*)
-                        (length colors))))))
+           (if *multi*
+             (let ((colors (if *dark* *dark-colors* *light-colors*)))
+               (aref colors
+                     (mod (+ (djb2 string) *start*)
+                          (length colors))))
+             (if *dark*
+               (rgb-code 5 2 3)
+               (rgb-code 2 0 2)))))
 
 (defun ansi-color-start (color)
   (format nil "~C[38;5;~Dm" #\Escape color))
@@ -128,6 +133,13 @@
     (return-from parse-explicit (cons string (rgb-code r g b))))
   (error 'malformed-explicit :spec spec))
 
+(adopt:defparameters (*option-multicolor* *option-no-multicolor*)
+  (adopt:make-boolean-options 'multicolor
+    :help "Use different colors for different matches (the default)."
+    :help-no "Use a single color for everything (a useful kludge)."
+    :long "multicolor"
+    :short #\m
+    :initial-value t))
 
 (adopt:defparameters (*option-randomize* *option-no-randomize*)
   (adopt:make-boolean-options 'randomize
@@ -187,6 +199,9 @@
   "If no FILEs are given, standard input will be used.  A file of - stands for ~
    standard input as well.~@
    ~@
+   As a handy kludge, the --no-multicolor option can be used to highlight ~
+   everything in a single color.~@
+   ~@
    Overlapping capturing groups are not supported because it's not clear what ~
    the result should be.  For example: what should ((f)oo|(b)oo) highlight when ~
    matched against 'foo'?  Should it highlight 'foo' in one color?  The 'f' in ~
@@ -224,7 +239,9 @@
                                                  *option-no-randomize*
                                                  *option-dark*
                                                  *option-light*
-                                                 *option-explicit*)))))
+                                                 *option-explicit*
+                                                 *option-multicolor*
+                                                 *option-no-multicolor*)))))
 
 
 (defmacro exit-on-ctrl-c (&body body)
@@ -237,7 +254,8 @@
   (setf *start* (if (gethash 'randomize options)
                   (random 256 (make-random-state t))
                   0)
-        *dark* (gethash 'dark options)))
+        *dark* (gethash 'dark options)
+        *multi* (gethash 'multicolor options)))
 
 (defun toplevel ()
   (sb-ext:disable-debugger)
--- a/lisp/clhs.lisp	Sun May 18 14:59:11 2025 -0400
+++ b/lisp/clhs.lisp	Wed Aug 27 16:19:24 2025 -0400
@@ -77,7 +77,8 @@
                            :direction :output
                            :if-exists :supersede
                            :if-does-not-exist :create)
-        (prin1 symbols out))
+        (let ((*print-length* nil))
+          (prin1 symbols out)))
       symbols)))
 
 (defun symbol-map ()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/parens.lisp	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,58 @@
+(eval-when (:compile-toplevel :load-toplevel :execute)
+  (ql:quickload
+    '(:adopt :losh :parenscript)
+    :silent t))
+
+(defpackage :parens
+  (:use :cl :iterate :losh)
+  (:export :toplevel :*ui*))
+
+(defpackage :parenscript-user
+  (:use :cl :parenscript))
+
+(in-package :parens)
+
+;;;; Run ----------------------------------------------------------------------
+(defun compile-with-parenscript (in-stream out-stream)
+  (let ((parenscript:*parenscript-stream* out-stream))
+    (ps:ps-compile-stream in-stream)))
+
+(defun run ()
+  (compile-with-parenscript *standard-input* *standard-output*))
+
+
+;;;; User Interface -----------------------------------------------------------
+(defparameter *option-help*
+  (adopt:make-option 'help
+    :help "Display help and exit."
+    :long "help"
+    :short #\h
+    :reduce (constantly t)))
+
+
+(adopt:define-string *help-text*
+  "parens is a helper program to invoke the parenscript compiler for standalone ~
+   files, so you can just use parenscript without the Common Lisp bits.")
+
+(defparameter *examples*
+  '(("Compile:" . "cat foo.lisp | parens > out.js")))
+
+(defparameter *ui*
+  (adopt:make-interface
+    :name "parens"
+    :usage "[OPTIONS]"
+    :summary "standalone parenscript compiler"
+    :help *help-text*
+    :examples *examples*
+    :contents (list *option-help*)))
+
+
+(defun toplevel ()
+  (multiple-value-bind (arguments options) (adopt:parse-options-or-exit *ui*)
+    (handler-case
+        (if (gethash 'help options)
+          (adopt:print-help-and-exit *ui*)
+          (progn (assert (null arguments))
+                 (run)))
+      (error (e) (adopt:print-error-and-exit e)))))
+
--- a/lisp/weather.lisp	Sun May 18 14:59:11 2025 -0400
+++ b/lisp/weather.lisp	Wed Aug 27 16:19:24 2025 -0400
@@ -1,6 +1,6 @@
 (eval-when (:compile-toplevel :load-toplevel :execute)
   (ql:quickload
-    '(:adopt :with-user-abort :split-sequence :iterate :losh :drakma :jarl :flexi-streams :local-time)
+    '(:adopt :with-user-abort :split-sequence :iterate :losh :drakma :jarl :flexi-streams :local-time :safe-read)
     :silent t))
 
 (defpackage :weather
@@ -10,6 +10,10 @@
 
 (in-package :weather)
 
+(defpackage :conf
+  (:use)
+  (:export :zip-code))
+
 
 ;;;; Zip Codes ----------------------------------------------------------------
 (defun load-zip-code-coordinates ()
@@ -28,27 +32,27 @@
 
 ;;;; Config -------------------------------------------------------------------
 (defvar *api-token* nil)
+(defvar *zip-code* nil)
 
 (defun load-config ()
   (let* ((token-paths (uiop:xdg-config-pathnames "weather/api-token"))
          (config-paths (uiop:xdg-config-pathnames "weather/config"))
          (token-path (find-if #'probe-file token-paths))
          (config-paths (reverse (remove-if-not #'probe-file config-paths))))
-    (declare (ignore config-paths)) ; TODO
     (if (null token-path)
       (error "Cannot find API token file at any of the following paths:~2%~
               ~{    ~A~%~}~%~
               Visit https://home.openweathermap.org/api_keys to get one."
              token-paths)
       (setf *api-token* (string-trim '(#\space #\newline)
-                                     (alexandria:read-file-into-string token-path))))))
+                                     (alexandria:read-file-into-string token-path))))
+    (dolist (config-path config-paths)
+      (with-open-file (f config-path)
+        (let ((config (safe-read:safe-read f (list :conf))))
+          (setf *zip-code* (getf config 'conf:zip-code *zip-code*)))))))
 
 
 ;;;; OpenWeatherMap -----------------------------------------------------------
-;;; TODO Switch to weather.gov some day to get my taxes' worth, e.g.
-;;; https://forecast.weather.gov/MapClick.php?lat=43.1577&lon=-77.6066&FcstType=digitalDWML
-;;; Sadly this is XML.
-
 (defclass* response ()
   ((hourly :json (vector hour)))
   (:metaclass jarl:json-class))
@@ -84,8 +88,9 @@
 
 
 (defun query-weather (latitude longitude)
+  (assert *api-token* () "API token not loaded, call (load-config) first.")
   (multiple-value-bind (body status headers uri stream needs-close reason)
-      (drakma:http-request (format nil "https://api.openweathermap.org/data/2.5/onecall")
+      (drakma:http-request (format nil "https://api.openweathermap.org/data/3.0/onecall")
                            :redirect t
                            :parameters `(("lat" . ,latitude)
                                          ("lon" . ,longitude)
@@ -105,13 +110,16 @@
 
 (defun display-hour (hour &optional force-date)
   (let* ((ts (timestamp hour))
-         (h (local-time:timestamp-hour ts)))
-    (format t "~12A ~2,'0D:00 ~4D°F ~4D%  ~{~A~^, ~}~%"
+         (h (local-time:timestamp-hour ts))
+         (temperature (temperature hour))
+         (feels-like (feels-like hour)))
+    (format t "~12A ~2,'0D:00 ~4D°F ~4D°f ~4D%  ~{~A~^, ~}~%"
             (if (or force-date (zerop h))
               (ymd ts)
               "")
             h
-            (round (temperature hour))
+            (round temperature)
+            (round feels-like)
             (round (* 100 (precipitation hour)))
             (map 'list #'description (weather hour)))))
 
@@ -179,7 +187,7 @@
             (adopt:print-help-and-exit *ui*)
             (progn
               (load-config)
-              (run (or (first arguments) "48105")
+              (run (or (first arguments) *zip-code*)
                    :hours (gethash 'hours options))))
         (error (e) (adopt:print-error-and-exit e))))))
 
--- a/lisprc	Sun May 18 14:59:11 2025 -0400
+++ b/lisprc	Wed Aug 27 16:19:24 2025 -0400
@@ -22,6 +22,9 @@
      (defglobal ,var nil)
      (setf ,var ,val)))
 
+(defun :vlime ()
+  (load "~/src/dotfiles/vim/bundle/vlime/lisp/start-vlime.lisp"))
+
 
 ;;;; Scratch Marker -----------------------------------------------------------
 (defun sharp-semicolon-reader (stream sub-char numarg)
--- a/lispwords	Sun May 18 14:59:11 2025 -0400
+++ b/lispwords	Wed Aug 27 16:19:24 2025 -0400
@@ -92,6 +92,7 @@
 (1 multiple-value-bind*)
 (1 do-repeat do-range do-irange do-ring-buffer do-vector do-file do-hash-set)
 (1 timing profile-when)
+(1 recase)
 
 ; qtools
 (1 qtenumcase)
@@ -117,7 +118,7 @@
 
 ; ppcre
 (2 register-groups-bind do-register-groups)
-(1 do-scans)
+(1 do-scans do-matches)
 
 ; lparallel
 (1 pdotimes)
@@ -145,6 +146,19 @@
 
 ; boots
 (1 event-case)
+(2 canvas)
 
 ; parsnip
 (1 let!)
+
+; sqlite
+(1 execute-non-query execute-script execute-to-list execute-single execute-non-query/named execute-single/named execute-to-list/named)
+
+; easy-routes
+(3 defroute)
+
+; djula
+(2 render-template*)
+
+; cl-who
+(1 who)
Binary file man/index.db has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mbsyncrc	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,81 @@
+# tweaked https://raw.githubusercontent.com/bm4cs/dots/Far/.mbsyncrc
+
+IMAPAccount fastmail
+Host imap.fastmail.com
+Port 993
+User steve@stevelosh.com
+PassCmd "pass show mutt/ouroboros/steve@stevelosh.com | head -n 1"
+SSLType IMAPS
+SSLVersion TLSv1.2
+CertificateFile /etc/ssl/certs/ca-certificates.crt
+
+# Remote
+IMAPStore mail-remote
+Account fastmail
+
+# Local
+MaildirStore mail-local
+Subfolders Verbatim
+Path ~/.mail2/sjl/
+Inbox ~/.mail2/sjl/INBOX
+
+# CONNECTIONS SPECIFY LINKS BETWEEN REMOTE AND LOCAL FOLDERS
+# CONNECTIONS ARE SPECIFIED USING PATTERNS, WHICH MATCH REMOTE MAIl
+# FOLDERS. SOME COMMONLY USED PATTERS INCLUDE:
+#
+# 1 "*" TO MATCH EVERYTHING
+# 2 "!DIR" TO EXCLUDE "DIR"
+# 3 "DIR" TO MATCH DIR
+
+Channel mail-inbox
+Far :mail-remote:
+Near :mail-local:
+Patterns INBOX
+Create Near
+Sync All
+Expunge Both
+SyncState *
+
+Channel mail-sent
+Far :mail-remote:
+Near :mail-local:
+Patterns "Sent"
+Create Both
+Sync All
+Expunge Both
+SyncState *
+
+Channel mail-spam
+Far :mail-remote:
+Near :mail-local:
+Patterns "Uncaught Spam"
+Create Near
+Sync All
+Expunge Both
+SyncState *
+
+Channel mail-drafts
+Far :mail-remote:
+Near :mail-local:
+Patterns "Drafts"
+Create Near
+Sync All
+Expunge Both
+SyncState *
+
+Channel mail-archive
+Far :mail-remote:
+Near :mail-local:
+Patterns "Arc*"
+Create Near
+Sync All
+Expunge Both
+SyncState *
+
+Group mail
+Channel mail-inbox
+Channel mail-spam
+Channel mail-drafts
+Channel mail-sent
+Channel mail-archive
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/md-to-html/Makefile	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,6 @@
+%.html: %.markdown template.html Makefile
+	pandoc \
+	  --template template.html \
+	  --table-of-contents \
+	  -f markdown -t html \
+	  "$<" > "$@"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/md-to-html/foo.html	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,57 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <title>Some Test</title>
+    <style type="text/css">
+        body {
+            margin:40px auto;
+            max-width: 800px;
+            line-height:1.5;
+            font-size:16px;
+            color: #222;
+            padding:0 10px;
+            font-family: serif;
+        }
+        h1,h2,h3 {
+            line-height:1.2
+        }
+        code {
+            font-size: 14px;
+            background-color: #eee;
+            border: 1px solid #ccc;
+            padding: 1px;
+        }
+        pre code {
+            border: none;
+            padding: none;
+        }
+        pre {
+            font-size: 14px;
+            background-color: #eee;
+            border: 1px solid #ccc;
+            padding: 1px;
+            overflow-x: auto;
+            margin: 24px 20px 24px 0px;
+            padding: 4px 10px;
+        }
+        a {
+            color: #e50053;
+        }
+
+        h1 {
+            text-align: center;
+        }
+        </style>
+  </head>
+  <body>
+      <h1>Some Test</h1>
+<ul>
+<li><a href="#example">Example</a></li>
+</ul>
+<h2 id="example">Example</h2>
+<p>This is a test.</p>
+<pre><code>Some code.</code></pre>
+<p>Done testing.</p>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/md-to-html/foo.markdown	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,11 @@
+---
+title: Some Test
+---
+
+## Example
+
+This is a test.
+
+    Some code.
+
+Done testing.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/md-to-html/template.html	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,63 @@
+<!doctype html>
+<html lang="en">
+    <head>
+        <meta charset="utf-8">
+        <title>$title$</title>
+        <style type="text/css">
+            body {
+                margin:40px auto;
+                max-width: 800px;
+                line-height:1.5;
+                font-size:16px;
+                color: #222;
+                padding:0 10px;
+                font-family: serif;
+            }
+            h1,h2,h3 {
+                line-height:1.2
+            }
+            code {
+                font-size: 14px;
+                background-color: #eee;
+                border: 1px solid #ccc;
+                padding: 1px 4px;
+            }
+            pre code {
+                border: 0;
+                padding: 0;
+            }
+            pre {
+                font-size: 14px;
+                background-color: #eee;
+                border: 1px solid #ccc;
+                padding: 1px;
+                overflow-x: auto;
+                margin: 24px 20px 24px 0px;
+                padding: 4px 10px;
+            }
+            a {
+                color: #e50053;
+            }
+
+            h1 {
+                text-align: center;
+            }
+            #table-of-contents > ul {
+                padding-left: 0;
+            }
+            #table-of-contents li {
+                list-style-type: none;
+            }
+        </style>
+    </head>
+    <body>
+        <h1>$title$</h1>
+        <details>
+            <summary>Table of contents</summary>
+            <div id="table-of-contents">$toc$</div>
+        </details>
+
+$body$
+
+    </body>
+</html>
--- a/remote/bash_profile	Sun May 18 14:59:11 2025 -0400
+++ b/remote/bash_profile	Wed Aug 27 16:19:24 2025 -0400
@@ -8,6 +8,7 @@
 
 shopt -s expand_aliases
 shopt -s histappend
+shopt -s checkwinsize
 
 # Save multiline commands as a single history entry.
 shopt -s cmdhist
@@ -23,12 +24,12 @@
     eval "$(dircolors -b ~/.dircolors)"
 fi
 
-D=$'\e[37m'
-RED=$'\e[31m'
-GREEN=$'\e[32m'
-ORANGE=$'\e[33m'
-BLUE=$'\e[34m'
-PINK=$'\e[35m'
+D=$'\x01\e[37m\x02'
+RED=$'\x01\e[31m\x02'
+GREEN=$'\x01\e[32m\x02'
+ORANGE=$'\x01\e[33m\x02'
+BLUE=$'\x01\e[34m\x02'
+PINK=$'\x01\e[35m\x02'
 # CYAN=$'\e[36m'
 
 function last_return_value() {
@@ -42,13 +43,22 @@
     history | grep "$@" | tac | f 2-
 }
 
+function mcd {
+    mkdir "$1" && cd "$1"
+}
+
 if [ -n "$SSH_CLIENT" ] || [ -n "$SSH_TTY" ]; then
   HOST_COLOR="$BLUE"
 else
   HOST_COLOR="$ORANGE"
 fi
 
-export PS1='\n${PINK}\u ${D}at ${HOST_COLOR}\h ${D}in ${GREEN}\w${D} $(last_return_value)$ '
+PS1_HOST='\h'
+if test -e ~/.cosmetic_hostname; then
+    PS1_HOST=$(cat ~/.cosmetic_hostname)
+fi
+
+export PS1='\n${PINK}\u ${D}at ${HOST_COLOR}'"$PS1_HOST"' ${D}in ${GREEN}\w${D} $(last_return_value)$ '
 
 alias ..="cd .."
 alias ...="cd ../.."
@@ -56,8 +66,6 @@
 alias .....="cd ../../../.."
 alias ......="cd ../../../../.."
 
-alias js='cd ~/scratch'
-
 alias :q=exit
 alias :qa=exit
 alias :wqa=exit
@@ -101,3 +109,12 @@
 export GPG_TTY
 
 export EDITOR=vim
+
+export _Z_CMD=j
+export _Z_DATA=$HOME/.z-bash
+
+. $HOME/src/dotfiles/z.sh
+
+if [ -f $HOME/.bash_profile_sjl_local ]; then
+    source $HOME/.bash_profile_sjl_local
+fi
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remote/bin/arblogs	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+sudo journalctl -fu arb_sequencing_worker "$@" \
+    | batchcolor '(ERROR|WARNING|INFO|[0-9]{8}_[0-9]{4}_[-A-Z0-9]+_[A-Z0-9]+_[a-z0-9]{8}|Checking for.* in )' \
+    | cut -d ' ' -f 6-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remote/bin/bclogs	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+sudo journalctl -fu arb_basecalling_worker "$@" \
+    | batchcolor '(ERROR|WARNING|INFO|[0-9]{8}_[0-9]{4}_[-A-Z0-9]+_[A-Z0-9]+_[a-z0-9]{8}|Checking for.* in )' \
+    | cut -d ' ' -f 6-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remote/bin/cax	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,1 @@
+../../bin/cax
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remote/bin/csv2tsv	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,1 @@
+../../bin/csv2tsv
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remote/bin/csvconcat	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,1 @@
+../../bin/csvconcat
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remote/bin/cux	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,1 @@
+../../bin/cux
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remote/bin/cv	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,1 @@
+../../bin/cv
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remote/bin/cvs	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,1 @@
+../../bin/cvs
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remote/bin/f,	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,1 @@
+../../bin/f,
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remote/bin/fnums	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,1 @@
+../../bin/fnums
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remote/bin/lesss	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,1 @@
+../../bin/lesss
\ No newline at end of file
--- a/remote/bin/lines	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-../../lisp/bin/lines
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remote/bin/minimap2-index	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,1 @@
+../../bin/minimap2-index
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remote/bin/mksb	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,46 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+name="$1"
+
+read -p "Account? "
+acct="$REPLY"
+
+read -p "Hours? "
+hours="$REPLY"
+
+read -p "CPUs? "
+cpus="$REPLY"
+
+read -p "Memory (gb)? "
+mem_gb="$REPLY"
+mem_mb=$(( mem_gb * 1024 ))
+
+cat << EOF > "$name".sbat
+#!/usr/bin/env bash
+
+#SBATCH --job-name=$name
+#SBATCH --mail-type=END
+#SBATCH --nodes=1
+#SBATCH --ntasks-per-node=1
+#SBATCH --cpus-per-task=$cpus
+#SBATCH --mem=$mem_mb
+#SBATCH --time=$hours:00:00
+#SBATCH --account=$acct
+#SBATCH --partition=standard
+#SBATCH --output=slurm-%x-%j.log
+#SBATCH --export=NONE
+
+set -euo pipefail
+
+my_job_header
+echo
+echo Arguments: "\$@"
+echo
+
+EOF
+
+chmod u+x "$name.sbat"
+
+"$EDITOR" "$name.sbat"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remote/bin/nicedate	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,1 @@
+../../bin/nicedate
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remote/bin/ont-basecalling-model-info	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+bam_file="$1"
+
+module load Bioinformatics samtools >/dev/null 2>&1
+
+#     view all the SAM headers
+#     only look at @RG
+#     pull out the basecall_model=… field
+#     coalesce
+
+samtools view -H "$bam_file" \
+    | grep -P '^@RG' \
+    | grep -P -o $'basecall_model=[^ \t]+' \
+    | sort | uniq -c
+
+samtools view -H "$bam_file" \
+    | grep -P '^@RG' \
+    | grep -P -o $'modbase_models=[^ \t]+' \
+    | sort | uniq -c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remote/bin/potential-to-bed	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+cut -f 1-4,8- | awk -F'\t' -v OFS='\t' '{print $1, $2, $3, $4 " " $5}'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remote/bin/run-igv	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+module load openjdk
+~/src/igv/build/IGV-dist/igv.sh
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remote/bin/slurm-shell	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,33 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+read -p "Account? "
+acct="$REPLY"
+
+read -p "Hours? "
+hours="$REPLY"
+
+read -p "CPUs? "
+cpus="$REPLY"
+
+read -p "Memory (gb)? "
+mem_gb="$REPLY"
+mem_kb=$(( mem_gb * 1024 * 1024 / cpus ))
+
+read -p "X11? "
+case $REPLY in
+    y | yes | Y | YES)
+    x11="--x11"
+    ;;
+
+    *)
+    x11=""
+    ;;
+esac
+
+if test -z "$x11"; then
+    exec salloc --account="$acct" --nodes=1 --ntasks-per-node=1 --mem-per-cpu="$mem_kb"K --cpus-per-task="$cpus" --time="$hours":00:00
+else
+    exec salloc --account="$acct" --nodes=1 --ntasks-per-node=1 --mem-per-cpu="$mem_kb"K --cpus-per-task="$cpus" --time="$hours":00:00 --x11
+fi
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remote/bin/sv	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,1 @@
+../../bin/sv
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remote/bin/temps	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,1 @@
+../../bin/temps
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remote/bin/trst	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,1 @@
+../../bin/trst
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remote/bin/tsv2csv	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,1 @@
+../../bin/tsv2csv
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remote/bin/tv	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,1 @@
+../../bin/tv
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remote/bin/tvs	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,1 @@
+../../bin/tvs
\ No newline at end of file
--- a/remote/bootstrap.sh	Sun May 18 14:59:11 2025 -0400
+++ b/remote/bootstrap.sh	Wed Aug 27 16:19:24 2025 -0400
@@ -21,12 +21,21 @@
 
 mkdir -p ~/.config/fish
 mkdir -p ~/.config/nvim
+mkdir -p ~/.config/htop
 mkdir -p ~/bin
+mkdir -p ~/.vim
+mkdir -p ~/.terminfo/s
 
+ensure_link "src/dotfiles/bash_profile"  ".bash_profile"
+ensure_link "src/dotfiles/config.fish"   ".config/fish/config.fish"
+ensure_link "src/dotfiles/dircolors"     ".dircolors"
+ensure_link "src/dotfiles/ffignore"      ".ffignore"
 ensure_link "src/dotfiles/gitconfig"     ".gitconfig"
 ensure_link "src/dotfiles/gitignore"     ".gitignore"
-ensure_link "src/dotfiles/bash_profile"  ".bash_profile"
-ensure_link "src/dotfiles/dircolors"     ".dircolors"
-ensure_link "src/dotfiles/ffignore"      ".ffignore"
-ensure_link "src/dotfiles/config.fish"   ".config/fish/config.fish"
+ensure_link "src/dotfiles/htoprc"        ".config/htop/htoprc"
 ensure_link "src/dotfiles/hushlogin"     ".hushlogin"
+ensure_link "src/dotfiles/st"            ".terminfo/s/st"
+ensure_link "src/dotfiles/st-256color"   ".terminfo/s/st-256color"
+ensure_link "src/dotfiles/vim-colors"    ".vim/colors"
+ensure_link "src/dotfiles/vimrc"         ".vimrc"
+ensure_link "src/dotfiles/vimrc-remote-local" ".vimrc_remote_local"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remote/htoprc	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,1 @@
+../htoprc
\ No newline at end of file
Binary file remote/st has changed
Binary file remote/st-256color has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remote/vim-colors	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,1 @@
+../vim/bundle/badwolf/colors/
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remote/vimrc	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,1 @@
+../vim/vimrc-minimal
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remote/vimrc-remote-local	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,1 @@
+colorscheme badwolf
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remote/z.sh	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,267 @@
+# Copyright (c) 2009 rupa deadwyler. Licensed under the WTFPL license, Version 2
+
+# maintains a jump-list of the directories you actually use
+#
+# INSTALL:
+#     * put something like this in your .bashrc/.zshrc:
+#         . /path/to/z.sh
+#     * cd around for a while to build up the db
+#     * PROFIT!!
+#     * optionally:
+#         set $_Z_CMD in .bashrc/.zshrc to change the command (default z).
+#         set $_Z_DATA in .bashrc/.zshrc to change the datafile (default ~/.z).
+#         set $_Z_MAX_SCORE lower to age entries out faster (default 9000).
+#         set $_Z_NO_RESOLVE_SYMLINKS to prevent symlink resolution.
+#         set $_Z_NO_PROMPT_COMMAND if you're handling PROMPT_COMMAND yourself.
+#         set $_Z_EXCLUDE_DIRS to an array of directories to exclude.
+#         set $_Z_OWNER to your username if you want use z while sudo with $HOME kept
+#
+# USE:
+#     * z foo     # cd to most frecent dir matching foo
+#     * z foo bar # cd to most frecent dir matching foo and bar
+#     * z -r foo  # cd to highest ranked dir matching foo
+#     * z -t foo  # cd to most recently accessed dir matching foo
+#     * z -l foo  # list matches instead of cd
+#     * z -e foo  # echo the best match, don't cd
+#     * z -c foo  # restrict matches to subdirs of $PWD
+#     * z -x      # remove the current directory from the datafile
+#     * z -h      # show a brief help message
+
+[ -d "${_Z_DATA:-$HOME/.z}" ] && {
+    echo "ERROR: z.sh's datafile (${_Z_DATA:-$HOME/.z}) is a directory."
+}
+
+_z() {
+
+    local datafile="${_Z_DATA:-$HOME/.z}"
+
+    # if symlink, dereference
+    [ -h "$datafile" ] && datafile=$(readlink "$datafile")
+
+    # bail if we don't own ~/.z and $_Z_OWNER not set
+    [ -z "$_Z_OWNER" -a -f "$datafile" -a ! -O "$datafile" ] && return
+
+    _z_dirs () {
+        [ -f "$datafile" ] || return
+
+        local line
+        while read line; do
+            # only count directories
+            [ -d "${line%%\|*}" ] && echo "$line"
+        done < "$datafile"
+        return 0
+    }
+
+    # add entries
+    if [ "$1" = "--add" ]; then
+        shift
+
+        # $HOME and / aren't worth matching
+        [ "$*" = "$HOME" -o "$*" = '/' ] && return
+
+        # don't track excluded directory trees
+        if [ ${#_Z_EXCLUDE_DIRS[@]} -gt 0 ]; then
+            local exclude
+            for exclude in "${_Z_EXCLUDE_DIRS[@]}"; do
+                case "$*" in "$exclude"*) return;; esac
+            done
+        fi
+
+        # maintain the data file
+        local tempfile="$datafile.$RANDOM"
+        local score=${_Z_MAX_SCORE:-9000}
+        _z_dirs | awk -v path="$*" -v now="$(date +%s)" -v score=$score -F"|" '
+            BEGIN {
+                rank[path] = 1
+                time[path] = now
+            }
+            $2 >= 1 {
+                # drop ranks below 1
+                if( $1 == path ) {
+                    rank[$1] = $2 + 1
+                    time[$1] = now
+                } else {
+                    rank[$1] = $2
+                    time[$1] = $3
+                }
+                count += $2
+            }
+            END {
+                if( count > score ) {
+                    # aging
+                    for( x in rank ) print x "|" 0.99*rank[x] "|" time[x]
+                } else for( x in rank ) print x "|" rank[x] "|" time[x]
+            }
+        ' 2>/dev/null >| "$tempfile"
+        # do our best to avoid clobbering the datafile in a race condition.
+        if [ $? -ne 0 -a -f "$datafile" ]; then
+            env rm -f "$tempfile"
+        else
+            [ "$_Z_OWNER" ] && chown $_Z_OWNER:"$(id -ng $_Z_OWNER)" "$tempfile"
+            env mv -f "$tempfile" "$datafile" || env rm -f "$tempfile"
+        fi
+
+    # tab completion
+    elif [ "$1" = "--complete" -a -s "$datafile" ]; then
+        _z_dirs | awk -v q="$2" -F"|" '
+            BEGIN {
+                q = substr(q, 3)
+                if( q == tolower(q) ) imatch = 1
+                gsub(/ /, ".*", q)
+            }
+            {
+                if( imatch ) {
+                    if( tolower($1) ~ q ) print $1
+                } else if( $1 ~ q ) print $1
+            }
+        ' 2>/dev/null
+
+    else
+        # list/go
+        local echo fnd last list opt typ
+        while [ "$1" ]; do case "$1" in
+            --) while [ "$1" ]; do shift; fnd="$fnd${fnd:+ }$1";done;;
+            -*) opt=${1:1}; while [ "$opt" ]; do case ${opt:0:1} in
+                    c) fnd="^$PWD $fnd";;
+                    e) echo=1;;
+                    h) echo "${_Z_CMD:-z} [-cehlrtx] args" >&2; return;;
+                    l) list=1;;
+                    r) typ="rank";;
+                    t) typ="recent";;
+                    x) sed -i -e "\:^${PWD}|.*:d" "$datafile";;
+                esac; opt=${opt:1}; done;;
+             *) fnd="$fnd${fnd:+ }$1";;
+        esac; last=$1; [ "$#" -gt 0 ] && shift; done
+        [ "$fnd" -a "$fnd" != "^$PWD " ] || list=1
+
+        # if we hit enter on a completion just go there
+        case "$last" in
+            # completions will always start with /
+            /*) [ -z "$list" -a -d "$last" ] && builtin cd "$last" && return;;
+        esac
+
+        # no file yet
+        [ -f "$datafile" ] || return
+
+        local cd
+        cd="$( < <( _z_dirs ) awk -v t="$(date +%s)" -v list="$list" -v typ="$typ" -v q="$fnd" -F"|" '
+            function frecent(rank, time) {
+              # relate frequency and time
+              dx = t - time
+              return int(10000 * rank * (3.75/((0.0001 * dx + 1) + 0.25)))
+            }
+            function output(matches, best_match, common) {
+                # list or return the desired directory
+                if( list ) {
+                    if( common ) {
+                        printf "%-10s %s\n", "common:", common > "/dev/stderr"
+                    }
+                    cmd = "sort -n >&2"
+                    for( x in matches ) {
+                        if( matches[x] ) {
+                            printf "%-10s %s\n", matches[x], x | cmd
+                        }
+                    }
+                } else {
+                    if( common && !typ ) best_match = common
+                    print best_match
+                }
+            }
+            function common(matches) {
+                # find the common root of a list of matches, if it exists
+                for( x in matches ) {
+                    if( matches[x] && (!short || length(x) < length(short)) ) {
+                        short = x
+                    }
+                }
+                if( short == "/" ) return
+                for( x in matches ) if( matches[x] && index(x, short) != 1 ) {
+                    return
+                }
+                return short
+            }
+            BEGIN {
+                gsub(" ", ".*", q)
+                hi_rank = ihi_rank = -9999999999
+            }
+            {
+                if( typ == "rank" ) {
+                    rank = $2
+                } else if( typ == "recent" ) {
+                    rank = $3 - t
+                } else rank = frecent($2, $3)
+                if( $1 ~ q ) {
+                    matches[$1] = rank
+                } else if( tolower($1) ~ tolower(q) ) imatches[$1] = rank
+                if( matches[$1] && matches[$1] > hi_rank ) {
+                    best_match = $1
+                    hi_rank = matches[$1]
+                } else if( imatches[$1] && imatches[$1] > ihi_rank ) {
+                    ibest_match = $1
+                    ihi_rank = imatches[$1]
+                }
+            }
+            END {
+                # prefer case sensitive
+                if( best_match ) {
+                    output(matches, best_match, common(matches))
+                    exit
+                } else if( ibest_match ) {
+                    output(imatches, ibest_match, common(imatches))
+                    exit
+                }
+                exit(1)
+            }
+        ')"
+
+        if [ "$?" -eq 0 ]; then
+          if [ "$cd" ]; then
+            if [ "$echo" ]; then echo "$cd"; else builtin cd "$cd"; fi
+          fi
+        else
+          return $?
+        fi
+    fi
+}
+
+alias ${_Z_CMD:-z}='_z 2>&1'
+
+[ "$_Z_NO_RESOLVE_SYMLINKS" ] || _Z_RESOLVE_SYMLINKS="-P"
+
+if type compctl >/dev/null 2>&1; then
+    # zsh
+    [ "$_Z_NO_PROMPT_COMMAND" ] || {
+        # populate directory list, avoid clobbering any other precmds.
+        if [ "$_Z_NO_RESOLVE_SYMLINKS" ]; then
+            _z_precmd() {
+                (_z --add "${PWD:a}" &)
+                : $RANDOM
+            }
+        else
+            _z_precmd() {
+                (_z --add "${PWD:A}" &)
+                : $RANDOM
+            }
+        fi
+        [[ -n "${precmd_functions[(r)_z_precmd]}" ]] || {
+            precmd_functions[$(($#precmd_functions+1))]=_z_precmd
+        }
+    }
+    _z_zsh_tab_completion() {
+        # tab completion
+        local compl
+        read -l compl
+        reply=(${(f)"$(_z --complete "$compl")"})
+    }
+    compctl -U -K _z_zsh_tab_completion _z
+elif type complete >/dev/null 2>&1; then
+    # bash
+    # tab completion
+    complete -o filenames -C '_z --complete "$COMP_LINE"' ${_Z_CMD:-z}
+    [ "$_Z_NO_PROMPT_COMMAND" ] || {
+        # populate directory list. avoid clobbering other PROMPT_COMMANDs.
+        grep "_z --add" <<< "$PROMPT_COMMAND" >/dev/null || {
+            PROMPT_COMMAND="$PROMPT_COMMAND"$'\n''(_z --add "$(command pwd '$_Z_RESOLVE_SYMLINKS' 2>/dev/null)" 2>/dev/null &);'
+        }
+    }
+fi
--- a/sqliterc	Sun May 18 14:59:11 2025 -0400
+++ b/sqliterc	Wed Aug 27 16:19:24 2025 -0400
@@ -1,5 +1,7 @@
 .timer on
 .headers on
-.mode tabs
+.mode columns
+
+.prompt "\nsqlite> " "> "
 
 .nullvalue ∅'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stumpwm/applications.lisp	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,37 @@
+(in-package :stumpwm-user)
+
+(defcommand igv () ()
+  (run-or-raise "igv" '(:class "org-broad-igv-ui-Main")))
+
+(defcommand spotify () ()
+  (run-or-raise "spotify" '(:class "Spotify")))
+
+(defcommand zoom-meeting () ()
+  (run-or-raise "echom 'No meeting running.'" '(:class "zoom" :title "Meeting")))
+
+(defcommand files () ()
+  (run-shell-command "open $HOME"))
+
+(defcommand browser () ()
+  (run-or-raise "firefox" '(:class "firefox")))
+
+(defcommand vlc () ()
+  (run-or-raise "vlc" '(:class "vlc")))
+
+(defcommand terminal () ()
+  (run-shell-command (format nil "st -f 'Ubuntu Mono:size=~D'" *terminal-font-size*)))
+
+(defcommand terminal-in (path) ((:string "Working directory: "))
+  (run-shell-command
+    (format nil "st -f 'Ubuntu Mono:size=~D' env -C '~A' fish" ; todo actually escape this
+            *terminal-font-size* path)))
+
+(defcommand terminal-apl () ()
+  (run-shell-command "st -f 'BQN386 Unicode:style=Regular:size=12'"))
+
+(defcommand gcontrol () ()
+  (run-or-raise "gcontrol" '(:class "Gnome-control-center")))
+
+(defcommand papers () ()
+  (run-or-raise "jabref" '(:class "org.jabref.gui.MainApplication")))
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stumpwm/bioinf.lisp	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,74 @@
+(in-package :stumpwm-user)
+
+
+(defun random-base ()
+  (random-elt "GCAT"))
+
+(defun random-dna-string (n)
+  (let ((result (make-string n)))
+    (dotimes (i n)
+      (setf (char result i) (random-base)))
+    result))
+
+(defcommand random-dna (n) ((:integer "Length: "))
+  (pbcopy (random-dna-string n)))
+
+(defun random-fasta-string (entries entry-length)
+  (str:join #\newline
+            (loop :for i :from 0 :below entries
+                  :collect (format nil ">seq ~D" i)
+                  :collect (random-dna-string entry-length))))
+
+(defcommand random-fasta
+    (entries entry-length)
+    ((:integer "Entries: ")
+     (:integer "Entry Length: "))
+  (check-type entries (integer 1 *))
+  (check-type entry-length (integer 1 *))
+  (pbcopy (random-fasta-string entries entry-length)))
+
+
+(defun kmers-of (k seq)
+  (loop :for start :from 0
+        :for end :from k :to (length seq)
+        :collect (subseq seq start end)))
+
+(defun kmerized-string (k seq)
+  (with-output-to-string (s)
+    (loop :for i :from 0
+          :for kmer :in (kmers-of k seq)
+          :do (format s "~,,V,@A~%" i kmer))))
+
+(defcommand kmerize (k) ((:integer "k: "))
+  (pbcopy (kmerized-string k (pbpaste))))
+
+(defun reverse-complement (seq)
+  (nreverse (map 'string (lambda (base)
+                           (case base
+                             (#\A #\T)
+                             (#\C #\G)
+                             (#\G #\C)
+                             (#\T #\A)
+                             (#\N #\N)
+                             (#\a #\t)
+                             (#\c #\g)
+                             (#\g #\c)
+                             (#\t #\a)
+                             (#\n #\n)
+                             (t #\?)))
+                 seq)))
+
+(defcommand revcomp () ()
+  (pbcopy (reverse-complement (pbpaste))))
+
+(defcommand rev () ()
+  (pbcopy (reverse (pbpaste))))
+
+
+#; Scratch --------------------------------------------------------------------
+
+(random-fasta-string 10 100)
+
+(format nil "~,,99,@A" "hello")
+
+(kmers-of 3 "ACTTAC")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stumpwm/brightness.lisp	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,26 @@
+(in-package :stumpwm-user)
+
+(defparameter *brightness-values* #(0 1 5 10 20 30 40 55 70 85 100))
+(defvar *brightness-index* 5)
+
+(defun brightness ()
+  (aref *brightness-values* *brightness-index*))
+
+(defun set-brightness (value)
+  (run-and-echo-shell-command
+    (hostcase
+      ((:gro :juss) (format nil "light -S ~D" value))
+      (t (message "Not sure how to set brightness on this machine.")))))
+
+(defun rotate-brightness (delta)
+  (setf *brightness-index*
+        (mod+ *brightness-index* delta (length *brightness-values*)))
+  (set-brightness (brightness)))
+
+
+(defcommand rotate-brightness-up () ()
+  (rotate-brightness 1))
+
+(defcommand rotate-brightness-down () ()
+  (rotate-brightness -1))
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stumpwm/budget.lisp	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,51 @@
+(in-package :stumpwm-user)
+
+(defparameter *tz/eastern*
+  (local-time:find-timezone-by-location-name "US/Eastern"))
+
+(defparameter *budget/start*
+  (local-time:encode-timestamp 0 0 0 0 29 8 2023 :timezone *tz/eastern*))
+
+(defun budget/per-day ()
+  (first (losh:read-all-from-file "/home/sjl/Sync/budget/per-day")))
+
+(defun budget/elapsed ()
+  (local-time:timestamp-difference (local-time:now) *budget/start*))
+
+(defun budget/days-elapsed ()
+  (floor (/ (budget/elapsed) (* 60 60 24))))
+
+(defun budget/in ()
+  (* (budget/days-elapsed) (budget/per-day)))
+
+(defun budget/out ()
+  (loop :for path :in (directory "/home/sjl/Sync/budget/hosts/*/total")
+        :summing (print (first (read-all-from-file (print path))))))
+
+(defun budget/current ()
+  (- (budget/in) (budget/out)))
+
+(defcommand budget-dump () ()
+  (message
+    (sh '("sh" "-c" "tail -n 5 /home/sjl/Sync/budget/hosts/*/records")
+        :result-type 'string)))
+
+(defcommand budget () ()
+  (message "$~D" (budget/current)))
+
+(defmacro with-budget-file ((f file &rest open-args) &body body)
+  `(with-open-file
+     (,f (format nil "/home/sjl/Sync/budget/hosts/~(~A~)/~A" *host* ,file)
+      ,@open-args)
+     ,@body))
+
+(defcommand spend (amount what) ((:integer "Amount: $") (:string "For: "))
+  (let ((current (with-budget-file (total "total")
+                   (first (read-all-from-file total))))
+        (timestamp (local-time:to-rfc3339-timestring (local-time:now))))
+    (with-budget-file (total "total" :direction :output :if-exists :supersede)
+      (print (+ current amount) total))
+    (with-budget-file (records "records" :direction :output :if-exists :append :if-does-not-exist :create)
+      (print (list timestamp amount what) records))
+    (message "Spent $~D for ~A at ~A" amount what timestamp)))
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stumpwm/clipboard.lisp	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,12 @@
+(in-package :stumpwm-user)
+
+(defcommand generate-random-uuid () ()
+  (pbcopy (string-downcase (princ-to-string (uuid:make-v4-uuid))))
+  (message "Copied random UUID to clipboard."))
+
+(defcommand bee-movie-script () ()
+  (run-shell-command "pbeecopy")
+  (message "Copied the entire Bee Movie script to clipboard."))
+
+#; Scratch --------------------------------------------------------------------
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stumpwm/config.lisp	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,32 @@
+(in-package :stumpwm-user)
+
+(set-prefix-key (kbd "C-space"))
+(local-time:reread-timezone-repository)
+
+(set-focus-color "#aaaaaa")
+(set-win-bg-color "#111111")
+(set-unfocus-color "#444444")
+(setf *normal-border-width* 1
+      *default-bg-color* #x222222
+      *window-border-style* :thin
+      (xlib:window-background (screen-root (current-screen))) *default-bg-color*)
+
+(defvar *redirected* (redirect-all-output (data-dir-file "debug" "log")))
+
+(setf *mouse-focus-policy* :click
+      *message-window-gravity* :center
+      *input-window-gravity* :center
+      *debug-level* 0
+      *resize-increment* 75
+      *new-frame-action* :empty
+      *window-format* "(%n%m%60t)"
+      *window-name-source* :title
+      *maximum-completions* 20
+      *shell-program* "/home/sjl/src/dotfiles/bin/bash-dammit"
+      losh:*pbcopy-command* "/home/sjl/src/dotfiles/bin/pbcopy"
+      losh:*pbpaste-command* "/home/sjl/src/dotfiles/bin/pbpaste")
+
+(defun stumpwm::input-insert-hyphen-or-space (input key)
+  ;; Unbreak typing
+  (declare (ignore key))
+  (input-insert-char input #\space))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stumpwm/external-screens.lisp	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,26 @@
+(in-package :stumpwm-user)
+
+(defcommand screen-laptop () ()
+  (only)
+  (hostcase
+    ((:gro :juss) (loop :with laptop = "eDP"
+                        :with extern = (hostcase (:gro "DisplayPort-0")
+                                                 (:juss "HDMI-A-0"))
+                        :for (output commands) :in `((,laptop ("--auto"))
+                                                     (,laptop ("--primary"))
+                                                     (,extern ("--off")))
+                        :do (progn (uiop:run-program `("xrandr" "--output" ,output ,@commands)))))
+    (t (message "Not configured on this system."))))
+
+(defcommand screen-external () ()
+  (only)
+  (hostcase
+    ((:gro :juss) (loop :with laptop = "eDP"
+                        :with extern = (hostcase (:gro "DisplayPort-0")
+                                                 (:juss "HDMI-A-0"))
+                        :for (output commands) :in `((,extern ("--auto"))
+                                                     (,extern ("--primary"))
+                                                     (,laptop ("--off")))
+                        :do (uiop:run-program `("xrandr" "--output" ,output ,@commands))))
+    (t (message "Not configured on this system."))))
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stumpwm/icelandic.lisp	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,50 @@
+(in-package :stumpwm-user)
+
+
+(defun send-keys (keys &key (win (current-window)) (sleep 0))
+  (dolist (k keys)
+    (send-key (kbd k) win)
+    (sleep sleep)))
+
+(defmacro defmultikey (name key compose-keys)
+  ;; Unfortunately we can't reliably autogen the name with something like
+  ;; (symb 'mk- compose-key) here because things like đ (th) and Đ (TH) would
+  ;; case fold to the same name.
+  `(progn
+     (defcommand ,name () ()
+       (send-keys '("Multi_key" ,@(map 'list #'string compose-keys))))
+     (define-key *top-map*
+       (kbd ,key) ,(string name))))
+
+(defmacro defmultikeys (&rest bindings)
+  `(progn ,@(loop for binding :in bindings :collect `(defmultikey ,@binding))))
+
+(defmultikeys
+  (isk-l-á "M-a" "'a")
+  (isk-u-Á "M-A" "'A")
+  (isk-l-é "M-e" "'e")
+  (isk-u-É "M-E" "'E")
+  (isk-l-í "M-i" "'i")
+  (isk-u-Í "M-I" "'I")
+  (isk-l-ó "M-o" "'o")
+  (isk-u-Ó "M-O" "'O")
+  (isk-l-ö "M-m" "\"o")
+  (isk-u-Ö "M-M" "\"O")
+  (isk-l-ú "M-u" "'u")
+  (isk-u-Ú "M-U" "'U")
+  (isk-l-ý "M-y" "'y")
+  (isk-u-Ý "M-Y" "'Y")
+  (isk-l-þ "M-t" "th")
+  (isk-u-Þ "M-T" "TH")
+  (isk-l-đ "M-d" "dh")
+  (isk-u-Đ "M-D" "DH")
+  (isk-l-æ "M-h" "ae")
+  (isk-u-Æ "M-H" "AE"))
+
+
+(defcommand thinkpad-ret () ()
+  (send-key (kbd "RET")))
+
+(defcommand thinkpad-bs () ()
+  (send-key (kbd "BackSpace")))
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stumpwm/igv.lisp	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,105 @@
+(defpackage :stumpwm-user/igv
+  (:use :cl :losh)
+  (:import-from :stumpwm :defcommand :echo)
+  (:export
+    :igv/supplementary-on
+    :igv/supplementary-off
+    :igv/group-none
+    :igv/group-selected
+    :igv/clear-read-selections
+    :igv/select-reads
+    :igv/goto
+    :igv/goto-read
+    :igv/zoom-in
+    :igv/zoom-out
+    :igv/init))
+
+(in-package :stumpwm-user/igv)
+
+
+;;;; Implementation -----------------------------------------------------------
+(defun send-igv-command (string &key want-resp)
+  (usocket:with-client-socket (socket stream "127.0.0.1" 60151)
+    ;; Do this here instead of passing :timeout above because that only sets
+    ;; *read* timeout and we don't want to permahang stump when IGV hangs and
+    ;; can't read.
+    (setf (sb-impl::fd-stream-timeout (usocket:socket-stream socket)) 5.0f0)
+    (unwind-protect (progn (write-line string stream)
+                           (force-output stream)
+                           (when want-resp
+                             (read-line stream)))
+      (usocket:socket-close socket))))
+
+(defun send-igv-batch-file (path)
+  (send-igv-command (alexandria:read-file-into-string path)))
+
+(defun alignment-tracks% ()
+  (str:split #\, (send-igv-command "alignmentTrackNames ," :want-resp t)))
+
+(defun group% (option)
+  (send-igv-command (format nil "group ~A" (ecase option
+                                             ((:selected) "selected")
+                                             ((nil) "none")))))
+
+(defun select-reads% ()
+  (dolist (track-name (alignment-tracks%))
+    (let ((read-names (ppcre:all-matches-as-strings
+                        "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"
+                        (pbpaste))))
+      (send-igv-command (format nil "selectByName ~A ~A ," track-name (str:join "," read-names)))
+      (echo (format nil "Selected ~D read~:P." (length read-names))))))
+
+(defun goto% ()
+  (ppcre:register-groups-bind (chr start end)
+      ("(chr[A-Za-z0-9_]+)[\\s:]+([\\d,]+)(?:[\\s:]+)?([\\d,]+)?" (pbpaste))
+    (send-igv-command (format nil "goto ~A:~A~@[-~A~]" chr start end))))
+
+(defun clear-read-selections% ()
+  (dolist (track-name (alignment-tracks%))
+    (send-igv-command (format nil "clearSelections ~A" track-name))))
+
+(defun zoom-in% ()
+  (send-igv-command "zoomin"))
+
+(defun zoom-out% ()
+  (send-igv-command "zoomout"))
+
+
+;;;; Commands -----------------------------------------------------------------
+(defcommand igv/supplementary-on () ()
+  (send-igv-command "preference SAM.FILTER_SUPPLEMENTARY_ALIGNMENTS FALSE")
+  (echo "Supplementary alignments now on."))
+
+(defcommand igv/supplementary-off () ()
+  (send-igv-command "preference SAM.FILTER_SUPPLEMENTARY_ALIGNMENTS TRUE")
+  (echo "Supplementary alignments now off."))
+
+(defcommand igv/group-none () ()
+  (group% nil))
+
+(defcommand igv/group-selected () ()
+  (group% :selected))
+
+(defcommand igv/clear-read-selections () ()
+  (clear-read-selections%))
+
+(defcommand igv/select-reads () ()
+  (select-reads%)
+  (group% :selected))
+
+(defcommand igv/goto () ()
+  (goto%))
+
+(defcommand igv/zoom-in () ()
+  (zoom-in%))
+
+(defcommand igv/zoom-out () ()
+  (zoom-out%))
+
+(defcommand igv/goto-read () ()
+  (clear-read-selections%)
+  (goto%)
+  (select-reads%))
+
+(defcommand igv/init () ()
+  (send-igv-batch-file "/home/sjl/bin/igv.batch"))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stumpwm/key-mapping.lisp	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,279 @@
+(in-package :stumpwm-user)
+
+(defcommand nop () ()
+  nil)
+
+
+;;; Conventions:
+;;;
+;;; * Hyper-dir: move focus
+;;; * Hyper-Shift-dir: move window
+;;; * Hyper-Shift-Control-dir: swap window
+;;; * Hyper-F*: hardware
+;;; * Shift-F*: timers
+;;; * Hyper-Super-*: layout
+;;; * Hyper-*: miscellaneous
+;;; * Super-GAMERMOUSE: duplicate stuff to avoid swapping back
+
+
+(defmacro define-top-keys (&body keyforms)
+  `(progn ,@(loop :for form :in keyforms
+                  :collect `(define-key *top-map*
+                              (kbd ,(first form))
+                              ,(second form)))))
+
+
+(define-top-keys ;; miscellaneous
+  ("H-m" "terminal")
+  ("H-M" "toggle-zoom-mute")
+  ("H-SunPageUp" "st-font-up")
+  ("H-SunPageDown" "st-font-down")
+  ("H-z" "fullscreen")
+  ("H-Z" "end-zoom")
+  ("C-S-F1" "bht")
+  ("H-F4" "switch-yubikeys")
+  ("H-\\" "pass-personal")
+  ("H-|" "generate-password")
+  ("s-1" "pass-um-1")
+  ("s-2" "pass-um-2")
+  ("H-b" "browser")
+  ("H-O" "spotify")
+  ("H-o" "files")
+  ("H-t" "zoom-meeting")
+  ("H-P" "papers")
+  ("F26"   "prev")
+  ("S-F26" "next")
+  ("H-q" "exec lock-screen")
+  ("H-y" "screenshot")
+  ("H-Y" "delayed-screenshot")
+  ("H-r" "rain")
+  ("H-V" "vlc")
+  ("H-e" "budget")
+  ("H-E" "spend")
+  ("S-XF86AudioMute" "terminal-font-size-show")
+  ("S-XF86AudioRaiseVolume" "terminal-font-size-down") ; todo unfuck the backwards mapping in qmk
+  ("S-XF86AudioLowerVolume" "terminal-font-size-up")
+  ("C-BackSpace" "clear-notifications"))
+
+
+(define-top-keys ;; clipboard
+  ("H-c" "show-clipboard-history")
+  ("H-C" "clear-clipboard-history")
+  ("H-u" "generate-random-uuid")
+  ("H-B" "bee-movie-script"))
+
+(define-top-keys ;; movement
+  ("H-h" "move-focus* left")
+  ("H-j" "move-focus down")
+  ("H-k" "move-focus up")
+  ("H-l" "move-focus* right")
+
+  ("H-H" "move-window left")
+  ("H-J" "move-window down")
+  ("H-K" "move-window up")
+  ("H-L" "move-window right")
+
+  ("H-1" "gselect 1")
+  ("H-2" "gselect 2")
+  ("H-3" "gselect 3")
+  ("H-4" "gselect 4")
+  ("H-5" "gselect 5")
+  ("H-6" "gselect 6")
+
+  ("KP_Insert"    "vgroups")
+  ("KP_End"       "gselect 1")
+  ("KP_Down"      "gselect 2")
+  ("KP_Page_Down" "gselect 3")
+  ("KP_Left"      "gselect 4")
+  ("KP_Begin"     "gselect 5")
+  ("KP_Right"     "gselect 6")
+  ("KP_Home"      "gselect 7")
+  ("KP_Up"        "gselect 8")
+  ("KP_Page_Up"   "gselect 9")
+
+  ("H-!" "gmove 1")
+  ("H-@" "gmove 2")
+  ("H-#" "gmove 3")
+  ("H-$" "gmove 4")
+  ("H-%" "gmove 5")
+  ("H-^" "gmove 6")
+
+  ("C-H-H" "exchange-direction left")
+  ("C-H-J" "exchange-direction down")
+  ("C-H-K" "exchange-direction up")
+  ("C-H-L" "exchange-direction right")
+
+  ("H-`" "next")
+  ("S-H-`" "prev")
+  ("H-n" "next-in-frame")
+  ("H-p" "prev-in-frame")
+  ("H-N" "pull-hidden-next")
+
+  ("H-," "pull-from-windowlist")
+  ("H-less" "title")
+
+  ("H-." "ggo")
+  ("H-greater" "grename")
+  )
+
+(define-top-keys ;; splitting
+  ("H-s" "sane-vsplit")
+  ("H-v" "sane-hsplit")
+  ("H-=" "balance-frames"))
+
+(define-top-keys ;; killing
+  ("H-w" "delete")
+  ("H-W" "kill")
+  ("H-BackSpace" "remove")
+  ("S-H-BackSpace" "kill-and-remove")
+  ("H-Delete" "gkill"))
+
+(define-top-keys ;; naming
+  ("H-'" "title")
+  ("H-\"" "grename"))
+
+(define-top-keys ;; sound
+  ("H-F1" "mute")
+  ("H-F2" "volume-down")
+  ("H-F3" "volume-up")
+  ("XF86AudioMute" "mute")
+  ("XF86AudioRaiseVolume" "volume-down") ; todo unfuck the backwards mapping in qmk
+  ("XF86AudioLowerVolume" "volume-up"))
+
+(define-top-keys ;; screen
+  ("H-F5" "rotate-brightness-down")
+  ("H-F6" "rotate-brightness-up")
+  ("H-F7" "screen-laptop")
+  ("H-F8" "screen-external"))
+
+(define-top-keys ;; layout
+  ("s-H-o" "only")
+  ("s-H-n" "restore-from-file notes")
+  ("s-H-t" "restore-from-file thirds")
+  ("s-H-m" "restore-from-file dev")
+  ("s-H-s" "restore-from-file streaming")
+  ("s-H-w" "restore-from-file work")
+  ("s-H-z" "restore-from-file zoom"))
+
+(define-top-keys ;; timers
+  ("s-F7"  "tea-timer")
+  ("s-F9"  "run-pop-timer")
+  ("s-F8"  "set-pop-timer")
+  ("s-p"   "posture-start")
+  ("s-P"   "posture-stop")
+  ("s-y"   "posture-answer-yes")
+  ("s-h"   "posture-answer-meh")
+  ("s-n"   "posture-answer-no")
+  ("s-\\"  "posture-toggle-pause")
+  ("s-o"   "posture-snooze"))
+
+(define-top-keys ;; stump
+  ("Pause" "terminal") ; jesus christ
+  ("H-F9"  "sleep-machine")
+  ("H-F10" "toggle-stumptray")
+  ("H-F11" "toggle-current-mode-line")
+  ("H-F12" "refresh-heads"))
+
+(defvar *keymap/igv* (make-sparse-keymap))
+
+(define-key *keymap/igv* (kbd "s") "igv/supplementary-on")
+(define-key *keymap/igv* (kbd "S") "igv/supplementary-off")
+(define-key *keymap/igv* (kbd "g") "igv/group-selected")
+(define-key *keymap/igv* (kbd "G") "igv/group-none")
+(define-key *keymap/igv* (kbd "r") "igv/select-reads")
+(define-key *keymap/igv* (kbd "R") "igv/clear-read-selections")
+(define-key *keymap/igv* (kbd "l") "igv/goto")
+(define-key *keymap/igv* (kbd "L") "igv/goto-read")
+(define-key *keymap/igv* (kbd "i") "igv/init")
+(define-key *keymap/igv* (kbd "H-i") "igv")
+
+(define-top-keys ;; alternate maps
+  ("H-i" *keymap/igv*))
+
+
+;;; The G A M E R M O U S E:
+;;;
+;;; +----------+----------+----------+                 _____
+;;; |          |    ^     |          |                /     \            +--------+
+;;; |   igv    |    |     |   igv    |               |       |           |        |
+;;; |   z out  |   move   |   z in   |        group  |       |  group    |        |
+;;; +----------+----------+----------+        <----- |       | ----->    |        |
+;;; |          |          |          |               |       |           |        |
+;;; |  move    |          |   move   |               |       |           |        |
+;;; |   <--    |          |   -->    |                \_____/            |  kill  |
+;;; +----------+----------+----------+                                   |        |
+;;; |          |   move   |          |                                   |        |
+;;; |          |    |     |          |              +---------+          |        |
+;;; |          |    v     |          |              |         |          |        |
+;;; +----------+----------+----------+              |  next   |          |        |
+;;; |  split   |  FULL    |  split   |              |         |          +--------+
+;;; |   [-]    |  SCREEN  |   [|]    |              +---------+
+;;; |          |          |          |              |         |
+;;; +----------+----------+----------+              |  prev   |
+;;;                                                 |         |
+;;;                                                 +---------+
+(define-top-keys ;; GAMER MOUSE
+  ;; Super layer
+  ("s-!" "igv/zoom-out")     ("s-@" "move-window up")   ("s-#" "igv/zoom-in")
+  ("s-$" "move-window left") ("s-%" "nop")              ("s-^" "move-window right")
+  ("s-&" "nop")              ("s-*" "move-window down") ("s-(" "nop")
+  ("s-)" "sane-vsplit")      ("s-_" "fullscreen")       ("s-+" "sane-hsplit")
+
+  ("M-s-Left"  "gprev") ("M-s-Right" "gnext")
+
+  ("s-Home" "next-in-frame")
+  ("s-End"  "prev-in-frame")
+
+  ("s-Delete" "remove")
+
+  ;; Hyper-Super layer
+  ("H-s-!" "nop")                   ("H-s-@" "resize-direction up")   ("H-s-#" "nop")
+  ("H-s-$" "resize-direction left") ("H-s-%" "fullscreen")            ("H-s-^" "resize-direction right")
+  ("H-s-&" "nop")                   ("H-s-*" "resize-direction down") ("H-s-(" "nop")
+  ("H-s-)" "nop")                   ("H-s-_" "nop")                   ("H-s-+" "nop")
+
+  )
+
+(define-top-keys ;; Single-key keyboard
+  ("S-C-F1" "igv"))
+
+
+(define-remapped-keys
+  '(("st-256color"
+     ("s-c" . "C-C")
+     ("s-v" . "C-V")
+     ("C-=" . "S-C-SunPageUp")
+     ("C--" . "S-C-SunPageDown")
+     ("C-0" . "S-C-Home"))
+    ("(firefox|Google-chrome|Chromium-browser)"
+     ("s-[" . "C-S-Tab")
+     ("s-]" . "C-Tab")
+     ("C-M-Left"  . "C-S-Tab")
+     ("C-M-Right" . "C-Tab")
+     ("C-a" . "Home")
+     ("C-e" . "End")
+     ;; I always try to hit ctrl-d to kill a browser window because I'm so used
+     ;; to terminal windows, and it ends up bookmarking the damn page.  In the
+     ;; interest of not having a random collection of bookmarks grow over time,
+     ;; I'll just add a mapping to compensate for my stupid brain.
+     ("C-d" . "C-w")
+     ;; todo debug why this breaks a really fast C-a-k roll
+     ;; ("C-a" . "Home")
+     ;; ("C-e" . "End")
+     ("s-a" . "C-a")
+     ("s-d" . "C-d")
+     ("s-l" . "C-l")
+     ("s-t" . "C-t")
+     ("s-w" . "C-w")
+     ("s-r" . "C-r")
+     ("s-f" . "C-f")
+     ("s-z" . "C-z")
+     ("s-x" . "C-x")
+     ("s-c" . "C-c")
+     ("s-v" . "C-v"))
+    (""
+     ("s-z" . "C-z")
+     ("s-x" . "C-x")
+     ("s-c" . "C-c")
+     ("s-v" . "C-v"))))
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stumpwm/local-share-stumpwm/522.dump	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,52 @@
+#S(GDUMP
+   :NUMBER 3
+   :NAME "code"
+   :TREE (((#S(FDUMP
+               :NUMBER 0
+               :X 0
+               :Y 0
+               :WIDTH 1200
+               :HEIGHT 1020
+               :WINDOWS (31457288)
+               :CURRENT 31457288)
+            #S(FDUMP
+               :NUMBER 3
+               :X 0
+               :Y 1020
+               :WIDTH 1200
+               :HEIGHT 420
+               :WINDOWS (29360133)
+               :CURRENT 29360133))
+           ((#S(FDUMP
+                :NUMBER 1
+                :X 1200
+                :Y 0
+                :WIDTH 1735
+                :HEIGHT 1440
+                :WINDOWS (33554437)
+                :CURRENT 33554437)
+             (#S(FDUMP
+                 :NUMBER 4
+                 :X 2935
+                 :Y 0
+                 :WIDTH 985
+                 :HEIGHT 720
+                 :WINDOWS (37748741)
+                 :CURRENT 37748741)
+              #S(FDUMP
+                 :NUMBER 5
+                 :X 2935
+                 :Y 720
+                 :WIDTH 985
+                 :HEIGHT 720
+                 :WINDOWS NIL
+                 :CURRENT NIL)))
+            #S(FDUMP
+               :NUMBER 2
+               :X 3920
+               :Y 0
+               :WIDTH 1200
+               :HEIGHT 1440
+               :WINDOWS (16977044)
+               :CURRENT 16977044))))
+   :CURRENT 1)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stumpwm/local-share-stumpwm/notes.dump	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,44 @@
+#S(GDUMP
+   :NUMBER 2
+   :NAME "notes"
+   :TREE (((#S(FDUMP
+               :NUMBER 0
+               :X 0
+               :Y 0
+               :WIDTH 2560/3
+               :HEIGHT 720
+               :WINDOWS (25165829)
+               :CURRENT 25165829)
+            #S(FDUMP
+               :NUMBER 4
+               :X 0
+               :Y 720
+               :WIDTH 2560/3
+               :HEIGHT 720
+               :WINDOWS (35651589)
+               :CURRENT 35651589))
+           (#S(FDUMP
+               :NUMBER 1
+               :X 2560/3
+               :Y 0
+               :WIDTH 2560/3
+               :HEIGHT 1440
+               :WINDOWS (27262981)
+               :CURRENT 27262981)
+            (#S(FDUMP
+                :NUMBER 2
+                :X 5120/3
+                :Y 0
+                :WIDTH 2560/3
+                :HEIGHT 720
+                :WINDOWS (29360133)
+                :CURRENT 29360133)
+             #S(FDUMP
+                :NUMBER 3
+                :X 5120/3
+                :Y 720
+                :WIDTH 2560/3
+                :HEIGHT 720
+                :WINDOWS (33554437)
+                :CURRENT 33554437)))))
+   :CURRENT 1)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stumpwm/local-share-stumpwm/uw-test.dump	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,7 @@
+#S(GDUMP
+   :NUMBER 1
+   :NAME "Default"
+   :TREE ((#S(FDUMP  :NUMBER 0 :X 0    :Y 0 :WIDTH 640 :HEIGHT 1440 :WINDOWS NIL :CURRENT NIL)
+           (#S(FDUMP :NUMBER 1 :X 640  :Y 0 :WIDTH 3840 :HEIGHT 1440 :WINDOWS NIL :CURRENT NIL)
+            #S(FDUMP :NUMBER 2 :X 4480 :Y 0 :WIDTH 640 :HEIGHT 1440 :WINDOWS NIL :CURRENT NIL))))
+   :CURRENT 1)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stumpwm/miscellaneous.lisp	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,112 @@
+(in-package :stumpwm-user)
+
+(defcommand sane-hsplit () ()
+  (hsplit)
+  (move-focus :right))
+
+(defcommand sane-vsplit () ()
+  (vsplit)
+  (move-focus :down))
+
+
+(defcommand move-focus* (direction)
+    ((:direction "Enter a direction: "))
+  (labels ((in-float-p ()
+             (typep (current-group) 'stumpwm::float-group))
+           (focus-first-frame ()
+             (unless (in-float-p)
+               ;; After moving to a new group we don't know which frame is
+               ;; focused, and unfortunately Stump doesn't give us a nice way to
+               ;; say "focus the leftmost frame" so we'll just move the focus
+               ;; a bunch of times and hope it's enough.  Sigh.
+               (loop :repeat 15
+                     :until (eql (current-frame)
+                                 (progn (move-focus (ecase direction
+                                                      (:left :right)
+                                                      (:right :left)))
+                                        (current-frame))))))
+           (next-group ()
+             (ecase direction
+               (:right (gnext))
+               (:left (gprev)))
+             (focus-first-frame)))
+    (unless (in-float-p)
+      (banish))
+    (if (in-float-p)
+      (next-group)
+      (let ((frame (current-frame)))
+        (move-focus direction)
+        (when (eql frame (current-frame))
+          (next-group))))))
+
+(defcommand toggle-current-mode-line () ()
+  (toggle-mode-line (current-screen) (current-head)))
+
+(defcommand toggle-stumptray () ()
+  (run-commands "stumptray"))
+
+(defcommand kill-and-remove () ()
+  (run-commands "kill" "remove"))
+
+(defcommand sleep-machine ()
+    ()
+  (hostcase
+    ((:gro :juss)
+     (run-shell-command "exec lock-screen")
+     (run-shell-command "systemctl suspend"))
+    (t (message "Not sleeping this machine for safety."))))
+
+(defcommand copy-clhs-url (s)
+    ((:string "Symbol: "))
+  (run-shell-command (format nil "clhs --url 'http://www.lispworks.com/documentation/HyperSpec/' --quiet --open echon '~A' | pbcopy" s)))
+
+(defcommand describe-window () ()
+  (show-window-properties))
+
+(defcached (weather :seconds 120) ()
+  (losh:sh '("/home/sjl/src/dotfiles/lisp/bin/weather" "48105" "-H" "36") :result-type 'list))
+
+(defcommand rain () ()
+  (_ (weather)
+    (mapcar (lambda (line) (ppcre:regex-replace " 1[0-9]:00 " line "^6\\&^*")) _)
+    (message "~{~A~^~%~}" _)))
+
+(defcommand mark (thing) ((:string "Mark: "))
+  (run-shell-command (format nil "mark ~A" thing)))
+
+(defcommand clear-notifications () ()
+  (run-shell-command "dunstctl close-all"))
+
+(defcommand start-vm () ()
+  (echo "Starting VM.")
+  (run-shell-command "/home/sjl/vms/run"))
+
+
+(defcommand toggle-zoom-mute () ()
+  (when-let-window (win "^Zoom Meeting.*")
+    ;; Zoom stupidly won't accept the shortcut unless it's in focus
+    (unless (eql (window-group win) (current-group))
+      ;;        jesus            christ        stump just export switch-to-group come on
+      (gselect (princ-to-string (group-number (window-group win)))))
+    (focus-window win t)
+    (meta (kbd "M-a"))))
+
+(defcommand end-zoom () ()
+  (when-let-window (win "^Zoom Meeting.*")
+    (kill-window win)
+    (message "Killed meeting")))
+
+
+(defcommand ggo () ()
+  (let* ((current-screen (current-screen))
+         (current-group (stumpwm::screen-current-group current-screen))
+         (all-groups (sort (copy-seq (screen-groups current-screen)) #'< :key #'group-number))
+         (groups (mapcar (lambda (group)
+                           (list (format nil "~2D~A~A"
+                                         (group-number group)
+                                         (if (eql current-group group) #\* #\space)
+                                         (group-name group))
+                                 group))
+                         all-groups))
+         (selected (select-from-menu current-screen groups)))
+    (when selected (stumpwm::switch-to-group (second selected)))))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stumpwm/modeline.lisp	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,51 @@
+(in-package :stumpwm-user)
+
+(defun ensure-mode-line ()
+  (when (not (stumpwm::head-mode-line (current-head)))
+    (toggle-mode-line (current-screen) (current-head))))
+
+
+(defun configure-modeline ()
+  (setf
+    *time-modeline-string*
+    "%a %b %e %H:%M"
+
+    cpu::*cpu-usage-modeline-fmt*
+    "^[~A~3D%^]"
+
+    cpu::*cpu-modeline-fmt*
+    "[%c] [%f]"
+
+    mem::*mem-modeline-fmt*
+    "%p"
+
+    *screen-mode-line-format*
+    (append
+      (list "[^B"
+            '(:eval (princ-to-string (group-number (current-group))))
+            ":%n^b@%h] %W^>")
+
+      (list "(V %V) ")
+
+      ;; battery and brightness for laptops
+      (hostcase
+        ((:gro :juss)
+         (list "(B %B)"
+               " (BR "
+               '(:eval (princ-to-string (brightness)))
+               "%) ")))
+
+      ;; temperature
+      (list "(TEMP %S) ")
+
+      ;; cpu, mem, time, tray
+      (list "(CPU %C) (MEM%M) %d %T")
+      ))
+
+  (setf *mode-line-timeout* 10)
+  (setf *mode-line-background-color* "#111111")
+
+  (ensure-mode-line))
+
+(configure-modeline)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stumpwm/modules.lisp	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,9 @@
+(in-package :stumpwm-user)
+
+(load-module "pass")
+(load-module "battery-portable")
+(load-module "cpu")
+(load-module "hostname")
+(load-module "mem")
+(load-module "stumptray")
+(load-module "clipboard-history")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stumpwm/package.lisp	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,5 @@
+(in-package :stumpwm-user)
+
+(shadow :window)
+
+(use-package :losh)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stumpwm/passwords.lisp	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,23 @@
+(in-package :stumpwm-user)
+
+(defcommand pass-personal () ()
+  (let ((pass:*password-store* "/home/sjl/.password-store/")
+        (pass:*pass-notification-message* t))
+    (pass:pass-copy)))
+
+(defcommand pass-um-1 () ()
+  (echo "Copying UM level 1 password, touch key.")
+  (run-shell-command "pass -c umich.edu/slosh"))
+
+(defcommand pass-um-2 () ()
+  (echo "Copying UM level 2 password, touch key.")
+  (run-shell-command "pass -c umich.edu/l2"))
+
+(defcommand switch-yubikeys () ()
+  (echo (run-shell-command "switch-yubikeys" t)))
+
+(defcommand generate-password () ()
+  (run-shell-command "genpass | pbc")
+  (message "Generated a fresh password and copied it to the clipboard."))
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stumpwm/posture.lisp	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,84 @@
+(in-package :stumpwm-user)
+
+(defparameter *posture-thread* nil)
+(defparameter *posture-should-stop* nil)
+(defparameter *posture-paused* nil)
+(defparameter *posture-snooze* nil)
+(defparameter *posture-current* 30)
+(defparameter *posture-min* 5)
+(defparameter *posture-max* (hours->seconds 2))
+
+(defun posture-paused-p ()
+  ;; this is the dumbest shit ever, but I can't figure out how to call into
+  ;; stumpish from the setguid slock process
+  (or *posture-paused* (probe-file "/tmp/.posture-pause")))
+
+(defun posture-snoozed-p ()
+  (and *posture-snooze*
+       (< (get-universal-time) *posture-snooze*)))
+
+(defcommand posture-pause () ()
+  (message "Pausing posture.")
+  (setf *posture-paused* t))
+
+(defcommand posture-unpause () ()
+  (message "Unpausing posture.")
+  (setf *posture-paused* nil))
+
+(defcommand posture-toggle-pause () ()
+  (if (setf *posture-paused* (not *posture-paused*))
+    (message "Posture is now paused.")
+    (message "Posture is now unpaused.")))
+
+(defcommand posture-snooze (hours)
+    ((:real "Snooze for how many hours? "))
+  (setf *posture-snooze* (+ (hours->seconds hours) (get-universal-time))))
+
+(defun posture-update (delta)
+  (setf *posture-current*
+        (clamp *posture-min* *posture-max* (* *posture-current* delta))))
+
+(defun posture-query ()
+  (speak "Is your posture okay?"))
+
+(defcommand posture-answer-yes () ()
+  (message "Good work.")
+  (run-shell-command "echo $(epochseconds) 1.0 >> ~/.posture.log")
+  (posture-update 11/10))
+
+(defcommand posture-answer-meh () ()
+  (message "Better than nothing.")
+  (run-shell-command "echo $(epochseconds) 0.5 >> ~/.posture.log"))
+
+(defcommand posture-answer-no () ()
+  (message "Try harder.")
+  (run-shell-command "echo $(epochseconds) 0.0 >> ~/.posture.log")
+  (posture-update 8/10))
+
+(defun posture% ()
+  (if *posture-should-stop*
+    nil
+    (progn (unless (or (posture-paused-p) (posture-snoozed-p))
+             (posture-query)
+             (sleep 10))
+           *posture-current*)))
+
+(defun posture-running-p ()
+  (and *posture-thread* (sb-thread:thread-alive-p *posture-thread*)))
+
+(defcommand posture-stop () ()
+  (setf *posture-should-stop* t))
+
+(defcommand posture-start () ()
+  (setf *posture-should-stop* nil)
+  (if (posture-running-p)
+    (message "Posture loop was already running.")
+    (setf *posture-thread*
+          (sb-thread:make-thread
+            (lambda ()
+              (loop :for seconds = (posture%)
+                    :while seconds
+                    :do (sleep seconds))
+              (message "Posture loop exiting."))
+            :name "Posture thread"))))
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stumpwm/screenshots.lisp	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,8 @@
+(in-package :stumpwm-user)
+
+(defcommand delayed-screenshot () ()
+  (run-shell-command "sleep 10 && screenshot"))
+
+(defcommand screenshot () ()
+  (run-shell-command "screenshot"))
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stumpwm/sensors.lisp	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,48 @@
+(in-package :stumpwm-user)
+
+(defun ? (obj &rest keys)
+  (if (null keys)
+      obj
+      (apply #'? (etypecase obj
+                   (hash-table (gethash (first keys) obj)))
+             (rest keys))))
+
+(defun parse-sensors ()
+  ;; sensors -j is stupid and will output errors before the actual output on
+  ;; standard out, instead of putting them on standard err like a reasonable
+  ;; program, e.g.:
+  ;;
+  ;;     ERROR: Can't get value of subfeature temp1_input: Can't read
+  ;;     {
+  ;;        "iwlwifi_1-virtual-0":{ … },
+  ;;        …
+  ;;
+  ;; So we'll have to drop the `ERROR` lines before we can get to the actual
+  ;; goddamn JSON.  UNIX programs are so great.
+  (let ((s (losh:sh '("sensors" "-j") :result-type 'stream)))
+    (loop :while (char= #\E (peek-char nil s)) :do (read-line s))
+    (jarl:read t s)))
+
+(defun sensors% (&aux (sensors (parse-sensors)))
+  (hostcase
+    (:ouroboros (format nil "[CPU ~D°C] [GPU ~D°C ~D°C ~D°C]"
+                        (round (? sensors "nct6779-isa-0290" "CPUTIN" "temp2_input"))
+                        (round (? sensors "amdgpu-pci-4500"  "edge"     "temp1_input"))
+                        (round (? sensors "amdgpu-pci-4500"  "junction" "temp2_input"))
+                        (round (? sensors "amdgpu-pci-4500"  "mem"      "temp3_input"))))
+    ((:gro :juss) (format nil "[CPU ~D°C] [GPU ~D°C]"
+                        (round (? sensors "thinkpad-isa-0000" "CPU"  "temp1_input"))
+                        (round (? sensors "amdgpu-pci-0400"   "edge" "temp1_input"))))
+    (t "?")))
+
+(defcached (sensors :seconds 10.0)
+  (sensors%))
+
+(defun sensors-modeline (ml)
+  (declare (ignore ml))
+  (sensors))
+
+(add-screen-mode-line-formatter #\S #'sensors-modeline)
+
+#; Scratch --------------------------------------------------------------------
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stumpwm/sound.lisp	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,40 @@
+(in-package :stumpwm-user)
+
+(defun volume% ()
+  (_ (run-shell-command "amixer sget Master" t)
+    (string-grep "Front Left:" _ :first-only t)
+    (string-split "[]" _)
+    second
+    (string-trim "%" _)
+    parse-integer))
+
+(defcached (volume :seconds 30)
+  (volume%))
+
+(defcommand mute () ()
+  (run-shell-command "mute")
+  (volume/uncache)
+  (message "Muted."))
+
+(defcommand volume-up () ()
+  (run-shell-command "amixer -q sset Master 5%+")
+  (volume/uncache)
+  (message "Volume: ~D%" (volume)))
+
+(defcommand volume-down () ()
+  (run-shell-command "amixer -q sset Master 5%-")
+  (volume/uncache)
+  (message "Volume: ~D%" (volume)))
+
+(defun volume-modeline (ml)
+  (declare (ignore ml))
+  (format nil "~D%" (volume)))
+
+(defcommand bht () ()
+  (run-shell-command (format nil "amixer -q sset Master 50%; bht; amixer -q sset Master ~D%" (volume)))
+  (volume/uncache))
+
+(add-screen-mode-line-formatter #\V #'volume-modeline)
+
+#; Scratch --------------------------------------------------------------------
+(volume)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stumpwm/stumpconfig.asd	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,40 @@
+(asdf:defsystem :stumpconfig
+  :description "My StumpWM configuration."
+  :author "Steve Losh <steve@stevelosh.com>"
+
+  :depends-on (:losh
+               :split-sequence
+               :alexandria
+               :parse-number
+               :str
+               :cl-ppcre
+               :bordeaux-threads
+               :jarl
+               :local-time
+               :usocket
+               :uuid)
+
+  :serial t
+  :components ((:file "package")
+               (:file "modules")
+               (:file "config")
+               (:file "utils")
+               (:file "posture")
+               (:file "budget")
+               (:file "screenshots")
+               (:file "sound")
+               (:file "brightness")
+               (:file "passwords")
+               (:file "terminal-fonts")
+               (:file "clipboard")
+               (:file "applications")
+               (:file "timers")
+               (:file "icelandic")
+               (:file "sensors")
+               (:file "modeline")
+               (:file "vlime")
+               (:file "external-screens")
+               (:file "miscellaneous")
+               (:file "igv")
+               (:file "bioinf")
+               (:file "key-mapping")))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stumpwm/stumpwmrc	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,20 @@
+(in-package :stumpwm-user)
+
+(ql:quickload :stumpconfig)
+
+(defvar *tray-loaded*
+  (run-commands "stumptray"))
+
+(defvar *dunst*
+  (run-shell-command "/usr/bin/dunst -conf ~/.dunstrc"))
+
+(defvar *clip*
+  (clipboard-history:start-clipboard-manager))
+
+(when (probe-file "/home/sjl/.stumpwmrc.local")
+  (load "/home/sjl/.stumpwmrc.local"))
+
+
+#; Scratch --------------------------------------------------------------------
+
+(princ *window-format*)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stumpwm/terminal-fonts.lisp	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,36 @@
+(in-package :stumpwm-user)
+
+(defun read-terminal-font-size-from-config ()
+  (if (probe-file "/home/sjl/.terminal-font")
+    (with-open-file (f "/home/sjl/.terminal-font")
+      (read f))
+    11))
+
+(defvar *terminal-font-size* (read-terminal-font-size-from-config))
+
+(defcommand reload-terminal-font-size () ()
+  (setf *terminal-font-size* (read-terminal-font-size-from-config)))
+
+(defcommand terminal-font-size-show () ()
+  (message "~D" *terminal-font-size*))
+
+(defcommand terminal-font-size-up () ()
+  (message "~D" (incf *terminal-font-size*)))
+
+(defcommand terminal-font-size-down () ()
+  (message "~D" (setf *terminal-font-size* (max 6 (1- *terminal-font-size*)))))
+
+(defcommand st-font-up () ()
+  (loop :repeat 7 :do (meta (kbd "C-S-SunPageUp"))))
+
+(defcommand st-font-down () ()
+  (loop :repeat 7 :do (meta (kbd "C-S-SunPageDown"))))
+
+(defcommand st-font-reset () ()
+  (meta (kbd "C-S-Home")))
+
+(defcommand tfont (size)
+    ((:integer "Size: "))
+  (setf *terminal-font-size* size))
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stumpwm/timers.lisp	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,38 @@
+(in-package :stumpwm-user)
+
+(defparameter *pop-timer-minutes* nil)
+(defparameter *pop-timer-seconds* nil)
+
+(defun pop-timer ()
+  (if (or (null *pop-timer-minutes*)
+          (null *pop-timer-seconds*))
+    (message "Pop timer is not configured.")
+    (progn
+      (message "Setting pop timer for ~D:~2,'0D."
+               *pop-timer-minutes* *pop-timer-seconds*)
+      (let* ((warning-time 30)
+             (total-time (+ (* *pop-timer-minutes* 60) *pop-timer-seconds*))
+             (initial-time (- total-time warning-time)))
+        (sb-thread:make-thread
+          (lambda ()
+            (if (plusp initial-time)
+              (progn (sleep initial-time)
+                     (speak "Pop soon.")
+                     (sleep warning-time))
+              (sleep total-time))
+            (speak "Pop!"))
+          :name "Pop Timer")))))
+
+(defcommand run-pop-timer () ()
+  (pop-timer))
+
+(defcommand set-pop-timer (minutes seconds)
+    ((:integer "Minutes: ")
+     (:integer "Seconds: "))
+  (setf *pop-timer-minutes* minutes
+        *pop-timer-seconds* seconds))
+
+(defcommand tea-timer (seconds)
+    ((:integer "Seconds: "))
+  (run-shell-command (format nil "tea ~D" seconds)))
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stumpwm/utils.lisp	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,156 @@
+(in-package :stumpwm-user)
+
+(defun send-key% (key &optional (win (current-window)))
+  "Send key press and key release events for KEY to window WIN."
+  ;; from https://github.com/alezost/stumpwm-config/blob/master/utils.lisp
+  (let ((xwin (window-xwin win)))
+    (multiple-value-bind (code state) (stumpwm::key-to-keycode+state key)
+      (flet ((send (event)
+               (xlib:send-event xwin event (xlib:make-event-mask event)
+                                :display *display*
+                                :root (screen-root (window-screen win))
+                                :x 0 :y 0 :root-x 0 :root-y 0
+                                :window xwin :event-window xwin
+                                :code code
+                                :state state)))
+        (send :key-press)
+        (send :key-release)
+        (xlib:display-finish-output *display*)))))
+
+(defcommand send-key (key &optional (win (current-window))) (:key)
+  "Send key press and key release events for KEY to window WIN."
+  (send-key% key win))
+
+
+(defun string-contains (needle string)
+  (and (search needle string :test #'char=) t))
+
+(defun string-grep (needle text &key first-only)
+  (_ text
+    (split-sequence:split-sequence #\newline _)
+    (if first-only
+      (find needle _ :test #'string-contains)
+      (remove-if-not (alexandria:curry #'string-contains needle) _))))
+
+(defun string-split (delimiters string)
+  (split-sequence:split-sequence delimiters string
+                                 :test (lambda (bag ch)
+                                         (find ch bag :test #'char=))))
+
+(defun run-and-echo-shell-command (command &rest args)
+  (message command)
+  (apply #'run-shell-command command args))
+
+
+(defun mod+ (n increment modulo)
+  (mod (+ n increment) modulo))
+
+
+(defmacro defcached ((name &key seconds) &body body)
+  (let ((seconds (coerce seconds 'double-float)))
+    (with-gensyms (ttl next value)
+      `(let ((,ttl ,seconds)
+             (,next nil)
+             (,value nil))
+         (defun ,name ()
+           (when (or (null ,next)
+                     (>= (get-internal-real-time) ,next))
+             (setf ,next (+ (get-internal-real-time)
+                            (* internal-time-units-per-second ,ttl))
+                   ,value (progn ,@body)))
+           ,value)
+         (defun ,(symb name '/uncache) ()
+           (setf ,next nil ,value nil))))))
+
+
+(defun current-frame ()
+  (stumpwm::tile-group-current-frame (current-group)))
+
+
+(defun keywordize (string)
+  (_ string
+    (string-trim (string #\newline) _)
+    string-upcase
+    (intern _ (find-package :keyword))))
+
+(defparameter *host* (keywordize (machine-instance)))
+
+
+(defmacro ehostcase (&body clauses)
+  `(ecase *host* ,@clauses))
+
+(defmacro hostcase (&body clauses)
+  `(case *host* ,@clauses))
+
+
+(defcommand speak (text)
+    ((:string "Text: "))
+  (message text)
+  (run-shell-command (format nil "~~/src/dotfiles/bin/say '~A'" text)))
+
+
+(defun seconds->hours (seconds)
+  (/ seconds 60 60))
+
+(defun hours->seconds (hours)
+  (* hours 60 60))
+
+
+(define-stumpwm-type :integer (input prompt)
+  ;; Annoyingly, StumpWM's built-in :number type isn't actually number, but is
+  ;; actually just integers.  Define a better-named type here.
+  (when-let ((n (or (argument-pop input)
+                    (read-one-line (current-screen) prompt))))
+    (handler-case
+        (parse-integer n)
+      (parse-error (c)
+        (declare (ignore c))
+        (throw 'error "Integer required.")))))
+
+(define-stumpwm-type :real (input prompt)
+  (when-let ((n (or (argument-pop input)
+                    (read-one-line (current-screen) prompt))))
+    (handler-case
+        (let ((result (parse-number:parse-number n)))
+          (assert (typep result 'real))
+          result)
+      (error (c)
+        (declare (ignore c))
+        (throw 'error "Real required.")))))
+
+
+(defun window-match-p (query window)
+  "Return whether `window` matches `query`.
+
+  `query` must be of the form `(query-type query-value)`.
+
+  `query-type` must be one of `:title` or `:class`.
+
+  `query-value` must either be a string (which must be matched exactly) or
+  a PPCRE scanner.
+
+  "
+  (destructuring-bind (query-type query) query
+    (let ((value (ecase query-type
+                   (:title (window-title window))
+                   (:class (window-class window)))))
+      (etypecase query
+        (string (string= query value))
+        (function (ppcre:scan query value))))))
+
+(defun all-windows ()
+  "Return a fresh list of all windows on all screens.  Yes, all of them."
+  (mapcan #'screen-windows *screen-list*))
+
+(defun find-window (query)
+  "Find and return the first window that matches `query` under `window-match-p`."
+  (find-if (lambda (w) (window-match-p query w)) (all-windows)))
+
+(defun find-windows (query)
+  "Find and return a fresh list of all windows that match `query` under `window-match-p`."
+  (remove-if-not (lambda (w) (window-match-p query w)) (all-windows)))
+
+(defmacro when-let-window ((symbol title-query) &body body)
+  `(when-let ((,symbol (find-window `(:title ,(ppcre:create-scanner ,title-query)))))
+     ,@body))
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stumpwm/vlime.lisp	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,15 @@
+(in-package :stumpwm-user)
+
+(defcommand vlime () ()
+  (load "~/src/dotfiles/vim/bundle/vlime/lisp/start-vlime.lisp")
+  (message "Started VLIME"))
+
+(defcommand vlime-port (port) ((:integer "Port: "))
+  "Start VLIME on the given port.
+
+  Good for bootstrapping a VLIME connection when you accidentally started a
+  VLIME instance on another port that you don't want to mess with.
+
+  "
+  (funcall (read-from-string "vlime-loader::run") port)
+  (message "Started VLIME"))
--- a/stumpwmrc	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,951 +0,0 @@
-(in-package :stumpwm-user)
-(shadow :window)
-
-(ql:quickload '(:losh :split-sequence :alexandria :parse-number :str :cl-ppcre :bordeaux-threads :jarl :local-time)
-              :silent t)
-
-(use-package :losh)
-
-;;;; Config -------------------------------------------------------------------
-(set-prefix-key (kbd "C-space"))
-(local-time:reread-timezone-repository)
-
-(defvar *redirected* (redirect-all-output (data-dir-file "debug" "log")))
-
-(setf *mouse-focus-policy* :click
-      *message-window-gravity* :center
-      *input-window-gravity* :center
-      *debug-level* 0
-      *resize-increment* 75
-      *new-frame-action* :empty
-      *window-format* "(%n%m%20t)"
-      *window-name-source* :title
-      *maximum-completions* 20
-      *shell-program* "/home/sjl/src/dotfiles/bin/bash-dammit"
-      losh:*pbcopy-command* "/home/sjl/src/dotfiles/bin/pbcopy"
-      losh:*pbpaste-command* "/home/sjl/src/dotfiles/bin/pbpaste")
-
-
-;;;; Utils --------------------------------------------------------------------
-(defun string-contains (needle string)
-  (and (search needle string :test #'char=) t))
-
-(defun string-grep (needle text &key first-only)
-  (_ text
-    (split-sequence:split-sequence #\newline _)
-    (if first-only
-      (find needle _ :test #'string-contains)
-      (remove-if-not (alexandria:curry #'string-contains needle) _))))
-
-(defun string-split (delimiters string)
-  (split-sequence:split-sequence delimiters string
-                                 :test (lambda (bag ch)
-                                         (find ch bag :test #'char=))))
-
-(defun run-and-echo-shell-command (command &rest args)
-  (message command)
-  (apply #'run-shell-command command args))
-
-
-(defun mod+ (n increment modulo)
-  (mod (+ n increment) modulo))
-
-
-(defun volume ()
-  (_ (run-shell-command "amixer sget Master" t)
-    (string-grep "Front Left:" _ :first-only t)
-    (string-split "[]" _)
-    second
-    (string-trim "%" _)
-    parse-integer))
-
-
-(defun current-frame ()
-  (stumpwm::tile-group-current-frame (current-group)))
-
-
-(defun keywordize (string)
-  (_ string
-    (string-trim (string #\newline) _)
-    string-upcase
-    (intern _ (find-package :keyword))))
-
-(defparameter *host* (keywordize (machine-instance)))
-
-
-(defmacro ehostcase (&body clauses)
-  `(ecase *host* ,@clauses))
-
-(defmacro hostcase (&body clauses)
-  `(case *host* ,@clauses))
-
-
-(defcommand speak (text)
-    ((:string "Text: "))
-  (message text)
-  (run-shell-command (format nil "~~/src/dotfiles/bin/say '~A'" text)))
-
-
-(defun seconds->hours (seconds)
-  (/ seconds 60 60))
-
-(defun hours->seconds (hours)
-  (* hours 60 60))
-
-
-(define-stumpwm-type :integer (input prompt)
-  ;; Annoyingly, StumpWM's built-in :number type isn't actually number, but is
-  ;; actually just integers.  Define a better-named type here.
-  (when-let ((n (or (argument-pop input)
-                    (read-one-line (current-screen) prompt))))
-    (handler-case
-        (parse-integer n)
-      (parse-error (c)
-        (declare (ignore c))
-        (throw 'error "Integer required.")))))
-
-(define-stumpwm-type :real (input prompt)
-  (when-let ((n (or (argument-pop input)
-                    (read-one-line (current-screen) prompt))))
-    (handler-case
-        (let ((result (parse-number:parse-number n)))
-          (assert (typep result 'real))
-          result)
-      (error (c)
-        (declare (ignore c))
-        (throw 'error "Real required.")))))
-
-
-(defun window-match-p (query window)
-  "Return whether `window` matches `query`.
-
-  `query` must be of the form `(query-type query-value)`.
-
-  `query-type` must be one of `:title` or `:class`.
-
-  `query-value` must either be a string (which must be matched exactly) or
-  a PPCRE scanner.
-
-  "
-  (destructuring-bind (query-type query) query
-    (let ((value (ecase query-type
-                   (:title (window-title window))
-                   (:class (window-class window)))))
-      (etypecase query
-        (string (string= query value))
-        (function (ppcre:scan query value))))))
-
-(defun all-windows ()
-  "Return a fresh list of all windows on all screens.  Yes, all of them."
-  (mapcan #'screen-windows *screen-list*))
-
-(defun find-window (query)
-  "Find and return the first window that matches `query` under `window-match-p`."
-  (find-if (lambda (w) (window-match-p query w)) (all-windows)))
-
-(defun find-windows (query)
-  "Find and return a fresh list of all windows that match `query` under `window-match-p`."
-  (remove-if-not (lambda (w) (window-match-p query w)) (all-windows)))
-
-(defmacro when-let-window ((symbol title-query) &body body)
-  `(when-let ((,symbol (find-window `(:title ,(ppcre:create-scanner ,title-query)))))
-     ,@body))
-
-
-;;;; Posture ------------------------------------------------------------------
-(defparameter *posture-thread* nil)
-(defparameter *posture-should-stop* nil)
-(defparameter *posture-paused* nil)
-(defparameter *posture-snooze* nil)
-(defparameter *posture-current* 30)
-(defparameter *posture-min* 5)
-(defparameter *posture-max* (hours->seconds 2))
-
-(defun posture-paused-p ()
-  ;; this is the dumbest shit ever, but I can't figure out how to call into
-  ;; stumpish from the setguid slock process
-  (or *posture-paused* (probe-file "/tmp/.posture-pause")))
-
-(defun posture-snoozed-p ()
-  (and *posture-snooze*
-       (< (get-universal-time) *posture-snooze*)))
-
-(defcommand posture-pause () ()
-  (message "Pausing posture.")
-  (setf *posture-paused* t))
-
-(defcommand posture-unpause () ()
-  (message "Unpausing posture.")
-  (setf *posture-paused* nil))
-
-(defcommand posture-toggle-pause () ()
-  (if (setf *posture-paused* (not *posture-paused*))
-    (message "Posture is now paused.")
-    (message "Posture is now unpaused.")))
-
-(defcommand posture-snooze (hours)
-    ((:real "Snooze for how many hours? "))
-  (setf *posture-snooze* (+ (hours->seconds hours) (get-universal-time))))
-
-(defun posture-update (delta)
-  (setf *posture-current*
-        (clamp *posture-min* *posture-max* (* *posture-current* delta))))
-
-(defun posture-query ()
-  (speak "Is your posture okay?"))
-
-(defcommand posture-answer-yes () ()
-  (message "Good work.")
-  (run-shell-command "echo $(epochseconds) 1.0 >> ~/.posture.log")
-  (posture-update 11/10))
-
-(defcommand posture-answer-meh () ()
-  (message "Better than nothing.")
-  (run-shell-command "echo $(epochseconds) 0.5 >> ~/.posture.log"))
-
-(defcommand posture-answer-no () ()
-  (message "Try harder.")
-  (run-shell-command "echo $(epochseconds) 0.0 >> ~/.posture.log")
-  (posture-update 8/10))
-
-(defun posture% ()
-  (if *posture-should-stop*
-    nil
-    (progn (unless (or (posture-paused-p) (posture-snoozed-p))
-             (posture-query)
-             (sleep 10))
-           *posture-current*)))
-
-(defun posture-running-p ()
-  (and *posture-thread* (sb-thread:thread-alive-p *posture-thread*)))
-
-(defcommand posture-stop () ()
-  (setf *posture-should-stop* t))
-
-(defcommand posture-start () ()
-  (setf *posture-should-stop* nil)
-  (if (posture-running-p)
-    (message "Posture loop was already running.")
-    (setf *posture-thread*
-          (sb-thread:make-thread
-            (lambda ()
-              (loop :for seconds = (posture%)
-                    :while seconds
-                    :do (sleep seconds))
-              (message "Posture loop exiting."))
-            :name "Posture thread"))))
-
-
-;;;; Budget ------------------------------------------------------------------
-(defparameter *tz/eastern*
-  (local-time:find-timezone-by-location-name "US/Eastern"))
-
-(defparameter *budget/start*
-  (local-time:encode-timestamp 0 0 0 0 29 8 2023 :timezone *tz/eastern*))
-
-(defun budget/per-day ()
-  (first (losh:read-all-from-file "/home/sjl/Sync/budget/per-day")))
-
-(defun budget/elapsed ()
-  (local-time:timestamp-difference (local-time:now) *budget/start*))
-
-(defun budget/days-elapsed ()
-  (floor (/ (budget/elapsed) (* 60 60 24))))
-
-(defun budget/in ()
-  (* (budget/days-elapsed) (budget/per-day)))
-
-(defun budget/out ()
-  (loop :for path :in (directory "/home/sjl/Sync/budget/hosts/*/total")
-        :summing (print (first (read-all-from-file (print path))))))
-
-(defun budget/current ()
-  (- (budget/in) (budget/out)))
-
-(defcommand budget-dump () ()
-  (message
-    (sh '("sh" "-c" "tail -n 5 /home/sjl/Sync/budget/hosts/*/records")
-        :result-type 'string)))
-
-(defcommand budget () ()
-  (message "$~D" (budget/current)))
-
-(defmacro with-budget-file ((f file &rest open-args) &body body)
-  `(with-open-file
-     (,f (format nil "/home/sjl/Sync/budget/hosts/~(~A~)/~A" *host* ,file)
-      ,@open-args)
-     ,@body))
-
-(defcommand spend (amount what) ((:integer "Amount: $") (:string "For: "))
-  (let ((current (with-budget-file (total "total")
-                   (first (read-all-from-file total))))
-        (timestamp (local-time:to-rfc3339-timestring (local-time:now))))
-    (with-budget-file (total "total" :direction :output :if-exists :supersede)
-      (print (+ current amount) total))
-    (with-budget-file (records "records" :direction :output :if-exists :append :if-does-not-exist :create)
-      (print (list timestamp amount what) records))
-    (message "Spent $~D for ~A at ~A" amount what timestamp)))
-
-
-;;;; Load ---------------------------------------------------------------------
-(load-module "pass")
-
-;;;; Screenshotting -----------------------------------------------------------
-(defcommand screenshot () ()
-  (run-shell-command "screenshot"))
-
-(defcommand save-fucked-screenshot () ()
-  (run-shell-command "broken-screenshot"))
-
-(defcommand delete-fucked-screenshot () ()
-  (run-shell-command "delete-broken-screenshot"))
-
-
-;;;; Brightness ---------------------------------------------------------------
-(defparameter *brightness-values* #(0 1 5 10 20 30 40 55 70 85 100))
-(defvar *brightness-index* 5)
-
-(defun brightness ()
-  (aref *brightness-values* *brightness-index*))
-
-(defun set-brightness (value)
-  (run-and-echo-shell-command
-    (hostcase
-      ((:gro) (format nil "xrandr --output ~A --brightness ~D"
-                      (hostcase (:gro "eDP"))
-                      (/ value 100.0)))
-      (t (message "Not sure how to set brightness on this machine.")))))
-
-(defun rotate-brightness (delta)
-  (setf *brightness-index*
-        (mod+ *brightness-index* delta (length *brightness-values*)))
-  (set-brightness (brightness)))
-
-
-(defcommand rotate-brightness-up () ()
-  (rotate-brightness 1))
-
-(defcommand rotate-brightness-down () ()
-  (rotate-brightness -1))
-
-
-;;;; Miscellaneous ------------------------------------------------------------
-(defcommand sane-hsplit () ()
-  (hsplit)
-  (move-focus :right))
-
-(defcommand sane-vsplit () ()
-  (vsplit)
-  (move-focus :down))
-
-
-(defcommand move-focus* (direction)
-    ((:direction "Enter a direction: "))
-  (labels ((in-float-p ()
-             (typep (current-group) 'stumpwm::float-group))
-           (focus-first-frame ()
-             (unless (in-float-p)
-               ;; After moving to a new group we don't know which frame is
-               ;; focused, and unfortunately Stump doesn't give us a nice way to
-               ;; say "focus the leftmost frame" so we'll just move the focus
-               ;; a bunch of times and hope it's enough.  Sigh.
-               (loop :repeat 15
-                     :until (eql (current-frame)
-                                 (progn (move-focus (ecase direction
-                                                      (:left :right)
-                                                      (:right :left)))
-                                        (current-frame))))))
-           (next-group ()
-             (ecase direction
-               (:right (gnext))
-               (:left (gprev)))
-             (focus-first-frame)))
-    (unless (in-float-p)
-      (banish))
-    (if (in-float-p)
-      (next-group)
-      (let ((frame (current-frame)))
-        (move-focus direction)
-        (when (eql frame (current-frame))
-          (next-group))))))
-
-(defcommand screen-laptop () ()
-  (only)
-  (loop :with laptop = "eDP-1"
-        :with extern = "DP-1"
-        :for (output commands) :in `((,laptop ("--auto"))
-                                   (,laptop ("--primary"))
-                                   (,extern ("--off")))
-        :do (progn (uiop:run-program `("xrandr" "--output" ,output ,@commands)))))
-
-(defcommand screen-external () ()
-  (only)
-  (loop :with laptop = "eDP-1"
-        :with extern = "DP-1"
-        :for (output commands) :in `(
-                                     ;; (,laptop ("--off"))
-                                     (,extern ("--auto"))
-                                     (,extern ("--primary"))
-                                     (,laptop ("--auto"))
-                                     (,laptop ("--left-of" ,extern))
-                                     )
-        :do (uiop:run-program `("xrandr" "--output" ,output ,@commands))))
-
-(defcommand vlime () ()
-  (load "~/src/dotfiles/vim/bundle/vlime/lisp/start-vlime.lisp")
-  (message "Started VLIME"))
-
-(defcommand vlime-port (port) ((:integer "Port: "))
-  "Start VLIME on the given port.
-
-  Good for bootstrapping a VLIME connection when you accidentally started a
-  VLIME instance on another port that you don't want to mess with.
-
-  "
-  (funcall (read-from-string "vlime-loader::run") port)
-  (message "Started VLIME"))
-
-(defcommand toggle-current-mode-line () ()
-  (toggle-mode-line (current-screen) (current-head)))
-
-(defcommand toggle-stumptray () ()
-  (run-commands "stumptray"))
-
-(defcommand pass-personal () ()
-  (let ((pass:*password-store* "/home/sjl/.password-store/")
-        (pass:*pass-notification-message* t))
-    (pass:pass-copy)))
-
-(defcommand generate-password () ()
-  (run-shell-command "genpass | pbc")
-  (message "Generated a fresh password and copied it to the clipboard."))
-
-(defcommand kill-and-remove () ()
-  (run-commands "kill" "remove"))
-
-(defcommand sleep-machine ()
-    ()
-  (hostcase
-    ((:gro)
-     (run-shell-command "exec lock-screen")
-     (run-shell-command "systemctl suspend"))
-    (t (message "Not sleeping this machine for safety."))))
-
-(defcommand copy-clhs-url (s)
-    ((:string "Symbol: "))
-  (run-shell-command (format nil "clhs --url 'http://www.lispworks.com/documentation/HyperSpec/' --quiet --open echon '~A' | pbcopy" s)))
-
-(defcommand describe-window () ()
-  (show-window-properties))
-
-(defcommand rain () ()
-  (_ '("/home/sjl/src/dotfiles/lisp/bin/weather" "-H" "36")
-    (losh:sh _ :result-type 'list)
-    (mapcar (lambda (line) (ppcre:regex-replace " 1[0-9]:00 " line "^6\\&^*")) _)
-    (message "~{~A~^~%~}" _)))
-
-(defcommand mark (thing) ((:string "Mark: "))
-  (run-shell-command (format nil "mark ~A" thing)))
-
-(defcommand toggle-zoom-mute () ()
-  (when-let-window (win "^Zoom Meeting.*")
-    ;; Zoom stupidly won't accept the shortcut unless it's in focus
-    (unless (eql (window-group win) (current-group))
-      ;;        jesus            christ        stump just export switch-to-group come on
-      (gselect (princ-to-string (group-number (window-group win)))))
-    (focus-window win t)
-    (meta (kbd "M-a"))))
-
-(defcommand end-zoom () ()
-  (when-let-window (win "^Zoom Meeting.*")
-    (kill-window win))
-  (sleep 2)
-  (when-let-window (win "^Zoom -.*")
-    (kill-window win)))
-
-(defcommand clear-notifications () ()
-  (run-shell-command "dunstctl close-all"))
-
-
-
-;;;; Terminal Fonts -----------------------------------------------------------
-(defcommand reload-terminal-font-size ()
-    ()
-  (setf *terminal-font-size*
-        (if (probe-file "/home/sjl/.terminal-font")
-          (with-open-file (f "/home/sjl/.terminal-font")
-            (read f))
-          11)))
-
-(defparameter *terminal-font-size* (if (probe-file "/home/sjl/.terminal-font")
-                                     (with-open-file (f "/home/sjl/.terminal-font")
-                                       (read f))
-                                     11))
-
-(defcommand st-font-up ()
-    ()
-  (loop :repeat 7 :do (meta (kbd "C-S-SunPageUp"))))
-
-(defcommand st-font-down ()
-    ()
-  (loop :repeat 7 :do (meta (kbd "C-S-SunPageDown"))))
-
-(defcommand st-font-reset ()
-    ()
-  (meta (kbd "C-S-Home")))
-
-
-;;;; Clipboard/Data Generation ------------------------------------------------
-(load-module "clipboard-history")
-(clipboard-history:start-clipboard-manager)
-
-(defcommand generate-random-uuid () ()
-  (run-shell-command "uuidgen | tr -d '\\n' | ~/src/dotfiles/bin/pbcopy")
-  (message "Copied random UUID to clipboard."))
-
-(defcommand bee-movie-script () ()
-  (run-shell-command "pbeecopy")
-  (message "Copied the entire Bee Movie script to clipboard."))
-
-(defcommand urlize-jira-issue () ()
-  (let ((issue (str:trim (pbpaste))))
-    (if (ppcre:scan "^[A-Z0-9]+-\\d+$" issue)
-      (let* ((endpoint (str:trim (run-shell-command "grep endpoint .jira.d/config.yml | sed -e 's/.*: //'" t)))
-             (url (format nil "~A/browse/~A" endpoint issue)))
-        (pbcopy url)
-        (message "Copied ~A to the clipboard." url))
-      (message "Clipboard does not look like a JIRA issue."))))
-
-
-;;;; Applications -------------------------------------------------------------
-(defcommand spotify () ()
-  (run-or-raise "spotify" '(:class "Spotify")))
-
-(defcommand files () ()
-  (run-shell-command "open $HOME"))
-
-(defcommand browser () ()
-  (run-or-raise "firefox" '(:class "firefox")))
-
-(defcommand vlc () ()
-  (run-or-raise "vlc" '(:class "vlc")))
-
-(defcommand terminal () ()
-  (run-shell-command (format nil "st -f 'Ubuntu Mono:size=~D'" *terminal-font-size*)))
-
-(defcommand gcontrol () ()
-  (run-or-raise "gcontrol" '(:class "Gnome-control-center")))
-
-(defcommand zoom () ()
-  (when-let-window (w "^Zoom Meeting.*")
-    (focus-window w t)))
-
-
-;;;; Timers -------------------------------------------------------------------
-(defparameter *pop-timer-minutes* nil)
-(defparameter *pop-timer-seconds* nil)
-
-(defun pop-timer ()
-  (if (or (null *pop-timer-minutes*)
-          (null *pop-timer-seconds*))
-    (message "Pop timer is not configured.")
-    (progn
-      (message "Setting pop timer for ~D:~2,'0D."
-               *pop-timer-minutes* *pop-timer-seconds*)
-      (let* ((warning-time 30)
-             (total-time (+ (* *pop-timer-minutes* 60) *pop-timer-seconds*))
-             (initial-time (- total-time warning-time)))
-        (sb-thread:make-thread
-          (lambda ()
-            (if (plusp initial-time)
-              (progn (sleep initial-time)
-                     (speak "Pop soon.")
-                     (sleep warning-time))
-              (sleep total-time))
-            (speak "Pop!"))
-          :name "Pop Timer")))))
-
-(defcommand run-pop-timer () ()
-  (pop-timer))
-
-(defcommand set-pop-timer (minutes seconds)
-    ((:integer "Minutes: ")
-     (:integer "Seconds: "))
-  (setf *pop-timer-minutes* minutes
-        *pop-timer-seconds* seconds))
-
-(defcommand tea-timer (seconds)
-    ((:integer "Seconds: "))
-  (run-shell-command (format nil "tea ~D" seconds)))
-
-
-;;;; Isk ----------------------------------------------------------------------
-(defcommand send-key (key &optional (win (current-window))) (:key)
-  "Send key press and key release events for KEY to window WIN."
-  ;; from https://github.com/alezost/stumpwm-config/blob/master/utils.lisp
-  (let ((xwin (window-xwin win)))
-    (multiple-value-bind (code state) (stumpwm::key-to-keycode+state key)
-      (flet ((send (event)
-               (xlib:send-event xwin event (xlib:make-event-mask event)
-                                :display *display*
-                                :root (screen-root (window-screen win))
-                                :x 0 :y 0 :root-x 0 :root-y 0
-                                :window xwin :event-window xwin
-                                :code code
-                                :state state)))
-        (send :key-press)
-        (send :key-release)
-        (xlib:display-finish-output *display*)))))
-
-(defun send-keys (keys &key (win (current-window)) (sleep 0))
-  (dolist (k keys)
-    (send-key (kbd k) win)
-    (sleep sleep)))
-
-(defmacro defmultikey (name key compose-keys)
-  ;; Unfortunately we can't reliably autogen the name with something like
-  ;; (symb 'mk- compose-key) here because things like đ (th) and Đ (TH) would
-  ;; case fold to the same name.
-  `(progn
-     (defcommand ,name () ()
-       (send-keys '("Multi_key" ,@(map 'list #'string compose-keys))))
-     (define-key *top-map*
-       (kbd ,key) ,(string name))))
-
-(defmacro defmultikeys (&rest bindings)
-  `(progn ,@(loop for binding :in bindings :collect `(defmultikey ,@binding))))
-
-(defmultikeys
-  (isk-l-á "M-a" "'a")
-  (isk-u-Á "M-A" "'A")
-  (isk-l-é "M-e" "'e")
-  (isk-u-É "M-E" "'E")
-  (isk-l-í "M-i" "'i")
-  (isk-u-Í "M-I" "'I")
-  (isk-l-ó "M-o" "'o")
-  (isk-u-Ó "M-O" "'O")
-  (isk-l-ö "M-m" "\"o")
-  (isk-u-Ö "M-M" "\"O")
-  (isk-l-ú "M-u" "'u")
-  (isk-u-Ú "M-U" "'U")
-  (isk-l-ý "M-y" "'y")
-  (isk-u-Ý "M-Y" "'Y")
-  (isk-l-þ "M-t" "th")
-  (isk-u-Þ "M-T" "TH")
-  (isk-l-đ "M-d" "dh")
-  (isk-u-Đ "M-D" "DH")
-  (isk-l-æ "M-h" "ae")
-  (isk-u-Æ "M-H" "AE"))
-
-
-;;;; Key Mapping --------------------------------------------------------------
-;;; Conventions:
-;;;
-;;; * Hyper-dir: move focus
-;;; * Hyper-Shift-dir: move window
-;;; * Hyper-Shift-Control-dir: swap window
-;;; * Hyper-F*: hardware
-;;; * Shift-F*: timers
-;;; * Hyper-Super-*: layout
-;;; * Hyper-*: miscellaneous
-
-(defmacro define-top-keys (&body keyforms)
-  `(progn ,@(loop :for form :in keyforms
-                  :collect `(define-key *top-map*
-                              (kbd ,(first form))
-                              ,(second form)))))
-
-
-(define-top-keys ;; miscellaneous
-  ("H-m" "terminal")
-  ("H-M" "mark")
-  ("H-SunPageUp" "st-font-up")
-  ("H-SunPageDown" "st-font-down")
-  ("H-Home" "st-font-reset")
-  ("H-\\" "pass-personal")
-  ("H-|" "generate-password")
-  ("H-b" "browser")
-  ("H-O" "spotify")
-  ("H-o" "files")
-  ("H-z" "zoom")
-  ("H-Z" "toggle-zoom-mute")
-  ("C-H-Z" "end-zoom")
-  ("F26"   "prev")
-  ("S-F26" "next")
-  ("H-q" "exec lock-screen")
-  ("H-y" "screenshot")
-  ("H-g" "gcontrol")
-  ("H-f" "save-fucked-screenshot")
-  ("H-F" "delete-fucked-screenshot")
-  ("H-R" "loadrc")
-  ("H-r" "rain")
-  ("H-V" "vlc")
-  ("H-4" "budget")
-  ("H-$" "spend")
-  ("C-BackSpace" "clear-notifications")
-  )
-
-(define-top-keys ;; clipboard
-  ("H-c" "show-clipboard-history")
-  ("H-C" "clear-clipboard-history")
-  ("H-u" "generate-random-uuid")
-  ("H-B" "bee-movie-script")
-  ("M-H-u" "urlize-jira-issue"))
-
-(define-top-keys ;; movement
-  ("H-h" "move-focus* left")
-  ("H-j" "move-focus down")
-  ("H-k" "move-focus up")
-  ("H-l" "move-focus* right")
-
-  ("H-H" "move-window left")
-  ("H-J" "move-window down")
-  ("H-K" "move-window up")
-  ("H-L" "move-window right")
-
-  ("H-1" "gselect 1")
-  ("H-2" "gselect 2")
-  ("H-3" "gselect 3")
-
-  ("H-!" "gmove 1")
-  ("H-@" "gmove 2")
-  ("H-#" "gmove 3")
-
-  ("C-H-H" "exchange-direction left")
-  ("C-H-J" "exchange-direction down")
-  ("C-H-K" "exchange-direction up")
-  ("C-H-L" "exchange-direction right")
-
-  ("H-`" "next")
-  ("H-~" "prev") ;; "
-  ("H-n" "next-in-frame")
-  ("H-p" "prev-in-frame")
-  ("H-N" "pull-hidden-next")
-  ("H-P" "prev-in-frame")
-
-  ("H-," "pull-from-windowlist"))
-
-(define-top-keys ;; splitting
-  ("H-s" "sane-vsplit")
-  ("H-v" "sane-hsplit")
-  ("H-=" "balance-frames"))
-
-(define-top-keys ;; killing
-  ("H-w" "delete")
-  ("H-W" "kill")
-  ("H-BackSpace" "remove")
-  ("S-H-BackSpace" "kill-and-remove"))
-
-(define-top-keys ;; naming
-  ("H-'" "title"))
-
-(define-top-keys ;; sound
-  ("H-F1" "mute")
-  ("H-F2" "exec amixer -q sset Master 5%-")
-  ("H-F3" "exec amixer -q sset Master 5%+"))
-
-(define-top-keys ;; screen
-  ("H-F5" "rotate-brightness-down")
-  ("H-F6" "rotate-brightness-up")
-  ("H-F7" "screen-laptop")
-  ("H-F8" "screen-external"))
-
-(define-top-keys ;; layout
-  ("s-H-t" "restore-from-file thirds")
-  ("s-H-m" "restore-from-file dev")
-  ("s-H-s" "restore-from-file streaming")
-  ("s-H-w" "restore-from-file work")
-  ("s-H-z" "restore-from-file zoom"))
-
-(define-top-keys ;; timers
-  ("s-F7"  "tea-timer")
-  ("s-F9"  "run-pop-timer")
-  ("s-F8"  "set-pop-timer")
-  ("s-p"   "posture-start")
-  ("s-P"   "posture-stop")
-  ("s-y"   "posture-answer-yes")
-  ("s-h"   "posture-answer-meh")
-  ("s-n"   "posture-answer-no")
-  ("s-\\"  "posture-toggle-pause")
-  ("s-o"   "posture-snooze"))
-
-(define-top-keys ;; stump
-  ("Pause" "terminal") ; jesus christ
-  ("H-F9"  "sleep-machine")
-  ("H-F10" "toggle-stumptray")
-  ("H-F11" "toggle-current-mode-line")
-  ("H-F12" "refresh-heads"))
-
-
-;; (stumpwm::unbind-remapped-keys)
-(define-remapped-keys
-  '(("st-256color"
-     ("s-c" . "C-C")
-     ("s-v" . "C-V")
-     ("C-=" . "S-C-SunPageUp")
-     ("C--" . "S-C-SunPageDown")
-     ("C-0" . "S-C-Home"))
-    ("(firefox|Google-chrome|Chromium-browser)"
-     ("s-1" . "C-S-Tab")
-     ("s-2" . "C-Tab")
-     ("C-a" . "Home")
-     ("C-e" . "End")
-     ;; I always try to hit ctrl-d to kill a browser window because I'm so used
-     ;; to terminal windows, and it ends up bookmarking the damn page.  In the
-     ;; interest of not having a random collection of bookmarks grow over time,
-     ;; I'll just add a mapping to compensate for my stupid brain.
-     ("C-d" . "C-w")
-     ;; todo debug why this breaks a really fast C-a-k roll
-     ;; ("C-a" . "Home")
-     ;; ("C-e" . "End")
-     ("s-a" . "C-a")
-     ("s-d" . "C-d")
-     ("s-l" . "C-l")
-     ("s-t" . "C-t")
-     ("s-w" . "C-w")
-     ("s-r" . "C-r")
-     ("s-f" . "C-f")
-     ("s-z" . "C-z")
-     ("s-x" . "C-x")
-     ("s-c" . "C-c")
-     ("s-v" . "C-v"))
-    (""
-     ("s-z" . "C-z")
-     ("s-x" . "C-x")
-     ("s-c" . "C-c")
-     ("s-v" . "C-v"))))
-
-
-;;;; Sensors ------------------------------------------------------------------
-(defun ? (obj &rest keys)
-  (if (null keys)
-      obj
-      (apply #'? (etypecase obj
-                   (hash-table (gethash (first keys) obj)))
-             (rest keys))))
-
-(defun parse-sensors ()
-  ;; sensors -j is stupid and will output errors before the actual output on
-  ;; standard out, instead of putting them on standard err like a reasonable
-  ;; program, e.g.:
-  ;;
-  ;;     ERROR: Can't get value of subfeature temp1_input: Can't read
-  ;;     {
-  ;;        "iwlwifi_1-virtual-0":{ … },
-  ;;        …
-  ;;
-  ;; So we'll have to drop the `ERROR` lines before we can get to the actual
-  ;; goddamn JSON.  UNIX programs are so great.
-  (let ((s (losh:sh '("sensors" "-j") :result-type 'stream)))
-    (loop :while (char= #\E (peek-char nil s)) :do (read-line s))
-    (jarl:read t s)))
-
-(defparameter *sensors-refresh-delay* 5.0 "How long between sensor refreshes (in seconds).")
-(defparameter *sensors-next-refresh* nil)
-(defparameter *sensors-cache* nil)
-
-(defun sensors% (&aux (sensors (parse-sensors)))
-  (hostcase
-    (:ouroboros (format nil "[CPU ~D°C] [GPU ~D°C ~D°C ~D°C]"
-                        (round (? sensors "nct6779-isa-0290" "CPUTIN" "temp2_input"))
-                        (round (? sensors "amdgpu-pci-4500"  "edge"     "temp1_input"))
-                        (round (? sensors "amdgpu-pci-4500"  "junction" "temp2_input"))
-                        (round (? sensors "amdgpu-pci-4500"  "mem"      "temp3_input"))))
-    (:gro (format nil "[CPU ~D°C] [GPU ~D°C]"
-                        (round (? sensors "thinkpad-isa-0000" "CPU"  "temp1_input"))
-                        (round (? sensors "amdgpu-pci-0600"   "edge" "temp1_input"))))
-    (t "?")))
-
-(defun sensors (&aux (now (get-internal-real-time)))
-  (if (or (null *sensors-next-refresh*)
-          (>= now *sensors-next-refresh*))
-      (setf *sensors-next-refresh* (+ now (* internal-time-units-per-second *sensors-refresh-delay*))
-            *sensors-cache* (sensors%))
-      *sensors-cache*))
-
-(defun sensors-modeline (ml)
-  (declare (ignore ml))
-  (sensors))
-
-(add-screen-mode-line-formatter #\S #'sensors-modeline)
-
-
-;;;; Modeline -----------------------------------------------------------------
-(load-module "battery-portable")
-(load-module "cpu")
-(load-module "hostname")
-(load-module "mem")
-
-(defun ensure-mode-line ()
-  (when (not (stumpwm::head-mode-line (current-head)))
-    (toggle-mode-line (current-screen) (current-head))))
-
-
-(defun configure-modeline ()
-  (setf
-    *time-modeline-string*
-    "%a %b %e %H:%M"
-
-    cpu::*cpu-usage-modeline-fmt*
-    "^[~A~3D%^]"
-
-    cpu::*cpu-modeline-fmt*
-    "[%c] [%f]"
-
-    mem::*mem-modeline-fmt*
-    "%b"
-
-    *screen-mode-line-format*
-    (append
-      (list "[^B%n^b@%h] %W^>")
-
-      #+todo-some-day (list ;; "(V "
-                            ;; ;; '(:eval (volume))
-                            ;; ")"
-                            " ")
-
-      ;; battery and brightness for laptops
-      (hostcase
-        ((:gro)
-         '("(B %B)"
-           " (BR "
-           (:eval (princ-to-string (brightness)))
-           "%)")))
-
-      ;; temp, cpu, mem, time, tray
-      #+no (list "(TEMP %S) (CPU %C) (MEM %M) %d %T")
-      (list "(CPU %C) (MEM %M) %d %T")
-      ))
-
-  (setf *mode-line-timeout* 10)
-  (setf *mode-line-background-color* "#111111")
-
-  (ensure-mode-line)
-  )
-
-(configure-modeline)
-
-
-;;;; System Tray --------------------------------------------------------------
-(load-module "stumptray")
-(defvar *tray-loaded* (run-commands "stumptray"))
-
-
-;;;; Unbreak Typing -----------------------------------------------------------
-(defun stumpwm::input-insert-hyphen-or-space (input key)
-  (declare (ignore key))
-  (input-insert-char input #\space))
-
-
-;;;; Startup ------------------------------------------------------------------
-;; (defvar *dropbox*
-  ;; (run-shell-command "~/.dropbox-dist/dropboxd"))
-
-#+no(defvar *dunst*
-  (run-shell-command "/usr/bin/dunst -conf ~/.dunstrc"))
-
-(when (probe-file "/home/sjl/.stumpwmrc.local")
-  (load "/home/sjl/.stumpwmrc.local"))
-
-
-#;;; Scratch ------------------------------------------------------------------
--- a/vim/custom-dictionary.utf-8.add	Sun May 18 14:59:11 2025 -0400
+++ b/vim/custom-dictionary.utf-8.add	Wed Aug 27 16:19:24 2025 -0400
@@ -336,3 +336,235 @@
 skald
 dwarven
 tradeoff
+Snakefile
+Snakemake
+melanosomes
+melanosome
+threonine
+multiomics
+ANOVA
+μL
+Opentrons
+walkthrough
+nanopore
+intron
+retrotranspose
+retrotransposition
+transposons
+retrotransposon
+transposon
+retrotransposons
+wildcard
+PIBS
+Thinkpad
+HG545
+Lenovo
+methylation
+CpG
+thermocycler
+CAS9
+RNP
+gDNA
+QuickCIP
+dephosphorylation
+labware
+thermocycling
+Torrin
+thermocycle
+Neisseria
+Gonorrhoeae
+Azithromycin
+Ceftriaxone
+Quinolone
+Quinolones
+folates
+sulfanilamide
+prodrug
+diethylene
+Prontosil
+transpeptidase
+lactam
+crosslinking
+crosslink
+Unicycler
+FASTQs
+Jupyter
+topoisomerases
+topoisomerase
+quinolone
+ribosomes
+macrolide
+Macrolides
+Trimmomatic
+microbiome
+BI529
+BS522
+BS521
+NCBI
+conda
+Dhatri
+OOMs
+BI602
+lactamase
+backends
+webapp
+webapps
+B2
+ggplot2
+AMRFinder
+AMRFinderPlus
+BI545
+PIBS800
+isoforms
+gyrase
+handwave
+SNPs
+Prensner
+acetylation
+arginine
+histone
+histones
+octamer
+heterochromatin
+trimethylated
+BIOINF
+astrocyte
+Armis
+Taubman
+H3K27M
+polycomb
+Cristian
+DBvolve
+sqlite
+Bree
+Midgewater
+Chetwood
+noncanonical
+XRT
+Snakemake's
+RiboTIE
+rawdogging
+multimapping
+Riboseq
+X11
+JS
+GRSA
+CCTC
+BLASTs
+mosaicism
+amyloid
+microcentrifuge
+P1000
+P10
+Trypan
+mL
+vortexer
+OOM
+Nanopal
+Slurm
+hardcode
+SDS
+dropwise
+vortexing
+segfaulting
+segfaulted
+IGV
+hardcoding
+nonlocal
+basecalling
+systemd
+basecall
+basecalls
+basecaller
+basecalled
+Cas9
+samtools
+mux
+hostnames
+wifi
+Minimap2
+hedera
+Ansible
+SMaHT
+NOP
+unboxing
+NUCs
+Torrin's
+Margit
+hederas
+auth
+MinKNOW
+MinION
+deduplicate
+snakemake
+repo's
+pericentromeric
+MEI
+inodes
+AluYb
+AluYa
+FASTQ
+BAM
+pseudobulk
+MEIs
+sneakernet
+CADIA
+deduplicates
+unmerged
+DCMB
+EECS
+GM12878
+WGS
+VCFs
+traceback
+ethernet
+reenabled
+deduplicated
+dedupe
+Alu
+Alus
+coreutils
+CRISPR
+overcounting
+speedrun
+FF6
+kimchi
+SAA
+DJ'ed
+TA'ed
+wildcards
+counterintuitive
+ish
+ECC
+Ypsi
+etouffée
+Ansibilize
+fstab
+Datura
+Ansibilized
+indels
+DSBs
+NHEJ
+LINEs
+LRS
+SV
+SRS
+HC
+transposase
+ATAC
+clusterfuck
+Kerrytown
+Friendsgiving
+LVM
+basecaller
+flowcell
+reparse
+venv
+env
+concat
+Beszel
+basecallers
+untar
+unclustered
+helicase
+reauthenticate
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vim/ftplugin/nextflow/folding.vim	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,16 @@
+setlocal foldmethod=expr
+setlocal foldexpr=GetNextflowFold(v:lnum)
+
+function! GetNextflowFold(lnum)
+    let line = getline(a:lnum)
+
+    if line =~ '^\S.*{$'
+        return '>1'
+    elseif line =~ '^}$'
+        return '<1'
+    elseif line =~ '^\S'
+        return 0
+    else
+        return "="
+    end
+endfunction
--- a/vim/ftplugin/snakemake/folding.vim	Sun May 18 14:59:11 2025 -0400
+++ b/vim/ftplugin/snakemake/folding.vim	Wed Aug 27 16:19:24 2025 -0400
@@ -9,16 +9,25 @@
 
     let thisline = getline(a:lnum)
 
-    " blank lines end folds
+    " two blank lines end folds
     if thisline =~? '\v^\s*$'
+        if ActualPreviousLineNonblank(a:lnum)
+            return "="
+        endif
         return '-1'
     " start fold on top level rules or python objects
     elseif thisline =~? '\v^(rule|def|checkpoint|class)'
         return ">1"
     elseif thisline =~? '\v^\S'
-        if PreviousLineIndented(a:lnum) && NextRuleIndented(a:lnum)
-            return ">1"
+        if IsEmpty(a:lnum+1)
+            if IsEmpty(a:lnum+2)
+                return ">1"
+            endif
         endif
+        return "0"
+        " if PreviousLineIndented(a:lnum) && NextRuleIndented(a:lnum)
+        "     return ">1"
+        " endif
     endif
 
     return "="
@@ -59,3 +68,35 @@
 
     return 0
 endfunction
+
+function! ActualPreviousLineNonblank(lnum)
+    let current = a:lnum - 1
+
+    if current >= 1
+        let thisline = getline(current)
+        if thisline =~? '\v^\s*\S'
+            return 1
+        else
+            return 0
+        endif
+    endwhile
+
+    return 0
+endfunction
+
+function! IsEmpty(lnum)
+    let numlines = line('$')
+    if a:lnum > numlines
+        return 0
+    elseif a:lnum < 1
+        return 0
+    else
+        let thisline = getline(a:lnum)
+        if thisline =~? '\v^$'
+            return 1
+        else
+            return 0
+        endif
+    endif
+endfunction
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vim/syntax/htmlten.vim	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,38 @@
+" based on the htmldjango syntax
+
+if exists("b:current_syntax")
+  finish
+endif
+
+if !exists("main_syntax")
+  let main_syntax = 'html'
+endif
+
+runtime! syntax/html.vim
+unlet b:current_syntax
+
+syn region tenArgument contained start=/"/ skip=/\\"/ end=/"/
+
+syn match tenError "%}\|}}\|#}"
+syn match tenTagError contained "#}\|{{\|[^%]}}\|[#]"
+syn match tenVarError contained "#}\|{%\|%}\|[<>!#]"
+
+syn cluster tenBlocks add=tenTagBlock,tenVarBlock,tenComment,tenComBlock
+
+syn region tenTagBlock start="{%" end="%}" contains=tenArgument,tenTagError display containedin=ALLBUT,@djangoBlocks
+syn region tenVarBlock start="{{" end="}}" contains=tenArgument,tenVarError display containedin=ALLBUT,@djangoBlocks
+syn region tenComment start="{%\s*comment\(\s\+.\{-}\)\?%}" end="{%\s*endcomment\s*%}" containedin=ALLBUT,@tenBlocks
+syn region tenComBlock start="{#" end="#}" containedin=ALLBUT,@tenBlocks
+
+
+hi def link tenTagBlock PreProc
+hi def link tenVarBlock PreProc
+hi def link tenComment Comment
+hi def link tenComBlock Comment
+
+hi def link tenArgument Constant
+hi def link tenError Error
+hi def link tenTagError Error
+hi def link tenVarError Error
+
+let b:current_syntax = "htmlten"
--- a/vim/syntax/lisp.vim	Sun May 18 14:59:11 2025 -0400
+++ b/vim/syntax/lisp.vim	Wed Aug 27 16:19:24 2025 -0400
@@ -63,8 +63,9 @@
 
 "                                              start  prefixparameters   modifiers      directive
 syn match lispStringFormatDirective contained /\v[~](([-+]?[0-9]*|'.),?)*(:\@|\@:|:|\@)?./
+syn match lispStringAprilComment contained /\v⍝(.)*/
 
-syn region			lispString			start=+"+ skip=+\\\\\|\\"+ end=+"+	contains=lispStringFormatDirective,@Spell
+syn region			lispString			start=+"+ skip=+\\\\\|\\"+ end=+"+	contains=lispStringFormatDirective,lispStringAprilComment,@Spell
 
 if exists("g:lisp_instring")
  syn region			lispInString			keepend matchgroup=Delimiter start=+"(+rs=s+1 skip=+|.\{-}|+ matchgroup=Delimiter end=+)"+ contains=@lispBaseListCluster,lispInStringString
@@ -106,6 +107,7 @@
   hi def link lispAtomMark		lispMark
   hi def link lispInStringString	lispString
   hi def link lispStringFormatDirective	SpecialChar
+  hi def link lispStringAprilComment	lispComment
 
   hi def link lispAtom			Identifier
   hi def link lispAtomBarSymbol		Special
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vim/ultisnips-bullshit/README.markdown	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,5 @@
+This directory would be named "snippets" like a reasonable person would
+immediately think to name it, except that UltiSnips hardcodes that one
+particular directory name and magically parses it as some other snippet format,
+which fails with an inscrutable error.  So we have to name it something else.
+Great first 30 second experience, UltiSnips.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vim/ultisnips-bullshit/html.snippets	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,191 @@
+snippet doctype "HTML - 5.0 (doctype)" b
+<!DOCTYPE html>
+endsnippet
+
+snippet html "tag: html" bi
+<html>
+    $1
+</html>
+endsnippet
+
+snippet head "tag: head" b
+<head>
+    $1
+</head>
+endsnippet
+
+snippet body "tag: body" b
+<body>
+    $1
+</body>
+endsnippet
+
+snippet nav "tag: nav" b
+<nav>
+    $1
+</nav>
+endsnippet
+
+snippet title "tag: title" b
+<title>$1</title>$0
+endsnippet
+
+snippet form "tag: form" b
+<form action="${1:/path}" method="${2:get}">
+    $0
+</form>
+endsnippet
+
+snippet fieldset "tag:fieldset" b
+<fieldset>
+    <legend>$1</legend>
+    $2
+</fieldset>
+endsnippet
+
+snippet label "tag: label" b
+<label for="${1:field}">$2</label>
+endsnippet
+
+snippet input "tag: input" b
+<input id="${1:field}" type="${2:text}" name="${3:name}" value="$4" />
+endsnippet
+
+snippet submit "tag: submit" b
+<input type="submit" value="${1:Search}" />
+endsnippet
+
+snippet style "stylesheet" b
+<link href="${1:style.css}"  rel="stylesheet" type="text/css" />
+endsnippet
+
+snippet main "tag: main" b
+<main>
+    $0
+</main>
+endsnippet
+
+snippet header "tag: header" b
+<header>
+    $0
+</header>
+endsnippet
+
+snippet footer "tag: footer" b
+<footer>
+    $0
+</footer>
+endsnippet
+
+snippet table "tag: table" b
+<table>
+    $0
+</table>
+endsnippet
+
+snippet thead "tag: thead" b
+<thead>
+    $0
+</thead>
+endsnippet
+
+snippet tbody "tag: tbody" b
+<tbody>
+    $0
+</tbody>
+endsnippet
+
+snippet tfoot "tag: tfoot" b
+<tfoot>
+    $0
+</tfoot>
+endsnippet
+
+
+snippet tr "tag: tr" b
+<tr>
+    $0
+</tr>
+endsnippet
+
+snippet td "tag: td" b
+<td>$1</td>
+endsnippet
+
+snippet th "tag: th" b
+<th>$1</th>
+endsnippet
+
+snippet href "tag: a href" i
+<a href="${1:https://example.com}">${2:Text}</a>
+endsnippet
+
+snippet " ." "class" i
+ class="$1"
+endsnippet
+
+snippet P "tag: p" b
+<p>
+    $1
+</p>
+endsnippet
+
+snippet p "tag: p" b
+<p>$1</p>
+endsnippet
+
+snippet legend "tag: legend" b
+<legend>$1</legend>
+endsnippet
+
+snippet span "tag: span" b
+<span$1>$2</span>
+endsnippet
+
+snippet div "tag: div" b
+<div$1>$2</div>
+endsnippet
+
+snippet Div "tag: div" b
+<div$1>
+    $2
+</div>
+endsnippet
+
+snippet ul "tag: ul" b
+<ul>
+    $1
+</ul>
+endsnippet
+
+snippet li "tag: li" b
+<li>$1</li>
+endsnippet
+
+snippet h1 "tag: h1" b
+<h1>$1</h1>
+endsnippet
+
+snippet h2 "tag: h2" b
+<h2>$1</h2>
+endsnippet
+
+snippet h3 "tag: h3" b
+<h3>$1</h3>
+endsnippet
+
+snippet h4 "tag: h4" b
+<h4>$1</h4>
+endsnippet
+
+snippet h5 "tag: h5" b
+<h5>$1</h5>
+endsnippet
+
+snippet h6 "tag: h6" b
+<h6>$1</h6>
+endsnippet
+
+snippet script "tag: script" b
+<script src="$1"></script>
+endsnippet
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vim/ultisnips-bullshit/htmldjango.snippets	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,47 @@
+extends html
+
+snippet % "Tag" i
+{% ${1:tag} %}
+    $0
+{% end %}
+endsnippet
+
+snippet { "Var" i
+{{ $1 }}$0
+endsnippet
+
+snippet include "include" b
+{% include "${1:foo.html}" %}
+endsnippet
+
+snippet includewith "include with" b
+{% include "${1:foo.html}" with ${2:var}=${3:val} ${4:only }%}
+endsnippet
+
+snippet with "With" b
+{% with ${1:var}=${2:val} %}
+    $0
+{% end %}
+endsnippet
+
+snippet for "For" b
+{% for ${1:var} in ${2:val} %}
+    $0
+{% endfor %}
+endsnippet
+
+snippet if "If" b
+{% if ${1:cond} %}
+    $0
+{% endif %}
+endsnippet
+
+snippet else "Else" b
+{% else %}
+    $0
+endsnippet
+
+snippet elif "Elif" b
+{% elif ${1:cond} %}
+    $0
+endsnippet
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vim/ultisnips-bullshit/htmlten.snippets	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,29 @@
+extends html
+
+snippet % "Tag" i
+{% ${1:tag} %}
+    $0
+{% end %}
+endsnippet
+
+snippet { "Var" i
+{{ $1 }}$0
+endsnippet
+
+snippet section "Section (single line)" i
+{% section ${1:name} %}$2{% end %}
+endsnippet
+
+snippet Section "Section (multiline)" i
+{% section ${1:name} %}
+    $2
+{% end %}
+endsnippet
+
+snippet template "Template" b
+{% template ${1:name} (${2::extends ${3:base}}) (&key$4) %}
+
+$5
+
+{% end %}
+endsnippet
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vim/ultisnips-bullshit/markdown.snippets	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,8 @@
+snippet scrum "Scrum template" b
+Scrum:
+
+* Yesterday:
+    * $1
+* Today:
+    * $2
+endsnippet
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vim/ultisnips-bullshit/python.snippets	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,27 @@
+snippet #! "#!" b
+#!/usr/bin/env python3
+endsnippet
+
+snippet ifmain "if main" b
+if __name__ == '__main__':
+    ${1:pass}
+endsnippet
+
+snippet p "print()" b
+print($1)
+endsnippet
+
+snippet i "if" b
+if ${1:cond}:
+    ${2:pass}
+endsnippet
+
+snippet ei "elif" b
+elif ${1:cond}:
+    ${2:pass}
+endsnippet
+
+snippet e "else" b
+else:
+    ${1:pass}
+endsnippet
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vim/ultisnips-bullshit/r.snippets	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,7 @@
+snippet lib "library()" b
+library(${1:name})
+endsnippet
+
+snippet inst "install.packages()" b
+install.packages("${1:name}")
+endsnippet
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vim/ultisnips-bullshit/sh.snippets	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,15 @@
+snippet sete "set -euo pipefail" b
+set -euo pipefail
+endsnippet
+
+snippet = "var" b
+${1:var}="${2:val}"
+endsnippet
+
+snippet #! "#!" b
+#!/usr/bin/env bash
+endsnippet
+
+snippet == "VAR=${$VAR:-default}" b
+${1:VAR}="\$\{$1:-${2:default}\}"
+endsnippet
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vim/ultisnips-bullshit/tex.snippets	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,85 @@
+global !p
+def math():
+  return vim.eval('vimtex#syntax#in_mathzone()') == '1'
+endglobal
+
+# Structure -------------------------------------------------------------------
+snippet b "An environment" b
+\begin{$1}
+    $0
+\end{$1}
+endsnippet
+
+snippet sec "\section*" b
+\section*{$1}$0
+endsnippet
+
+snippet ssec "\subsection*" b
+\subsection*{$1}$0
+endsnippet
+
+snippet desc "\begin{description}" b
+\begin{description}
+    $0
+\end{description}
+endsnippet
+
+snippet lst "\begin{lstlisting}" b
+\begin{lstlisting}
+$0
+\end{lstlisting}
+endsnippet
+
+snippet i "\item[]" b
+\item[$1]$0
+endsnippet
+
+snippet eq "Equation environment" b
+\begin{equation*}
+\begin{split}
+    $0
+\end{split}
+\end{equation*}
+endsnippet
+
+snippet jot "Set jot length (should go between equation and split envs)" b
+\setlength{\jot}{${1:10}pt}$0
+endsnippet
+
+# Markup ----------------------------------------------------------------------
+
+snippet href "The hyperref package's \href{}{} command (for url links)"
+\href{${1:url}}{${2:display name}}$0
+endsnippet
+
+snippet url "A linked URL"
+\url{${1:url}}$0
+endsnippet
+
+snippet fn "Footnote"
+\footnote{${1:Note}}$0
+endsnippet
+
+snippet em "\emph{}"
+\emph{${1:${VISUAL:}}}$0
+endsnippet
+
+# Math ------------------------------------------------------------------------
+snippet "(^|[^a-zA-Z])mm" "Inline LaTeX math" rA
+`!p snip.rv = match.group(1)`\$ ${1:${VISUAL:}} \$$0
+endsnippet
+
+context "math()"
+snippet ff "(math) \frac{}{}"
+\frac{$1}{$2}$0
+endsnippet
+
+context "math()"
+snippet rm "(math) \mathrm{}"
+\mathrm{$1}$0
+endsnippet
+
+context "math()"
+snippet __ "(math) _{}" iA
+_{ $1 }$0
+endsnippet
--- a/vim/vimrc	Sun May 18 14:59:11 2025 -0400
+++ b/vim/vimrc	Wed Aug 27 16:19:24 2025 -0400
@@ -2,8 +2,9 @@
 " Author: Steve Losh <steve@stevelosh.com>
 " Source: https://hg.stevelosh.com/dotfiles/file/tip/vim/vimrc
 
+" See also vimrc-minimal for the rest of this.
+
 " Preamble ---------------------------------------------------------------- {{{
-"
 set shell=/bin/bash\ --login
 
 filetype off
@@ -12,39 +13,12 @@
 set nocompatible
 
 " }}}
-" Basic options ----------------------------------------------------------- {{{
-
-set modelines=0
-set autoindent
-set showmode
-set showcmd
-set hidden
-set visualbell
-set ttyfast
-set ruler
-set backspace=indent,eol,start
-set nonumber
-set norelativenumber
-set laststatus=2
-set history=1000
-set undofile
-set undoreload=10000
-set list
-set listchars=tab:▸\ ,eol:¬,extends:❯,precedes:❮
-set lazyredraw
-set matchtime=3
-set showbreak=↪
-set splitbelow
-set splitright
-set autowrite
-set autoread
-set shiftround
-set title
-set linebreak
-set colorcolumn=+1
-set diffopt+=vertical
-
-" Spelling
+" Load Minimal ------------------------------------------------------------ {{{
+
+source ~/.vimrc-minimal
+
+" }}}
+" Spelling ---------------------------------------------------------------- {{{
 "
 " There are three dictionaries I use for spellchecking:
 "
@@ -63,136 +37,15 @@
 set spellfile=~/.vim/custom-dictionary.utf-8.add,~/.vim-local-dictionary.utf-8.add
 nnoremap zG 2zg
 
-" Don't try to highlight lines longer than 500 characters.
-set synmaxcol=500
-
-" Time out on key codes but not mappings.
-" Basically this makes terminal Vim work sanely.
-set notimeout
-set ttimeout
-set ttimeoutlen=10
-
-" Make Vim able to edit crontab files again.
-set backupskip=/tmp/*,/private/tmp/*"
-
-" Better Completion
-set complete=.,w,b,u,t
-set completeopt=longest,menuone
-inoremap <c-o> <c-x><c-o>
-
-" Save when losing focus
-au FocusLost * :silent! wall
-
-" Leader
-let mapleader = ","
-let maplocalleader = "\\"
-
-" Cursorline {{{
-" Only show cursorline in the current window and in normal mode.
-
-augroup cline
-    au!
-    au WinLeave,InsertEnter * set nocursorline
-    au WinEnter,InsertLeave * set cursorline
-augroup END
-
-" }}}
-" cpoptions+=J, dammit {{{
-
-" Something occasionally removes this.  If I manage to find it I'm going to
-" comment out the line and replace all its characters with 'FUCK'.
-augroup twospace
-    au!
-    au BufRead * :set cpoptions+=J
-augroup END
-
-" }}}
-" Trailing whitespace {{{
-" Only shown when not in insert mode so I don't go insane.
-
-augroup trailing
-    au!
-    au InsertEnter * :set listchars-=trail:⌴
-    au InsertLeave * :set listchars+=trail:⌴
-augroup END
+" Invoke spellcheck in insert mode with c-s, or c-S to do it and autotake the
+" first suggestion for stuff I intentually use spellcheck to type.
+inoremap <c-s>   <c-x><c-s>
+inoremap <c-s-s> <c-x><c-s><c-n><c-y>
 
 " }}}
-" Wildmenu completion {{{
-
-set wildmenu
-set wildmode=list:longest
-
-set wildignore+=.hg,.git,.svn                    " Version control
-set wildignore+=*.aux,*.out,*.toc                " LaTeX intermediate files
-set wildignore+=*.jpg,*.bmp,*.gif,*.png,*.jpeg   " binary images
-set wildignore+=*.o,*.obj,*.exe,*.dll,*.manifest " compiled object files
-set wildignore+=*.spl                            " compiled spelling word lists
-set wildignore+=*.sw?                            " Vim swap files
-set wildignore+=*.DS_Store                       " OSX bullshit
-
-set wildignore+=*.luac                           " Lua byte code
-
-set wildignore+=migrations                       " Django migrations
-set wildignore+=*.pyc                            " Python byte code
-
-set wildignore+=*.orig                           " Merge resolution files
-
-set wildignore+=*.fasl                           " Lisp FASLs
-set wildignore+=*.dx64fsl                        " CCL
-set wildignore+=*.lx64fsl                        " CCL
-
-" }}}
-" Line Return {{{
-
-" Make sure Vim returns to the same line when you reopen a file.
-" Thanks, Amit
-augroup line_return
-    au!
-    au BufReadPost *
-        \ if line("'\"") > 0 && line("'\"") <= line("$") |
-        \     execute 'normal! g`"zvzz' |
-        \ endif
-augroup END
-
-" }}}
-" Tabs, spaces, wrapping {{{
-
-set tabstop=8
-set shiftwidth=4
-set softtabstop=4
-set expandtab
-set wrap
-set textwidth=80
-set formatoptions=qrn1j
-set colorcolumn=+1
-
-" }}}
-" Backups {{{
-
-set backup                        " enable backups
-set noswapfile                    " it's 2013, Vim.
-
-set undodir=~/.vim/tmp/undo//     " undo files
-set backupdir=~/.vim/tmp/backup// " backups
-set directory=~/.vim/tmp/swap//   " swap files
-
-" Make those folders automatically if they don't already exist.
-if !isdirectory(expand(&undodir))
-    call mkdir(expand(&undodir), "p")
-endif
-if !isdirectory(expand(&backupdir))
-    call mkdir(expand(&backupdir), "p")
-endif
-if !isdirectory(expand(&directory))
-    call mkdir(expand(&directory), "p")
-endif
-
-" }}}
-" Color scheme {{{
-
-syntax on
+" Color scheme ------------------------------------------------------------ {{{
+
 set termguicolors
-set background=dark
 let g:badwolf_tabline = 2
 let g:badwolf_html_link_underline = 0
 colorscheme goodwolf
@@ -207,81 +60,11 @@
 " Highlight VCS conflict markers
 match ErrorMsg '^\(<\|=\|>\)\{7\}\([^=].\+\)\?$'
 
-" }}}
-
-" }}}
-" Abbreviations & Digraphs ------------------------------------------------ {{{
-
-iabbrev todo TODO
-
-silent! digr -. 8230 "U+2026=…    HORIZONTAL ELLIPSIS
-silent! digr !, 8816 "U+2270=≰    NEITHER LESS-THAN NOR EQUAL TO
-silent! digr !. 8817 "U+2271=≱    NEITHER GREATER-THAN NOR EQUAL TO
-silent! digr es 8337 "U+2091=ₑ    SUBSCRIPT E
-silent! digr xs 8339 "U+2093=ₓ    SUBSCRIPT X
-silent! digr ls 8343 "U+2097=ₗ    SUBSCRIPT L
-silent! digr ms 8344 "U+2098=ₗ    SUBSCRIPT M
-silent! digr ns 8345 "U+2099=ₙ    SUBSCRIPT N
-silent! digr ps 8346 "U+209A=ₚ    SUBSCRIPT P
-silent! digr ss 8347 "U+209B=ₛ    SUBSCRIPT S
-silent! digr ts 8348 "U+209C=ₜ    SUBSCRIPT T
-silent! digr >< 8652 "U+21cc=⇌    EQUILIBRIUM
-silent! digr o+ 8853 "U+2295=⊕    CIRCLED PLUS
-silent! digr -^ 8593 "U+2191=↑    UPWARDS ARROW
-
-silent! digr -- 8212 "U+2014=—    EM DASH
-
-silent! digr // 9585 "U+2571=╱    BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT
-silent! digr \\ 9586 "U+2572=╲    BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT
-
-silent! digr \|\| 8214 "U+2016=‖  DOUBLE VERTICAL LINE
-
-silent! digr ~~ 8967  "U+2307=⌇   WAVY LINE
+nnoremap <leader>B :call GoodWolfToggleBoldStrings()<cr>
 
 " }}}
 " Convenience mappings ---------------------------------------------------- {{{
 
-" Fuck you, help key.
-noremap  <F1> :checktime<cr>
-inoremap <F1> <esc>:checktime<cr>
-
-" Stop it, hash key.
-inoremap # X<BS>#
-
-" Kill window
-nnoremap K :q<cr>
-
-" Save
-nnoremap s :w<cr>
-
-" Man
-nnoremap M K
-
-" Clean up windows
-nnoremap - :wincmd =<cr>
-
-" Toggle line numbers
-nnoremap <leader>n :setlocal number!<cr>
-
-" Sort lines
-nnoremap <leader>s vip:sort<cr>
-vnoremap <leader>s :sort<cr>
-
-" Tabs
-nnoremap <leader>( :tabprev<cr>
-nnoremap <leader>) :tabnext<cr>
-
-" My garbage brain can't ever remember digraph codes
-inoremap <c-k><c-k> <esc>:help digraph-table<cr>
-
-" Wrap
-" mnemonic: less' -S command/option
-nnoremap <leader>S :set wrap!<cr>
-
-" Inserting blank lines
-" I never use the default behavior of <cr> and this saves me a keystroke...
-nnoremap <cr> o<esc>
-
 " Copying/pasting text to the system clipboard.
 noremap  <leader>p "+p
 vnoremap <leader>y "+y
@@ -296,21 +79,6 @@
 nnoremap <leader>o "zyiW:call Open(@z)<cr>
 vnoremap <leader>o "zy:call Open(@z)<cr>
 
-" Delete to black hole register
-nnoremap dD "_dd
-vnoremap D "_d
-
-" Yank to end of line
-nnoremap Y y$
-
-" Reselect last-pasted text
-nnoremap gv `[v`]
-
-" I constantly hit "u" in visual mode when I mean to "y". Use "gu" for those rare occasions.
-" From https://github.com/henrik/dotfiles/blob/master/vim/config/mappings.vim
-vnoremap u <nop>
-vnoremap gu u
-
 " Rebuild Ctags (mnemonic RC -> CR -> <cr>)
 nnoremap <leader><cr> :silent !myctags >/dev/null 2>&1 &<cr>:redraw!<cr>
 
@@ -319,169 +87,15 @@
                         \ . synIDattr(synID(line("."),col("."),0),"name") . "> lo<"
                         \ . synIDattr(synIDtrans(synID(line("."),col("."),1)),"name") . ">"<CR>
 
-" Clean trailing whitespace
-nnoremap <leader>ww mz:%s/\s\+$//<cr>:let @/=''<cr>`z
-
 " Send visual selection to paste.stevelosh.com
 vnoremap <leader>P :w !pb && open `pbpaste`<cr>
 
-" Select entire buffer
-nnoremap vaa ggvGg_
-nnoremap Vaa ggVG
-
-" Fix from spellcheck
-" I can never remember if it's zg or z=, and the wrong one adds the word to
-" the DB (lol), so fuck it, just add an easier mapping.
-nnoremap zz z=
-nnoremap z= :echo "use zz you idiot"<cr>
-
-" "Uppercase word" mapping.
-"
-" This mapping allows you to press <c-u> in insert mode to convert the current
-" word to uppercase.  It's handy when you're writing names of constants and
-" don't want to use Capslock.
-"
-" To use it you type the name of the constant in lowercase.  While your
-" cursor is at the end of the word, press <c-u> to uppercase it, and then
-" continue happily on your way:
-"
-"                            cursor
-"                            v
-"     max_connections_allowed|
-"     <c-u>
-"     MAX_CONNECTIONS_ALLOWED|
-"                            ^
-"                            cursor
-"
-" It works by exiting out of insert mode, recording the current cursor location
-" in the z mark, using gUiw to uppercase inside the current word, moving back to
-" the z mark, and entering insert mode again.
-"
-" Note that this will overwrite the contents of the z mark.  I never use it, but
-" if you do you'll probably want to use another mark.
-inoremap <C-u> <esc>mzgUiw`za
-
-" Panic Button
-nnoremap <f9> mzggg?G`z
-
-" zt is okay for putting something at the top of the screen, but when I'm
-" writing prose I often want to put something at not-quite-the-top of the
-" screen.  zh is "zoom to head level"
-nnoremap zh mzzt10<c-u>`z
-
-" Diffoff
-nnoremap <leader>D :diffoff!<cr>
-
-" Formatting, TextMate-style
-nnoremap Q gqip
-vnoremap Q gq
-
-" Reformat line.
-" I never use l as a macro register anyway.
-nnoremap ql gqq
-
-" Indent/dedent/autoindent what you just pasted.
-nnoremap <lt>> V`]<
-nnoremap ><lt> V`]>
-nnoremap =- V`]=
-
-" Keep the cursor in place while joining lines
-nnoremap J mzJ`z
-
-" Join an entire paragraph.
-"
-" Useful for writing GitHub comments in actual Markdown and then translating it
-" to their bastardized version of Markdown.
-nnoremap <leader>j mzvipJ`z
-
-" Split line (sister to [J]oin lines)
-" The normal use of S is covered by cc, so don't worry about shadowing it.
-nnoremap S i<cr><esc>^mwgk:silent! s/\v +$//<cr>:noh<cr>`w
-
-" Substitute
-nnoremap <c-s> :%s/
-vnoremap <c-s> :s/
-
-" Marks and Quotes
-noremap ' `
-noremap æ '
-noremap ` <C-^>
-
-" Select (charwise) the contents of the current line, excluding indentation.
-" Great for pasting Python lines into REPLs.
-nnoremap vv ^vg_
-
-" Typos
-command! -bang E e<bang>
-command! -bang Q q<bang>
-command! -bang W w<bang>
-command! -bang QA qa<bang>
-command! -bang Qa qa<bang>
-command! -bang Wa wa<bang>
-command! -bang WA wa<bang>
-command! -bang Wq wq<bang>
-command! -bang WQ wq<bang>
-command! -bang Wqa wqa<bang>
-
-" Unfuck my screen
-nnoremap U :syntax sync fromstart<cr>:redraw!<cr>
-
-" Zip Right
-"
-" Moves the character under the cursor to the end of the line.  Handy when you
-" have something like:
-"
-"     foo
-"
-" And you want to wrap it in a method call, so you type:
-"
-"     println()foo
-"
-" Once you hit escape your cursor is on the closing paren, so you can 'zip' it
-" over to the right with this mapping.
-"
-" This should preserve your last yank/delete as well.
-nnoremap zl :let @z=@"<cr>x$p:let @"=@z<cr>
-
-" Indent from insert mode
-" has to be imap because we want to be able to use the "go-indent" mapping
-imap <c-l> <c-o>gi
-
-" Diff Navigation
-nnoremap ]d ]c
-nnoremap [d [c
-
-" Typo navigation
-nnoremap ]z ]S
-nnoremap [z [S
-nnoremap ]Z ]Sz=
-nnoremap [Z [Sz=
-
-" Header Lines
-nnoremap <leader>- o<esc>80a-<esc>kJ079lD
-
-" Insert Mode Completion {{{
-
-inoremap <c-f> <c-x><c-f>
-inoremap <c-]> <c-x><c-]>
-inoremap <c-l> <c-x><c-l>
-
-" }}}
-
-" Window Resizing {{{
-" right/up : bigger
-" left/down : smaller
-nnoremap <m-right> :vertical resize +3<cr>
-nnoremap <m-left> :vertical resize -3<cr>
-nnoremap <m-up> :resize +3<cr>
-nnoremap <m-down> :resize -3<cr>
-" }}}
-
 " }}}
 " Quick editing ----------------------------------------------------------- {{{
 
 nnoremap <leader>eb :vsplit ~/Dropbox/bitly.txt<cr>
-nnoremap <leader>ed :vsplit ~/.vim/custom-dictionary.utf-8.add<cr>
+nnoremap <leader>ez :vsplit ~/.vim/custom-dictionary.utf-8.add<cr>Gmzzt10<c-u>`z
+nnoremap <leader>eZ :vsplit ~/.vim-local-dictionary.utf-8.add<cr>Gmzzt10<c-u>`z
 nnoremap <leader>ef :vsplit ~/.config/fish/config.fish<cr>
 nnoremap <leader>eg :vsplit ~/.gitconfig<cr>
 nnoremap <leader>es :vsplit ~/.stumpwmrc<cr>
@@ -492,7 +106,8 @@
 nnoremap <leader>eln :vsplit ~/lab/README.markdown<cr>
 nnoremap <leader>eq :vsplit ~/Dropbox/quotes.txt<cr>Gzz
 nnoremap <leader>et :vsplit ~/.tmux.conf<cr>
-nnoremap <leader>ev :vsplit ~/.vimrc<cr>
+nnoremap <leader>evf :vsplit ~/.vimrc<cr>
+nnoremap <leader>evm :vsplit ~/.vimrc-minimal<cr>
 
 " }}}
 " Status Line ------------------------------------------------------------- {{{
@@ -604,191 +219,76 @@
 " set statusline+=%=
 
 " }}}
-" Searching and movement -------------------------------------------------- {{{
-
-" Use sane regexes.
-nnoremap / /\v
-vnoremap / /\v
-
-set ignorecase
-set smartcase
-set incsearch
-set showmatch
-set hlsearch
-set gdefault
-
-set scrolloff=5
-set sidescroll=1
-set sidescrolloff=10
-
-set virtualedit+=block
-
-noremap <silent> <leader><space> :noh<cr>:call clearmatches()<cr>
-
-runtime macros/matchit.vim
-map <tab> %
-silent! unmap [%
-silent! unmap ]%
-
-" Made D behave
-nnoremap D d$
-
-" Don't move on *
-" I'd use a function for this but Vim clobbers the last search when you're in
-" a function so fuck it, practicality beats purity.
-nnoremap <silent> * :let stay_star_view = winsaveview()<cr>*:call winrestview(stay_star_view)<cr>
-
-" Jumping to tags.
-"
-" Basically, <c-]> jumps to tags (like normal) and <c-\> opens the tag in a new
-" split instead.
-"
-" Both of them will align the destination line to the upper middle part of the
-" screen.  Both will pulse the cursor line so you can see where the hell you
-" are.  <c-\> will also fold everything in the buffer and then unfold just
-" enough for you to see the destination line.
-"
-function! JumpTo(jumpcommand)
-    execute a:jumpcommand
-    call FocusLine()
-    Pulse
-endfunction
-function! JumpToInSplit(jumpcommand)
-    execute "normal! \<c-w>v"
-    execute a:jumpcommand
-    Pulse
-endfunction
-
-function! JumpToTag()
-    call JumpTo("normal! \<c-]>")
-endfunction
-function! JumpToTagInSplit()
-    call JumpToInSplit("normal \<c-]>")
-endfunction
-
-nnoremap <c-]> :silent! call JumpToTag()<cr>
-nnoremap <c-\> :silent! call JumpToTagInSplit()<cr>
-
-" Keep search matches in the middle of the window.
-nnoremap n nzzzv
-nnoremap N Nzzzv
-
-" Same when jumping around
-nnoremap g; g;zz
-nnoremap g, g,zz
-nnoremap <c-o> <c-o>zz
-
-" Easier to type, and I never use the default behavior.
-noremap H ^
-noremap L $
-vnoremap L g_
-
-" Heresy
-inoremap <c-a> <esc>I
-inoremap <c-e> <esc>A
-cnoremap <c-a> <home>
-cnoremap <c-e> <end>
-
-" go indent
-nnoremap gi mzVap=`z
-nnoremap gI mzgg=G`z
-
-" Fix linewise visual selection of various text objects
-nnoremap VV V
-nnoremap Vit vitVkoj
-nnoremap Vat vatV
-nnoremap Vab vabV
-nnoremap VaB vaBV
-
-" Directional Keys {{{
-
-" It's 2013.
-noremap j gj
-noremap k gk
-noremap gj j
-noremap gk k
-
-" Easy buffer navigation
-noremap <C-h> <C-w>h
-noremap <C-j> <C-w>j
-noremap <C-k> <C-w>k
-noremap <C-l> <C-w>l
-
-noremap <leader>v <C-w>v
+" Filetype-specific ------------------------------------------------------- {{{
+
+" April {{{
+
+augroup ft_commonlisp_april " {{{
+    au!
+
+    au FileType lisp inoremap <buffer> <c-j>;  ⍝
+    au FileType lisp inoremap <buffer> <c-j>i  ⍳
+    au FileType lisp inoremap <buffer> <c-j>r  ⍴
+    au FileType lisp inoremap <buffer> <c-j>e  ∊
+    au FileType lisp inoremap <buffer> <c-j>_i ⍸
+    au FileType lisp inoremap <buffer> <c-j>_, ⍪
+
+    au FileType lisp inoremap <buffer> <c-j>1  ¨
+    au FileType lisp inoremap <buffer> <c-j>F  ⍨
+    au FileType lisp inoremap <buffer> <c-j>P  ⍣
+    au FileType lisp inoremap <buffer> <c-j>R  ⍤
+
+    au FileType lisp inoremap <buffer> <c-j>*  ×
+    au FileType lisp inoremap <buffer> <c-j>/  ÷
+    au FileType lisp inoremap <buffer> <c-j>0  ∘
+    au FileType lisp inoremap <buffer> <c-j>-  ¯
+    au FileType lisp inoremap <buffer> <c-j>l  ⍟
+
+    au FileType lisp inoremap <buffer> <c-j>c  ⌈
+    au FileType lisp inoremap <buffer> <c-j>f  ⌊
+
+    au FileType lisp inoremap <buffer> <c-j><c-j> ←
+    au FileType lisp inoremap <buffer> <c-j><  ←
+    au FileType lisp inoremap <buffer> <c-j>>  →
+    au FileType lisp inoremap <buffer> <c-j>^  ↑
+    au FileType lisp inoremap <buffer> <c-j>v  ↓
+
+    au FileType lisp inoremap <buffer> <c-j>d  ∆
+    au FileType lisp inoremap <buffer> <c-j>D  ⍙
+
+    au FileType lisp inoremap <buffer> <c-j>Gk ⍋
+    au FileType lisp inoremap <buffer> <c-j>Gj ⍒
+
+    au FileType lisp inoremap <buffer> <c-j>oo  ○
+    au FileType lisp inoremap <buffer> <c-j>o\| ⌽
+    au FileType lisp inoremap <buffer> <c-j>o\  ⍉
+    au FileType lisp inoremap <buffer> <c-j>o-  ⊖
+
+    au FileType lisp inoremap <buffer> <c-j>A  ∧
+    au FileType lisp inoremap <buffer> <c-j>O  ∨
+
+    au FileType lisp inoremap <buffer> <c-j>=< ≤
+    au FileType lisp inoremap <buffer> <c-j>=< ≥
+    au FileType lisp inoremap <buffer> <c-j>=/ ≠
+    au FileType lisp inoremap <buffer> <c-j>=== ≡
+    au FileType lisp inoremap <buffer> <c-j>==/ ≢
+
+    au FileType lisp inoremap <buffer> <c-j>q  ⎕
+    au FileType lisp inoremap <buffer> <c-j>Q  ⌷
+    au FileType lisp inoremap <buffer> <c-j>t  ⊢
+
+    au FileType lisp inoremap <buffer> <c-j>u  ∪
+    au FileType lisp inoremap <buffer> <c-j>U  ∩
+    au FileType lisp inoremap <buffer> <c-j>+  ⌿
+    au FileType lisp inoremap <buffer> <c-j>[  ⊂
+    au FileType lisp inoremap <buffer> <c-j>]  ⊃
+
+    au FileType lisp inoremap <buffer> <c-j>a  ⍺
+    au FileType lisp inoremap <buffer> <c-j>w  ⍵
+    au FileType lisp inoremap <buffer> <c-j>z  ⍬
+augroup END " }}}
 
 " }}}
-" Visual Mode */# from Scrooloose {{{
-
-function! s:VSetSearch()
-  let temp = @@
-  norm! gvy
-  let @/ = '\V' . substitute(escape(@@, '\'), '\n', '\\n', 'g')
-  let @@ = temp
-endfunction
-
-vnoremap * :<C-u>call <SID>VSetSearch()<CR>//<CR><c-o>
-vnoremap # :<C-u>call <SID>VSetSearch()<CR>??<CR><c-o>
-
-" }}}
-" List navigation {{{
-
-nnoremap <left>  :cprev<cr>zvzz
-nnoremap <right> :cnext<cr>zvzz
-nnoremap <up>    :lprev<cr>zvzz
-nnoremap <down>  :lnext<cr>zvzz
-
-" }}}
-
-" }}}
-" Folding ----------------------------------------------------------------- {{{
-
-set foldlevelstart=0
-
-" Space to toggle folds.
-nnoremap <Space> za
-vnoremap <Space> za
-
-" Make zO recursively open whatever fold we're in, even if it's partially open.
-nnoremap zO zczO
-
-" "Focus" the current line.  Basically:
-"
-" 1. Close all folds.
-" 2. Open just the folds containing the current line.
-" 3. Move the line to a bit (25 lines) down from the top of the screen.
-" 4. Pulse the line.
-"
-" This mapping wipes out the z mark, which I never use.
-"
-" I use :sus for the rare times I want to actually background Vim.
-function! FocusLine()
-    let oldscrolloff = &scrolloff
-    set scrolloff=0
-    execute "keepjumps normal! mzzMzvzt25\<c-y>`z:Pulse\<cr>"
-    let &scrolloff = oldscrolloff
-endfunction
-nnoremap <c-z> :call FocusLine()<cr>
-
-function! MyFoldText() " {{{
-    let line = getline(v:foldstart)
-
-    let nucolwidth = &fdc + &number * &numberwidth
-    let windowwidth = winwidth(0) - nucolwidth - 3
-    let foldedlinecount = v:foldend - v:foldstart
-
-    " expand tabs into spaces
-    let onetab = strpart('          ', 0, &tabstop)
-    let line = substitute(line, '\t', onetab, 'g')
-
-    let line = strpart(line, 0, windowwidth - 2 -len(foldedlinecount))
-    let fillcharcount = windowwidth - len(line) - len(foldedlinecount)
-    return line . '…' . repeat(" ",fillcharcount) . foldedlinecount . '…' . ' '
-endfunction " }}}
-set foldtext=MyFoldText()
-
-" }}}
-" Filetype-specific ------------------------------------------------------- {{{
-
 " Assembly {{{
 
 augroup ft_asm
@@ -1069,6 +569,7 @@
     au BufNewFile,BufRead *.paren set filetype=lisp
     au BufNewFile,BufRead .abclrc set filetype=lisp
     au BufNewFile,BufRead .lisprc set filetype=lisp
+    au BufNewFile,BufRead stumpwmrc set filetype=lisp
     au BufNewFile,BufRead .stumpwmrc set filetype=lisp
     au BufNewFile,BufRead .stumpwmrc.local set filetype=lisp
 
@@ -1125,57 +626,6 @@
 
     " Writing
     au FileType lisp noremap <buffer> <localleader>=  I; => <esc>
-
-    " April
-    au FileType lisp inoremap <buffer> <c-j>;  ⍝
-    au FileType lisp inoremap <buffer> <c-j>i  ⍳
-    au FileType lisp inoremap <buffer> <c-j>r  ⍴
-    au FileType lisp inoremap <buffer> <c-j>e  ∊
-    au FileType lisp inoremap <buffer> <c-j>_i ⍸
-    au FileType lisp inoremap <buffer> <c-j>_, ⍪
-    au FileType lisp inoremap <buffer> <c-j>:~ ⍨
-    au FileType lisp inoremap <buffer> <c-j>:^ ¨
-
-    au FileType lisp inoremap <buffer> <c-j>*  ×
-    au FileType lisp inoremap <buffer> <c-j>/  ÷
-    au FileType lisp inoremap <buffer> <c-j>0  ∘
-    au FileType lisp inoremap <buffer> <c-j>-  ¯
-
-    au FileType lisp inoremap <buffer> <c-j>c  ⌈
-    au FileType lisp inoremap <buffer> <c-j>f  ⌊
-
-    au FileType lisp inoremap <buffer> <c-j><  ←
-    au FileType lisp inoremap <buffer> <c-j>>  →
-    au FileType lisp inoremap <buffer> <c-j>^  ↑
-    au FileType lisp inoremap <buffer> <c-j>v  ↓
-
-    au FileType lisp inoremap <buffer> <c-j>G^ ⍋
-    au FileType lisp inoremap <buffer> <c-j>Gv ⍒
-
-    au FileType lisp inoremap <buffer> <c-j>o\| ⌽
-    au FileType lisp inoremap <buffer> <c-j>o/  ⍉
-    au FileType lisp inoremap <buffer> <c-j>o-  ⊖
-
-    au FileType lisp inoremap <buffer> <c-j>A  ∧
-    au FileType lisp inoremap <buffer> <c-j>O  ∨
-
-    au FileType lisp inoremap <buffer> <c-j>=< ≤
-    au FileType lisp inoremap <buffer> <c-j>=< ≥
-    au FileType lisp inoremap <buffer> <c-j>=/ ≠
-    au FileType lisp inoremap <buffer> <c-j>=== ≡
-    au FileType lisp inoremap <buffer> <c-j>==/ ≢
-
-    au FileType lisp inoremap <buffer> <c-j>q  ⎕
-    au FileType lisp inoremap <buffer> <c-j>Q  ⌷
-    au FileType lisp inoremap <buffer> <c-j>t  ⊢
-
-    au FileType lisp inoremap <buffer> <c-j>u  ∪
-    au FileType lisp inoremap <buffer> <c-j>U  ∩
-    au FileType lisp inoremap <buffer> <c-j>+  ⌿
-    au FileType lisp inoremap <buffer> <c-j>[  ⊂
-    au FileType lisp inoremap <buffer> <c-j>]  ⊃
-
-    au FileType lisp inoremap <buffer> <c-j>z  ⍬
 augroup END " }}}
 
 " }}}
@@ -1270,6 +720,9 @@
     au BufNewFile,BufRead dashboard.py      normal! zR
     au BufNewFile,BufRead local_settings.py normal! zR
 
+    au BufNewFile,BufRead *.htmldjango     setlocal filetype=htmldjango
+    au BufNewFile,BufRead *.htmldjango.hx  setlocal filetype=htmldjango
+
     au BufNewFile,BufRead admin.py     setlocal filetype=python.django
     au BufNewFile,BufRead urls.py      setlocal filetype=python.django
     au BufNewFile,BufRead models.py    setlocal filetype=python.django
@@ -1480,11 +933,6 @@
     au FileType go iabbrev <buffer> enilrs if err != nil {<cr>return "", err<down>
     au FileType go iabbrev <buffer> enilrz if err != nil {<cr>return 0, err<down>
 
-    au FileType go iabbrev <buffer> enilrw if err != nil {<cr>return fmt.Errorf(": %w", err<left><left><left><left><left><left><left><left><left><left><C-R>=Eatchar('\s')<cr>
-    au FileType go iabbrev <buffer> enilrwn if err != nil {<cr>return nil, fmt.Errorf(": %w", err<left><left><left><left><left><left><left><left><left><left><C-R>=Eatchar('\s')<cr>
-    au FileType go iabbrev <buffer> enilrws if err != nil {<cr>return "", fmt.Errorf(": %w", err<left><left><left><left><left><left><left><left><left><left><C-R>=Eatchar('\s')<cr>
-    au FileType go iabbrev <buffer> enilrwz if err != nil {<cr>return 0, fmt.Errorf(": %w", err<left><left><left><left><left><left><left><left><left><left><C-R>=Eatchar('\s')<cr>
-
     au FileType gohtmltmpl setlocal shiftwidth=4
 augroup END
 
@@ -1501,30 +949,21 @@
 " }}}
 " HTML, Django, Jinja, Dram, Go, Kill Me {{{
 
-let g:html_indent_tags = ['p', 'li']
+let g:html_indent_tags = ['p', 'li', 'div']
+
+let g:html_indent_inctags = "html,body,head,tbody,div"
 
 augroup ft_html
     au!
 
-    au BufNewFile,BufRead *.html setlocal filetype=gohtmltmpl
+    au BufNewFile,BufRead *.djula setlocal filetype=htmldjango
     au BufNewFile,BufRead *.dram setlocal filetype=htmldjango
 
     au FileType html,jinja,htmldjango,gohtmltmpl setlocal foldmethod=manual
-
-    " Use <localleader>f to fold the current tag.
-    au FileType html,jinja,htmldjango,gohtmltmpl nnoremap <buffer> <localleader>f Vatzf
-
-    " Use <localleader>t to fold the current templatetag.
-    au FileType html,jinja,htmldjango nmap <buffer> <localleader>t viikojozf
-
-    " Indent tag
-    au FileType html,jinja,htmldjango,gohtmltmpl nnoremap <buffer> <localleader>= Vat=
-
-    " Django tags
-    au FileType jinja,htmldjango inoremap <buffer> <c-t> {%<space><space>%}<left><left><left>
-
-    " Django variables
-    au FileType jinja,htmldjango inoremap <buffer> <c-b> {{<space><space>}}<left><left><left>
+    au FileType html,jinja,htmldjango,gohtmltmpl setlocal foldmethod=manual
+
+    au FileType html,jinja,htmldjango,gohtmltmp inoremap <silent> <C-c> </<C-X><C-O><C-X><esc>a
+    au FileType html,jinja,htmldjango,gohtmltmp inoremap <c-cr> <cr><esc>O
 augroup END
 
 " }}}
@@ -1558,12 +997,24 @@
 augroup END
 
 " }}}
+" JSON {{{
+
+augroup ft_json
+    au!
+
+    au FileType json setlocal sw=4
+augroup END
+
+" }}}
 " Latex {{{
 
 let g:tex_flavor = 'latex'
 
 augroup ft_latex
     au!
+
+    au Filetype tex inoremap <buffer> <c-b> \begin{}<left>
+    au Filetype tex nnoremap <buffer> <localleader>q :%s/[‘’]/'/<cr>
 augroup END
 
 " }}}
@@ -1657,8 +1108,6 @@
     au Filetype markdown nnoremap <buffer> <localleader>3 mzI###<space><esc>`zllll
     au Filetype markdown nnoremap <buffer> <localleader>4 mzI####<space><esc>`zlllll
 
-    au Filetype markdown inoremap <buffer> <c-cr> <cr><esc>mz?^ *\*?e<cr>"zy0:noh<cr>`z"zpA* <esc>a
-    au Filetype markdown inoremap <buffer> <s-c-cr> <cr><esc>mz?^ *\*?e<cr>"zy0:noh<cr>`z"zpA  * <esc>a
     au Filetype markdown inoremap <buffer> <s-tab> <esc>mz0xx`za
     au Filetype markdown inoremap <buffer> <c-tab> <esc>mzI  <esc>`zlla
 augroup END
@@ -1693,6 +1142,15 @@
 augroup END
 
 " }}}
+" NextFlow {{{
+
+augroup ft_nextflow
+    au!
+
+    " au BufRead,BufNewFile *.nf set ft=groovy
+augroup END
+
+" }}}
 " Nginx {{{
 
 augroup ft_nginx
@@ -1761,17 +1219,51 @@
 " }}}
 " Python {{{
 
+" Helper Functions {{{
+let g:current_python_lsp_client = 0
+
+function! PythonLSPConnect() "{{{
+    if g:current_python_lsp_client == 0
+        lua vim.lsp.start_client({cmd={"nc", "127.0.0.1", "9898"}})
+        " TODO lol
+        let g:current_python_lsp_client = 1
+    endif
+endfunction "}}}
+
+function! PythonLSPAttach() "{{{
+    call PythonLSPConnect()
+    lua vim.lsp.buf_attach_client(0, 1)
+    call PythonLSPMappings()
+endfunction "}}}
+
+function! PythonLSPSig() "{{{
+    lua vim.lsp.buf.signature_help()
+    return ""
+endfunction "}}}
+
+function! PythonLSPMappings() "{{{
+    setlocal omnifunc=v:lua.vim.lsp.omnifunc
+    inoremap <buffer> <c-n>    <c-x><c-o>
+    nnoremap <buffer> <c-]>    :lua vim.lsp.buf.definition()<cr>
+    nnoremap <buffer> M        :lua vim.lsp.buf.hover()<cr>
+    inoremap <buffer> <c-m>    <c-r>=PythonLSPSig()<cr>
+endfunction "}}}
+
+" }}}
+
 augroup ft_python
     au!
 
     au FileType python setlocal define=^\s*\\(def\\\\|class\\)
-    au FileType python setlocal textwidth=100
+    au FileType python setlocal textwidth=88
 
     " Jesus tapdancing Christ, built-in Python syntax, you couldn't let me
     " override this in a normal way, could you?
     au FileType python if exists("python_space_error_highlight") | unlet python_space_error_highlight | endif
 
     au FileType python nnoremap <buffer> gi :Neoformat black<cr>zx
+
+    au FileType python nnoremap <buffer> <localleader>cc :call PythonLSPAttach()<cr>
 augroup END
 
 " }}}
@@ -1786,6 +1278,59 @@
 augroup END
 
 " }}}
+" R {{{
+
+let R_external_term = 'st -t "R REPL" --'
+let R_args = ['--no-save', '--quiet']
+let R_save_win_pos = 0
+let R_arrange_windows = 0
+let R_assign = 0
+let R_clear_line = 1
+let R_nvim_wd = 1
+let R_user_maps_only = 1
+let R_nvimpager = 'vertical'
+let R_help_w = 81
+let r_indent_ess_comments = 0
+let r_indent_ess_compatible = 0
+let r_indent_align_args = 0
+
+function! s:customNvimRMappings()
+   " Normal Mode
+    nmap <buffer> <localleader>Or <Plug>RStart
+    nmap <buffer> <localleader>Cr <Plug>RClose
+    nmap <buffer> <localleader>c  <Plug>RClearConsole
+    nmap <buffer> <localleader>h  <Plug>RHelp
+    nmap <buffer> M               <Plug>RShowArgs
+    nmap <buffer> <localleader>e  <Plug>RSendParagraph
+    nmap <buffer> <localleader>w  mzviw<Plug>RSendSelection<esc>'z
+    nmap <buffer> <localleader>W  mzviW<Plug>RSendSelection<esc>'z
+    nmap <buffer> <localleader>S  <Plug>RSendLine
+    nmap <buffer> <localleader>E  <Plug>RSendLine
+    nmap <buffer> <localleader>f  <Plug>RSendFile
+    nmap <buffer> <localleader>i  <Plug>RViewDFv
+    nmap <buffer> <localleader>I  <Plug>RViewDFs
+    nmap <buffer> gi              mzvip:Rformat<cr>'z
+
+    inoremap <buffer> <c-n> <c-x><c-o>
+
+    " Insert Mode
+    inoremap <buffer> <c-.> <esc>A \|><cr>
+    inoremap <buffer> <c-,> <-<space>
+    inoremap <buffer> <c-'> <esc>A +<cr>
+
+    " Visual Mode
+    vmap <buffer> <localleader>e  <Plug>RSendSelection
+    vmap <buffer> gi              :Rformat<cr>
+endfunction
+
+augroup ft_r
+    au!
+
+    autocmd filetype r setlocal shiftwidth=2
+    autocmd filetype r call s:customNvimRMappings()
+augroup END
+
+" }}}
 " Sh {{{
 
 function! SendShellParagraph() "{{{
@@ -1795,6 +1340,8 @@
 augroup ft_sh
     au!
 
+    au BufRead,BufNewFile *.sbat set ft=sh
+
     au FileType sh nnoremap <buffer> <localleader>e :call NeoReplSendCurrentLine()<cr>
     au FileType sh nnoremap <buffer> <localleader>E :call SendShellParagraph()<cr>
     au FileType sh nnoremap <buffer> <localleader>F :call NeoReplSendEntireFile(1)<cr>
@@ -1830,6 +1377,39 @@
 augroup END
 
 " }}}
+" TEN templates {{{
+
+function! RecompileTENTemplates() "{{{
+    let systems = split(system('ls -1 *.asd | grep -v test | cut -d. -f1 | uniq')) " its fine
+    if len(systems) == 0
+        echom "Could not find any .asd files..."
+        return
+    elseif len(systems) > 1
+        echom "Found too many any .asd files..."
+        return
+    endif
+
+    call vlime#plugin#SendToREPL("(ql:quickload :" . systems[0] . ")")
+endfunction "}}}
+
+augroup ft_ten
+    au!
+
+    au BufNewFile,BufRead *.ten setlocal filetype=htmlten
+    au BufNewFile,BufRead *.ten nnoremap <buffer> <localleader>q :call RecompileTENTemplates()<cr>
+augroup END
+
+" }}}
+" TODOs {{{
+
+augroup ft_todos
+    au!
+
+    au BufWritePost /home/sjl/Sync/school/todo/TODO           :silent !hg -R /home/sjl/Sync/school/todo cmore
+    au BufWritePost /home/sjl/Sync/school/todo/irons.markdown :silent !hg -R /home/sjl/Sync/school/todo cmore
+augroup END
+
+" }}}
 " Vagrant {{{
 
 augroup ft_vagrant
@@ -1860,6 +1440,7 @@
     au!
 
     au FileType yaml set shiftwidth=2
+    au FileType yaml set foldmethod=marker foldmarker={{{,}}}
 augroup END
 
 " }}}
@@ -1899,6 +1480,7 @@
 vmap -< ++<
 vmap -^ ++^
 vmap -V ++v
+vmap -- +-
 
 " }}}
 " Clam {{{
@@ -1923,12 +1505,17 @@
     au FileType lisp setlocal commentstring=;;\ %s
     au FileType makerlisp setlocal commentstring=;;\ %s
     au FileType puppet setlocal commentstring=#\ %s
+    au FileType snakemake setlocal commentstring=#\ %s
     au FileType nginx setlocal commentstring=#\ %s
     au FileType fish setlocal commentstring=#\ %s
     au FileType gnuplot setlocal commentstring=#\ %s
+    au FileType singularity setlocal commentstring=#\ %s
     au FileType cs setlocal commentstring=//\ %s
+    au FileType c setlocal commentstring=//\ %s
+    au FileType cpp setlocal commentstring=//\ %s
     au FileType arduino setlocal commentstring=//\ %s
     au FileType pandabt setlocal commentstring=//\ %s
+    au FileType nextflow setlocal commentstring=//\ %s
 augroup END
 
 " }}}
@@ -2021,6 +1608,7 @@
 let g:gundo_preview_bottom = 1
 let g:gundo_tree_statusline = "Gundo"
 let g:gundo_preview_statusline = "Gundo Preview"
+let g:gundo_prefer_python3 = 1
 
 " }}}
 " HTML5 {{{
@@ -2043,6 +1631,16 @@
 
 nnoremap <f6> :Neoformat<cr>
 
+let g:neoformat_snakemake_snakefmt = {
+        \ 'exe': '/home/sjl/bin/venvs/tools/bin/snakefmt',
+        \ 'args': [],
+        \ 'replace': 1,
+        \ 'stdin': 0,
+        \ 'valid_exit_codes': [0]
+        \ }
+
+let g:neoformat_enabled_snakemake = ['snakefmt']
+
 " }}}
 " NeoRepl {{{
 
@@ -2118,7 +1716,7 @@
 
 let g:paredit_smartjump = 1
 let g:paredit_shortmaps = 0
-let g:paredit_electric_return = 0
+let g:paredit_electric_return = 1
 let g:paredit_matchlines = 200
 
 let g:paredit_disable_lisp = 1
@@ -2407,6 +2005,15 @@
 let g:targets_pairs = '()b {}B []r <>'
 
 " }}}
+" Ultisnips {{{
+
+let g:UltiSnipsExpandTrigger       = '<Tab>'
+let g:UltiSnipsJumpForwardTrigger  = '<Tab>'
+let g:UltiSnipsJumpBackwardTrigger = '<S-Tab>'
+
+let g:UltiSnipsSnippetDirectories=[$HOME.'/.vim/ultisnips-bullshit']
+
+" }}}
 " Vlime {{{
 
 
@@ -2513,7 +2120,7 @@
     au FileType lisp,vlime_repl,vlime_inspector,vlime_sldb,vlime_notes,vlime_xref,vlime_preview call MapVlimeKeys()
 
     " Fix <cr>
-    au FileType lisp            inoremap <buffer> <cr> <cr><c-r>=vlime#plugin#VlimeKey("cr")<cr>
+    au FileType lisp            inoremap <buffer> <cr> <c-r>=vlime#plugin#VlimeKey("cr")<cr>
     au FileType vlime_xref      nnoremap <buffer> <cr> :call vlime#ui#xref#OpenCurXref()<cr>
     au FileType vlime_notes     nnoremap <buffer> <cr> :call vlime#ui#compiler_notes#OpenCurNote()<cr>
     au FileType vlime_sldb      nnoremap <buffer> <cr> :call vlime#ui#sldb#ChooseCurRestart()<cr>
@@ -2533,69 +2140,7 @@
 " Windowswap {{{
 
 let g:windowswap_map_keys = 0 "prevent default bindings
-nnoremap <silent> <leader>ws :call WindowSwap#EasyWindowSwap()<CR>
-
-" }}}
-
-" }}}
-" Text objects ------------------------------------------------------------ {{{
-
-" Folds {{{
-
-onoremap if :<c-u>normal! [zv]z<cr>
-onoremap af :<c-u>normal! [zV]z<cr>
-vnoremap if :<c-u>normal! ]zv[z<cr>
-vnoremap af :<c-u>normal! ]zV[z<cr>
-
-" }}}
-" Shortcut for [] {{{
-
-onoremap ir i[
-onoremap ar a[
-vnoremap ir i[
-vnoremap ar a[
-
-" }}}
-" Numbers {{{
-
-" Motion for numbers.  Great for CSS.  Lets you do things like this:
-"
-" margin-top: 200px; -> daN -> margin-top: px;
-"              ^                          ^
-" TODO: Handle floats.
-
-onoremap N :<c-u>call <SID>NumberTextObject(0)<cr>
-xnoremap N :<c-u>call <SID>NumberTextObject(0)<cr>
-onoremap aN :<c-u>call <SID>NumberTextObject(1)<cr>
-xnoremap aN :<c-u>call <SID>NumberTextObject(1)<cr>
-onoremap iN :<c-u>call <SID>NumberTextObject(1)<cr>
-xnoremap iN :<c-u>call <SID>NumberTextObject(1)<cr>
-
-function! s:NumberTextObject(whole)
-    let num = '\v[0-9]'
-
-    " If the current char isn't a number, walk forward.
-    while getline('.')[col('.') - 1] !~# num
-        normal! l
-    endwhile
-
-    " Now that we're on a number, start selecting it.
-    normal! v
-
-    " If the char after the cursor is a number, select it.
-    while getline('.')[col('.')] =~# num
-        normal! l
-    endwhile
-
-    " If we want an entire word, flip the select point and walk.
-    if a:whole
-        normal! o
-
-        while col('.') > 1 && getline('.')[col('.') - 2] =~# num
-            normal! h
-        endwhile
-    endif
-endfunction
+nnoremap <silent> <c-w><c-w> :call WindowSwap#EasyWindowSwap()<CR>
 
 " }}}
 
@@ -2902,6 +2447,7 @@
 nnoremap <silent> <leader>4 :call HiInterestingWord(4)<cr>
 nnoremap <silent> <leader>5 :call HiInterestingWord(5)<cr>
 nnoremap <silent> <leader>6 :call HiInterestingWord(6)<cr>
+nnoremap <silent> <leader>7 :call HiInterestingWord(7)<cr>
 
 " }}}
 " Default Highlights {{{
@@ -2912,6 +2458,7 @@
 hi def InterestingWord4 guifg=#000000 ctermfg=16 guibg=#b88853 ctermbg=137
 hi def InterestingWord5 guifg=#000000 ctermfg=16 guibg=#ff9eb8 ctermbg=211
 hi def InterestingWord6 guifg=#000000 ctermfg=16 guibg=#ff2c4b ctermbg=195
+hi def InterestingWord7 guifg=#000000 ctermfg=16 guibg=#af5fff ctermbg=135
 
 " }}}
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vim/vimrc-minimal	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,672 @@
+" .vimrc-minimal
+" Author: Steve Losh <steve@stevelosh.com>
+" Source: https://hg.stevelosh.com/dotfiles/file/tip/vim/vimrc
+
+" A minimal subset of my vimrc settings, suitable for syncing on its own to
+" a server so I have some basic settings, but don't have to install the whole
+" hog.
+
+" Preamble ---------------------------------------------------------------- {{{
+
+set shell=/bin/bash\ --login
+
+" }}}
+" Basic options ----------------------------------------------------------- {{{
+
+set modelines=0
+set autoindent
+set showmode
+set showcmd
+set hidden
+set visualbell
+set ttyfast
+set ruler
+set backspace=indent,eol,start
+set nonumber
+set norelativenumber
+set laststatus=2
+set history=1000
+set undofile
+set undoreload=10000
+set list
+set listchars=tab:▸\ ,eol:¬,extends:❯,precedes:❮
+set lazyredraw
+set matchtime=3
+set showbreak=↪
+set splitbelow
+set splitright
+set autowrite
+set autoread
+set shiftround
+set title
+set linebreak
+set colorcolumn=+1
+set diffopt+=vertical
+
+" Don't try to highlight lines longer than 500 characters.
+set synmaxcol=500
+
+" Time out on key codes but not mappings.
+" Basically this makes terminal Vim work sanely.
+set notimeout
+set ttimeout
+set ttimeoutlen=10
+
+" Make Vim able to edit crontab files again.
+set backupskip=/tmp/*,/private/tmp/*"
+
+" Better Completion
+set complete=.,w,b,u,t
+set completeopt=longest,menuone
+inoremap <c-o> <c-x><c-o>
+
+" Save when losing focus
+au FocusLost * :silent! wall
+
+" Leader
+let mapleader = ","
+let maplocalleader = "\\"
+
+" Cursorline {{{
+" Only show cursorline in the current window and in normal mode.
+
+augroup cline
+    au!
+    au WinLeave,InsertEnter * set nocursorline
+    au WinEnter,InsertLeave * set cursorline
+augroup END
+
+" }}}
+" cpoptions+=J, dammit {{{
+
+" Something occasionally removes this.  If I manage to find it I'm going to
+" comment out the line and replace all its characters with 'FUCK'.
+augroup twospace
+    au!
+    au BufRead * :set cpoptions+=J
+augroup END
+
+" }}}
+" Trailing whitespace {{{
+" Only shown when not in insert mode so I don't go insane.
+
+augroup trailing
+    au!
+    au InsertEnter * :set listchars-=trail:⌴
+    au InsertLeave * :set listchars+=trail:⌴
+augroup END
+
+" }}}
+" Wildmenu completion {{{
+
+set wildmenu
+set wildmode=list:longest
+
+set wildignore+=.hg,.git,.svn                    " Version control
+set wildignore+=*.aux,*.out,*.toc                " LaTeX intermediate files
+set wildignore+=*.jpg,*.bmp,*.gif,*.png,*.jpeg   " binary images
+set wildignore+=*.o,*.obj,*.exe,*.dll,*.manifest " compiled object files
+set wildignore+=*.spl                            " compiled spelling word lists
+set wildignore+=*.sw?                            " Vim swap files
+set wildignore+=*.DS_Store                       " OSX bullshit
+
+set wildignore+=*.luac                           " Lua byte code
+
+set wildignore+=migrations                       " Django migrations
+set wildignore+=*.pyc                            " Python byte code
+
+set wildignore+=*.orig                           " Merge resolution files
+
+set wildignore+=*.fasl                           " Lisp FASLs
+set wildignore+=*.dx64fsl                        " CCL
+set wildignore+=*.lx64fsl                        " CCL
+
+" }}}
+" Line Return {{{
+
+" Make sure Vim returns to the same line when you reopen a file.
+" Thanks, Amit
+augroup line_return
+    au!
+    au BufReadPost *
+        \ if line("'\"") > 0 && line("'\"") <= line("$") |
+        \     execute 'normal! g`"zvzz' |
+        \ endif
+augroup END
+
+" }}}
+" Tabs, spaces, wrapping {{{
+
+set tabstop=8
+set shiftwidth=4
+set softtabstop=4
+set expandtab
+set wrap
+set textwidth=80
+set formatoptions=qrn1j
+set colorcolumn=+1
+
+" }}}
+" Backups {{{
+
+set backup                        " enable backups
+set noswapfile                    " it's 2013, Vim.
+
+set undodir=~/.vim/tmp/undo//     " undo files
+set backupdir=~/.vim/tmp/backup// " backups
+set directory=~/.vim/tmp/swap//   " swap files
+
+" Make those folders automatically if they don't already exist.
+if !isdirectory(expand(&undodir))
+    call mkdir(expand(&undodir), "p")
+endif
+if !isdirectory(expand(&backupdir))
+    call mkdir(expand(&backupdir), "p")
+endif
+if !isdirectory(expand(&directory))
+    call mkdir(expand(&directory), "p")
+endif
+
+" }}}
+" Color scheme {{{
+
+syntax on
+set background=dark
+
+" }}}
+
+" }}}
+" Abbreviations & Digraphs ------------------------------------------------ {{{
+
+iabbrev todo TODO
+
+silent! digr -. 8230 "U+2026=…    HORIZONTAL ELLIPSIS
+silent! digr !, 8816 "U+2270=≰    NEITHER LESS-THAN NOR EQUAL TO
+silent! digr !. 8817 "U+2271=≱    NEITHER GREATER-THAN NOR EQUAL TO
+silent! digr es 8337 "U+2091=ₑ    SUBSCRIPT E
+silent! digr xs 8339 "U+2093=ₓ    SUBSCRIPT X
+silent! digr ls 8343 "U+2097=ₗ    SUBSCRIPT L
+silent! digr ms 8344 "U+2098=ₗ    SUBSCRIPT M
+silent! digr ns 8345 "U+2099=ₙ    SUBSCRIPT N
+silent! digr ps 8346 "U+209A=ₚ    SUBSCRIPT P
+silent! digr ss 8347 "U+209B=ₛ    SUBSCRIPT S
+silent! digr ts 8348 "U+209C=ₜ    SUBSCRIPT T
+silent! digr >< 8652 "U+21cc=⇌    EQUILIBRIUM
+silent! digr o+ 8853 "U+2295=⊕    CIRCLED PLUS
+silent! digr -^ 8593 "U+2191=↑    UPWARDS ARROW
+
+silent! digr -- 8212 "U+2014=—    EM DASH
+
+silent! digr // 9585 "U+2571=╱    BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT
+silent! digr \\ 9586 "U+2572=╲    BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT
+
+silent! digr \|\| 8214 "U+2016=‖  DOUBLE VERTICAL LINE
+
+silent! digr ~~ 8967  "U+2307=⌇   WAVY LINE
+
+" }}}
+" Convenience mappings ---------------------------------------------------- {{{
+
+" Fuck you, help key.
+noremap  <F1> :checktime<cr>
+inoremap <F1> <esc>:checktime<cr>
+
+" Stop it, hash key.
+inoremap # X<BS>#
+
+" Kill window
+nnoremap K :q<cr>
+
+" Save
+nnoremap s :w<cr>
+
+" Man
+nnoremap M K
+
+" Clean up windows
+nnoremap - :wincmd =<cr>
+
+" Toggle line numbers
+nnoremap <leader>n :setlocal number!<cr>
+
+" Sort lines
+nnoremap <leader>s vip:sort<cr>
+vnoremap <leader>s :sort<cr>
+
+" Tabs
+nnoremap <leader>( :tabprev<cr>
+nnoremap <leader>) :tabnext<cr>
+
+" My garbage brain can't ever remember digraph codes
+inoremap <c-k><c-k> <esc>:help digraph-table<cr>
+
+" Wrap
+" mnemonic: less' -S command/option
+nnoremap <leader>S :set wrap!<cr>
+
+" Inserting blank lines
+" I never use the default behavior of <cr> and this saves me a keystroke...
+nnoremap <cr> o<esc>
+
+" Delete to black hole register
+nnoremap dD "_dd
+vnoremap D "_d
+
+" Yank to end of line
+nnoremap Y y$
+
+" Reselect last-pasted text
+nnoremap gv `[v`]
+
+" I constantly hit "u" in visual mode when I mean to "y". Use "gu" for those rare occasions.
+" From https://github.com/henrik/dotfiles/blob/master/vim/config/mappings.vim
+vnoremap u <nop>
+vnoremap gu u
+
+" Clean trailing whitespace
+nnoremap <leader>ww mz:%s/\s\+$//<cr>:let @/=''<cr>`z
+
+" Select entire buffer
+nnoremap vaa ggvGg_
+nnoremap Vaa ggVG
+
+" Fix from spellcheck
+" I can never remember if it's zg or z=, and the wrong one adds the word to
+" the DB (lol), so fuck it, just add an easier mapping.
+nnoremap zz z=
+nnoremap z= :echo "use zz you idiot"<cr>
+
+" "Uppercase word" mapping.
+"
+" This mapping allows you to press <c-u> in insert mode to convert the current
+" word to uppercase.  It's handy when you're writing names of constants and
+" don't want to use Capslock.
+"
+" To use it you type the name of the constant in lowercase.  While your
+" cursor is at the end of the word, press <c-u> to uppercase it, and then
+" continue happily on your way:
+"
+"                            cursor
+"                            v
+"     max_connections_allowed|
+"     <c-u>
+"     MAX_CONNECTIONS_ALLOWED|
+"                            ^
+"                            cursor
+"
+" It works by exiting out of insert mode, recording the current cursor location
+" in the z mark, using gUiw to uppercase inside the current word, moving back to
+" the z mark, and entering insert mode again.
+"
+" Note that this will overwrite the contents of the z mark.  I never use it, but
+" if you do you'll probably want to use another mark.
+inoremap <C-u> <esc>mzgUiw`za
+
+" Wrap Toggle
+nnoremap <f9> :set nowrap!<cr>
+
+" zt is okay for putting something at the top of the screen, but when I'm
+" writing prose I often want to put something at not-quite-the-top of the
+" screen.  zh is "zoom to head level"
+nnoremap zh mzzt10<c-u>`z
+
+" Diffoff
+nnoremap <leader>D :diffoff!<cr>
+
+" Formatting, TextMate-style
+nnoremap Q gqip
+vnoremap Q gq
+
+" Reformat line.
+" I never use l as a macro register anyway.
+nnoremap ql gqq
+
+" Indent/dedent/autoindent what you just pasted.
+nnoremap <lt>> V`]<
+nnoremap ><lt> V`]>
+nnoremap =- V`]=
+
+" Keep the cursor in place while joining lines
+nnoremap J mzJ`z
+
+" Join an entire paragraph.
+"
+" Useful for writing GitHub comments in actual Markdown and then translating it
+" to their bastardized version of Markdown.
+nnoremap <leader>j mzvipJ`z
+
+" Split line (sister to [J]oin lines)
+" The normal use of S is covered by cc, so don't worry about shadowing it.
+nnoremap S i<cr><esc>^mwgk:silent! s/\v +$//<cr>:noh<cr>`w
+
+" Substitute
+nnoremap <c-s> :%s/
+vnoremap <c-s> :s/
+
+" Marks and Quotes
+noremap ' `
+noremap æ '
+noremap ` <C-^>
+
+" Select (charwise) the contents of the current line, excluding indentation.
+" Great for pasting Python lines into REPLs.
+nnoremap vv ^vg_
+
+" Typos
+command! -bang E e<bang>
+command! -bang Q q<bang>
+command! -bang W w<bang>
+command! -bang QA qa<bang>
+command! -bang Qa qa<bang>
+command! -bang Wa wa<bang>
+command! -bang WA wa<bang>
+command! -bang Wq wq<bang>
+command! -bang WQ wq<bang>
+command! -bang Wqa wqa<bang>
+
+" Unfuck my screen
+nnoremap U :syntax sync fromstart<cr>:redraw!<cr>
+
+" Zip Right
+"
+" Moves the character under the cursor to the end of the line.  Handy when you
+" have something like:
+"
+"     foo
+"
+" And you want to wrap it in a method call, so you type:
+"
+"     println()foo
+"
+" Once you hit escape your cursor is on the closing paren, so you can 'zip' it
+" over to the right with this mapping.
+"
+" This should preserve your last yank/delete as well.
+nnoremap zl :let @z=@"<cr>x$p:let @"=@z<cr>
+
+" Diff Navigation
+nnoremap ]d ]c
+nnoremap [d [c
+
+" Typo navigation
+nnoremap ]z ]S
+nnoremap [z [S
+nnoremap ]Z ]Sz=
+nnoremap [Z [Sz=
+
+" Header Lines
+nnoremap <leader>- o<esc>80a-<esc>kJ079lD
+
+" Insert Mode Completion
+inoremap <c-f> <c-x><c-f>
+inoremap <c-]> <c-x><c-]>
+inoremap <c-l> <c-x><c-l>
+
+" Window Resizing
+" right/up : bigger
+" left/down : smaller
+nnoremap <m-right> :vertical resize +3<cr>
+nnoremap <m-left> :vertical resize -3<cr>
+nnoremap <m-up> :resize +3<cr>
+nnoremap <m-down> :resize -3<cr>
+
+" Newline without having to go to the end of the line
+inoremap <c-cr> <esc>o
+
+" Spelling correct without having to exit insert mode
+inoremap <c-z> <esc>z=
+
+" }}}
+" Searching and movement -------------------------------------------------- {{{
+
+" Use sane regexes.
+nnoremap / /\v
+vnoremap / /\v
+
+set ignorecase
+set smartcase
+set incsearch
+set showmatch
+set hlsearch
+set gdefault
+
+set scrolloff=5
+set sidescroll=1
+set sidescrolloff=10
+
+set virtualedit+=block
+
+noremap <silent> <leader><space> :noh<cr>:call clearmatches()<cr>
+
+runtime macros/matchit.vim
+map <tab> %
+silent! unmap [%
+silent! unmap ]%
+
+" Made D behave
+nnoremap D d$
+
+" Don't move on *
+" I'd use a function for this but Vim clobbers the last search when you're in
+" a function so fuck it, practicality beats purity.
+nnoremap <silent> * :let stay_star_view = winsaveview()<cr>*:call winrestview(stay_star_view)<cr>
+
+" Jumping to tags.
+"
+" Basically, <c-]> jumps to tags (like normal) and <c-\> opens the tag in a new
+" split instead.
+"
+" Both of them will align the destination line to the upper middle part of the
+" screen.  Both will pulse the cursor line so you can see where the hell you
+" are.  <c-\> will also fold everything in the buffer and then unfold just
+" enough for you to see the destination line.
+"
+function! JumpTo(jumpcommand)
+    execute a:jumpcommand
+    call FocusLine()
+    Pulse
+endfunction
+function! JumpToInSplit(jumpcommand)
+    execute "normal! \<c-w>v"
+    execute a:jumpcommand
+    Pulse
+endfunction
+
+function! JumpToTag()
+    call JumpTo("normal! \<c-]>")
+endfunction
+function! JumpToTagInSplit()
+    call JumpToInSplit("normal \<c-]>")
+endfunction
+
+nnoremap <c-]> :silent! call JumpToTag()<cr>
+nnoremap <c-\> :silent! call JumpToTagInSplit()<cr>
+
+" Keep search matches in the middle of the window.
+nnoremap n nzzzv
+nnoremap N Nzzzv
+
+" Same when jumping around
+nnoremap g; g;zz
+nnoremap g, g,zz
+nnoremap <c-o> <c-o>zz
+
+" Easier to type, and I never use the default behavior.
+noremap H ^
+noremap L $
+vnoremap L g_
+
+" Heresy
+inoremap <c-a> <esc>I
+inoremap <c-e> <esc>A
+cnoremap <c-a> <home>
+cnoremap <c-e> <end>
+
+" go indent
+nnoremap gi mzVap=`z
+nnoremap gI mzgg=G`z
+
+" Fix linewise visual selection of various text objects
+nnoremap VV V
+nnoremap Vit vitVkoj
+nnoremap Vat vatV
+nnoremap Vab vabV
+nnoremap VaB vaBV
+
+
+" Directional Keys {{{
+
+" It's 2013.
+noremap j gj
+noremap k gk
+noremap gj j
+noremap gk k
+
+" Easy buffer navigation
+noremap <C-h> <C-w>h
+noremap <C-j> <C-w>j
+noremap <C-k> <C-w>k
+noremap <C-l> <C-w>l
+
+noremap <leader>v <C-w>v
+
+" }}}
+" Visual Mode */# from Scrooloose {{{
+
+function! s:VSetSearch()
+  let temp = @@
+  norm! gvy
+  let @/ = '\V' . substitute(escape(@@, '\'), '\n', '\\n', 'g')
+  let @@ = temp
+endfunction
+
+vnoremap * :<C-u>call <SID>VSetSearch()<CR>//<CR><c-o>
+vnoremap # :<C-u>call <SID>VSetSearch()<CR>??<CR><c-o>
+
+" }}}
+" List navigation {{{
+
+nnoremap <left>  :cprev<cr>zvzz
+nnoremap <right> :cnext<cr>zvzz
+nnoremap <up>    :lprev<cr>zvzz
+nnoremap <down>  :lnext<cr>zvzz
+
+" }}}
+
+" }}}
+" Folding ----------------------------------------------------------------- {{{
+
+set foldlevelstart=0
+
+" Space to toggle folds.
+nnoremap <Space> za
+vnoremap <Space> za
+
+" Make zO recursively open whatever fold we're in, even if it's partially open.
+nnoremap zO zczO
+
+" "Focus" the current line.  Basically:
+"
+" 1. Close all folds.
+" 2. Open just the folds containing the current line.
+" 3. Move the line to a bit (25 lines) down from the top of the screen.
+" 4. Pulse the line.
+"
+" This mapping wipes out the z mark, which I never use.
+"
+" I use :sus for the rare times I want to actually background Vim.
+function! FocusLine()
+    let oldscrolloff = &scrolloff
+    set scrolloff=0
+    execute "keepjumps normal! mzzMzvzt25\<c-y>`z:Pulse\<cr>"
+    let &scrolloff = oldscrolloff
+endfunction
+nnoremap <c-z> :call FocusLine()<cr>
+
+function! MyFoldText() " {{{
+    let line = getline(v:foldstart)
+
+    let nucolwidth = &fdc + &number * &numberwidth
+    let windowwidth = winwidth(0) - nucolwidth - 3
+    let foldedlinecount = v:foldend - v:foldstart
+
+    " expand tabs into spaces
+    let onetab = strpart('          ', 0, &tabstop)
+    let line = substitute(line, '\t', onetab, 'g')
+
+    let line = strpart(line, 0, windowwidth - 2 -len(foldedlinecount))
+    let fillcharcount = windowwidth - len(line) - len(foldedlinecount)
+    return line . '…' . repeat(" ",fillcharcount) . foldedlinecount . '…' . ' '
+endfunction " }}}
+set foldtext=MyFoldText()
+
+" }}}
+" Text objects ------------------------------------------------------------ {{{
+
+" Folds {{{
+
+onoremap if :<c-u>normal! [zv]z<cr>
+onoremap af :<c-u>normal! [zV]z<cr>
+vnoremap if :<c-u>normal! ]zv[z<cr>
+vnoremap af :<c-u>normal! ]zV[z<cr>
+
+" }}}
+" Shortcut for [] {{{
+
+onoremap ir i[
+onoremap ar a[
+vnoremap ir i[
+vnoremap ar a[
+
+" }}}
+" Numbers {{{
+
+" Motion for numbers.  Great for CSS.  Lets you do things like this:
+"
+" margin-top: 200px; -> daN -> margin-top: px;
+"              ^                          ^
+" TODO: Handle floats.
+
+onoremap N :<c-u>call <SID>NumberTextObject(0)<cr>
+xnoremap N :<c-u>call <SID>NumberTextObject(0)<cr>
+onoremap aN :<c-u>call <SID>NumberTextObject(1)<cr>
+xnoremap aN :<c-u>call <SID>NumberTextObject(1)<cr>
+onoremap iN :<c-u>call <SID>NumberTextObject(1)<cr>
+xnoremap iN :<c-u>call <SID>NumberTextObject(1)<cr>
+
+function! s:NumberTextObject(whole)
+    let num = '\v[0-9]'
+
+    " If the current char isn't a number, walk forward.
+    while getline('.')[col('.') - 1] !~# num
+        normal! l
+    endwhile
+
+    " Now that we're on a number, start selecting it.
+    normal! v
+
+    " If the char after the cursor is a number, select it.
+    while getline('.')[col('.')] =~# num
+        normal! l
+    endwhile
+
+    " If we want an entire word, flip the select point and walk.
+    if a:whole
+        normal! o
+
+        while col('.') > 1 && getline('.')[col('.') - 2] =~# num
+            normal! h
+        endwhile
+    endif
+endfunction
+
+" }}}
+
+" }}}
+" Optional Remote-Local Vimrc --------------------------------------------- {{{
+
+if filereadable(expand('~/.vimrc_remote_local'))
+    source ~/.vimrc_remote_local
+endif
+
+" }}}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/.agignore	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,2 @@
+logs/
+urls.log
Binary file weechat-old/GandiStandardSSLCA.crt has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/alias.conf	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,45 @@
+#
+# weechat -- alias.conf
+#
+# WARNING: It is NOT recommended to edit this file by hand,
+# especially if WeeChat is running.
+#
+# Use commands like /set or /fset to change settings in WeeChat.
+#
+# For more info, see: https://weechat.org/doc/quickstart/
+#
+
+[cmd]
+AAWAY = "allserv /away"
+AME = "allchan /me"
+AMSG = "allchan /msg *"
+ANICK = "allserv /nick"
+b = "/buffer"
+BYE = "quit"
+C = "buffer clear"
+CHAT = "dcc chat"
+CL = "buffer clear"
+CLOSE = "buffer close"
+EXIT = "quit"
+IG = "ignore"
+J = "join"
+K = "kick"
+KB = "kickban"
+LEAVE = "part"
+M = "msg"
+MUB = "unban *"
+N = "names"
+Q = "query"
+REDRAW = "window refresh"
+SAY = "msg *"
+SIGNOFF = "quit"
+T = "topic"
+UB = "unban"
+V = "command core version"
+W = "who"
+WC = "window merge"
+WI = "whois"
+WII = "whois $1 $1"
+WW = "whowas"
+
+[completion]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/aspell.conf	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,33 @@
+#
+# weechat -- aspell.conf
+#
+# WARNING: It is NOT recommended to edit this file by hand,
+# especially if WeeChat is running.
+#
+# Use /set or similar command to change settings in WeeChat.
+#
+# For more info, see: https://weechat.org/doc/quickstart
+#
+
+[color]
+misspelled = lightred
+suggestion = default
+suggestion_delimiter_dict = cyan
+suggestion_delimiter_word = cyan
+
+[check]
+commands = "ame,amsg,away,command,cycle,kick,kickban,me,msg,notice,part,query,quit,topic"
+default_dict = "en"
+during_search = off
+enabled = off
+real_time = off
+suggestions = -1
+word_min_length = 2
+
+[dict]
+
+[look]
+suggestion_delimiter_dict = " / "
+suggestion_delimiter_word = ","
+
+[option]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/autosort.conf	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,24 @@
+#
+# weechat -- autosort.conf
+#
+# WARNING: It is NOT recommended to edit this file by hand,
+# especially if WeeChat is running.
+#
+# Use commands like /set or /fset to change settings in WeeChat.
+#
+# For more info, see: https://weechat.org/doc/quickstart/
+#
+
+[sorting]
+case_sensitive = off
+debug_log = off
+replacements = "[]"
+rules = "[["core", 0], ["irc", 2], ["*", 1], ["irc.irc_raw", 0], ["irc.server", 1]]"
+signal_delay = 5
+signals = "buffer_opened buffer_merged buffer_unmerged buffer_renamed"
+sort_limit = 100
+sort_on_config_change = on
+
+[v3]
+helpers = "{"core_first": "${if:${buffer.full_name}!=core.weechat}", "irc_raw_first": "${if:${buffer.full_name}!=irc.irc_raw}", "irc_raw_last": "${if:${buffer.full_name}==irc.irc_raw}", "hashless_name": "${info:autosort_replace,#,,${info:autosort_escape,${buffer.name}}}", "script_or_plugin": "${if:${script_name}?${script_name}:${plugin}}"}"
+rules = "["${core_first}", "${info:autosort_order,${info:autosort_escape,${script_or_plugin}},core,*,irc,bitlbee,matrix,slack}", "${script_or_plugin}", "${irc_raw_first}", "${server}", "${info:autosort_order,${type},server,*,channel,private}", "${hashless_name}", "${buffer.full_name}"]"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/buffers.conf	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,77 @@
+#
+# weechat -- buffers.conf
+#
+# WARNING: It is NOT recommended to edit this file by hand,
+# especially if WeeChat is running.
+#
+# Use /set or similar command to change settings in WeeChat.
+#
+# For more info, see: https://weechat.org/doc/quickstart
+#
+
+[color]
+current_bg = green
+current_fg = black
+default_bg = default
+default_fg = default
+hotlist_highlight_bg = default
+hotlist_highlight_fg = *magenta
+hotlist_low_bg = default
+hotlist_low_fg = white
+hotlist_message_bg = default
+hotlist_message_fg = green
+hotlist_private_bg = default
+hotlist_private_fg = *magenta
+none_channel_bg = default
+none_channel_fg = 240
+number = green
+number_char = green
+prefix_bufname = default
+queries_default_bg = default
+queries_default_fg = default
+queries_highlight_bg = default
+queries_highlight_fg = default
+queries_message_bg = default
+queries_message_fg = default
+suffix_bufname = default
+whitelist_default_bg = default
+whitelist_default_fg = default
+whitelist_highlight_bg = default
+whitelist_highlight_fg = default
+whitelist_low_bg = default
+whitelist_low_fg = default
+whitelist_message_bg = default
+whitelist_message_fg = default
+whitelist_private_bg = default
+whitelist_private_fg = default
+
+[look]
+core_to_front = off
+detach = 0
+detach_buffer_immediately = ""
+detach_display_window_number = off
+detach_displayed_buffers = on
+detach_free_content = off
+detach_query = off
+hide_merged_buffers = none
+hotlist_counter = off
+immune_detach_buffers = ""
+indenting = on
+indenting_number = on
+jump_prev_next_visited_buffer = off
+mark_inactive = off
+mouse_move_buffer = on
+name_crop_suffix = "+"
+name_size_max = 0
+number_char = " "
+prefix = off
+prefix_bufname = ""
+prefix_empty = on
+prefix_for_query = ""
+short_names = on
+show_lag = off
+show_number = on
+sort = number
+suffix_bufname = ""
+toogle_bar = on
+whitelist_buffers = ""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/buflist.conf	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,41 @@
+#
+# weechat -- buflist.conf
+#
+# WARNING: It is NOT recommended to edit this file by hand,
+# especially if WeeChat is running.
+#
+# Use commands like /set or /fset to change settings in WeeChat.
+#
+# For more info, see: https://weechat.org/doc/quickstart/
+#
+
+[look]
+add_newline = on
+auto_scroll = 50
+display_conditions = "${buffer.hidden}==0"
+enabled = on
+mouse_jump_visited_buffer = off
+mouse_move_buffer = on
+mouse_wheel = on
+nick_prefix = off
+nick_prefix_empty = on
+signals_refresh = ""
+sort = "number,-active"
+use_items = 1
+
+[format]
+buffer = "${format_number}${indent}${format_nick_prefix}${color_hotlist}${format_name}"
+buffer_current = "${color:,blue}${format_buffer}"
+hotlist = " ${color:green}(${hotlist}${color:green})"
+hotlist_highlight = "${color:magenta}"
+hotlist_low = "${color:white}"
+hotlist_message = "${color:green}"
+hotlist_none = "${color:default}"
+hotlist_private = "${color:magenta}"
+hotlist_separator = "${color:default},"
+indent = "  "
+lag = " ${color:green}[${color:brown}${lag}${color:green}]"
+name = "${name}"
+nick_prefix = "${color_nick_prefix}${nick_prefix}"
+number = "${color:green}${number}${if:${number_displayed}?.: }"
+tls_version = " ${color:default}(${if:${tls_version}==TLS1.3?${color:green}:${if:${tls_version}==TLS1.2?${color:yellow}:${color:red}}}${translate:${tls_version}}${color:default})"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/charset.conf	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,18 @@
+#
+# weechat -- charset.conf
+#
+# WARNING: It is NOT recommended to edit this file by hand,
+# especially if WeeChat is running.
+#
+# Use commands like /set or /fset to change settings in WeeChat.
+#
+# For more info, see: https://weechat.org/doc/quickstart/
+#
+
+[default]
+decode = "iso-8859-1"
+encode = ""
+
+[decode]
+
+[encode]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/exec.conf	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,19 @@
+#
+# weechat -- exec.conf
+#
+# WARNING: It is NOT recommended to edit this file by hand,
+# especially if WeeChat is running.
+#
+# Use commands like /set or /fset to change settings in WeeChat.
+#
+# For more info, see: https://weechat.org/doc/quickstart/
+#
+
+[command]
+default_options = ""
+purge_delay = 0
+shell = "${env:SHELL}"
+
+[color]
+flag_finished = lightred
+flag_running = lightgreen
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/fifo.conf	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,14 @@
+#
+# weechat -- fifo.conf
+#
+# WARNING: It is NOT recommended to edit this file by hand,
+# especially if WeeChat is running.
+#
+# Use commands like /set or /fset to change settings in WeeChat.
+#
+# For more info, see: https://weechat.org/doc/quickstart/
+#
+
+[file]
+enabled = on
+path = "%h/weechat_fifo"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/fset.conf	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,96 @@
+#
+# weechat -- fset.conf
+#
+# WARNING: It is NOT recommended to edit this file by hand,
+# especially if WeeChat is running.
+#
+# Use commands like /set or /fset to change settings in WeeChat.
+#
+# For more info, see: https://weechat.org/doc/quickstart/
+#
+
+[look]
+auto_refresh = "*"
+auto_unmark = off
+condition_catch_set = "${count} >= 1"
+export_help_default = on
+format_number = 1
+marked_string = "*"
+scroll_horizontal = 10
+show_plugins_desc = off
+sort = "~name"
+unmarked_string = " "
+use_color_value = off
+use_keys = on
+use_mute = off
+
+[format]
+export_help = "# ${description2}"
+export_option = "/set ${name} ${quoted_value}"
+export_option_null = "/unset ${name}"
+option1 = ""
+option2 = "${marked} ${name}  ${type}  ${value2}${newline}  ${empty_name}  ${_default_value}${color:darkgray} -- ${min}..${max}${newline}  ${empty_name}  ${description}"
+
+[color]
+default_value = default
+default_value_selected = white
+description = default
+description_selected = white
+file = default
+file_changed = brown
+file_changed_selected = yellow
+file_selected = white
+help_default_value = white
+help_description = default
+help_name = white
+help_quotes = darkgray
+help_values = default
+index = cyan
+index_selected = lightcyan
+line_marked_bg1 = default
+line_marked_bg2 = default
+line_selected_bg1 = blue
+line_selected_bg2 = red
+marked = brown
+marked_selected = yellow
+max = default
+max_selected = white
+min = default
+min_selected = white
+name = default
+name_changed = brown
+name_changed_selected = yellow
+name_selected = white
+option = default
+option_changed = brown
+option_changed_selected = yellow
+option_selected = white
+parent_name = default
+parent_name_selected = white
+parent_value = cyan
+parent_value_selected = lightcyan
+quotes = darkgray
+quotes_changed = default
+quotes_changed_selected = white
+quotes_selected = default
+section = default
+section_changed = brown
+section_changed_selected = yellow
+section_selected = white
+string_values = default
+string_values_selected = white
+title_count_options = cyan
+title_current_option = lightcyan
+title_filter = yellow
+title_marked_options = lightgreen
+title_sort = white
+type = green
+type_selected = lightgreen
+unmarked = default
+unmarked_selected = white
+value = cyan
+value_changed = brown
+value_changed_selected = yellow
+value_selected = lightcyan
+value_undef = magenta
+value_undef_selected = lightmagenta
Binary file weechat-old/icon.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/logger.conf	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,36 @@
+#
+# weechat -- logger.conf
+#
+# WARNING: It is NOT recommended to edit this file by hand,
+# especially if WeeChat is running.
+#
+# Use commands like /set or /fset to change settings in WeeChat.
+#
+# For more info, see: https://weechat.org/doc/quickstart/
+#
+
+[look]
+backlog = 20
+backlog_conditions = ""
+
+[color]
+backlog_end = darkgray
+backlog_line = darkgray
+
+[file]
+auto_log = on
+color_lines = off
+flush_delay = 120
+fsync = off
+info_lines = off
+mask = "$plugin.$name.weechatlog"
+name_lower_case = on
+nick_prefix = " <"
+nick_suffix = "> "
+path = "%h/logs/"
+replacement_char = "_"
+time_format = "%Y-%m-%d %H:%M:%S"
+
+[level]
+
+[mask]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/lua.conf	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,14 @@
+#
+# weechat -- lua.conf
+#
+# WARNING: It is NOT recommended to edit this file by hand,
+# especially if WeeChat is running.
+#
+# Use /set or similar command to change settings in WeeChat.
+#
+# For more info, see: https://weechat.org/doc/quickstart
+#
+
+[look]
+check_license = off
+eval_keep_context = on
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/perl.conf	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,14 @@
+#
+# weechat -- perl.conf
+#
+# WARNING: It is NOT recommended to edit this file by hand,
+# especially if WeeChat is running.
+#
+# Use commands like /set or /fset to change settings in WeeChat.
+#
+# For more info, see: https://weechat.org/doc/quickstart
+#
+
+[look]
+check_license = off
+eval_keep_context = on
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/perl/autoload/colorize_lines.pl	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,251 @@
+#
+# Copyright (c) 2010-2013 by Nils Görs <weechatter@arcor.de>
+# Copyleft (ɔ) 2013 by oakkitten
+#
+# colors the channel text with nick color and also highlight the whole line
+# colorize_nicks.py script will be supported
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# with version 3.0 some options were renamed or have new possible values:
+# old:                  new:
+# avail_buffer          buffer
+# blacklist_channels    blacklist_buffers
+# highlight             new values
+
+# obsolete options:
+# buffer_autoset
+# hotlist_max_level_nicks_add
+# highlight_regex
+# highlight_words
+# shuffle
+# chat                  see option highlight
+
+# history:
+# 3.0: large part of script rewritten
+#      fix: works nicely with irc colors
+#      improved: highlight_regex and highlight_words work in a natural way
+#      removed: command /colorize_lines
+#      removed: option shuffle
+# 2.2: fix: regex with [tab] in message (patch by sqrrl)
+# 2.1: fix: changing highlight color did not apply messages already displayed (reported by rafi_)
+# 2.0: fix: debugging weechat::print() removed (thanks demure)
+# 1.9: fix: display bug with nick_mode
+# 1.8  add: option "use_irc_colors" (requested by Zertap)
+#      fix: empty char for nick_mode was used, even when "irc.look.nick_mode_empty" was OFF (reported by FlashCode)
+# 1.7: fix: broken lines in dcc chat (reported by equatorping)
+# 1.6: improved: wildcard "*" can be used for server and/or nick. (requested by ldvx)
+#    : add: new value, "only", for option "own_lines" (read help!)
+# 1.5: sync: option weechat.look.nickmode changed in 0.3.9 to "irc.look.nick_mode"
+# 1.4: fix: whole ctcp message was display in prefix (reported by : Mkaysi)
+# 1.3: fix: now using weechat::buffer_get_string() instead of regex to prevent problems with dots inside server-/channelnames (reported by surfhai)
+# 1.2: add: hook_modifier("colorize_lines") to use colorize_lines with another script.
+#    : fix: regex was too greedy and also hit tag "prefix_nick_ccc"
+# 1.1: fix:  problems with temporary server (reported by nand`)
+#    : improved: using weechat_string_has_highlight()
+# 1.0: fix: irc.look.nick_prefix wasn't supported
+# 0.9: added: option "own_nick" (idea by travkin)
+#    : new value (always) for option highlight
+#    : clean up code
+# 0.8.1: fix: regex()
+# 0.8: added: option "avail_buffer" and "nicks" (please read help-page) (suggested by ldvx)
+#    : fix: blacklist_buffers wasn't load at start
+#    : fix: nick_modes wasn't displayed since v0.7
+#    : rewrote init() routine
+#    : thanks to stfn for hint with unescaped variables in regex.
+# 0.7: fix: bug when irc.look.nick_suffix was set (reported and beta-testing by: hw2) (>= weechat 0.3.4)
+#      blacklist_buffers option supports servername
+#      clean up code
+# 0.6: code optimazations.
+#      rename of script (rainbow_text.pl -> colorize_lines.pl) (suggested by xt and flashcode)
+# 0.5: support of hotlist_max_level_nicks_add and weechat.color.chat_nick_colors (>= weechat 0.3.4)
+# 0.4: support of weechat.look.highlight_regex option (>= weechat 0.3.4)
+#    : support of weechat.look.highlight option
+#    : highlighted line did not work with "." inside servername
+#    ; internal "autoset" function fixed
+# 0.3: support of colorize_nicks.py implemented.
+#    : /me text displayed wrong nick colour (colour from suffix was used)
+#    : highlight messages will be checked case insensitiv
+# 0.2: supports highlight_words_add from buffer_autoset.py script (suggested: Emralegna)
+#    : correct look_nickmode colour will be used (bug reported by: Emralegna)
+#    : /me text will be coloured, too
+# 0.1: initial release
+#
+# Development is currently hosted at
+# https://github.com/weechatter/weechat-scripts
+
+# use Data::Dumper
+# $Data::Dumper::Useqq=1;
+
+use strict;
+my $prgname	= "colorize_lines";
+my $version	= "3.0";
+my $description	= "colors text in chat area with according nick color, including highlights";
+
+my %config = ("buffers"             => "all",       # all, channel, query
+              "blacklist_buffers"   => "",          # "a,b,c"
+              "lines"               => "on",
+              "highlight"           => "on",        # on, off, nicks
+              "nicks"               => "",          # "d,e,f", "/file"
+              "own_lines"           => "on",        # on, off, only
+);
+
+my %help_desc = ("buffers"             => "buffer type affected by the script (all/channel/query, default: all)",
+                 "blacklist_buffers"   => "comma-separated list of channels to be ignored (e.g. freenode.#weechat,*.#python)",
+                 "lines"               => "apply nickname color to the non-highlighted lines (off/on/nicks). the latter will limit highlighting to nicknames in option 'nicks'",
+                 "highlight"           => "apply highlight color to the highlighted lines (off/on/nicks). the latter will limit highlighting to nicknames in option 'nicks'",
+                 "nicks"               => "comma-separater list of nicks (e.g. freenode.cat,*.dog) OR file name starting with '/' (e.g. /file.txt). in the latter case, nicknames will get loaded from that file inside weechat folder (e.g. from ~/.weechat/file.txt). nicknames in file are newline-separated (e.g. freenode.dog\\n*.cat)",
+                 "own_lines"           => "apply nickname color to own lines (off/on/only). the latter turns off all other kinds of coloring altogether",
+);
+
+#################################################################################################### config
+
+# program starts here
+sub colorize_cb {
+    my ( $data, $modifier, $modifier_data, $string ) = @_;
+
+    # quit if it's not a privmsg or ctcp
+    # or we are not supposed to
+    if ((index($modifier_data,"irc_privmsg") == -1) ||
+        (index($modifier_data,"irc_ctcp") >= 0)) {
+        return $string;
+    }
+
+    # find buffer pointer
+    $modifier_data =~ m/([^;]*);([^;]*);/;
+    my $buffer = weechat::buffer_search($1, $2);
+    return $string if ($buffer eq "");
+
+    # find buffer name, server name
+    # return if buffer is in a blacklist
+    my $buffername = weechat::buffer_get_string($buffer, "name");
+    return $string if weechat::string_has_highlight($buffername, $config{blacklist_buffers});
+    my $servername = weechat::buffer_get_string($buffer, "localvar_server");
+
+    # find stuff between \t
+    $string =~ m/^([^\t]*)\t(.*)/;
+    my $left = $1;
+    my $right = $2;
+
+    # find nick of the sender
+    # find out if we are doing an action
+    my $nick = ($modifier_data =~ m/(^|,)nick_([^,]*)/) ? $2 : weechat::string_remove_color($left, "");
+    my $action = ($modifier_data =~ m/\birc_action\b/) ? 1 : 0;
+
+    ######################################## get color
+
+    my $color = "";
+    my $my_nick = weechat::buffer_get_string($buffer, "localvar_nick");
+    if ($my_nick eq $nick) {
+        # it's our own line
+        # process only if own_lines is "on" or "only" (i.e. not "off")
+        return $string if ($config{own_lines} eq "off");
+        $color = weechat::color("chat_nick_self");
+    } else {
+        # it's someone else's line
+        # don't process is own_lines are "only"
+        # in order to get correct matching, remove colors from the string
+        return $string if ($config{own_lines} eq "only");
+        my $right_nocolor = weechat::string_remove_color($right, "");
+        if (weechat::string_has_highlight($right_nocolor, weechat::buffer_string_replace_local_var($buffer, weechat::buffer_get_string($buffer, "highlight_words"))) ||
+            weechat::string_has_highlight($right_nocolor, weechat::config_string(weechat::config_get("weechat.look.highlight"))) ||
+            weechat::string_has_highlight_regex($right_nocolor, weechat::config_string(weechat::config_get("weechat.look.highlight_regex"))) ||
+            weechat::string_has_highlight_regex($right_nocolor, weechat::buffer_get_string($buffer, "highlight_regex"))
+           ) {
+            # we have a hilight! get a hilight color
+            # and replace the first occurance of coloring, that'd be nick color
+            # process only if highlight is "on" OR "nicks" & nick's in nicks
+            return $string if ($config{highlight} eq "off" ||
+                ($config{highlight} eq "nicks" && !weechat::string_has_highlight("$servername.$nick", $config{nicks})));
+            $color = weechat::color('chat_highlight');
+            $right =~ s/\31[^\31 ]+?\Q$nick/$color$nick/ if ($action);
+        } else {
+            # that's not a highlight
+            # process only if lines is "on" OR "nicks" & nick's in nicks
+            return $string if ($config{lines} eq "off" ||
+                ($config{lines} eq "nicks" && !weechat::string_has_highlight("$servername.$nick", $config{nicks})));
+            $color = weechat::info_get('irc_nick_color', $nick);
+        }
+    }
+
+    ######################################## inject colors and go!
+
+    my $out = "";
+    if ($action) {
+        # remove the first color reset - after * nick
+        # make other resets reset to our color
+        $right =~ s/\34//;
+        $right =~ s/\34/\34$color/g;
+        $out = $left . "\t" . $right . "\34"
+    } else {
+        # make other resets reset to our color
+        $right =~ s/\34/\34$color/g;
+        $out = $left . "\t" . $color . $right . "\34"
+    }
+    #weechat::print("", ""); weechat::print("", "\$str " . Dumper($string)); weechat::print("", "\$out " . Dumper($out));
+    return $out;
+}
+
+#################################################################################################### config
+
+# read nicknames if $conf{nisks} starts with /
+# after this, $conf{nisks} is of form a,b,c,d
+# if it doesnt start with /, assume it's already a,b,c,d
+sub nicklist_read {
+    return if (substr($config{nicks}, 0, 1) ne "/");
+    my $file = weechat::info_get("weechat_dir", "") . $config{nicks};
+    return unless -e $file;
+    my $nili = "";
+    open (WL, "<", $file) || DEBUG("$file: $!");
+    while (<WL>) {
+        chomp;                                                         # kill LF
+        $nili .= $_ . ",";
+    }
+    close WL;
+    chop $nili;                                                        # remove last ","
+    $config{nicks} = $nili;
+}
+
+# called when a config option ha been changed
+# $name = plugins.var.perl.$prgname.nicks etc
+sub toggle_config_by_set {
+    my ($pointer, $name, $value) = @_;
+    $name = substr($name,length("plugins.var.perl.$prgname."),length($name));
+    $config{$name} = lc($value);
+    nicklist_read() if ($name eq "nicks");
+}
+
+# read configuration from weechat OR
+#   set default options and
+#   set dectription if weechat >= 0.3.5
+# after done, read nicklist from file if needed
+sub init_config {
+    my $weechat_version = weechat::info_get('version_number', '') || 0;
+    foreach my $option (keys %config){
+        if (!weechat::config_is_set_plugin($option)) {
+            weechat::config_set_plugin($option, $config{$option});
+            weechat::config_set_desc_plugin($option, $help_desc{$option}) if ($weechat_version >= 0x00030500); # v0.3.5
+        } else {
+            $config{$option} = lc(weechat::config_get_plugin($option));
+        }
+    }
+    nicklist_read();
+}
+
+#################################################################################################### start
+
+weechat::register($prgname, "Nils Görs <weechatter\@arcor.de>", $version, "GPL3", $description, "", "");
+weechat::hook_modifier("500|weechat_print","colorize_cb", "");
+init_config();
+weechat::hook_config("plugins.var.perl.$prgname.*", "toggle_config_by_set", "");
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/python.conf	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,14 @@
+#
+# weechat -- python.conf
+#
+# WARNING: It is NOT recommended to edit this file by hand,
+# especially if WeeChat is running.
+#
+# Use commands like /set or /fset to change settings in WeeChat.
+#
+# For more info, see: https://weechat.org/doc/quickstart/
+#
+
+[look]
+check_license = off
+eval_keep_context = on
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/python/autoload/autosort.py	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,1 @@
+../autosort.py
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/python/autoload/brows.py	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,77 @@
+import subprocess
+import os
+
+SCRIPT_NAME = 'brows'
+SCRIPT_AUTHOR = 'Steve Losh <steve@stevelosh.com>'
+SCRIPT_VERSION = '1.0'
+SCRIPT_LICENSE = 'MIT/X11'
+SCRIPT_DESC = 'Launch brows to view URLs'
+SCRIPT_COMMAND = 'brows'
+
+import_ok = True
+
+BROWS = os.environ.get('BROWS', 'brows')
+
+try:
+    import weechat
+except ImportError:
+    print('This is a weechat script, what are you doing, run it in weechat, jesus')
+    import_ok = False
+
+weechat_version = 0
+
+def hd(fn, name, obj, attr):
+    return fn(weechat.hdata_get(name), obj, attr)
+
+def hdp(name, obj, attr):
+    return hd(weechat.hdata_pointer, name, obj, attr)
+
+def hds(name, obj, attr):
+    return hd(weechat.hdata_string, name, obj, attr)
+
+def get_lines(buffer, numlines):
+    lines = hdp("buffer", buffer, "own_lines")
+    if not lines:
+        # null pointer wat do
+        return None
+
+    last_lines = []
+
+    line = hdp("lines", lines, "last_line")
+    for _ in range(numlines):
+        if not line:
+            # shit we're at the top of the buffer
+            break
+
+        data = hdp("line", line, "data")
+        msg = hds("line_data", data, "message")
+        msg = weechat.string_remove_color(msg, "")
+
+        last_lines.append(msg.strip())
+
+        line = hdp("line", line, "prev_line")
+
+    return last_lines
+
+def brows(data, buffer, args, numlines=100):
+    lines = get_lines(buffer, numlines)
+
+    proc = subprocess.Popen([BROWS], stdin=subprocess.PIPE)
+    proc.communicate(input='\n'.join(lines))
+    weechat.command("", "/window refresh")
+
+    return weechat.WEECHAT_RC_OK
+
+
+if __name__ == '__main__' and import_ok:
+    if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION,
+                        SCRIPT_LICENSE, SCRIPT_DESC, '', ''):
+        weechat_version = weechat.info_get('version_number', '') or 0
+        weechat.hook_command(
+            SCRIPT_COMMAND,
+            'Launch brows to view URLs',
+            '',
+            '',
+            '',
+            'brows',
+            '')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/python/autoload/editor.py	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,65 @@
+import subprocess
+import os
+import tempfile
+
+SCRIPT_NAME = 'editor'
+SCRIPT_AUTHOR = 'Steve Losh <steve@stevelosh.com>'
+SCRIPT_VERSION = '1.0'
+SCRIPT_LICENSE = 'MIT/X11'
+SCRIPT_DESC = 'Launch an external editor to compose a message'
+SCRIPT_COMMAND = 'editor'
+
+import_ok = True
+
+EDITOR = os.environ.get('EDITOR','vim')
+
+try:
+    import weechat
+except ImportError:
+    print('This is a weechat script, what are you doing, run it in weechat, jesus')
+    import_ok = False
+
+weechat_version = 0
+
+
+def get_data(suffix, initial_data):
+    with tempfile.NamedTemporaryFile(suffix=".%s" % suffix, mode="w+") as tf:
+        tf.write(initial_data)
+        tf.flush()
+
+        if subprocess.call([EDITOR, tf.name]) != 0:
+            return None
+
+        # Reopen, because most editors do atomic write-tmp+rename saves which
+        # fucks with Python here.
+        tf.file.close()
+        with open(tf.name) as tf2:
+            return tf2.read()
+
+def editor(data, buffer, args):
+    suffix = args or "tmp"
+
+    line = weechat.buffer_get_string(buffer, "input")
+
+    data = get_data(suffix, line)
+    if data:
+        weechat.command(buffer, "/input delete_line")
+        weechat.command(buffer, data.strip())
+
+    weechat.command("", "/window refresh")
+
+    return weechat.WEECHAT_RC_OK
+
+
+if __name__ == '__main__' and import_ok:
+    if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION,
+                        SCRIPT_LICENSE, SCRIPT_DESC, '', ''):
+        weechat_version = weechat.info_get('version_number', '') or 0
+        weechat.hook_command(
+            SCRIPT_COMMAND,
+            'Open $EDITOR to compose a message',
+            '[file-extension]',
+            'If an argument is given, it will be used as the extension for the temporary file.',
+            '',
+            'editor',
+            '')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/python/autoload/notify.py	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,29 @@
+import weechat, subprocess
+
+SCRIPT_NAME = 'notify'
+SCRIPT_AUTHOR = 'Steve Losh <steve@stevelosh.com>'
+SCRIPT_VERSION = '0.0.1'
+SCRIPT_LICENSE = 'MIT'
+SCRIPT_DESC = 'notify-send for weechat'
+
+weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, '', '')
+
+weechat.hook_print('', 'irc_privmsg', '', 1, 'notify', '')
+
+def _notify(text):
+    subprocess.call(['notify-send', text])
+
+def notify(data, buffer, date, tags, displayed, highlight, prefix, message):
+    # ignore if it's yourself
+    own_nick = weechat.buffer_get_string(buffer, 'localvar_nick')
+
+    if prefix == own_nick or prefix == ('@%s' % own_nick):
+        return weechat.WEECHAT_RC_OK
+
+    if int(highlight):
+        channel = weechat.buffer_get_string(buffer, 'localvar_channel')
+        _notify('%s %s\n%s' % (prefix, channel, message))
+    elif 'notify_private' in tags:
+        _notify('%s [PM]\n%s' % (prefix, message))
+
+    return weechat.WEECHAT_RC_OK
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/python/autoload/quotes.py	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,124 @@
+import subprocess, os
+
+SCRIPT_NAME = 'quotes'
+SCRIPT_AUTHOR = 'Steve Losh <steve@stevelosh.com>'
+SCRIPT_VERSION = '1.0'
+SCRIPT_LICENSE = 'MIT/X11'
+SCRIPT_DESC = 'Grab quotes and shove them into a text file.'
+SCRIPT_COMMAND = 'quo'
+SCRIPT_COMMAND_LONG = 'quoo'
+SCRIPT_COMMAND_COPY = 'cop'
+
+HOME = os.getenv("HOME")
+QUOTE_FILE = '%s/Dropbox/quotes.txt' % HOME
+COPY_FILE = '%s/.ircopy.irc' % HOME
+
+import_ok = True
+
+try:
+    import weechat
+except ImportError:
+    print('This is a weechat script, what are you doing, run it in weechat, jesus')
+    import_ok = False
+
+weechat_version = 0
+
+def hd(fn, name, obj, attr):
+    return fn(weechat.hdata_get(name), obj, attr)
+
+def hdp(name, obj, attr):
+    return hd(weechat.hdata_pointer, name, obj, attr)
+
+def hds(name, obj, attr):
+    return hd(weechat.hdata_string, name, obj, attr)
+
+def get_lines(buffer, numlines):
+    lines = hdp("buffer", buffer, "own_lines")
+    if not lines:
+        # null pointer wat do
+        return None
+
+    last_lines = []
+
+    line = hdp("lines", lines, "last_line")
+    for _ in range(numlines):
+        if not line:
+            # shit we're at the top of the buffer
+            break
+
+        data = hdp("line", line, "data")
+        msg = hds("line_data", data, "message")
+        pre = hds("line_data", data, "prefix")
+
+        msg = weechat.string_remove_color(msg, "")
+        pre = weechat.string_remove_color(pre, "")
+
+        last_lines.append("<%s> %s" % (pre.strip(), msg.strip()))
+
+        line = hdp("line", line, "prev_line")
+
+    last_lines.reverse()
+    return last_lines
+
+def quote_grab(data, buffer, args, numlines=15):
+    lines = get_lines(buffer, numlines)
+
+    with open(QUOTE_FILE, 'a') as f:
+        f.write("\n---\n")
+        f.write('\n'.join(lines))
+
+    subprocess.call(["nvim", QUOTE_FILE,
+                     # start at the bottom of the file
+                     "+",
+                     # move up N lines, where N is how many we appended
+                     "-c", "normal! %dk" % len(lines)])
+    weechat.command("", "/window refresh")
+
+    return weechat.WEECHAT_RC_OK
+
+def quote_grab_long(data, buffer, args):
+    return quote_grab(data, buffer, args, 75)
+
+def quote_grab_copy(data, buffer, args):
+    lines = get_lines(buffer, 1000)
+
+    with open(COPY_FILE, 'w') as f:
+        f.write('\n'.join(lines))
+
+    subprocess.call(["nvim", COPY_FILE,
+                     # start at the bottom of the file
+                     "+"])
+    weechat.command("", "/window refresh")
+
+    return weechat.WEECHAT_RC_OK
+
+if __name__ == '__main__' and import_ok:
+    if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION,
+                        SCRIPT_LICENSE, SCRIPT_DESC, '', ''):
+        weechat_version = weechat.info_get('version_number', '') or 0
+        weechat.hook_command(
+            SCRIPT_COMMAND,
+            'Appends the last 15 lines of the current buffer to your quotes '
+            'file and opens it in Vim so you can trim it.',
+            '',
+            '',
+            '',
+            'quote_grab',
+            '')
+        weechat.hook_command(
+            SCRIPT_COMMAND_LONG,
+            'Appends the last 75 lines of the current buffer to your quotes '
+            'file and opens it in Vim so you can trim it.',
+            '',
+            '',
+            '',
+            'quote_grab_long',
+            '')
+        weechat.hook_command(
+            SCRIPT_COMMAND_COPY,
+            'Open the last 1000 lines of the file in Vim so you can copy.',
+            '',
+            '',
+            '',
+            'quote_grab_copy',
+            '')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/python/autoload/urlgrab.py	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,699 @@
+# -*- coding: utf-8 -*-
+#
+# UrlGrab, for weechat version >= 0.3.0
+#
+#   Listens to all channels for URLs, collects them in a list, and launches
+#   them in your favourite web server on the local host or a remote server.
+#   Copies url to X11 clipboard via xsel
+#      (http://www.vergenet.net/~conrad/software/xsel)
+#
+# Usage:
+#
+#   The /url command provides access to all UrlGrab functions.  Run
+#   '/help url' for complete command usage.
+#
+#   In general, use '/url list' to list the entire url list for the current
+#   channel, and '/url <n>' to launch the nth url in the list.  For
+#   example, to launch the first (and most-recently added) url in the list,
+#   you would run '/url 1'
+#
+#   From the server window, you must specify a specific channel for the
+#   list and launch commands, for example:
+#     /url list weechat
+#     /url 3 weechat
+#
+# Configuration:
+#
+#   The '/url set' command lets you get and set the following options:
+#
+#   historysize
+#     The maximum number of URLs saved per channel.  Default is 10
+#
+#   method
+#     Must be one of 'local' or 'remote' - Defines how URLs are launched by
+#     the script.  If 'local', the script will run 'localcmd' on the host.
+#     If 'remote', the script will run 'remotessh remotehost remotecmd' on
+#     the local host which should normally use ssh to connect to another
+#     host and run the browser command there.
+#
+#   localcmd
+#     The command to run on the local host to launch URLs in 'local' mode.
+#     The string '%s' will be replaced with the URL.  The default is
+#     'firefox %s'.
+#
+#   remotessh
+#     The command (and arguments) used to connect to the remote host for
+#     'remote' mode.  The default is 'ssh -x' which will connect as the
+#     current username via ssh and disable X11 forwarding.
+#
+#   remotehost
+#     The remote host to which we will connect in 'remote' mode.  For ssh,
+#     this can just be a hostname or 'user@host' to specify a username
+#     other than your current login name.  The default is 'localhost'.
+#
+#   remotecmd
+#     The command to execute on the remote host for 'remote' mode.  The
+#     default is 'bash -c "DISPLAY=:0.0 firefox '%s'"'  Which runs bash, sets
+#     up the environment to display on the remote host's main X display,
+#     and runs firefox.  As with 'localcmd', the string '%s' will be
+#     replaced with the URL.
+#
+#   cmdoutput
+#     The file where the command output (if any) is saved.  Overwritten
+#     each time you launch a new URL.  Default is ~/.weechat/urllaunch.log
+#
+#   default
+#     The command that will be run if no arguemnts to /url are given.
+#     Default is show
+#
+# Requirements:
+#
+#  - Designed to run with weechat version 0.3 or better.
+#      http://www.weechat.org/
+#
+# Acknowlegements:
+#
+#  - Based on an earlier version called 'urlcollector.py' by 'kolter' of
+#    irc.freenode.net/#weechat Honestly, I just cleaned up the code a bit and
+#    made the settings a little more useful (to me).
+#
+#  - With changes by Leonid Evdokimov (weechat at darkk dot net another dot ru):
+#    http://darkk.net.ru/weechat/urlgrab.py
+#    v1.1:  added better handling of dead zombie-childs
+#           added parsing of private messages
+#           added default command setting
+#           added parsing of scrollback buffers on load
+#    v1.2:  `historysize` was ignored
+#
+#  - With changes by ExclusivE (exclusive_tm at mail dot ru):
+#    v1.3: X11 clipboard support
+#
+#  - V1.4 Just ported it over to weechat 0.2.7  drubin AT smartcube dot co dot za
+#  - V1.5  1) I created a logging feature for urls, Time, Date, buffer, and url.
+#           2) Added selectable urls support, similar to the iset plugin (Thanks FlashCode)
+#           3) Colors/formats are configuarable.
+#           4) browser now uses hook_process (Please test with remote clients)
+#           5) Added /url open http://url.com functionality
+#           6) Changed urls detection to use regexpressions so should be much better
+#                Thanks to xt of #weechat bassed on on urlbar.py
+#  - V1.6 FlashCode <flashcode@flashtux.org>: Increase timeout for hook_process
+#         (from 1 second to 1 minute)
+#  - V1.7 FlashCode <flashcode@flashtux.org>: Update WeeChat site
+#  - V1.8 drubin <drubin [at] smartcube . co.za>:
+#           - Changed remote cmd to be single option
+#           - Added scrolling on up and down arrow keys for /url show
+#           - Changed remotecmd to include options with public/private keys password auth doesn't work
+#  - V1.9 Specimen <spinifer [at] gmail . com>:
+#           - Changed the default command when /url is run with no arguments to 'show'
+#           - Removed '/url help' command, because /help <command> is the standard way
+#  - V2.0 Xilov: replace "/url help" by "/help url"
+#  - V2.1 nand: Changed default: firefox %s to firefox '%s' (localcmd)
+#  - V2.2 Sébastien Helleu <flashcode@flashtux.org>: fix reload of config file
+#  - V2.3 nand: Allowed trailing )s for unmatched (s in URLs
+#  - V2.4 nand: Escaped URLs via URL-encoding instead of shell escaping, fixes '
+#  - V2.5 nand: Fixed some URLs that got incorrectly mangled by escaping
+#  - V2.6 nesthib: Fixed escaping of "="
+#                  Added missing quotes in default parameter (firefox '%s')
+#                  Removed the mix of tabs and spaces in the file indentation
+#  - V2.7 dobbymoodge <john.w.lamb [at] gmail . com>
+#                     ( https://github.com/dobbymoodge/ ):
+#           - Added 'copycmd' setting, users can set command to pipe into
+#             for '/url copy'
+#  - V2.8 Simmo Saan <simmo.saan@gmail.com>:
+#           - Changed print hook to ignore filtered lines
+#  - V2.9 Dominik Heidler <dominik@heidler.eu>:
+#           - Updated script for python3 support (now python2 and 3 are both supported)
+#  - V3.0 Sébastien Helleu <flashcode@flashtux.org>:
+#           - Fix python 3 compatibility (replace "has_key" by "in")
+#
+# Copyright (C) 2005 David Rubin <drubin AT smartcube dot co dot za>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+# USA.
+#
+
+from __future__ import print_function
+import sys
+import os
+try:
+    import weechat
+    import_ok = True
+except:
+    print("This script must be run under WeeChat.")
+    print("Get WeeChat now at: http://www.weechat.org/")
+    import_ok = False
+import subprocess
+import time
+try:
+    from urllib import quote
+except ImportError:
+    from urllib.parse import quote
+import re
+try:
+    from UserDict import UserDict
+except ImportError:
+    from collections import UserDict
+
+
+octet = r'(?:2(?:[0-4]\d|5[0-5])|1\d\d|\d{1,2})'
+ipAddr = r'%s(?:\.%s){3}' % (octet, octet)
+# Base domain regex off RFC 1034 and 1738
+label = r'[0-9a-z][-0-9a-z]*[0-9a-z]?'
+domain = r'%s(?:\.%s)*\.[a-z][-0-9a-z]*[a-z]?' % (label, label)
+urlRe = re.compile(r'(\w+://(?:%s|%s)(?::\d+)?(?:/[^\]>\s]*)?)' % (domain, ipAddr), re.I)
+
+
+SCRIPT_NAME    = "urlgrab"
+SCRIPT_AUTHOR  = "David Rubin <drubin [At] smartcube [dot] co [dot] za>"
+SCRIPT_VERSION = "3.0"
+SCRIPT_LICENSE = "GPL"
+SCRIPT_DESC    = "Url functionality Loggin, opening of browser, selectable links"
+CONFIG_FILE_NAME= "urlgrab"
+SCRIPT_COMMAND = "url"
+
+
+def stripParens(url):
+    return dropChar(')', url.count(')') - url.count('('), url[::-1])[::-1]
+
+def dropChar(c, n, xs):
+    if n == 0 or xs == []:
+        return xs
+    elif xs[0] == c:
+        return dropChar(c, n-1, xs[1:])
+    else:
+        return xs
+
+def urlGrabPrint(message):
+    bufferd=weechat.current_buffer()
+    if urlGrabSettings['output_main_buffer'] == 1 :
+        weechat.prnt("","[%s] %s" % ( SCRIPT_NAME, message ) )
+    else :
+        weechat.prnt(bufferd,"[%s] %s" % ( SCRIPT_NAME, message ) )
+
+def hashBufferName(bufferp):
+    if not weechat.buffer_get_string(bufferp, "short_name"):
+        bufferd = weechat.buffer_get_string(bufferp, "name")
+    else:
+        bufferd = weechat.buffer_get_string(bufferp, "short_name")
+    return bufferd
+
+def ug_config_reload_cb(data, config_file):
+    """ Reload configuration file. """
+    return weechat.config_reload(config_file)
+
+class UrlGrabSettings(UserDict):
+    def __init__(self):
+        UserDict.__init__(self)
+        self.data = {}
+        self.config_file = weechat.config_new(CONFIG_FILE_NAME,
+                                        "ug_config_reload_cb", "")
+        if not self.config_file:
+            return
+
+        section_color = weechat.config_new_section(
+            self.config_file, "color", 0, 0, "", "", "", "", "", "",
+                     "", "", "", "")
+        section_default = weechat.config_new_section(
+            self.config_file, "default", 0, 0, "", "", "", "", "", "",
+                     "", "", "", "")
+
+        self.data['color_buffer']=weechat.config_new_option(
+            self.config_file, section_color,
+            "color_buffer", "color", "Color to display buffer name", "", 0, 0,
+            "red", "red", 0, "", "", "", "", "", "")
+
+        self.data['color_url']=weechat.config_new_option(
+            self.config_file, section_color,
+            "color_url", "color", "Color to display urls", "", 0, 0,
+            "blue", "blue", 0, "", "", "", "", "", "")
+
+        self.data['color_time']=weechat.config_new_option(
+            self.config_file, section_color,
+            "color_time", "color", "Color to display time", "", 0, 0,
+            "cyan", "cyan", 0, "", "", "", "", "", "")
+
+        self.data['color_buffer_selected']=weechat.config_new_option(
+            self.config_file, section_color,
+            "color_buffer_selected", "color",
+            "Color to display buffer selected name", "", 0, 0, "red", "red",
+            0, "", "", "", "", "", "")
+
+        self.data['color_url_selected']=weechat.config_new_option(
+            self.config_file, section_color,
+            "color_url_selected", "color", "Color to display url selected",
+             "", 0, 0, "blue", "blue", 0, "", "", "", "", "", "")
+
+        self.data['color_time_selected']=weechat.config_new_option(
+            self.config_file, section_color,
+            "color_time_selected", "color", "Color to display tim selected",
+            "", 0, 0, "cyan", "cyan", 0, "", "", "", "", "", "")
+
+        self.data['color_bg_selected']=weechat.config_new_option(
+            self.config_file, section_color,
+            "color_bg_selected", "color", "Background for selected row", "", 0, 0,
+            "green", "green", 0, "", "", "", "", "", "")
+
+        self.data['historysize']=weechat.config_new_option(
+            self.config_file, section_default,
+            "historysize", "integer", "Max number of urls to store per buffer",
+            "", 0, 999, "10", "10", 0, "", "", "", "", "", "")
+
+        self.data['method']=weechat.config_new_option(
+            self.config_file, section_default,
+            "method", "string", """Where to launch URLs
+            If 'local', runs %localcmd%.
+            If 'remote' runs the following command:
+            '%remodecmd%'""", "", 0, 0,
+            "local", "local", 0, "", "", "", "", "", "")
+
+        self.data['copycmd']=weechat.config_new_option(
+            self.config_file, section_default,
+            "copycmd", "string",
+            "Command to pipe into for 'url copy'. "
+            "E.g. to copy into the CLIPBOARD buffer "
+            "instead of PRIMARY, you can use 'xsel -b "
+            "-i' here.", "", 0, 0,
+            "xsel -i", "xsel -i", 0, "", "", "", "", "", "")
+
+        self.data['localcmd']=weechat.config_new_option(
+            self.config_file, section_default,
+            "localcmd", "string", """Local command to execute""", "", 0, 0,
+            "firefox '%s'", "firefox '%s'", 0, "", "", "", "", "", "")
+
+        remotecmd="ssh -x localhost -i ~/.ssh/id_rsa -C \"export DISPLAY=\":0.0\" &&  firefox '%s'\""
+        self.data['remotecmd']=weechat.config_new_option(
+            self.config_file, section_default,
+            "remotecmd", "string", remotecmd, "", 0, 0,
+            remotecmd, remotecmd, 0, "", "", "", "", "", "")
+
+        self.data['url_log']=weechat.config_new_option(
+            self.config_file, section_default,
+            "url_log", "string", """log location""", "", 0, 0,
+            "~/.weechat/urls.log", "~/.weechat/urls.log", 0, "", "", "", "", "", "")
+
+        self.data['time_format']=weechat.config_new_option(
+            self.config_file, section_default,
+            "time_format", "string", """TIme format""", "", 0, 0,
+            "%H:%M:%S", "%H:%M:%S", 0, "", "", "", "", "", "")
+
+        self.data['output_main_buffer']=weechat.config_new_option(
+            self.config_file, section_default,
+            "output_main_buffer", "boolean",
+            """Print text to main buffer or current one""", "", 0, 0, "1", "1",
+             0, "", "", "", "", "", "")
+        weechat.config_read(self.config_file)
+
+    def __getitem__(self, key):
+        if key == "historysize":
+            return weechat.config_integer(self.data[key])
+        elif key == 'output_main_buffer':
+            return weechat.config_boolean(self.data[key])
+        #elif key.startswith('color'):
+        #    return weechat.config_color(self.data[key])
+        else:
+            return weechat.config_string(self.data[key])
+
+    def prnt(self, name, verbose = True):
+        weechat.prnt( ""," %s = %s" % (name.ljust(11), self.data[name]) )
+
+    def prntall(self):
+        for key in self.names():
+            self.prnt(key, verbose = False)
+
+    def createCmd(self, url):
+        str =""
+        if self['method'] == 'remote':
+            str = self['remotecmd']  % url
+        else:
+            str =  self['localcmd']  % url
+        return str
+
+class UrlGrabber:
+    def __init__(self, historysize):
+        # init
+        self.urls = {}
+        self.globalUrls = []
+        self.historysize = 5
+        # control
+        self.setHistorysize(historysize)
+
+    def setHistorysize(self, count):
+        if count > 1:
+            self.historysize = count
+
+    def getHistorysize(self):
+        return self.historysize
+
+    def addUrl(self, bufferp,url ):
+        global urlGrabSettings
+        self.globalUrls.insert(0,{"buffer":bufferp,
+            "url":url, "time":time.strftime(urlGrabSettings["time_format"])})
+        #Log urls only if we have set a log path.
+        if urlGrabSettings['url_log']:
+            try :
+                index = self.globalUrls[0]
+                logfile = os.path.expanduser(urlGrabSettings['url_log'])
+                dout = open(logfile, "a")
+                dout.write("%s %s %s\n" % (index['time'],
+                                           index['buffer'], index['url']))
+                dout.close()
+            except :
+                print("failed to log url check that %s is valid path" % urlGrabSettings['url_log'])
+                pass
+
+        # check for buffer
+        if not bufferp in self.urls:
+            self.urls[bufferp] = []
+        # add url
+        if url in self.urls[bufferp]:
+            self.urls[bufferp].remove(url)
+        self.urls[bufferp].insert(0, url)
+        # removing old urls
+        while len(self.urls[bufferp]) > self.historysize:
+            self.urls[bufferp].pop()
+
+    def hasIndex( self, bufferp, index ):
+        return bufferp in self.urls and \
+                len(self.url[bufferp]) >= index
+
+    def hasBuffer( self, bufferp ):
+        return bufferp in self.urls
+
+
+    def getUrl(self, bufferp, index):
+        url = ""
+        if  bufferp in self.urls:
+            if len(self.urls[bufferp]) >= index:
+                    url = self.urls[bufferp][index-1]
+        return url
+
+
+    def prnt(self, buff):
+        found = True
+        if buff in self.urls:
+            if len(self.urls[buff]) > 0:
+                i = 1
+                for url in self.urls[buff]:
+                    urlGrabPrint("--> " + str(i) + " : " + url)
+                    i += 1
+            else:
+                found = False
+        elif buff == "*":
+            for b in self.urls.keys():
+              self.prnt(b)
+        else:
+            found = False
+
+        if not found:
+            urlGrabPrint(buff + ": no entries")
+
+def urlGrabCheckMsgline(bufferp, message, isdisplayed):
+    global urlGrab, max_buffer_length
+    if not message or isdisplayed == 0:
+        return
+    # Ignore output from 'tinyurl.py' and our selfs
+    if ( message.startswith( "[AKA] http://tinyurl.com" ) or
+        message.startswith("[urlgrab]") ):
+        return
+    # Check for URLs
+    for url in urlRe.findall(message):
+        urlGrab.addUrl(bufferp,stripParens(url))
+        if max_buffer_length < len(bufferp):
+            max_buffer_length = len(bufferp)
+        if urlgrab_buffer:
+            refresh()
+
+
+def urlGrabCheck(data, bufferp, uber_empty, tagsn, isdisplayed, ishilight, prefix, message):
+    urlGrabCheckMsgline(hashBufferName(bufferp), message, isdisplayed)
+    return weechat.WEECHAT_RC_OK
+
+def urlGrabCopy(bufferd, index):
+    global urlGrab
+    if bufferd == "":
+        urlGrabPrint( "No current channel, you must activate one" )
+    elif not urlGrab.hasBuffer(bufferd):
+        urlGrabPrint("No URL found - Invalid channel")
+    else:
+        if index <= 0:
+            urlGrabPrint("No URL found - Invalid index")
+            return
+        url = urlGrab.getUrl(bufferd,index)
+    if url == "":
+        urlGrabPrint("No URL found - Invalid index")
+    else:
+        try:
+            pipe = os.popen(urlGrabSettings['copycmd'],"w")
+            pipe.write(url)
+            pipe.close()
+            urlGrabPrint("Url: %s gone to clipboard." % url)
+        except:
+            urlGrabPrint("Url: %s failed to copy to clipboard." % url)
+
+def urlGrabOpenUrl(url):
+    global urlGrab, urlGrabSettings
+    argl = urlGrabSettings.createCmd( quote(url, '/:#%?&+=') )
+    weechat.hook_process(argl,60000, "ug_open_cb", "")
+
+def ug_open_cb(data, command, code, out, err):
+    # weechat.prnt("", out)
+    # weechat.prnt("", err)
+    return weechat.WEECHAT_RC_OK
+
+
+def urlGrabOpen(bufferd, index):
+    global urlGrab, urlGrabSettings
+    if bufferd == "":
+        urlGrabPrint( "No current channel, you must specify one" )
+    elif not urlGrab.hasBuffer(bufferd) :
+        urlGrabPrint("No URL found - Invalid channel")
+    else:
+        if index <= 0:
+            urlGrabPrint("No URL found - Invalid index")
+            return
+        url =  urlGrab.getUrl(bufferd,index)
+        if url == "":
+            urlGrabPrint("No URL found - Invalid index")
+        else:
+            urlGrabPrint("loading %s %sly" % (url, urlGrabSettings["method"]))
+            urlGrabOpenUrl (url)
+
+def urlGrabList( args ):
+    global urlGrab
+    if len(args) == 0:
+        buf = hashBufferName(weechat.current_buffer())
+    else:
+        buf = args[0]
+    if buf == "" or buf == "all":
+        buf = "*"
+    urlGrab.prnt(buf)
+
+
+def urlGrabMain(data, bufferp, args):
+    if args[0:2] == "**":
+        keyEvent(data, bufferp, args[2:])
+        return weechat.WEECHAT_RC_OK
+
+    bufferd = hashBufferName(bufferp)
+    largs = args.split(" ")
+    #strip spaces
+    while '' in largs:
+        largs.remove('')
+    while ' ' in largs:
+        largs.remove(' ')
+    if len(largs) == 0 or largs[0] == "show":
+        if not urlgrab_buffer:
+            init()
+        refresh()
+        weechat.buffer_set(urlgrab_buffer, "display", "1")
+    elif largs[0] == 'open' and len(largs) == 2:
+        urlGrabOpenUrl(largs[1])
+    elif largs[0] == 'list':
+        urlGrabList( largs[1:] )
+    elif largs[0] == 'copy':
+        if len(largs) > 1:
+            no = int(largs[1])
+            urlGrabCopy(bufferd, no)
+        else:
+            urlGrabCopy(bufferd,1)
+    else:
+        try:
+            no = int(largs[0])
+            if len(largs) > 1:
+                urlGrabOpen(largs[1], no)
+            else:
+                urlGrabOpen(bufferd, no)
+        except ValueError:
+            #not a valid number so try opening it as a url..
+            for url in urlRe.findall(largs[0]):
+                urlGrabOpenUrl(stripParens(url))
+            urlGrabPrint( "Unknown command '%s'.  Try '/help url' for usage" % largs[0])
+    return weechat.WEECHAT_RC_OK
+
+def buffer_input(*kwargs):
+    return weechat.WEECHAT_RC_OK
+
+def buffer_close(*kwargs):
+    global urlgrab_buffer
+    urlgrab_buffer =  None
+    return weechat.WEECHAT_RC_OK
+
+def keyEvent (data, bufferp, args):
+    global urlGrab , urlGrabSettings, urlgrab_buffer, current_line
+    if args == "refresh":
+        refresh()
+    elif args == "up":
+        if current_line > 0:
+            current_line = current_line -1
+            refresh_line (current_line + 1)
+            refresh_line (current_line)
+            ugCheckLineOutsideWindow()
+    elif args == "down":
+         if current_line < len(urlGrab.globalUrls) - 1:
+            current_line = current_line +1
+            refresh_line (current_line - 1)
+            refresh_line (current_line)
+            ugCheckLineOutsideWindow()
+    elif args == "scroll_top":
+        temp_current = current_line
+        current_line =  0
+        refresh_line (temp_current)
+        refresh_line (current_line)
+        weechat.command(urlgrab_buffer, "/window scroll_top")
+        pass
+    elif args == "scroll_bottom":
+        temp_current = current_line
+        current_line =  len(urlGrab.globalUrls)
+        refresh_line (temp_current)
+        refresh_line (current_line)
+        weechat.command(urlgrab_buffer, "/window scroll_bottom")
+    elif args == "enter":
+        if urlGrab.globalUrls[current_line]:
+            urlGrabOpenUrl (urlGrab.globalUrls[current_line]['url'])
+
+def refresh_line (y):
+    global urlGrab , urlGrabSettings, urlgrab_buffer, current_line, max_buffer_length
+    #Print format  Time(space)buffer(max4 spaces, but lined up)url
+    format = "%%s%%s %%s%%-%ds%%s%%s" % (max_buffer_length+4)
+
+    #non selected colors
+    color_buffer = urlGrabSettings["color_buffer"]
+    color_url = urlGrabSettings["color_url"]
+    color_time =urlGrabSettings["color_time"]
+    #selected colors
+    color_buffer_selected = urlGrabSettings["color_buffer_selected"]
+    color_url_selected = urlGrabSettings["color_url_selected"]
+    color_time_selected = urlGrabSettings["color_time_selected"]
+
+    color_bg_selected = urlGrabSettings["color_bg_selected"]
+
+    color1 = color_time
+    color2 = color_buffer
+    color3 = color_url
+
+    #If this line is selected we change the colors.
+    if y == current_line:
+          color1 = "%s,%s" % (color_time_selected, color_bg_selected)
+          color2 = "%s,%s" % (color_buffer_selected, color_bg_selected)
+          color3 = "%s,%s" % (color_url_selected, color_bg_selected)
+
+    color1 = weechat.color(color1)
+    color2 = weechat.color(color2)
+    color3 = weechat.color(color3)
+    text = format % (color1,
+                    urlGrab.globalUrls[y]['time'],
+                    color2,
+                    urlGrab.globalUrls[y]['buffer'],
+                    color3,
+                    urlGrab.globalUrls[y]['url'] )
+    weechat.prnt_y(urlgrab_buffer,y,text)
+
+def ugCheckLineOutsideWindow():
+    global urlGrab , urlGrabSettings, urlgrab_buffer, current_line, max_buffer_length
+    if (urlgrab_buffer):
+        infolist = weechat.infolist_get("window", "", "current")
+        if (weechat.infolist_next(infolist)):
+            start_line_y = weechat.infolist_integer(infolist, "start_line_y")
+            chat_height = weechat.infolist_integer(infolist, "chat_height")
+            if(start_line_y > current_line):
+                weechat.command(urlgrab_buffer, "/window scroll -%i" %(start_line_y - current_line))
+            elif(start_line_y <= current_line - chat_height):
+                weechat.command(urlgrab_buffer, "/window scroll +%i"%(current_line - start_line_y - chat_height + 1))
+        weechat.infolist_free(infolist)
+
+
+def refresh():
+    global urlGrab
+    y=0
+    for x in urlGrab.globalUrls:
+        refresh_line (y)
+        y += 1
+
+
+def init():
+    global urlGrab , urlGrabSettings, urlgrab_buffer
+    if not urlgrab_buffer:
+        urlgrab_buffer = weechat.buffer_new("urlgrab", "buffer_input", "", "buffer_close", "")
+    if urlgrab_buffer:
+        weechat.buffer_set(urlgrab_buffer, "type", "free")
+        weechat.buffer_set(urlgrab_buffer, "key_bind_ctrl-R",        "/url **refresh")
+        weechat.buffer_set(urlgrab_buffer, "key_bind_meta2-A",       "/url **up")
+        weechat.buffer_set(urlgrab_buffer, "key_bind_meta2-B",       "/url **down")
+        weechat.buffer_set(urlgrab_buffer, "key_bind_meta-ctrl-J",   "/url **enter")
+        weechat.buffer_set(urlgrab_buffer, "key_bind_meta-ctrl-M",   "/url **enter")
+        weechat.buffer_set(urlgrab_buffer, "key_bind_meta-meta2-1./~", "/url **scroll_top")
+        weechat.buffer_set(urlgrab_buffer, "key_bind_meta-meta2-4~", "/url **scroll_bottom")
+        weechat.buffer_set(urlgrab_buffer, "title","Lists the urls in the applications")
+        weechat.buffer_set(urlgrab_buffer, "display", "1")
+
+def completion_urls_cb(data, completion_item, bufferp, completion):
+    """ Complete with URLS, for command '/url'. """
+    global urlGrab
+    bufferd = hashBufferName( bufferp)
+    for url in urlGrab.globalUrls :
+        if url['buffer'] == bufferd:
+            weechat.hook_completion_list_add(completion, url['url'], 0, weechat.WEECHAT_LIST_POS_SORT)
+    return weechat.WEECHAT_RC_OK
+
+def ug_unload_script():
+    """ Function called when script is unloaded. """
+    global urlGrabSettings
+    weechat.config_write(urlGrabSettings.config_file)
+    return weechat.WEECHAT_RC_OK
+
+#Main stuff
+if ( import_ok and
+    weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION,
+        SCRIPT_LICENSE,SCRIPT_DESC, "ug_unload_script", "") ):
+    urlgrab_buffer = None
+    current_line = 0
+    max_buffer_length = 0
+    urlGrabSettings = UrlGrabSettings()
+    urlGrab = UrlGrabber( urlGrabSettings['historysize'])
+    weechat.hook_print("", "", "", 1, "urlGrabCheck", "")
+    weechat.hook_command(SCRIPT_COMMAND,
+                             "Url Grabber",
+                             "[open <url> | <url> | show | copy [n] | [n] | list]",
+                             "open or <url>: opens the url\n"
+                             "show: Opens the select buffer to allow for url selection\n"
+                             "copy: Copies the nth url to the system clipboard\n"
+                             "list: Lists the urls in the current buffer\n",
+                             "open %(urlgrab_urls) || %(urlgrab_urls) || "
+                             "copy || show || list",
+                             "urlGrabMain", "")
+    weechat.hook_completion("urlgrab_urls", "list of URLs",
+                                "completion_urls_cb", "")
+else:
+    print("failed to load weechat")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/python/autoload/wee_slack.py	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,1 @@
+/home/sjl/src/wee-slack/wee_slack.py
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/python/autosort.py	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,1075 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2013-2017 Maarten de Vries <maarten@de-vri.es>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+#
+# Autosort automatically keeps your buffers sorted and grouped by server.
+# You can define your own sorting rules. See /help autosort for more details.
+#
+# https://github.com/de-vri-es/weechat-autosort
+#
+
+#
+# Changelog:
+# 3.9:
+#   * Remove `buffers.pl` from recommended settings.
+# 3,8:
+#   * Fix relative sorting on script name in default rules.
+#   * Document a useful property of stable sort algorithms.
+# 3.7:
+#   * Make default rules work with bitlbee, matrix and slack.
+# 3.6:
+#   * Add more documentation on provided info hooks.
+# 3.5:
+#   * Add ${info:autosort_escape,...} to escape arguments for other info hooks.
+# 3.4:
+#   * Fix rate-limit of sorting to prevent high CPU load and lock-ups.
+#   * Fix bug in parsing empty arguments for info hooks.
+#   * Add debug_log option to aid with debugging.
+#   * Correct a few typos.
+# 3.3:
+#   * Fix the /autosort debug command for unicode.
+#   * Update the default rules to work better with Slack.
+# 3.2:
+#   * Fix python3 compatiblity.
+# 3.1:
+#   * Use colors to format the help text.
+# 3.0:
+#   * Switch to evaluated expressions for sorting.
+#   * Add `/autosort debug` command.
+#   * Add ${info:autosort_replace,from,to,text} to replace substrings in sort rules.
+#   * Add ${info:autosort_order,value,first,second,third} to ease writing sort rules.
+#   * Make tab completion context aware.
+# 2.8:
+#   * Fix compatibility with python 3 regarding unicode handling.
+# 2.7:
+#   * Fix sorting of buffers with spaces in their name.
+# 2.6:
+#   * Ignore case in rules when doing case insensitive sorting.
+# 2.5:
+#   * Fix handling unicode buffer names.
+#   * Add hint to set irc.look.server_buffer to independent and buffers.look.indenting to on.
+# 2.4:
+#   * Make script python3 compatible.
+# 2.3:
+#   * Fix sorting items without score last (regressed in 2.2).
+# 2.2:
+#   * Add configuration option for signals that trigger a sort.
+#   * Add command to manually trigger a sort (/autosort sort).
+#   * Add replacement patterns to apply before sorting.
+# 2.1:
+#   * Fix some minor style issues.
+# 2.0:
+#   * Allow for custom sort rules.
+#
+
+
+import json
+import math
+import re
+import sys
+import time
+import weechat
+
+SCRIPT_NAME     = 'autosort'
+SCRIPT_AUTHOR   = 'Maarten de Vries <maarten@de-vri.es>'
+SCRIPT_VERSION  = '3.9'
+SCRIPT_LICENSE  = 'GPL3'
+SCRIPT_DESC     = 'Flexible automatic (or manual) buffer sorting based on eval expressions.'
+
+
+config             = None
+hooks              = []
+signal_delay_timer = None
+sort_limit_timer   = None
+sort_queued        = False
+
+
+# Make sure that unicode, bytes and str are always available in python2 and 3.
+# For python 2, str == bytes
+# For python 3, str == unicode
+if sys.version_info[0] >= 3:
+	unicode = str
+
+def ensure_str(input):
+	'''
+	Make sure the given type if the correct string type for the current python version.
+	That means bytes for python2 and unicode for python3.
+	'''
+	if not isinstance(input, str):
+		if isinstance(input, bytes):
+			return input.encode('utf-8')
+		if isinstance(input, unicode):
+			return input.decode('utf-8')
+	return input
+
+
+if hasattr(time, 'perf_counter'):
+	perf_counter = time.perf_counter
+else:
+	perf_counter = time.clock
+
+def casefold(string):
+	if hasattr(string, 'casefold'): return string.casefold()
+	# Fall back to lowercasing for python2.
+	return string.lower()
+
+def list_swap(values, a, b):
+	values[a], values[b] = values[b], values[a]
+
+def list_move(values, old_index, new_index):
+	values.insert(new_index, values.pop(old_index))
+
+def list_find(collection, value):
+	for i, elem in enumerate(collection):
+		if elem == value: return i
+	return None
+
+class HumanReadableError(Exception):
+	pass
+
+def parse_int(arg, arg_name = 'argument'):
+	''' Parse an integer and provide a more human readable error. '''
+	arg = arg.strip()
+	try:
+		return int(arg)
+	except ValueError:
+		raise HumanReadableError('Invalid {0}: expected integer, got "{1}".'.format(arg_name, arg))
+
+def decode_rules(blob):
+	parsed = json.loads(blob)
+	if not isinstance(parsed, list):
+		log('Malformed rules, expected a JSON encoded list of strings, but got a {0}. No rules have been loaded. Please fix the setting manually.'.format(type(parsed)))
+		return []
+
+	for i, entry in enumerate(parsed):
+		if not isinstance(entry, (str, unicode)):
+			log('Rule #{0} is not a string but a {1}. No rules have been loaded. Please fix the setting manually.'.format(i, type(entry)))
+			return []
+
+	return parsed
+
+def decode_helpers(blob):
+	parsed = json.loads(blob)
+	if not isinstance(parsed, dict):
+		log('Malformed helpers, expected a JSON encoded dictionary but got a {0}. No helpers have been loaded. Please fix the setting manually.'.format(type(parsed)))
+		return {}
+
+	for key, value in parsed.items():
+		if not isinstance(value, (str, unicode)):
+			log('Helper "{0}" is not a string but a {1}. No helpers have been loaded. Please fix setting manually.'.format(key, type(value)))
+			return {}
+	return parsed
+
+class Config:
+	''' The autosort configuration. '''
+
+	default_rules = json.dumps([
+		'${core_first}',
+		'${info:autosort_order,${info:autosort_escape,${script_or_plugin}},core,*,irc,bitlbee,matrix,slack}',
+		'${script_or_plugin}',
+		'${irc_raw_first}',
+		'${server}',
+		'${info:autosort_order,${type},server,*,channel,private}',
+		'${hashless_name}',
+		'${buffer.full_name}',
+	])
+
+	default_helpers = json.dumps({
+		'core_first':       '${if:${buffer.full_name}!=core.weechat}',
+		'irc_raw_first':    '${if:${buffer.full_name}!=irc.irc_raw}',
+		'irc_raw_last':     '${if:${buffer.full_name}==irc.irc_raw}',
+		'hashless_name':    '${info:autosort_replace,#,,${info:autosort_escape,${buffer.name}}}',
+		'script_or_plugin': '${if:${script_name}?${script_name}:${plugin}}',
+	})
+
+	default_signal_delay = 5
+	default_sort_limit   = 100
+
+	default_signals = 'buffer_opened buffer_merged buffer_unmerged buffer_renamed'
+
+	def __init__(self, filename):
+		''' Initialize the configuration. '''
+
+		self.filename         = filename
+		self.config_file      = weechat.config_new(self.filename, '', '')
+		self.sorting_section  = None
+		self.v3_section       = None
+
+		self.case_sensitive   = False
+		self.rules            = []
+		self.helpers          = {}
+		self.signals          = []
+		self.signal_delay     = Config.default_signal_delay,
+		self.sort_limit       = Config.default_sort_limit,
+		self.sort_on_config   = True
+		self.debug_log        = False
+
+		self.__case_sensitive = None
+		self.__rules          = None
+		self.__helpers        = None
+		self.__signals        = None
+		self.__signal_delay   = None
+		self.__sort_limit     = None
+		self.__sort_on_config = None
+		self.__debug_log      = None
+
+		if not self.config_file:
+			log('Failed to initialize configuration file "{0}".'.format(self.filename))
+			return
+
+		self.sorting_section = weechat.config_new_section(self.config_file, 'sorting', False, False, '', '', '', '', '', '', '', '', '', '')
+		self.v3_section      = weechat.config_new_section(self.config_file, 'v3',      False, False, '', '', '', '', '', '', '', '', '', '')
+
+		if not self.sorting_section:
+			log('Failed to initialize section "sorting" of configuration file.')
+			weechat.config_free(self.config_file)
+			return
+
+		self.__case_sensitive = weechat.config_new_option(
+			self.config_file, self.sorting_section,
+			'case_sensitive', 'boolean',
+			'If this option is on, sorting is case sensitive.',
+			'', 0, 0, 'off', 'off', 0,
+			'', '', '', '', '', ''
+		)
+
+		weechat.config_new_option(
+			self.config_file, self.sorting_section,
+			'rules', 'string',
+			'Sort rules used by autosort v2.x and below. Not used by autosort anymore.',
+			'', 0, 0, '', '', 0,
+			'', '', '', '', '', ''
+		)
+
+		weechat.config_new_option(
+			self.config_file, self.sorting_section,
+			'replacements', 'string',
+			'Replacement patterns used by autosort v2.x and below. Not used by autosort anymore.',
+			'', 0, 0, '', '', 0,
+			'', '', '', '', '', ''
+		)
+
+		self.__rules = weechat.config_new_option(
+			self.config_file, self.v3_section,
+			'rules', 'string',
+			'An ordered list of sorting rules encoded as JSON. See /help autosort for commands to manipulate these rules.',
+			'', 0, 0, Config.default_rules, Config.default_rules, 0,
+			'', '', '', '', '', ''
+		)
+
+		self.__helpers = weechat.config_new_option(
+			self.config_file, self.v3_section,
+			'helpers', 'string',
+			'A dictionary helper variables to use in the sorting rules, encoded as JSON. See /help autosort for commands to manipulate these helpers.',
+			'', 0, 0, Config.default_helpers, Config.default_helpers, 0,
+			'', '', '', '', '', ''
+		)
+
+		self.__signals = weechat.config_new_option(
+			self.config_file, self.sorting_section,
+			'signals', 'string',
+			'A space separated list of signals that will cause autosort to resort your buffer list.',
+			'', 0, 0, Config.default_signals, Config.default_signals, 0,
+			'', '', '', '', '', ''
+		)
+
+		self.__signal_delay = weechat.config_new_option(
+			self.config_file, self.sorting_section,
+			'signal_delay', 'integer',
+			'Delay in milliseconds to wait after a signal before sorting the buffer list. This prevents triggering many times if multiple signals arrive in a short time. It can also be needed to wait for buffer localvars to be available.',
+			'', 0, 1000, str(Config.default_signal_delay), str(Config.default_signal_delay), 0,
+			'', '', '', '', '', ''
+		)
+
+		self.__sort_limit = weechat.config_new_option(
+			self.config_file, self.sorting_section,
+			'sort_limit', 'integer',
+			'Minimum delay in milliseconds to wait after sorting before signals can trigger a sort again. This is effectively a rate limit on sorting. Keeping signal_delay low while setting this higher can reduce excessive sorting without a long initial delay.',
+			'', 0, 1000, str(Config.default_sort_limit), str(Config.default_sort_limit), 0,
+			'', '', '', '', '', ''
+		)
+
+		self.__sort_on_config = weechat.config_new_option(
+			self.config_file, self.sorting_section,
+			'sort_on_config_change', 'boolean',
+			'Decides if the buffer list should be sorted when autosort configuration changes.',
+			'', 0, 0, 'on', 'on', 0,
+			'', '', '', '', '', ''
+		)
+
+		self.__debug_log = weechat.config_new_option(
+			self.config_file, self.sorting_section,
+			'debug_log', 'boolean',
+			'If enabled, print more debug messages. Not recommended for normal usage.',
+			'', 0, 0, 'off', 'off', 0,
+			'', '', '', '', '', ''
+		)
+
+		if weechat.config_read(self.config_file) != weechat.WEECHAT_RC_OK:
+			log('Failed to load configuration file.')
+
+		if weechat.config_write(self.config_file) != weechat.WEECHAT_RC_OK:
+			log('Failed to write configuration file.')
+
+		self.reload()
+
+	def reload(self):
+		''' Load configuration variables. '''
+
+		self.case_sensitive = weechat.config_boolean(self.__case_sensitive)
+
+		rules_blob    = weechat.config_string(self.__rules)
+		helpers_blob  = weechat.config_string(self.__helpers)
+		signals_blob  = weechat.config_string(self.__signals)
+
+		self.rules          = decode_rules(rules_blob)
+		self.helpers        = decode_helpers(helpers_blob)
+		self.signals        = signals_blob.split()
+		self.signal_delay   = weechat.config_integer(self.__signal_delay)
+		self.sort_limit     = weechat.config_integer(self.__sort_limit)
+		self.sort_on_config = weechat.config_boolean(self.__sort_on_config)
+		self.debug_log      = weechat.config_boolean(self.__debug_log)
+
+	def save_rules(self, run_callback = True):
+		''' Save the current rules to the configuration. '''
+		weechat.config_option_set(self.__rules, json.dumps(self.rules), run_callback)
+
+	def save_helpers(self, run_callback = True):
+		''' Save the current helpers to the configuration. '''
+		weechat.config_option_set(self.__helpers, json.dumps(self.helpers), run_callback)
+
+
+def pad(sequence, length, padding = None):
+	''' Pad a list until is has a certain length. '''
+	return sequence + [padding] * max(0, (length - len(sequence)))
+
+def log(message, buffer = 'NULL'):
+	weechat.prnt(buffer, 'autosort: {0}'.format(message))
+
+def debug(message, buffer = 'NULL'):
+	if config.debug_log:
+		weechat.prnt(buffer, 'autosort: debug: {0}'.format(message))
+
+def get_buffers():
+	''' Get a list of all the buffers in weechat. '''
+	hdata  = weechat.hdata_get('buffer')
+	buffer = weechat.hdata_get_list(hdata, "gui_buffers");
+
+	result = []
+	while buffer:
+		number = weechat.hdata_integer(hdata, buffer, 'number')
+		result.append((number, buffer))
+		buffer = weechat.hdata_pointer(hdata, buffer, 'next_buffer')
+	return hdata, result
+
+class MergedBuffers(list):
+	""" A list of merged buffers, possibly of size 1. """
+	def __init__(self, number):
+		super(MergedBuffers, self).__init__()
+		self.number = number
+
+def merge_buffer_list(buffers):
+	'''
+	Group merged buffers together.
+	The output is a list of MergedBuffers.
+	'''
+	if not buffers: return []
+	result = {}
+	for number, buffer in buffers:
+		if number not in result: result[number] = MergedBuffers(number)
+		result[number].append(buffer)
+	return result.values()
+
+def sort_buffers(hdata, buffers, rules, helpers, case_sensitive):
+	for merged in buffers:
+		for buffer in merged:
+			name = weechat.hdata_string(hdata, buffer, 'name')
+
+	return sorted(buffers, key=merged_sort_key(rules, helpers, case_sensitive))
+
+def buffer_sort_key(rules, helpers, case_sensitive):
+	''' Create a sort key function for a list of lists of merged buffers. '''
+	def key(buffer):
+		extra_vars = {}
+		for helper_name, helper in sorted(helpers.items()):
+			expanded = weechat.string_eval_expression(helper, {"buffer": buffer}, {}, {})
+			extra_vars[helper_name] = expanded if case_sensitive else casefold(expanded)
+		result = []
+		for rule in rules:
+			expanded = weechat.string_eval_expression(rule, {"buffer": buffer}, extra_vars, {})
+			result.append(expanded if case_sensitive else casefold(expanded))
+		return result
+
+	return key
+
+def merged_sort_key(rules, helpers, case_sensitive):
+	buffer_key = buffer_sort_key(rules, helpers, case_sensitive)
+	def key(merged):
+		best = None
+		for buffer in merged:
+			this = buffer_key(buffer)
+			if best is None or this < best: best = this
+		return best
+	return key
+
+def apply_buffer_order(buffers):
+	''' Sort the buffers in weechat according to the given order. '''
+	for i, buffer in enumerate(buffers):
+		weechat.buffer_set(buffer[0], "number", str(i + 1))
+
+def split_args(args, expected, optional = 0):
+	''' Split an argument string in the desired number of arguments. '''
+	split = args.split(' ', expected - 1)
+	if (len(split) < expected):
+		raise HumanReadableError('Expected at least {0} arguments, got {1}.'.format(expected, len(split)))
+	return split[:-1] + pad(split[-1].split(' ', optional), optional + 1, '')
+
+def do_sort(verbose = False):
+	start = perf_counter()
+
+	hdata, buffers = get_buffers()
+	buffers = merge_buffer_list(buffers)
+	buffers = sort_buffers(hdata, buffers, config.rules, config.helpers, config.case_sensitive)
+	apply_buffer_order(buffers)
+
+	elapsed = perf_counter() - start
+	if verbose:
+		log("Finished sorting buffers in {0:.4f} seconds.".format(elapsed))
+	else:
+		debug("Finished sorting buffers in {0:.4f} seconds.".format(elapsed))
+
+def command_sort(buffer, command, args):
+	''' Sort the buffers and print a confirmation. '''
+	do_sort(True)
+	return weechat.WEECHAT_RC_OK
+
+def command_debug(buffer, command, args):
+	hdata, buffers = get_buffers()
+	buffers = merge_buffer_list(buffers)
+
+	# Show evaluation results.
+	log('Individual evaluation results:')
+	start = perf_counter()
+	key = buffer_sort_key(config.rules, config.helpers, config.case_sensitive)
+	results = []
+	for merged in buffers:
+		for buffer in merged:
+			fullname = weechat.hdata_string(hdata, buffer, 'full_name')
+			results.append((fullname, key(buffer)))
+	elapsed = perf_counter() - start
+
+	for fullname, result in results:
+		fullname = ensure_str(fullname)
+		result = [ensure_str(x) for x in result]
+		log('{0}: {1}'.format(fullname, result))
+	log('Computing evaluation results took {0:.4f} seconds.'.format(elapsed))
+
+	return weechat.WEECHAT_RC_OK
+
+def command_rule_list(buffer, command, args):
+	''' Show the list of sorting rules. '''
+	output = 'Sorting rules:\n'
+	for i, rule in enumerate(config.rules):
+		output += '    {0}: {1}\n'.format(i, rule)
+	if not len(config.rules):
+		output += '    No sorting rules configured.\n'
+	log(output )
+
+	return weechat.WEECHAT_RC_OK
+
+
+def command_rule_add(buffer, command, args):
+	''' Add a rule to the rule list. '''
+	config.rules.append(args)
+	config.save_rules()
+	command_rule_list(buffer, command, '')
+
+	return weechat.WEECHAT_RC_OK
+
+
+def command_rule_insert(buffer, command, args):
+	''' Insert a rule at the desired position in the rule list. '''
+	index, rule = split_args(args, 2)
+	index = parse_int(index, 'index')
+
+	config.rules.insert(index, rule)
+	config.save_rules()
+	command_rule_list(buffer, command, '')
+	return weechat.WEECHAT_RC_OK
+
+
+def command_rule_update(buffer, command, args):
+	''' Update a rule in the rule list. '''
+	index, rule = split_args(args, 2)
+	index = parse_int(index, 'index')
+
+	config.rules[index] = rule
+	config.save_rules()
+	command_rule_list(buffer, command, '')
+	return weechat.WEECHAT_RC_OK
+
+
+def command_rule_delete(buffer, command, args):
+	''' Delete a rule from the rule list. '''
+	index = args.strip()
+	index = parse_int(index, 'index')
+
+	config.rules.pop(index)
+	config.save_rules()
+	command_rule_list(buffer, command, '')
+	return weechat.WEECHAT_RC_OK
+
+
+def command_rule_move(buffer, command, args):
+	''' Move a rule to a new position. '''
+	index_a, index_b = split_args(args, 2)
+	index_a = parse_int(index_a, 'index')
+	index_b = parse_int(index_b, 'index')
+
+	list_move(config.rules, index_a, index_b)
+	config.save_rules()
+	command_rule_list(buffer, command, '')
+	return weechat.WEECHAT_RC_OK
+
+
+def command_rule_swap(buffer, command, args):
+	''' Swap two rules. '''
+	index_a, index_b = split_args(args, 2)
+	index_a = parse_int(index_a, 'index')
+	index_b = parse_int(index_b, 'index')
+
+	list_swap(config.rules, index_a, index_b)
+	config.save_rules()
+	command_rule_list(buffer, command, '')
+	return weechat.WEECHAT_RC_OK
+
+
+def command_helper_list(buffer, command, args):
+	''' Show the list of helpers. '''
+	output = 'Helper variables:\n'
+
+	width = max(map(lambda x: len(x) if len(x) <= 30 else 0, config.helpers.keys()))
+
+	for name, expression in sorted(config.helpers.items()):
+		output += '    {0:>{width}}: {1}\n'.format(name, expression, width=width)
+	if not len(config.helpers):
+		output += '    No helper variables configured.'
+	log(output)
+
+	return weechat.WEECHAT_RC_OK
+
+
+def command_helper_set(buffer, command, args):
+	''' Add/update a helper to the helper list. '''
+	name, expression = split_args(args, 2)
+
+	config.helpers[name] = expression
+	config.save_helpers()
+	command_helper_list(buffer, command, '')
+
+	return weechat.WEECHAT_RC_OK
+
+def command_helper_delete(buffer, command, args):
+	''' Delete a helper from the helper list. '''
+	name = args.strip()
+
+	del config.helpers[name]
+	config.save_helpers()
+	command_helper_list(buffer, command, '')
+	return weechat.WEECHAT_RC_OK
+
+
+def command_helper_rename(buffer, command, args):
+	''' Rename a helper to a new position. '''
+	old_name, new_name = split_args(args, 2)
+
+	try:
+		config.helpers[new_name] = config.helpers[old_name]
+		del config.helpers[old_name]
+	except KeyError:
+		raise HumanReadableError('No such helper: {0}'.format(old_name))
+	config.save_helpers()
+	command_helper_list(buffer, command, '')
+	return weechat.WEECHAT_RC_OK
+
+
+def command_helper_swap(buffer, command, args):
+	''' Swap two helpers. '''
+	a, b = split_args(args, 2)
+	try:
+		config.helpers[b], config.helpers[a] = config.helpers[a], config.helpers[b]
+	except KeyError as e:
+		raise HumanReadableError('No such helper: {0}'.format(e.args[0]))
+
+	config.helpers.swap(index_a, index_b)
+	config.save_helpers()
+	command_helper_list(buffer, command, '')
+	return weechat.WEECHAT_RC_OK
+
+def call_command(buffer, command, args, subcommands):
+	''' Call a subcommand from a dictionary. '''
+	subcommand, tail = pad(args.split(' ', 1), 2, '')
+	subcommand = subcommand.strip()
+	if (subcommand == ''):
+		child   = subcommands.get(' ')
+	else:
+		command = command + [subcommand]
+		child   = subcommands.get(subcommand)
+
+	if isinstance(child, dict):
+		return call_command(buffer, command, tail, child)
+	elif callable(child):
+		return child(buffer, command, tail)
+
+	log('{0}: command not found'.format(' '.join(command)))
+	return weechat.WEECHAT_RC_ERROR
+
+def on_signal(data, signal, signal_data):
+	global signal_delay_timer
+	global sort_queued
+
+	# If the sort limit timeout is started, we're in the hold-off time after sorting, just queue a sort.
+	if sort_limit_timer is not None:
+		if sort_queued:
+			debug('Signal {0} ignored, sort limit timeout is active and sort is already queued.'.format(signal))
+		else:
+			debug('Signal {0} received but sort limit timeout is active, sort is now queued.'.format(signal))
+		sort_queued = True
+		return weechat.WEECHAT_RC_OK
+
+	# If the signal delay timeout is started, a signal was recently received, so ignore this signal.
+	if signal_delay_timer is not None:
+		debug('Signal {0} ignored, signal delay timeout active.'.format(signal))
+		return weechat.WEECHAT_RC_OK
+
+	# Otherwise, start the signal delay timeout.
+	debug('Signal {0} received, starting signal delay timeout of {1} ms.'.format(signal, config.signal_delay))
+	weechat.hook_timer(config.signal_delay, 0, 1, "on_signal_delay_timeout", "")
+	return weechat.WEECHAT_RC_OK
+
+def on_signal_delay_timeout(pointer, remaining_calls):
+	""" Called when the signal_delay_timer triggers. """
+	global signal_delay_timer
+	global sort_limit_timer
+	global sort_queued
+
+	signal_delay_timer = None
+
+	# If the sort limit timeout was started, we're still in the no-sort period, so just queue a sort.
+	if sort_limit_timer is not None:
+		debug('Signal delay timeout expired, but sort limit timeout is active, sort is now queued.')
+		sort_queued = True
+		return weechat.WEECHAT_RC_OK
+
+	# Time to sort!
+	debug('Signal delay timeout expired, starting sort.')
+	do_sort()
+
+	# Start the sort limit timeout if not disabled.
+	if config.sort_limit > 0:
+		debug('Starting sort limit timeout of {0} ms.'.format(config.sort_limit))
+		sort_limit_timer = weechat.hook_timer(config.sort_limit, 0, 1, "on_sort_limit_timeout", "")
+
+	return weechat.WEECHAT_RC_OK
+
+def on_sort_limit_timeout(pointer, remainin_calls):
+	""" Called when de sort_limit_timer triggers. """
+	global sort_limit_timer
+	global sort_queued
+
+	# If no signal was received during the timeout, we're done.
+	if not sort_queued:
+		debug('Sort limit timeout expired without receiving a signal.')
+		sort_limit_timer = None
+		return weechat.WEECHAT_RC_OK
+
+	# Otherwise it's time to sort.
+	debug('Signal received during sort limit timeout, starting queued sort.')
+	do_sort()
+	sort_queued = False
+
+	# Start the sort limit timeout again if not disabled.
+	if config.sort_limit > 0:
+		debug('Starting sort limit timeout of {0} ms.'.format(config.sort_limit))
+		sort_limit_timer = weechat.hook_timer(config.sort_limit, 0, 1, "on_sort_limit_timeout", "")
+
+	return weechat.WEECHAT_RC_OK
+
+
+def apply_config():
+	# Unhook all signals and hook the new ones.
+	for hook in hooks:
+		weechat.unhook(hook)
+	for signal in config.signals:
+		hooks.append(weechat.hook_signal(signal, 'on_signal', ''))
+
+	if config.sort_on_config:
+		debug('Sorting because configuration changed.')
+		do_sort()
+
+def on_config_changed(*args, **kwargs):
+	''' Called whenever the configuration changes. '''
+	config.reload()
+	apply_config()
+
+	return weechat.WEECHAT_RC_OK
+
+def parse_arg(args):
+	if not args: return '', None
+
+	result  = ''
+	escaped = False
+	for i, c in enumerate(args):
+		if not escaped:
+			if c == '\\':
+				escaped = True
+				continue
+			elif c == ',':
+				return result, args[i+1:]
+		result  += c
+		escaped  = False
+	return result, None
+
+def parse_args(args, max = None):
+	result = []
+	i = 0
+	while max is None or i < max:
+		i += 1
+		arg, args = parse_arg(args)
+		if arg is None: break
+		result.append(arg)
+		if args is None: break
+	return result, args
+
+def on_info_escape(pointer, name, arguments):
+	result = ''
+	for c in arguments:
+		if c == '\\':
+			result += '\\\\'
+		elif c == ',':
+			result += '\\,'
+		else:
+			result +=c
+	return result
+
+def on_info_replace(pointer, name, arguments):
+	arguments, rest = parse_args(arguments, 3)
+	if rest or len(arguments) < 3:
+		log('usage: ${{info:{0},old,new,text}}'.format(name))
+		return ''
+	old, new, text = arguments
+
+	return text.replace(old, new)
+
+def on_info_order(pointer, name, arguments):
+	arguments, rest = parse_args(arguments)
+	if len(arguments) < 1:
+		log('usage: ${{info:{0},value,first,second,third,...}}'.format(name))
+		return ''
+
+	value = arguments[0]
+	keys  = arguments[1:]
+	if not keys: return '0'
+
+	# Find the value in the keys (or '*' if we can't find it)
+	result = list_find(keys, value)
+	if result is None: result = list_find(keys, '*')
+	if result is None: result = len(keys)
+
+	# Pad result with leading zero to make sure string sorting works.
+	width = int(math.log10(len(keys))) + 1
+	return '{0:0{1}}'.format(result, width)
+
+
+def on_autosort_command(data, buffer, args):
+	''' Called when the autosort command is invoked. '''
+	try:
+		return call_command(buffer, ['/autosort'], args, {
+			' ':      command_sort,
+			'sort':   command_sort,
+			'debug':  command_debug,
+
+			'rules': {
+				' ':         command_rule_list,
+				'list':      command_rule_list,
+				'add':       command_rule_add,
+				'insert':    command_rule_insert,
+				'update':    command_rule_update,
+				'delete':    command_rule_delete,
+				'move':      command_rule_move,
+				'swap':      command_rule_swap,
+			},
+			'helpers': {
+				' ':      command_helper_list,
+				'list':   command_helper_list,
+				'set':    command_helper_set,
+				'delete': command_helper_delete,
+				'rename': command_helper_rename,
+				'swap':   command_helper_swap,
+			},
+		})
+	except HumanReadableError as e:
+		log(e)
+		return weechat.WEECHAT_RC_ERROR
+
+def add_completions(completion, words):
+	for word in words:
+		weechat.hook_completion_list_add(completion, word, 0, weechat.WEECHAT_LIST_POS_END)
+
+def autosort_complete_rules(words, completion):
+	if len(words) == 0:
+		add_completions(completion, ['add', 'delete', 'insert', 'list', 'move', 'swap', 'update'])
+	if len(words) == 1 and words[0] in ('delete', 'insert', 'move', 'swap', 'update'):
+		add_completions(completion, map(str, range(len(config.rules))))
+	if len(words) == 2 and words[0] in ('move', 'swap'):
+		add_completions(completion, map(str, range(len(config.rules))))
+	if len(words) == 2 and words[0] in ('update'):
+		try:
+			add_completions(completion, [config.rules[int(words[1])]])
+		except KeyError: pass
+		except ValueError: pass
+	else:
+		add_completions(completion, [''])
+	return weechat.WEECHAT_RC_OK
+
+def autosort_complete_helpers(words, completion):
+	if len(words) == 0:
+		add_completions(completion, ['delete', 'list', 'rename', 'set', 'swap'])
+	elif len(words) == 1 and words[0] in ('delete', 'rename', 'set', 'swap'):
+		add_completions(completion, sorted(config.helpers.keys()))
+	elif len(words) == 2 and words[0] == 'swap':
+		add_completions(completion, sorted(config.helpers.keys()))
+	elif len(words) == 2 and words[0] == 'rename':
+		add_completions(completion, sorted(config.helpers.keys()))
+	elif len(words) == 2 and words[0] == 'set':
+		try:
+			add_completions(completion, [config.helpers[words[1]]])
+		except KeyError: pass
+	return weechat.WEECHAT_RC_OK
+
+def on_autosort_complete(data, name, buffer, completion):
+	cmdline = weechat.buffer_get_string(buffer, "input")
+	cursor  = weechat.buffer_get_integer(buffer, "input_pos")
+	prefix  = cmdline[:cursor]
+	words   = prefix.split()[1:]
+
+	# If the current word isn't finished yet,
+	# ignore it for coming up with completion suggestions.
+	if prefix[-1] != ' ': words = words[:-1]
+
+	if len(words) == 0:
+		add_completions(completion, ['debug', 'helpers', 'rules', 'sort'])
+	elif words[0] == 'rules':
+		return autosort_complete_rules(words[1:], completion)
+	elif words[0] == 'helpers':
+		return autosort_complete_helpers(words[1:], completion)
+	return weechat.WEECHAT_RC_OK
+
+command_description = r'''{*white}# General commands{reset}
+
+{*white}/autosort {brown}sort{reset}
+Manually trigger the buffer sorting.
+
+{*white}/autosort {brown}debug{reset}
+Show the evaluation results of the sort rules for each buffer.
+
+
+{*white}# Sorting rule commands{reset}
+
+{*white}/autosort{brown} rules list{reset}
+Print the list of sort rules.
+
+{*white}/autosort {brown}rules add {cyan}<expression>{reset}
+Add a new rule at the end of the list.
+
+{*white}/autosort {brown}rules insert {cyan}<index> <expression>{reset}
+Insert a new rule at the given index in the list.
+
+{*white}/autosort {brown}rules update {cyan}<index> <expression>{reset}
+Update a rule in the list with a new expression.
+
+{*white}/autosort {brown}rules delete {cyan}<index>
+Delete a rule from the list.
+
+{*white}/autosort {brown}rules move {cyan}<index_from> <index_to>{reset}
+Move a rule from one position in the list to another.
+
+{*white}/autosort {brown}rules swap {cyan}<index_a> <index_b>{reset}
+Swap two rules in the list
+
+
+{*white}# Helper variable commands{reset}
+
+{*white}/autosort {brown}helpers list
+Print the list of helper variables.
+
+{*white}/autosort {brown}helpers set {cyan}<name> <expression>
+Add or update a helper variable with the given name.
+
+{*white}/autosort {brown}helpers delete {cyan}<name>
+Delete a helper variable.
+
+{*white}/autosort {brown}helpers rename {cyan}<old_name> <new_name>
+Rename a helper variable.
+
+{*white}/autosort {brown}helpers swap {cyan}<name_a> <name_b>
+Swap the expressions of two helper variables in the list.
+
+
+{*white}# Info hooks{reset}
+Autosort comes with a number of info hooks to add some extra functionality to regular weechat eval strings.
+Info hooks can be used in eval strings in the form of {cyan}${{info:some_hook,arguments}}{reset}.
+
+Commas and backslashes in arguments to autosort info hooks (except for {cyan}${{info:autosort_escape}}{reset}) must be escaped with a backslash.
+
+{*white}${{info:{brown}autosort_replace{white},{cyan}pattern{white},{cyan}replacement{white},{cyan}source{white}}}{reset}
+Replace all occurrences of {cyan}pattern{reset} with {cyan}replacement{reset} in the string {cyan}source{reset}.
+Can be used to ignore certain strings when sorting by replacing them with an empty string.
+
+For example: {cyan}${{info:autosort_replace,cat,dog,the dog is meowing}}{reset} expands to "the cat is meowing".
+
+{*white}${{info:{brown}autosort_order{white},{cyan}value{white},{cyan}option0{white},{cyan}option1{white},{cyan}option2{white},{cyan}...{white}}}
+Generate a zero-padded number that corresponds to the index of {cyan}value{reset} in the list of options.
+If one of the options is the special value {brown}*{reset}, then any value not explicitly mentioned will be sorted at that position.
+Otherwise, any value that does not match an option is assigned the highest number available.
+Can be used to easily sort buffers based on a manual sequence.
+
+For example: {cyan}${{info:autosort_order,${{server}},freenode,oftc,efnet}}{reset} will sort freenode before oftc, followed by efnet and then any remaining servers.
+Alternatively, {cyan}${{info:autosort_order,${{server}},freenode,oftc,*,efnet}}{reset} will sort any unlisted servers after freenode and oftc, but before efnet.
+
+{*white}${{info:{brown}autosort_escape{white},{cyan}text{white}}}{reset}
+Escape commas and backslashes in {cyan}text{reset} by prepending them with a backslash.
+This is mainly useful to pass arbitrary eval strings as arguments to other autosort info hooks.
+Otherwise, an eval string that expands to something with a comma would be interpreted as multiple arguments.
+
+For example, it can be used to safely pass buffer names to {cyan}${{info:autosort_replace}}{reset} like so:
+{cyan}${{info:autosort_replace,##,#,${{info:autosort_escape,${{buffer.name}}}}}}{reset}.
+
+
+{*white}# Description
+Autosort is a weechat script to automatically keep your buffers sorted. The sort
+order can be customized by defining your own sort rules, but the default should
+be sane enough for most people. It can also group IRC channel/private buffers
+under their server buffer if you like.
+
+Autosort uses a stable sorting algorithm, meaning that you can manually move buffers
+to change their relative order, if they sort equal with your rule set.
+
+{*white}# Sort rules{reset}
+Autosort evaluates a list of eval expressions (see {*default}/help eval{reset}) and sorts the
+buffers based on evaluated result. Earlier rules will be considered first. Only
+if earlier rules produced identical results is the result of the next rule
+considered for sorting purposes.
+
+You can debug your sort rules with the `{*default}/autosort debug{reset}` command, which will
+print the evaluation results of each rule for each buffer.
+
+{*brown}NOTE:{reset} The sort rules for version 3 are not compatible with version 2 or vice
+versa. You will have to manually port your old rules to version 3 if you have any.
+
+{*white}# Helper variables{reset}
+You may define helper variables for the main sort rules to keep your rules
+readable. They can be used in the main sort rules as variables. For example,
+a helper variable named `{cyan}foo{reset}` can be accessed in a main rule with the
+string `{cyan}${{foo}}{reset}`.
+
+{*white}# Automatic or manual sorting{reset}
+By default, autosort will automatically sort your buffer list whenever a buffer
+is opened, merged, unmerged or renamed. This should keep your buffers sorted in
+almost all situations. However, you may wish to change the list of signals that
+cause your buffer list to be sorted. Simply edit the `{cyan}autosort.sorting.signals{reset}`
+option to add or remove any signal you like.
+
+If you remove all signals you can still sort your buffers manually with the
+`{*default}/autosort sort{reset}` command. To prevent all automatic sorting, the option
+`{cyan}autosort.sorting.sort_on_config_change{reset}` should also be disabled.
+
+{*white}# Recommended settings
+For the best visual effect, consider setting the following options:
+  {*white}/set {cyan}irc.look.server_buffer{reset} {brown}independent{reset}
+
+This setting allows server buffers to be sorted independently, which is
+needed to create a hierarchical tree view of the server and channel buffers.
+
+If you are using the {*default}buflist{reset} plugin you can (ab)use Unicode to draw a tree
+structure with the following setting (modify to suit your need):
+  {*white}/set {cyan}buflist.format.indent {brown}"${{color:237}}${{if:${{buffer.next_buffer.local_variables.type}}=~^(channel|private)$?├─:└─}}"{reset}
+'''
+
+command_completion = '%(plugin_autosort) %(plugin_autosort) %(plugin_autosort) %(plugin_autosort) %(plugin_autosort)'
+
+info_replace_description = (
+	'Replace all occurrences of `pattern` with `replacement` in the string `source`. '
+	'Can be used to ignore certain strings when sorting by replacing them with an empty string. '
+	'See /help autosort for examples.'
+)
+info_replace_arguments = 'pattern,replacement,source'
+
+info_order_description = (
+	'Generate a zero-padded number that corresponds to the index of `value` in the list of options. '
+	'If one of the options is the special value `*`, then any value not explicitly mentioned will be sorted at that position. '
+	'Otherwise, any value that does not match an option is assigned the highest number available. '
+	'Can be used to easily sort buffers based on a manual sequence. '
+	'See /help autosort for examples.'
+)
+info_order_arguments = 'value,first,second,third,...'
+
+info_escape_description = (
+	'Escape commas and backslashes in `text` by prepending them with a backslash. '
+	'This is mainly useful to pass arbitrary eval strings as arguments to other autosort info hooks. '
+	'Otherwise, an eval string that expands to something with a comma would be interpreted as multiple arguments.'
+	'See /help autosort for examples.'
+)
+info_escape_arguments = 'text'
+
+
+if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", ""):
+	config = Config('autosort')
+
+	colors = {
+		'default':  weechat.color('default'),
+		'reset':    weechat.color('reset'),
+		'black':    weechat.color('black'),
+		'red':      weechat.color('red'),
+		'green':    weechat.color('green'),
+		'brown':    weechat.color('brown'),
+		'yellow':   weechat.color('yellow'),
+		'blue':     weechat.color('blue'),
+		'magenta':  weechat.color('magenta'),
+		'cyan':     weechat.color('cyan'),
+		'white':    weechat.color('white'),
+		'*default': weechat.color('*default'),
+		'*black':   weechat.color('*black'),
+		'*red':     weechat.color('*red'),
+		'*green':   weechat.color('*green'),
+		'*brown':   weechat.color('*brown'),
+		'*yellow':  weechat.color('*yellow'),
+		'*blue':    weechat.color('*blue'),
+		'*magenta': weechat.color('*magenta'),
+		'*cyan':    weechat.color('*cyan'),
+		'*white':   weechat.color('*white'),
+	}
+
+	weechat.hook_config('autosort.*', 'on_config_changed',  '')
+	weechat.hook_completion('plugin_autosort', '', 'on_autosort_complete', '')
+	weechat.hook_command('autosort', command_description.format(**colors), '', '', command_completion, 'on_autosort_command', '')
+	weechat.hook_info('autosort_escape',  info_escape_description,  info_escape_arguments,  'on_info_escape', '')
+	weechat.hook_info('autosort_replace', info_replace_description, info_replace_arguments, 'on_info_replace', '')
+	weechat.hook_info('autosort_order',   info_order_description,   info_order_arguments,   'on_info_order',   '')
+
+	apply_config()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/python/colon_complete.py	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,69 @@
+SCRIPT_NAME='coloncomplete'
+SCRIPT_AUTHOR='Steve Losh <steve@stevelosh.com>'
+SCRIPT_VERSION='1.0'
+SCRIPT_LICENSE='MIT/X11'
+SCRIPT_DESC='Add a colon after nick completion when all the previous words in the input are also nicks.'
+
+EXTRA_NICKS = ['all', 'backend', 'clojerks', 'ops', 'support']
+
+import_ok=True
+
+try:
+    import weechat
+except ImportError:
+    print 'This script must be run under WeeChat'
+    print 'You can obtain a copy of WeeChat, for free, at http://www.weechat.org'
+    import_ok=False
+
+weechat_version=0
+
+def get_nicks(buffer, prefix=''):
+    channel = weechat.buffer_get_string(buffer, 'localvar_channel')
+    server = weechat.buffer_get_string(buffer, 'localvar_server')
+    prefix = prefix.lower()
+
+    matches = []
+
+    infolist = weechat.infolist_get('irc_nick', '', '%s,%s' % (server, channel))
+    while weechat.infolist_next(infolist):
+        nick = weechat.infolist_string(infolist, 'name')
+        if nick != 'localhost' and nick.lower().startswith(prefix):
+            matches.append(nick)
+    weechat.infolist_free(infolist)
+
+    for nick in EXTRA_NICKS:
+        if nick.lower().startswith(prefix):
+            matches.append(nick)
+
+    return matches
+
+def completer(data, buffer, command):
+    cb = weechat.current_buffer()
+    if command == "/input complete_next":
+        line = weechat.buffer_get_string(cb, "input")
+        words = line.split(' ')
+        prefix = words[-1]
+        if prefix and words and all([s.endswith(':') for s in words[:-1] if s]):
+            nicks = get_nicks(cb, prefix)
+            if len(nicks) == 1:
+                for _ in range(len(prefix)):
+                    weechat.command(buffer, "/input delete_previous_char")
+                weechat.command(buffer, "/input insert " + nicks[-1] + ":\\x20")
+            elif len(nicks) > 1:
+                l = min(len(nick) for nick in nicks)
+                for i in range(len(prefix), l):
+                    if len(set(nick[i] for nick in nicks)) > 1:
+                        break
+                    else:
+                        weechat.command(buffer, "/input insert " + nicks[0][i])
+
+                for nick in nicks:
+                    weechat.prnt(cb, "==> " + nick)
+                return weechat.WEECHAT_RC_OK_EAT
+
+    return weechat.WEECHAT_RC_OK
+
+if __name__ == "__main__" and import_ok:
+    if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", ""):
+        weechat_version = weechat.info_get("version_number", "") or 0
+        weechat.hook_command_run('/input complete*', 'completer', '')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/python/listbuffer.py	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,467 @@
+# -*- coding: utf-8 -*-
+#
+# ListBuffer, version 0.8.1 for WeeChat version 0.3
+# Latest development version: https://github.com/FiXato/listbuffer
+#
+#   Show /list results in a common buffer and interact with them.
+#
+#   This script allows you to easily join channels from the /list output.
+#   It will open a common buffer for the /list result, through which you
+#   browse with your cursor keys, and join with the meta-enter keys.
+#   Adjust sorting with meta->, meta-< and meta-/ keybindings.
+#
+## History:
+### 2011-09-08: FiXato:
+#
+# * version 0.1:  initial release.
+#     * added a common buffer for /list results
+#     * added highlighting for currently selected line
+#     * added /join support via enter key
+#     * added scroll_top and scroll_bottom support
+#
+# * version 0.2:  /list format bugfix
+#     * added support for /list results without modes
+#     * some servers don't send 321 (/list start). Taken into account.
+#
+# * version 0.3: Sorting support
+#     * Added some basic sorting support. Scroll through sort options
+#       with meta-> and meta-< (users, channel, topic, modes)
+#
+### 2011-09-19: FiXato
+#
+# * version 0.4:
+#     * Case-insensitive buffer lookup fix.
+#     * Removed default enter keybind
+#
+### 2011-12-28: troydm:
+#
+# * version 0.5: It's an upside-down-world
+#     * Added inverted sorting support provided by Dmitry "troydm" Geurkov
+#       Use meta-/ to switch between inverted and regular sorting.
+#
+### 2012-02-10: FiXato:
+#
+# * version 0.6: Stop shoving that buffer in my face!
+#     * The listbuffer should no longer pop up by itself when you load the script.
+#       It should only pop up now when you actually do a /list query.
+#
+# * version 0.7: .. but please pop it up in my current window when I ask for it
+#     * Added setting plugins.var.python.listbuffer.autofocus
+#       This will autofocus the listbuffer in the current window if another window isn't
+#       already showing it, and of course only when the user issues /list
+#
+### 2012-07-10: FiXato:
+#
+# * version 0.7.1: Forgetful bugfix
+#     * Made sure lb_curline global variable is defined
+#
+### 2013-03-19: FiXato:
+#
+# * version 0.8: Sorted out the sorting
+#     * Added automatically updating options for sorting:
+#       * plugins.var.python.listbuffer.sort_inverted
+#       * plugins.var.python.listbuffer.sort_order
+# * version 0.8.1: Pad it baby!
+#     * Channel modes are equally padded even when there are no channel modes.
+#     * Added padding options:
+#       * plugins.var.python.listbuffer.modes_min_width
+#       * plugins.var.python.listbuffer.channel_min_width
+#       * plugins.var.python.listbuffer.users_min_width
+#
+## Acknowledgements:
+# * Dmitry "troydm" Geurkov, for providing the inverse-sorting patch to the project.
+# * Sebastien "Flashcode" Helleu, for developing the kick-ass IRC client WeeChat
+#    and the iset.pl script which inspired me to this script.
+# * Nils "nils_2" Görs, for his contributions to iset.pl which served as
+#    example code.
+# * David "drubin" Rubin, for his urlgrab.py script, which also served
+#    as example code.
+# * ArZa, whose listsort.pl script helped me getting started with
+#    grabbing the /list results. Parts of his code have been shamelessly
+#    copied and ported to Python.
+# * Khaled Mardam-Bey, for making me yearn for similar /list support in
+#    WeeChat as mIRC already offered. :P
+# * mave_, for pointing out that sort orders weren't remembered.
+#
+## TODO:
+#   - Auto-scroll selected line upon window scroll.
+#   - Add option to hide already joined channels.
+#   - Improve sorting methods
+#   - Add auto-join support
+#   - Detect if channel is already in auto-join
+#   - Allow automatically switching to the listbuffer
+#   - Add support for ALIS (/squery alis LIST * -mix 100 (IRCNet)
+#   - Make colours configurable
+#   - Limit number of channels to parse
+#   - Add filter support a la iset
+#   - Allow selecting multiple channels
+#   - Add optional command redirection.
+#
+## Copyright (c) 2011,2012,2013 Filip H.F. "FiXato" Slagter,
+#   <FiXato [at] Gmail [dot] com>
+#   http://profile.fixato.org
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+SCRIPT_NAME    = "listbuffer"
+SCRIPT_AUTHOR  = "Filip H.F. 'FiXato' Slagter <fixato [at] gmail [dot] com>"
+SCRIPT_VERSION = "0.8.1"
+SCRIPT_LICENSE = "MIT"
+SCRIPT_DESC    = "A common buffer for /list output."
+SCRIPT_COMMAND = "listbuffer"
+
+import_ok = True
+
+try:
+  import weechat
+except ImportError:
+  print "This script must be run under WeeChat."
+  import_ok = False
+
+import re
+
+lb_settings = (
+  ("autofocus", "on", "Focus the listbuffer in the current window if it isn't already displayed by a window."),
+  ("sort_order", "users", "Last used sort order for the channel list."),
+  ("sort_inverted", "on", "Invert the sort order for the channel list."),
+  ("modes_min_width", "8", "The minimum width used for modes in the channel list. If a channel has less modes than this amount, the column will be padded with spaces."),
+  ("channel_min_width", "25", "The minimum width used for the channel name in the channel list. If a channelname is shorter than this amount, the column will be padded with spaces."),
+  ("users_min_width", "8", "The minimum width used for the usercount in the channel list. If the usercount has less digits than this amount, the column will be padded with spaces."),
+)
+lb_buffer = None
+lb_curline = 0
+lb_channels = []
+lb_network = None
+lb_list_started = False
+lb_current_sort = None
+lb_sort_inverted = False
+lb_sort_options = (
+  'channel',
+  'users',
+  'modes',
+  'topic',
+)
+
+#                              server numeric Nick Chan  Users     Modes    Topic
+lb_channel_list_expression = '(:\S+) (\d{3}) (\S+) (\S+) (\d+) :(\[(.*?)\] )?(.*)'
+
+# Create listbuffer.
+def lb_create_buffer():
+  global lb_buffer, lb_curline
+
+  if not lb_buffer:
+    lb_buffer = weechat.buffer_new("listbuffer", "lb_input_cb", \
+                "", "lb_close_cb", "")
+    lb_set_buffer_title()
+    # Sets notify to 0 as this buffer does not need to be in hotlist.
+    weechat.buffer_set(lb_buffer, "notify", "0")
+    weechat.buffer_set(lb_buffer, "nicklist", "0")
+    weechat.buffer_set(lb_buffer, "type", "free")
+    weechat.buffer_set(lb_buffer, "key_bind_ctrl-L", "/listbuffer **refresh")
+    weechat.buffer_set(lb_buffer, "key_bind_meta2-A", "/listbuffer **up")
+    weechat.buffer_set(lb_buffer, "key_bind_meta2-B", "/listbuffer **down")
+    weechat.buffer_set(lb_buffer, "key_bind_meta2-1~", "/listbuffer **scroll_top")
+    weechat.buffer_set(lb_buffer, "key_bind_meta2-4~", "/listbuffer **scroll_bottom")
+    weechat.buffer_set(lb_buffer, "key_bind_meta-ctrl-J", "/listbuffer **enter")
+    weechat.buffer_set(lb_buffer, "key_bind_meta-ctrl-M", "/listbuffer **enter")
+    weechat.buffer_set(lb_buffer, "key_bind_meta->", "/listbuffer **sort_next")
+    weechat.buffer_set(lb_buffer, "key_bind_meta-<", "/listbuffer **sort_previous")
+    weechat.buffer_set(lb_buffer, "key_bind_meta-/", "/listbuffer **sort_invert")
+    lb_curline = 0
+  if weechat.config_get_plugin("autofocus") == "on":
+    if not weechat.window_search_with_buffer(lb_buffer):
+      weechat.command("", "/buffer " + weechat.buffer_get_string(lb_buffer,"name"))
+
+def lb_set_buffer_title():
+  global lb_buffer, lb_current_sort
+  ascdesc = '(v)' if lb_sort_inverted else '(^)'
+  weechat.buffer_set(lb_buffer, "title", lb_line_format({
+    'channel': 'Channel name%s' % (ascdesc if lb_current_sort == 'channel' else ''),
+    'users': 'Users%s' % (ascdesc if lb_current_sort == 'users' else ''),
+    'modes': 'Modes%s' % (ascdesc if lb_current_sort == 'modes' else ''),
+    'topic': 'Topic%s' % (ascdesc if lb_current_sort == 'topic' else ''),
+    'nomodes': None,
+  }))
+
+def lb_list_start(data, signal, message):
+  lb_initialise_list
+
+  return weechat.WEECHAT_RC_OK
+
+def lb_initialise_list(signal):
+  global lb_channels, lb_network, lb_list_started
+
+  lb_create_buffer()
+  lb_channels = []
+  lb_network = signal.split(',')[0]
+  lb_list_started = True
+  return
+
+
+def lb_list_chan(data, signal, message):
+  global lb_channels, lb_buffer, lb_list_started
+
+  # Work-around for IRCds which don't send 321 Numeric (/List start)
+  if not lb_list_started:
+    lb_initialise_list(signal)
+
+  for chan_data in re.findall(lb_channel_list_expression,message):
+    lb_channels.append({
+      'server':  chan_data[0][1:-1],
+      'numeric': chan_data[1],
+      'nick':    chan_data[2],
+      'channel': chan_data[3],
+      'users':   chan_data[4],
+      'nomodes': chan_data[5] == '',
+      'modes':   chan_data[6],
+      'topic':   weechat.hook_modifier_exec("irc_color_decode", "1", chan_data[7])
+    })
+  return weechat.WEECHAT_RC_OK
+
+def lb_list_end(data, signal, message):
+  global lb_list_started
+
+  # Work-around for IRCds which don't send 321 Numeric (/List start)
+  if not lb_list_started:
+    lb_initialise_list(signal)
+
+  lb_list_started = False
+  if lb_current_sort:
+    lb_sort()
+  lb_refresh()
+  return weechat.WEECHAT_RC_OK
+
+def keyEvent (data, buffer, args):
+  global lb_options
+  lb_options[args]()
+
+def lb_input_cb(data, buffer, input_data):
+  global lb_options, lb_curline
+  lb_options[input_data]()
+  return weechat.WEECHAT_RC_OK
+
+def lb_refresh():
+  global lb_channels, lb_buffer
+  weechat.buffer_clear(lb_buffer)
+
+  y = 0
+  for list_data in lb_channels:
+    lb_refresh_line(y)
+    y += 1
+  return
+
+def lb_refresh_line(y):
+  global lb_buffer, lb_curline, lb_channels
+  if y >= 0 and y < len(lb_channels):
+    formatted_line = lb_line_format(lb_channels[y], y == lb_curline)
+    weechat.prnt_y(lb_buffer, y, formatted_line)
+
+def lb_refresh_curline():
+  global lb_curline
+  lb_refresh_line(lb_curline-1)
+  lb_refresh_line(lb_curline)
+  lb_refresh_line(lb_curline+1)
+  return
+
+def lb_line_format(list_data,curr=False):
+  str = ""
+  if (curr):
+    str += weechat.color("yellow,red")
+  channel_text = list_data['channel'].ljust(int(weechat.config_get_plugin('channel_min_width')))
+  users_text = "(%s)" % list_data['users']
+  padded_users_text = users_text.rjust(int(weechat.config_get_plugin('users_min_width')) + 2)
+  str += "%s%s %s " % (weechat.color("bold"), channel_text, padded_users_text)
+  if not list_data['nomodes']:
+    modes = "[%s]" % list_data['modes']
+  else:
+    modes = "[]"
+  str += "%s: " % modes.rjust(int(weechat.config_get_plugin('modes_min_width')) + 2)
+  str += "%s" % list_data['topic']
+  return str
+
+def lb_line_up():
+  global lb_curline
+  if lb_curline <= 0:
+    return
+  lb_curline -= 1
+  lb_refresh_curline()
+  lb_check_outside_window()
+  return
+
+def lb_line_down():
+  global lb_curline, lb_channels
+  if lb_curline+1 >= len(lb_channels):
+    return
+  lb_curline += 1
+  lb_refresh_curline()
+  lb_check_outside_window()
+  return
+
+def lb_line_run():
+  global lb_channels, lb_curline, lb_network
+  buff = weechat.info_get("irc_buffer", lb_network)
+  channel = lb_channels[lb_curline]['channel']
+  command = "/join %s" % channel
+  weechat.command(buff, command)
+  return
+
+def lb_line_select():
+  return
+
+def lb_scroll_top():
+  global lb_curline
+  old_y = lb_curline
+  lb_curline = 0
+  lb_refresh_curline()
+  lb_refresh_line(old_y)
+  weechat.command(lb_buffer, "/window scroll_top")
+  return
+
+def lb_scroll_bottom():
+  global lb_curline, lb_channels
+  old_y = lb_curline
+  lb_curline = len(lb_channels)-1
+  lb_refresh_curline()
+  lb_refresh_line(old_y)
+  weechat.command(lb_buffer, "/window scroll_bottom")
+  return
+
+def lb_check_outside_window():
+  global lb_buffer, lb_curline
+  if (lb_buffer):
+    infolist = weechat.infolist_get("window", "", "current")
+    if (weechat.infolist_next(infolist)):
+      start_line_y = weechat.infolist_integer(infolist, "start_line_y")
+      chat_height = weechat.infolist_integer(infolist, "chat_height")
+      if(start_line_y > lb_curline):
+        weechat.command(lb_buffer, "/window scroll -%i" %(start_line_y - lb_curline))
+      elif(start_line_y <= lb_curline - chat_height):
+        weechat.command(lb_buffer, "/window scroll +%i"%(lb_curline - start_line_y - chat_height + 1))
+    weechat.infolist_free(infolist)
+
+def lb_sort_next():
+  global lb_current_sort, lb_sort_options
+  if lb_current_sort:
+    new_index = lb_sort_options.index(lb_current_sort) + 1
+  else:
+    new_index = 0
+
+  if len(lb_sort_options) <= new_index:
+    new_index = 0
+
+  lb_set_current_sort_order(lb_sort_options[new_index])
+  lb_sort()
+
+def lb_set_current_sort_order(value):
+  global lb_current_sort
+  lb_current_sort = value
+  weechat.config_set_plugin('sort_order', lb_current_sort)
+
+def lb_set_invert_sort_order(value):
+  global lb_sort_inverted
+  lb_sort_inverted = value
+  weechat.config_set_plugin('sort_inverted', ('on' if lb_sort_inverted else 'off'))
+
+def lb_sort_previous():
+  global lb_current_sort, lb_sort_options
+  if lb_current_sort:
+    new_index = lb_sort_options.index(lb_current_sort) - 1
+  else:
+    new_index = 0
+
+  if new_index < 0:
+    new_index = len(lb_sort_options) - 1
+
+  lb_set_current_sort_order(lb_sort_options[new_index])
+  lb_sort()
+
+def lb_sort(sort_key=None):
+  global lb_channels, lb_current_sort, lb_sort_inverted
+  if sort_key:
+    lb_set_current_sort_order(sort_key)
+  if lb_current_sort == 'users':
+    lb_channels = sorted(lb_channels, key=lambda chan_data: int(chan_data[lb_current_sort]))
+  else:
+    lb_channels = sorted(lb_channels, key=lambda chan_data: chan_data[lb_current_sort])
+  if lb_sort_inverted:
+    lb_channels.reverse()
+  lb_set_buffer_title()
+  lb_refresh()
+
+def lb_sort_invert():
+  global lb_current_sort, lb_sort_inverted
+  if lb_current_sort:
+    lb_set_invert_sort_order(not lb_sort_inverted)
+    lb_sort()
+
+def lb_close_cb(*kwargs):
+  """ A callback for buffer closing. """
+  global lb_buffer
+
+  lb_buffer = None
+  return weechat.WEECHAT_RC_OK
+
+lb_options = {
+  'refresh'     : lb_refresh,
+  'up'          : lb_line_up,
+  'down'        : lb_line_down,
+  'enter'       : lb_line_run,
+  'space'       : lb_line_select,
+  'scroll_top'  : lb_scroll_top,
+  'scroll_bottom': lb_scroll_bottom,
+  'sort_next'   : lb_sort_next,
+  'sort_previous': lb_sort_previous,
+  'sort_invert': lb_sort_invert
+}
+
+def lb_command_main(data, buffer, args):
+  if args[0:2] == "**":
+    keyEvent(data, buffer, args[2:])
+  return weechat.WEECHAT_RC_OK
+
+def lb_set_default_settings():
+  global lb_settings
+  # Set default settings
+  for option, default_value, description in lb_settings:
+     if not weechat.config_is_set_plugin(option):
+         weechat.config_set_plugin(option, default_value)
+         version = weechat.info_get("version_number", "") or 0
+         if int(version) >= 0x00030500:
+             weechat.config_set_desc_plugin(option, description)
+
+def lb_reset_stored_sort_order():
+  global lb_current_sort, lb_sort_inverted
+  lb_current_sort = weechat.config_get_plugin('sort_order')
+  lb_sort_inverted = (True if weechat.config_get_plugin('sort_inverted') == 'on' else False)
+
+if __name__ == "__main__" and import_ok:
+  if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION,
+                      SCRIPT_LICENSE, SCRIPT_DESC, "lb_close_cb", ""):
+    lb_set_default_settings()
+    lb_reset_stored_sort_order()
+    lb_buffer = weechat.buffer_search("python", "listbuffer")
+
+    weechat.hook_signal("*,irc_in_321", "lb_list_start", "")
+    weechat.hook_signal("*,irc_in_322", "lb_list_chan", "")
+    weechat.hook_signal("*,irc_in_323", "lb_list_end", "")
+    weechat.hook_command(SCRIPT_COMMAND,
+                          "List Buffer",
+                          "", "", "",
+                          "lb_command_main", "")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/python/sanitize_jira.py	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,33 @@
+import re, weechat, subprocess
+
+SCRIPT_NAME = 'sanitize_jira'
+SCRIPT_AUTHOR = 'Steve Losh <steve@stevelosh.com>'
+SCRIPT_VERSION = '0.0.1'
+SCRIPT_LICENSE = 'MIT'
+SCRIPT_DESC = 'clean up the garbage jirabot sends to channels into something readable'
+
+weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, '', '')
+
+weechat.hook_line('*', '', 'nick_Jira_Cloud', 'sanitize_jira', '')
+
+first_line_re = re.compile(
+    r'(?P<link>https://[^/]+/browse/[^?]+)[?]atlOrigin=[^ ]+ [(](?P<title>.+)[)]'
+)
+
+detail_line_re = re.compile(
+    r'''Status: \x1a\x01[*](?P<status>[^*]+)[*]\x1b\x01.*Type: \x1a\x01[*](?P<type>[^*]+)[*]\x1b\x01.*Assignee: \x1a\x01[*](?P<assignee>[^*]+)[*]\x1b\x01.*Priority: \x1a\x01[*](?P<priority>[^*]+)[*]\x1b\x01'''
+)
+
+def sanitize_jira(data, line):
+    if 'sign up for an Atlassian account to view this link' in line['message']:
+        return {'message': ' '}
+
+    m = first_line_re.search(line['message'])
+    if m:
+        return {'message': '%s | %s' % (m.group('title'), m.group('link'))}
+
+    m = detail_line_re.search(line['message'])
+    if m:
+        return {'message': '%s / %s / %s' % (m.group('type'), m.group('status'), m.group('assignee'))}
+
+    return {}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/relay.conf	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,59 @@
+#
+# weechat -- relay.conf
+#
+# WARNING: It is NOT recommended to edit this file by hand,
+# especially if WeeChat is running.
+#
+# Use commands like /set or /fset to change settings in WeeChat.
+#
+# For more info, see: https://weechat.org/doc/quickstart/
+#
+
+[look]
+auto_open_buffer = on
+raw_messages = 256
+
+[color]
+client = cyan
+status_active = lightblue
+status_auth_failed = lightred
+status_connecting = yellow
+status_disconnected = lightred
+status_waiting_auth = brown
+text = default
+text_bg = default
+text_selected = white
+
+[network]
+allow_empty_password = off
+allowed_ips = ""
+auth_timeout = 60
+bind_address = ""
+clients_purge_delay = 0
+compression = 20
+ipv6 = on
+max_clients = 5
+nonce_size = 16
+password = ""
+password_hash_algo = "*"
+password_hash_iterations = 100000
+ssl_cert_key = "%h/ssl/relay.pem"
+ssl_priorities = "NORMAL:-VERS-SSL3.0"
+totp_secret = ""
+totp_window = 0
+websocket_allowed_origins = ""
+
+[irc]
+backlog_max_minutes = 1440
+backlog_max_number = 256
+backlog_since_last_disconnect = on
+backlog_since_last_message = off
+backlog_tags = "irc_privmsg"
+backlog_time_format = "[%H:%M] "
+
+[weechat]
+commands = ""
+
+[port]
+
+[path]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/rmodifier.conf	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,11 @@
+#
+# rmodifier.conf -- weechat v0.4.3
+#
+
+[look]
+hide_char = "*"
+
+[modifier]
+nickserv = "history_add,input_text_display;^(/(msg|quote) +nickserv +(identify|ghost \S+) +)(.*);1,4*"
+oper = "history_add,input_text_display;^(/oper +\S+ +)(.*);1,2*"
+set_pass = "history_add;^(/set +\S*password\S* +)(.*);1,2*"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/ruby.conf	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,14 @@
+#
+# weechat -- ruby.conf
+#
+# WARNING: It is NOT recommended to edit this file by hand,
+# especially if WeeChat is running.
+#
+# Use commands like /set or /fset to change settings in WeeChat.
+#
+# For more info, see: https://weechat.org/doc/quickstart
+#
+
+[look]
+check_license = off
+eval_keep_context = on
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/script.conf	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,57 @@
+#
+# weechat -- script.conf
+#
+# WARNING: It is NOT recommended to edit this file by hand,
+# especially if WeeChat is running.
+#
+# Use commands like /set or /fset to change settings in WeeChat.
+#
+# For more info, see: https://weechat.org/doc/quickstart/
+#
+
+[look]
+columns = "%s %n %V %v %u | %d | %t"
+diff_color = on
+diff_command = "auto"
+display_source = on
+quiet_actions = on
+sort = "p,n"
+translate_description = on
+use_keys = on
+
+[color]
+status_autoloaded = cyan
+status_held = white
+status_installed = lightcyan
+status_obsolete = lightmagenta
+status_popular = yellow
+status_running = lightgreen
+status_unknown = lightred
+text = default
+text_bg = default
+text_bg_selected = red
+text_date = default
+text_date_selected = white
+text_delimiters = darkgray
+text_description = default
+text_description_selected = white
+text_extension = default
+text_extension_selected = white
+text_name = cyan
+text_name_selected = lightcyan
+text_selected = white
+text_tags = brown
+text_tags_selected = yellow
+text_version = magenta
+text_version_loaded = default
+text_version_loaded_selected = white
+text_version_selected = lightmagenta
+
+[scripts]
+autoload = on
+cache_expire = 60
+download_enabled = on
+download_timeout = 30
+hold = ""
+path = "%h/script"
+url = "http://www.weechat.org/files/plugins.xml.gz"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/spell.conf	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,33 @@
+#
+# weechat -- spell.conf
+#
+# WARNING: It is NOT recommended to edit this file by hand,
+# especially if WeeChat is running.
+#
+# Use commands like /set or /fset to change settings in WeeChat.
+#
+# For more info, see: https://weechat.org/doc/quickstart
+#
+
+[color]
+misspelled = lightred
+suggestion = default
+suggestion_delimiter_dict = cyan
+suggestion_delimiter_word = cyan
+
+[check]
+commands = "away,command,cycle,kick,kickban,me,msg,notice,part,query,quit,topic"
+default_dict = ""
+during_search = off
+enabled = off
+real_time = off
+suggestions = -1
+word_min_length = 2
+
+[dict]
+
+[look]
+suggestion_delimiter_dict = " / "
+suggestion_delimiter_word = ","
+
+[option]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/tcl.conf	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,14 @@
+#
+# weechat -- tcl.conf
+#
+# WARNING: It is NOT recommended to edit this file by hand,
+# especially if WeeChat is running.
+#
+# Use /set or similar command to change settings in WeeChat.
+#
+# For more info, see: https://weechat.org/doc/quickstart
+#
+
+[look]
+check_license = off
+eval_keep_context = on
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/trigger.conf	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,83 @@
+#
+# weechat -- trigger.conf
+#
+# WARNING: It is NOT recommended to edit this file by hand,
+# especially if WeeChat is running.
+#
+# Use commands like /set or /fset to change settings in WeeChat.
+#
+# For more info, see: https://weechat.org/doc/quickstart/
+#
+
+[look]
+enabled = on
+monitor_strip_colors = off
+
+[color]
+flag_command = lightgreen
+flag_conditions = yellow
+flag_post_action = lightblue
+flag_regex = lightcyan
+flag_return_code = lightmagenta
+regex = white
+replace = cyan
+trigger = green
+trigger_disabled = red
+
+[trigger]
+beep.arguments = ""
+beep.command = "/print -beep"
+beep.conditions = "${tg_highlight} || ${tg_msg_pv}"
+beep.enabled = on
+beep.hook = print
+beep.post_action = none
+beep.regex = ""
+beep.return_code = ok
+cmd_pass.arguments = "5000|input_text_display;5000|history_add;5000|irc_command_auth"
+cmd_pass.command = ""
+cmd_pass.conditions = ""
+cmd_pass.enabled = on
+cmd_pass.hook = modifier
+cmd_pass.post_action = none
+cmd_pass.regex = "==^((/(msg|quote) +nickserv +(id|identify|register|ghost +[^ ]+|release +[^ ]+|regain +[^ ]+) +)|/oper +[^ ]+ +|/quote +pass +|/set +[^ ]*password[^ ]* +|/secure +(passphrase|decrypt|set +[^ ]+) +)(.*)==$1$.*+"
+cmd_pass.return_code = ok
+dumbass_buffer.arguments = "4000|input_text_for_buffer;4000|history_add"
+dumbass_buffer.command = ""
+dumbass_buffer.conditions = ""
+dumbass_buffer.enabled = on
+dumbass_buffer.hook = modifier
+dumbass_buffer.post_action = none
+dumbass_buffer.regex = "==^ +/?b (.+)==/b ${re:1}"
+dumbass_buffer.return_code = ok
+idiot_buffer.arguments = "4000|input_text_for_buffer;4000|history_add"
+idiot_buffer.command = ""
+idiot_buffer.conditions = ""
+idiot_buffer.enabled = on
+idiot_buffer.hook = modifier
+idiot_buffer.post_action = none
+idiot_buffer.regex = "==^b (.+)==/b ${re:1}"
+idiot_buffer.return_code = ok
+msg_auth.arguments = "5000|irc_message_auth"
+msg_auth.command = ""
+msg_auth.conditions = ""
+msg_auth.enabled = on
+msg_auth.hook = modifier
+msg_auth.post_action = none
+msg_auth.regex = "==^(.*(id|identify|register|ghost +[^ ]+|release +[^ ]+) +)(.*)==$1$.*+"
+msg_auth.return_code = ok
+server_pass.arguments = "5000|input_text_display;5000|history_add"
+server_pass.command = ""
+server_pass.conditions = ""
+server_pass.enabled = on
+server_pass.hook = modifier
+server_pass.post_action = none
+server_pass.regex = "==^(/(server|connect) .*-(sasl_)?password=)([^ ]+)(.*)==$1$.*4$5"
+server_pass.return_code = ok
+uncc.arguments = "weechat_print"
+uncc.command = ""
+uncc.conditions = "${tg_tag_nick}"
+uncc.enabled = on
+uncc.hook = modifier
+uncc.post_action = none
+uncc.regex = "== \[cc: [^ ]+\]===="
+uncc.return_code = ok
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/typing.conf	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,19 @@
+#
+# weechat -- typing.conf
+#
+# WARNING: It is NOT recommended to edit this file by hand,
+# especially if WeeChat is running.
+#
+# Use commands like /set or /fset to change settings in WeeChat.
+#
+# For more info, see: https://weechat.org/doc/quickstart/
+#
+
+[look]
+delay_purge_paused = 30
+delay_purge_typing = 6
+delay_set_paused = 10
+enabled_nicks = off
+enabled_self = off
+input_min_chars = 4
+item_max_length = 0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/urlgrab.conf	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,29 @@
+#
+# weechat -- urlgrab.conf
+#
+# WARNING: It is NOT recommended to edit this file by hand,
+# especially if WeeChat is running.
+#
+# Use commands like /set or /fset to change settings in WeeChat.
+#
+# For more info, see: https://weechat.org/doc/quickstart/
+#
+
+[color]
+color_bg_selected = green
+color_buffer = red
+color_buffer_selected = red
+color_time = cyan
+color_time_selected = cyan
+color_url = blue
+color_url_selected = blue
+
+[default]
+copycmd = "xsel -i"
+historysize = 20
+localcmd = "xdg-open %s"
+method = "local"
+output_main_buffer = off
+remotecmd = "ssh -x localhost -i ~/.ssh/id_rsa -C "export DISPLAY=":0.0" &&  firefox %s""
+time_format = "%H:%M:%S"
+url_log = "~/.weechat/urls.log"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/weechat.conf	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,693 @@
+#
+# WeeChat -- weechat.conf
+#
+# WARNING: It is NOT recommended to edit this file by hand,
+# especially if WeeChat is running.
+#
+# Use commands like /set or /fset to change settings in WeeChat.
+#
+# For more info, see: https://weechat.org/doc/quickstart
+#
+
+[debug]
+
+[startup]
+command_after_plugins = ""
+command_before_plugins = ""
+display_logo = on
+display_version = on
+sys_rlimit = ""
+
+[look]
+align_end_of_lines = message
+align_multiline_words = on
+bar_more_down = "++"
+bar_more_left = "<<"
+bar_more_right = ">>"
+bar_more_up = "--"
+bare_display_exit_on_input = on
+bare_display_time_format = "%H:%M"
+buffer_auto_renumber = on
+buffer_notify_default = all
+buffer_position = end
+buffer_search_case_sensitive = off
+buffer_search_force_default = off
+buffer_search_regex = off
+buffer_search_where = prefix_message
+buffer_time_format = "%H:%M"
+buffer_time_same = ""
+color_basic_force_bold = off
+color_inactive_buffer = off
+color_inactive_message = on
+color_inactive_prefix = on
+color_inactive_prefix_buffer = on
+color_inactive_time = off
+color_inactive_window = off
+color_nick_offline = off
+color_pairs_auto_reset = 5
+color_real_white = off
+command_chars = ""
+command_incomplete = off
+confirm_quit = off
+confirm_upgrade = off
+day_change = on
+day_change_message_1date = "-- %a, %d %b %Y --"
+day_change_message_2dates = "-- %%a, %%d %%b %%Y (%a, %d %b %Y) --"
+eat_newline_glitch = off
+emphasized_attributes = ""
+highlight = "sjl,slosh,slj,horrifying,steve.losh,@steve.losh,stevelosh"
+highlight_regex = "(steve losh|rob ford|(jesus )?fucking christ|(horse|mouse|clown)fuckers?|((mother)?fuck([ie]ng?|er)?|(god?)?damn(ed)?|dammit|(bull|horse)?shite?){3,})"
+highlight_tags = ""
+hotlist_add_conditions = "${away} || ${buffer.num_displayed} == 0"
+hotlist_buffer_separator = ", "
+hotlist_count_max = 2
+hotlist_count_min_msg = 2
+hotlist_names_count = 3
+hotlist_names_length = 0
+hotlist_names_level = 12
+hotlist_names_merged_buffers = off
+hotlist_prefix = "H: "
+hotlist_remove = merged
+hotlist_short_names = on
+hotlist_sort = group_time_asc
+hotlist_suffix = ""
+hotlist_unique_numbers = on
+hotlist_update_on_buffer_switch = on
+input_cursor_scroll = 20
+input_share = none
+input_share_overwrite = off
+input_undo_max = 32
+item_away_message = on
+item_buffer_filter = "*"
+item_buffer_zoom = "!"
+item_mouse_status = "M"
+item_time_format = "%H:%M"
+jump_current_to_previous_buffer = on
+jump_previous_buffer_when_closing = on
+jump_smart_back_to_buffer = on
+key_bind_safe = on
+key_grab_delay = 800
+mouse = off
+mouse_timer_delay = 100
+nick_color_force = ""
+nick_color_hash = djb2
+nick_color_hash_salt = ""
+nick_color_stop_chars = "_|["
+nick_prefix = ""
+nick_suffix = ""
+paste_auto_add_newline = on
+paste_bracketed = off
+paste_bracketed_timer_delay = 10
+paste_max_lines = 3
+prefix_action = " *"
+prefix_align = right
+prefix_align_max = 15
+prefix_align_min = 0
+prefix_align_more = "+"
+prefix_align_more_after = on
+prefix_buffer_align = right
+prefix_buffer_align_max = 0
+prefix_buffer_align_more = "+"
+prefix_buffer_align_more_after = on
+prefix_error = "=!="
+prefix_join = "✔"
+prefix_network = "--"
+prefix_quit = "✘"
+prefix_same_nick = ""
+prefix_same_nick_middle = ""
+prefix_suffix = "|"
+quote_nick_prefix = "<"
+quote_nick_suffix = ">"
+quote_time_format = "%H:%M:%S"
+read_marker = line
+read_marker_always_show = on
+read_marker_string = "─"
+read_marker_update_on_buffer_switch = on
+save_config_on_exit = off
+save_config_with_fsync = off
+save_layout_on_exit = all
+scroll_amount = 3
+scroll_bottom_after_switch = off
+scroll_page_percent = 100
+search_text_not_found_alert = on
+separator_horizontal = "-"
+separator_vertical = ""
+tab_width = 1
+time_format = "%a, %d %b %Y %T"
+window_auto_zoom = off
+window_separator_horizontal = on
+window_separator_vertical = on
+window_title = ""
+word_chars_highlight = "!\u00A0,-,_,|,@,.,alnum"
+word_chars_input = "!\u00A0,-,_,|,alnum"
+
+[palette]
+
+[color]
+bar_more = magenta
+chat = default
+chat_bg = default
+chat_buffer = white
+chat_channel = white
+chat_day_change = cyan
+chat_delimiters = green
+chat_highlight = 207
+chat_highlight_bg = default
+chat_host = cyan
+chat_inactive_buffer = darkgray
+chat_inactive_window = darkgray
+chat_nick = lightcyan
+chat_nick_colors = "027,048,068,028,081,082,099,112,129,136,169,178,208,226,113,196,161,23,59,222"
+chat_nick_offline = darkgray
+chat_nick_offline_highlight = default
+chat_nick_offline_highlight_bg = darkgray
+chat_nick_other = cyan
+chat_nick_prefix = green
+chat_nick_self = white
+chat_nick_suffix = green
+chat_prefix_action = white
+chat_prefix_buffer = brown
+chat_prefix_buffer_inactive_buffer = darkgray
+chat_prefix_error = yellow
+chat_prefix_join = lightgreen
+chat_prefix_more = lightmagenta
+chat_prefix_network = magenta
+chat_prefix_quit = lightred
+chat_prefix_suffix = green
+chat_read_marker = green
+chat_read_marker_bg = default
+chat_server = brown
+chat_tags = red
+chat_text_found = yellow
+chat_text_found_bg = lightmagenta
+chat_time = 238
+chat_time_delimiters = 236
+chat_value = cyan
+chat_value_null = blue
+emphasized = yellow
+emphasized_bg = magenta
+input_actions = lightgreen
+input_text_not_found = red
+item_away = yellow
+nicklist_away = cyan
+nicklist_group = green
+separator = green
+status_count_highlight = magenta
+status_count_msg = brown
+status_count_other = 16
+status_count_private = green
+status_data_highlight = lightmagenta
+status_data_msg = yellow
+status_data_other = 16
+status_data_private = lightgreen
+status_filter = green
+status_more = 16
+status_mouse = green
+status_name = *16
+status_name_ssl = *16
+status_nicklist_count = default
+status_number = 16
+status_time = *16
+
+[completion]
+base_word_until_cursor = on
+command_inline = on
+default_template = "%(nicks)|%(irc_channels)"
+nick_add_space = on
+nick_case_sensitive = off
+nick_completer = ":"
+nick_first_only = off
+nick_ignore_chars = "[]`_-^"
+partial_completion_alert = on
+partial_completion_command = off
+partial_completion_command_arg = off
+partial_completion_count = on
+partial_completion_other = off
+partial_completion_templates = "config_options"
+
+[history]
+display_default = 5
+max_buffer_lines_minutes = 0
+max_buffer_lines_number = 4096
+max_commands = 100
+max_visited_buffers = 50
+
+[proxy]
+
+[network]
+connection_timeout = 60
+gnutls_ca_system = on
+gnutls_ca_user = ""
+gnutls_handshake_timeout = 30
+proxy_curl = ""
+
+[plugin]
+autoload = "*"
+debug = off
+extension = ".so"
+path = "%h/plugins"
+save_config_on_unload = on
+
+[signal]
+sighup = "${if:${info:weechat_headless}?/reload:/quit -yes}"
+sigquit = "/quit -yes"
+sigterm = "/quit -yes"
+sigusr1 = ""
+sigusr2 = ""
+
+[bar]
+buffers.color_bg = default
+buffers.color_bg_inactive = default
+buffers.color_delim = default
+buffers.color_fg = default
+buffers.conditions = ""
+buffers.filling_left_right = vertical
+buffers.filling_top_bottom = horizontal
+buffers.hidden = on
+buffers.items = "buffers"
+buffers.position = left
+buffers.priority = 0
+buffers.separator = on
+buffers.size = 0
+buffers.size_max = 0
+buffers.type = root
+buflist.color_bg = default
+buflist.color_bg_inactive = default
+buflist.color_delim = default
+buflist.color_fg = default
+buflist.conditions = ""
+buflist.filling_left_right = vertical
+buflist.filling_top_bottom = columns_vertical
+buflist.hidden = off
+buflist.items = "buflist"
+buflist.position = left
+buflist.priority = 0
+buflist.separator = on
+buflist.size = 0
+buflist.size_max = 25
+buflist.type = root
+fset.color_bg = default
+fset.color_bg_inactive = default
+fset.color_delim = cyan
+fset.color_fg = default
+fset.conditions = "${buffer.full_name} == fset.fset"
+fset.filling_left_right = vertical
+fset.filling_top_bottom = horizontal
+fset.hidden = off
+fset.items = "fset"
+fset.position = top
+fset.priority = 0
+fset.separator = on
+fset.size = 3
+fset.size_max = 3
+fset.type = window
+input.color_bg = default
+input.color_bg_inactive = default
+input.color_delim = green
+input.color_fg = default
+input.conditions = ""
+input.filling_left_right = vertical
+input.filling_top_bottom = horizontal
+input.hidden = off
+input.items = "[input_prompt]+(away),[input_search],[input_paste],input_text"
+input.position = bottom
+input.priority = 1000
+input.separator = off
+input.size = 1
+input.size_max = 0
+input.type = window
+nicklist.color_bg = default
+nicklist.color_bg_inactive = default
+nicklist.color_delim = cyan
+nicklist.color_fg = default
+nicklist.conditions = "nicklist"
+nicklist.filling_left_right = vertical
+nicklist.filling_top_bottom = columns_vertical
+nicklist.hidden = on
+nicklist.items = "buffer_nicklist"
+nicklist.position = right
+nicklist.priority = 200
+nicklist.separator = on
+nicklist.size = 0
+nicklist.size_max = 0
+nicklist.type = window
+status.color_bg = green
+status.color_bg_inactive = default
+status.color_delim = 0
+status.color_fg = 0
+status.conditions = ""
+status.filling_left_right = vertical
+status.filling_top_bottom = horizontal
+status.hidden = off
+status.items = "[time],buffer_number+:+buffer_name,buffer_title"
+status.position = bottom
+status.priority = 500
+status.separator = off
+status.size = 1
+status.size_max = 0
+status.type = window
+title.color_bg = green
+title.color_bg_inactive = default
+title.color_delim = cyan
+title.color_fg = 16
+title.conditions = ""
+title.filling_left_right = vertical
+title.filling_top_bottom = horizontal
+title.hidden = on
+title.items = "buffer_title"
+title.position = top
+title.priority = 500
+title.separator = off
+title.size = 1
+title.size_max = 0
+title.type = window
+
+[layout]
+
+[notify]
+python.slack.10xgenomics.&cloud-alerts-pagerduty = highlight
+python.slack.10xgenomics.&cloud-sumo-prod-support-alerts = highlight
+python.slack.10xgenomics.&lacework-10xdev = highlight
+python.slack.10xgenomics.&lacework-10xprod = highlight
+python.slack.10xgenomics.&testing1234 = highlight
+
+[filter]
+irc_smart = on;*;irc_smart_filter;*
+nicks = on;*;irc_366;*
+
+[key]
+ctrl-? = "/input delete_previous_char"
+ctrl-A = "/input move_beginning_of_line"
+ctrl-B = "/brows"
+ctrl-Cb = "/input insert \x02"
+ctrl-Cc = "/input insert \x03"
+ctrl-Ci = "/input insert \x1D"
+ctrl-Co = "/input insert \x0F"
+ctrl-Cr = "/input insert \x12"
+ctrl-Cu = "/input insert \x15"
+ctrl-D = "/buffer close"
+ctrl-E = "/input move_end_of_line"
+ctrl-F = "/input move_next_char"
+ctrl-H = "/input delete_previous_char"
+ctrl-I = "/input complete_next"
+ctrl-J = "/input jump_smart"
+ctrl-K = "/input delete_end_of_line"
+ctrl-L = "/window refresh"
+ctrl-M = "/input return"
+ctrl-N = "/buffer +1"
+ctrl-O = "/editor"
+ctrl-P = "/buffer -1"
+ctrl-R = "/input search_text"
+ctrl-Sctrl-U = "/input set_unread"
+ctrl-T = "/input transpose_chars"
+ctrl-U = "/url 1"
+ctrl-W = "/input delete_previous_word"
+ctrl-X = "/input switch_active_buffer"
+ctrl-Y = "/input clipboard_paste"
+meta-meta-OP = "/bar scroll buflist * b"
+meta-meta-OQ = "/bar scroll buflist * e"
+meta-meta2-11~ = "/bar scroll buflist * b"
+meta-meta2-12~ = "/bar scroll buflist * e"
+meta-meta2-1~ = "/window scroll_top"
+meta-meta2-23~ = "/bar scroll nicklist * yb"
+meta-meta2-24~ = "/bar scroll nicklist * ye"
+meta-meta2-4~ = "/window scroll_bottom"
+meta-meta2-5~ = "/window scroll_up"
+meta-meta2-6~ = "/window scroll_down"
+meta-meta2-7~ = "/window scroll_top"
+meta-meta2-8~ = "/window scroll_bottom"
+meta-meta2-A = "/buffer move -1"
+meta-meta2-B = "/buffer move +1"
+meta-meta2-C = "/buffer +1"
+meta-meta2-D = "/buffer -1"
+meta-0 = "/buffer *10"
+meta-1 = "/buffer *1"
+meta-2 = "/buffer *2"
+meta-3 = "/buffer *3"
+meta-4 = "/buffer *4"
+meta-5 = "/buffer *5"
+meta-6 = "/buffer *6"
+meta-7 = "/buffer *7"
+meta-8 = "/buffer *8"
+meta-9 = "/buffer *9"
+meta-< = "/input jump_previously_visited_buffer"
+meta-= = "/filter toggle"
+meta-> = "/input jump_next_visited_buffer"
+meta-B = "/buflist toggle"
+meta-OA = "/input history_global_previous"
+meta-OB = "/input history_global_next"
+meta-OC = "/input move_next_word"
+meta-OD = "/input move_previous_word"
+meta-OF = "/input move_end_of_line"
+meta-OH = "/input move_beginning_of_line"
+meta-OP = "/bar scroll buflist * -100%"
+meta-OQ = "/bar scroll buflist * +100%"
+meta-Oa = "/input history_global_previous"
+meta-Ob = "/input history_global_next"
+meta-Oc = "/input move_next_word"
+meta-Od = "/input move_previous_word"
+meta2-11^ = "/bar scroll buflist * -100%"
+meta2-11~ = "/bar scroll buflist * -100%"
+meta2-12^ = "/bar scroll buflist * +100%"
+meta2-12~ = "/bar scroll buflist * +100%"
+meta2-15~ = "/bar scroll nicklist * y-100%"
+meta2-17~ = "/bar scroll nicklist * y+100%"
+meta2-18~ = "/window -1"
+meta2-19~ = "/window +1"
+meta2-1;3A = "/buffer -1"
+meta2-1;3B = "/buffer +1"
+meta2-1;3C = "/buffer +1"
+meta2-1;3D = "/buffer -1"
+meta2-1;3P = "/bar scroll buflist * b"
+meta2-1;3Q = "/bar scroll buflist * e"
+meta2-1;5A = "/input history_global_previous"
+meta2-1;5B = "/input history_global_next"
+meta2-1;5P = "/bar scroll buflist * -100%"
+meta2-1;5Q = "/bar scroll buflist * +100%"
+meta2-1;9A = "/buffer move -1"
+meta2-1;9B = "/buffer move +1"
+meta2-1~ = "/input move_beginning_of_line"
+meta2-20~ = "/bar scroll title * x-50%"
+meta2-21~ = "/bar scroll title * x+50%"
+meta2-23~ = "/bar scroll nicklist * y-100%"
+meta2-24~ = "/bar scroll nicklist * y+100%"
+meta2-3~ = "/input delete_next_char"
+meta2-4~ = "/input move_end_of_line"
+meta2-5;3~ = "/window scroll_up"
+meta2-5~ = "/window page_up"
+meta2-6;3~ = "/window scroll_down"
+meta2-6~ = "/window page_down"
+meta2-7~ = "/input move_beginning_of_line"
+meta2-8~ = "/input move_end_of_line"
+meta2-A = "/input history_previous"
+meta2-B = "/input history_next"
+meta2-C = "/input move_next_char"
+meta2-D = "/input move_previous_char"
+meta2-F = "/input move_end_of_line"
+meta2-G = "/window page_down"
+meta2-H = "/input move_beginning_of_line"
+meta2-I = "/window page_up"
+meta2-Z = "/input complete_previous"
+meta-_ = "/input redo"
+meta-a = "/input jump_smart"
+meta-b = "/input move_previous_word"
+meta-d = "/input delete_next_word"
+meta-f = "/input move_next_word"
+meta-h = "/input hotlist_clear"
+meta-jmeta-l = "/input jump_last_buffer"
+meta-jmeta-r = "/server raw"
+meta-jmeta-s = "/server jump"
+meta-j01 = "/buffer 1"
+meta-j02 = "/buffer 2"
+meta-j03 = "/buffer 3"
+meta-j04 = "/buffer 4"
+meta-j05 = "/buffer 5"
+meta-j06 = "/buffer 6"
+meta-j07 = "/buffer 7"
+meta-j08 = "/buffer 8"
+meta-j09 = "/buffer 9"
+meta-j10 = "/buffer 10"
+meta-j11 = "/buffer 11"
+meta-j12 = "/buffer 12"
+meta-j13 = "/buffer 13"
+meta-j14 = "/buffer 14"
+meta-j15 = "/buffer 15"
+meta-j16 = "/buffer 16"
+meta-j17 = "/buffer 17"
+meta-j18 = "/buffer 18"
+meta-j19 = "/buffer 19"
+meta-j20 = "/buffer 20"
+meta-j21 = "/buffer 21"
+meta-j22 = "/buffer 22"
+meta-j23 = "/buffer 23"
+meta-j24 = "/buffer 24"
+meta-j25 = "/buffer 25"
+meta-j26 = "/buffer 26"
+meta-j27 = "/buffer 27"
+meta-j28 = "/buffer 28"
+meta-j29 = "/buffer 29"
+meta-j30 = "/buffer 30"
+meta-j31 = "/buffer 31"
+meta-j32 = "/buffer 32"
+meta-j33 = "/buffer 33"
+meta-j34 = "/buffer 34"
+meta-j35 = "/buffer 35"
+meta-j36 = "/buffer 36"
+meta-j37 = "/buffer 37"
+meta-j38 = "/buffer 38"
+meta-j39 = "/buffer 39"
+meta-j40 = "/buffer 40"
+meta-j41 = "/buffer 41"
+meta-j42 = "/buffer 42"
+meta-j43 = "/buffer 43"
+meta-j44 = "/buffer 44"
+meta-j45 = "/buffer 45"
+meta-j46 = "/buffer 46"
+meta-j47 = "/buffer 47"
+meta-j48 = "/buffer 48"
+meta-j49 = "/buffer 49"
+meta-j50 = "/buffer 50"
+meta-j51 = "/buffer 51"
+meta-j52 = "/buffer 52"
+meta-j53 = "/buffer 53"
+meta-j54 = "/buffer 54"
+meta-j55 = "/buffer 55"
+meta-j56 = "/buffer 56"
+meta-j57 = "/buffer 57"
+meta-j58 = "/buffer 58"
+meta-j59 = "/buffer 59"
+meta-j60 = "/buffer 60"
+meta-j61 = "/buffer 61"
+meta-j62 = "/buffer 62"
+meta-j63 = "/buffer 63"
+meta-j64 = "/buffer 64"
+meta-j65 = "/buffer 65"
+meta-j66 = "/buffer 66"
+meta-j67 = "/buffer 67"
+meta-j68 = "/buffer 68"
+meta-j69 = "/buffer 69"
+meta-j70 = "/buffer 70"
+meta-j71 = "/buffer 71"
+meta-j72 = "/buffer 72"
+meta-j73 = "/buffer 73"
+meta-j74 = "/buffer 74"
+meta-j75 = "/buffer 75"
+meta-j76 = "/buffer 76"
+meta-j77 = "/buffer 77"
+meta-j78 = "/buffer 78"
+meta-j79 = "/buffer 79"
+meta-j80 = "/buffer 80"
+meta-j81 = "/buffer 81"
+meta-j82 = "/buffer 82"
+meta-j83 = "/buffer 83"
+meta-j84 = "/buffer 84"
+meta-j85 = "/buffer 85"
+meta-j86 = "/buffer 86"
+meta-j87 = "/buffer 87"
+meta-j88 = "/buffer 88"
+meta-j89 = "/buffer 89"
+meta-j90 = "/buffer 90"
+meta-j91 = "/buffer 91"
+meta-j92 = "/buffer 92"
+meta-j93 = "/buffer 93"
+meta-j94 = "/buffer 94"
+meta-j95 = "/buffer 95"
+meta-j96 = "/buffer 96"
+meta-j97 = "/buffer 97"
+meta-j98 = "/buffer 98"
+meta-j99 = "/buffer 99"
+meta-k = "/input grab_key_command"
+meta-n = "/window scroll_next_highlight"
+meta-p = "/window scroll_previous_highlight"
+meta-r = "/input delete_line"
+meta-u = "/input scroll_unread"
+meta-wmeta-meta2-A = "/window up"
+meta-wmeta-meta2-B = "/window down"
+meta-wmeta-meta2-C = "/window right"
+meta-wmeta-meta2-D = "/window left"
+meta-wmeta2-1;3A = "/window up"
+meta-wmeta2-1;3B = "/window down"
+meta-wmeta2-1;3C = "/window right"
+meta-wmeta2-1;3D = "/window left"
+meta-wmeta-b = "/window balance"
+meta-wmeta-s = "/window swap"
+meta-x = "/bar toggle nicklist"
+meta-z = "/window zoom"
+ctrl-_ = "/input undo"
+
+[key_search]
+ctrl-J = "/input search_stop"
+ctrl-M = "/input search_stop"
+ctrl-R = "/input search_switch_case"
+meta2-A = "/input search_previous"
+meta2-B = "/input search_next"
+
+[key_cursor]
+ctrl-J = "/cursor stop"
+ctrl-M = "/cursor stop"
+meta-meta2-A = "/cursor move area_up"
+meta-meta2-B = "/cursor move area_down"
+meta-meta2-C = "/cursor move area_right"
+meta-meta2-D = "/cursor move area_left"
+meta2-1;3A = "/cursor move area_up"
+meta2-1;3B = "/cursor move area_down"
+meta2-1;3C = "/cursor move area_right"
+meta2-1;3D = "/cursor move area_left"
+meta2-A = "/cursor move up"
+meta2-B = "/cursor move down"
+meta2-C = "/cursor move right"
+meta2-D = "/cursor move left"
+@chat(python.*):D = "hsignal:slack_cursor_delete"
+@chat(python.*):L = "hsignal:slack_cursor_linkarchive"
+@chat(python.*):M = "hsignal:slack_cursor_message"
+@chat(python.*):R = "hsignal:slack_cursor_reply"
+@chat(python.*):T = "hsignal:slack_cursor_thread"
+@item(buffer_nicklist):K = "/window ${_window_number};/kickban ${nick}"
+@item(buffer_nicklist):b = "/window ${_window_number};/ban ${nick}"
+@item(buffer_nicklist):k = "/window ${_window_number};/kick ${nick}"
+@item(buffer_nicklist):q = "/window ${_window_number};/query ${nick};/cursor stop"
+@item(buffer_nicklist):w = "/window ${_window_number};/whois ${nick}"
+@chat:Q = "hsignal:chat_quote_time_prefix_message;/cursor stop"
+@chat:m = "hsignal:chat_quote_message;/cursor stop"
+@chat:q = "hsignal:chat_quote_prefix_message;/cursor stop"
+
+[key_mouse]
+@bar(buflist):ctrl-wheeldown = "hsignal:buflist_mouse"
+@bar(buflist):ctrl-wheelup = "hsignal:buflist_mouse"
+@bar(input):button2 = "/input grab_mouse_area"
+@bar(nicklist):button1-gesture-down = "/bar scroll nicklist ${_window_number} +100%"
+@bar(nicklist):button1-gesture-down-long = "/bar scroll nicklist ${_window_number} e"
+@bar(nicklist):button1-gesture-up = "/bar scroll nicklist ${_window_number} -100%"
+@bar(nicklist):button1-gesture-up-long = "/bar scroll nicklist ${_window_number} b"
+@chat(fset.fset):button1 = "/window ${_window_number};/fset -go ${_chat_line_y}"
+@chat(fset.fset):button2* = "hsignal:fset_mouse"
+@chat(fset.fset):wheeldown = "/fset -down 5"
+@chat(fset.fset):wheelup = "/fset -up 5"
+@chat(python.*):button2 = "hsignal:slack_mouse"
+@chat(script.scripts):button1 = "/window ${_window_number};/script go ${_chat_line_y}"
+@chat(script.scripts):button2 = "/window ${_window_number};/script go ${_chat_line_y};/script installremove -q ${script_name_with_extension}"
+@chat(script.scripts):wheeldown = "/script down 5"
+@chat(script.scripts):wheelup = "/script up 5"
+@item(buffer_nicklist):button1 = "/window ${_window_number};/query ${nick}"
+@item(buffer_nicklist):button1-gesture-left = "/window ${_window_number};/kick ${nick}"
+@item(buffer_nicklist):button1-gesture-left-long = "/window ${_window_number};/kickban ${nick}"
+@item(buffer_nicklist):button2 = "/window ${_window_number};/whois ${nick}"
+@item(buffer_nicklist):button2-gesture-left = "/window ${_window_number};/ban ${nick}"
+@item(buffers):button1* = "hsignal:buffers_mouse"
+@item(buffers):button2* = "hsignal:buffers_mouse"
+@item(buflist):button1* = "hsignal:buflist_mouse"
+@item(buflist):button2* = "hsignal:buflist_mouse"
+@item(buflist2):button1* = "hsignal:buflist_mouse"
+@item(buflist2):button2* = "hsignal:buflist_mouse"
+@item(buflist3):button1* = "hsignal:buflist_mouse"
+@item(buflist3):button2* = "hsignal:buflist_mouse"
+@bar:wheeldown = "/bar scroll ${_bar_name} ${_window_number} +20%"
+@bar:wheelup = "/bar scroll ${_bar_name} ${_window_number} -20%"
+@chat:button1 = "/window ${_window_number}"
+@chat:button1-gesture-left = "/window ${_window_number};/buffer -1"
+@chat:button1-gesture-left-long = "/window ${_window_number};/buffer 1"
+@chat:button1-gesture-right = "/window ${_window_number};/buffer +1"
+@chat:button1-gesture-right-long = "/window ${_window_number};/input jump_last_buffer"
+@chat:wheeldown = "/window scroll_down -window ${_window_number}"
+@chat:wheelup = "/window scroll_up -window ${_window_number}"
+@*:button3 = "/cursor go ${_x},${_y}"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat-old/xfer.conf	Wed Aug 27 16:19:24 2025 -0400
@@ -0,0 +1,49 @@
+#
+# weechat -- xfer.conf
+#
+# WARNING: It is NOT recommended to edit this file by hand,
+# especially if WeeChat is running.
+#
+# Use commands like /set or /fset to change settings in WeeChat.
+#
+# For more info, see: https://weechat.org/doc/quickstart/
+#
+
+[look]
+auto_open_buffer = on
+progress_bar_size = 20
+pv_tags = "notify_private"
+
+[color]
+status_aborted = lightred
+status_active = lightblue
+status_connecting = yellow
+status_done = lightgreen
+status_failed = lightred
+status_waiting = lightcyan
+text = default
+text_bg = default
+text_selected = white
+
+[network]
+blocksize = 65536
+fast_send = on
+own_ip = ""
+port_range = ""
+send_ack = on
+speed_limit_recv = 0
+speed_limit_send = 0
+timeout = 300
+
+[file]
+auto_accept_chats = off
+auto_accept_files = off
+auto_accept_nicks = ""
+auto_check_crc32 = off
+auto_rename = on
+auto_resume = on
+convert_spaces = on
+download_path = "%h/xfer"
+download_temporary_suffix = ".part"
+upload_path = "~"
+use_nick_in_filename = on
--- a/weechat/.agignore	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-logs/
-urls.log
Binary file weechat/GandiStandardSSLCA.crt has changed
--- a/weechat/alias.conf	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,45 +0,0 @@
-#
-# weechat -- alias.conf
-#
-# WARNING: It is NOT recommended to edit this file by hand,
-# especially if WeeChat is running.
-#
-# Use commands like /set or /fset to change settings in WeeChat.
-#
-# For more info, see: https://weechat.org/doc/quickstart/
-#
-
-[cmd]
-AAWAY = "allserv /away"
-AME = "allchan /me"
-AMSG = "allchan /msg *"
-ANICK = "allserv /nick"
-b = "/buffer"
-BYE = "quit"
-C = "buffer clear"
-CHAT = "dcc chat"
-CL = "buffer clear"
-CLOSE = "buffer close"
-EXIT = "quit"
-IG = "ignore"
-J = "join"
-K = "kick"
-KB = "kickban"
-LEAVE = "part"
-M = "msg"
-MUB = "unban *"
-N = "names"
-Q = "query"
-REDRAW = "window refresh"
-SAY = "msg *"
-SIGNOFF = "quit"
-T = "topic"
-UB = "unban"
-V = "command core version"
-W = "who"
-WC = "window merge"
-WI = "whois"
-WII = "whois $1 $1"
-WW = "whowas"
-
-[completion]
--- a/weechat/aspell.conf	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,33 +0,0 @@
-#
-# weechat -- aspell.conf
-#
-# WARNING: It is NOT recommended to edit this file by hand,
-# especially if WeeChat is running.
-#
-# Use /set or similar command to change settings in WeeChat.
-#
-# For more info, see: https://weechat.org/doc/quickstart
-#
-
-[color]
-misspelled = lightred
-suggestion = default
-suggestion_delimiter_dict = cyan
-suggestion_delimiter_word = cyan
-
-[check]
-commands = "ame,amsg,away,command,cycle,kick,kickban,me,msg,notice,part,query,quit,topic"
-default_dict = "en"
-during_search = off
-enabled = off
-real_time = off
-suggestions = -1
-word_min_length = 2
-
-[dict]
-
-[look]
-suggestion_delimiter_dict = " / "
-suggestion_delimiter_word = ","
-
-[option]
--- a/weechat/autosort.conf	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-#
-# weechat -- autosort.conf
-#
-# WARNING: It is NOT recommended to edit this file by hand,
-# especially if WeeChat is running.
-#
-# Use commands like /set or /fset to change settings in WeeChat.
-#
-# For more info, see: https://weechat.org/doc/quickstart/
-#
-
-[sorting]
-case_sensitive = off
-debug_log = off
-replacements = "[]"
-rules = "[["core", 0], ["irc", 2], ["*", 1], ["irc.irc_raw", 0], ["irc.server", 1]]"
-signal_delay = 5
-signals = "buffer_opened buffer_merged buffer_unmerged buffer_renamed"
-sort_limit = 100
-sort_on_config_change = on
-
-[v3]
-helpers = "{"core_first": "${if:${buffer.full_name}!=core.weechat}", "irc_raw_first": "${if:${buffer.full_name}!=irc.irc_raw}", "irc_raw_last": "${if:${buffer.full_name}==irc.irc_raw}", "hashless_name": "${info:autosort_replace,#,,${info:autosort_escape,${buffer.name}}}", "script_or_plugin": "${if:${script_name}?${script_name}:${plugin}}"}"
-rules = "["${core_first}", "${info:autosort_order,${info:autosort_escape,${script_or_plugin}},core,*,irc,bitlbee,matrix,slack}", "${script_or_plugin}", "${irc_raw_first}", "${server}", "${info:autosort_order,${type},server,*,channel,private}", "${hashless_name}", "${buffer.full_name}"]"
--- a/weechat/buffers.conf	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,77 +0,0 @@
-#
-# weechat -- buffers.conf
-#
-# WARNING: It is NOT recommended to edit this file by hand,
-# especially if WeeChat is running.
-#
-# Use /set or similar command to change settings in WeeChat.
-#
-# For more info, see: https://weechat.org/doc/quickstart
-#
-
-[color]
-current_bg = green
-current_fg = black
-default_bg = default
-default_fg = default
-hotlist_highlight_bg = default
-hotlist_highlight_fg = *magenta
-hotlist_low_bg = default
-hotlist_low_fg = white
-hotlist_message_bg = default
-hotlist_message_fg = green
-hotlist_private_bg = default
-hotlist_private_fg = *magenta
-none_channel_bg = default
-none_channel_fg = 240
-number = green
-number_char = green
-prefix_bufname = default
-queries_default_bg = default
-queries_default_fg = default
-queries_highlight_bg = default
-queries_highlight_fg = default
-queries_message_bg = default
-queries_message_fg = default
-suffix_bufname = default
-whitelist_default_bg = default
-whitelist_default_fg = default
-whitelist_highlight_bg = default
-whitelist_highlight_fg = default
-whitelist_low_bg = default
-whitelist_low_fg = default
-whitelist_message_bg = default
-whitelist_message_fg = default
-whitelist_private_bg = default
-whitelist_private_fg = default
-
-[look]
-core_to_front = off
-detach = 0
-detach_buffer_immediately = ""
-detach_display_window_number = off
-detach_displayed_buffers = on
-detach_free_content = off
-detach_query = off
-hide_merged_buffers = none
-hotlist_counter = off
-immune_detach_buffers = ""
-indenting = on
-indenting_number = on
-jump_prev_next_visited_buffer = off
-mark_inactive = off
-mouse_move_buffer = on
-name_crop_suffix = "+"
-name_size_max = 0
-number_char = " "
-prefix = off
-prefix_bufname = ""
-prefix_empty = on
-prefix_for_query = ""
-short_names = on
-show_lag = off
-show_number = on
-sort = number
-suffix_bufname = ""
-toogle_bar = on
-whitelist_buffers = ""
--- a/weechat/buflist.conf	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-#
-# weechat -- buflist.conf
-#
-# WARNING: It is NOT recommended to edit this file by hand,
-# especially if WeeChat is running.
-#
-# Use commands like /set or /fset to change settings in WeeChat.
-#
-# For more info, see: https://weechat.org/doc/quickstart/
-#
-
-[look]
-add_newline = on
-auto_scroll = 50
-display_conditions = "${buffer.hidden}==0"
-enabled = on
-mouse_jump_visited_buffer = off
-mouse_move_buffer = on
-mouse_wheel = on
-nick_prefix = off
-nick_prefix_empty = on
-signals_refresh = ""
-sort = "number,-active"
-use_items = 1
-
-[format]
-buffer = "${format_number}${indent}${format_nick_prefix}${color_hotlist}${format_name}"
-buffer_current = "${color:,blue}${format_buffer}"
-hotlist = " ${color:green}(${hotlist}${color:green})"
-hotlist_highlight = "${color:magenta}"
-hotlist_low = "${color:white}"
-hotlist_message = "${color:green}"
-hotlist_none = "${color:default}"
-hotlist_private = "${color:magenta}"
-hotlist_separator = "${color:default},"
-indent = "  "
-lag = " ${color:green}[${color:brown}${lag}${color:green}]"
-name = "${name}"
-nick_prefix = "${color_nick_prefix}${nick_prefix}"
-number = "${color:green}${number}${if:${number_displayed}?.: }"
-tls_version = " ${color:default}(${if:${tls_version}==TLS1.3?${color:green}:${if:${tls_version}==TLS1.2?${color:yellow}:${color:red}}}${translate:${tls_version}}${color:default})"
--- a/weechat/charset.conf	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-#
-# weechat -- charset.conf
-#
-# WARNING: It is NOT recommended to edit this file by hand,
-# especially if WeeChat is running.
-#
-# Use commands like /set or /fset to change settings in WeeChat.
-#
-# For more info, see: https://weechat.org/doc/quickstart/
-#
-
-[default]
-decode = "iso-8859-1"
-encode = ""
-
-[decode]
-
-[encode]
--- a/weechat/exec.conf	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,19 +0,0 @@
-#
-# weechat -- exec.conf
-#
-# WARNING: It is NOT recommended to edit this file by hand,
-# especially if WeeChat is running.
-#
-# Use commands like /set or /fset to change settings in WeeChat.
-#
-# For more info, see: https://weechat.org/doc/quickstart/
-#
-
-[command]
-default_options = ""
-purge_delay = 0
-shell = "${env:SHELL}"
-
-[color]
-flag_finished = lightred
-flag_running = lightgreen
--- a/weechat/fifo.conf	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-#
-# weechat -- fifo.conf
-#
-# WARNING: It is NOT recommended to edit this file by hand,
-# especially if WeeChat is running.
-#
-# Use commands like /set or /fset to change settings in WeeChat.
-#
-# For more info, see: https://weechat.org/doc/quickstart/
-#
-
-[file]
-enabled = on
-path = "%h/weechat_fifo"
--- a/weechat/fset.conf	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,96 +0,0 @@
-#
-# weechat -- fset.conf
-#
-# WARNING: It is NOT recommended to edit this file by hand,
-# especially if WeeChat is running.
-#
-# Use commands like /set or /fset to change settings in WeeChat.
-#
-# For more info, see: https://weechat.org/doc/quickstart/
-#
-
-[look]
-auto_refresh = "*"
-auto_unmark = off
-condition_catch_set = "${count} >= 1"
-export_help_default = on
-format_number = 1
-marked_string = "*"
-scroll_horizontal = 10
-show_plugins_desc = off
-sort = "~name"
-unmarked_string = " "
-use_color_value = off
-use_keys = on
-use_mute = off
-
-[format]
-export_help = "# ${description2}"
-export_option = "/set ${name} ${quoted_value}"
-export_option_null = "/unset ${name}"
-option1 = ""
-option2 = "${marked} ${name}  ${type}  ${value2}${newline}  ${empty_name}  ${_default_value}${color:darkgray} -- ${min}..${max}${newline}  ${empty_name}  ${description}"
-
-[color]
-default_value = default
-default_value_selected = white
-description = default
-description_selected = white
-file = default
-file_changed = brown
-file_changed_selected = yellow
-file_selected = white
-help_default_value = white
-help_description = default
-help_name = white
-help_quotes = darkgray
-help_values = default
-index = cyan
-index_selected = lightcyan
-line_marked_bg1 = default
-line_marked_bg2 = default
-line_selected_bg1 = blue
-line_selected_bg2 = red
-marked = brown
-marked_selected = yellow
-max = default
-max_selected = white
-min = default
-min_selected = white
-name = default
-name_changed = brown
-name_changed_selected = yellow
-name_selected = white
-option = default
-option_changed = brown
-option_changed_selected = yellow
-option_selected = white
-parent_name = default
-parent_name_selected = white
-parent_value = cyan
-parent_value_selected = lightcyan
-quotes = darkgray
-quotes_changed = default
-quotes_changed_selected = white
-quotes_selected = default
-section = default
-section_changed = brown
-section_changed_selected = yellow
-section_selected = white
-string_values = default
-string_values_selected = white
-title_count_options = cyan
-title_current_option = lightcyan
-title_filter = yellow
-title_marked_options = lightgreen
-title_sort = white
-type = green
-type_selected = lightgreen
-unmarked = default
-unmarked_selected = white
-value = cyan
-value_changed = brown
-value_changed_selected = yellow
-value_selected = lightcyan
-value_undef = magenta
-value_undef_selected = lightmagenta
Binary file weechat/icon.png has changed
--- a/weechat/logger.conf	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-#
-# weechat -- logger.conf
-#
-# WARNING: It is NOT recommended to edit this file by hand,
-# especially if WeeChat is running.
-#
-# Use commands like /set or /fset to change settings in WeeChat.
-#
-# For more info, see: https://weechat.org/doc/quickstart/
-#
-
-[look]
-backlog = 20
-backlog_conditions = ""
-
-[color]
-backlog_end = darkgray
-backlog_line = darkgray
-
-[file]
-auto_log = on
-color_lines = off
-flush_delay = 120
-fsync = off
-info_lines = off
-mask = "$plugin.$name.weechatlog"
-name_lower_case = on
-nick_prefix = " <"
-nick_suffix = "> "
-path = "%h/logs/"
-replacement_char = "_"
-time_format = "%Y-%m-%d %H:%M:%S"
-
-[level]
-
-[mask]
--- a/weechat/lua.conf	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-#
-# weechat -- lua.conf
-#
-# WARNING: It is NOT recommended to edit this file by hand,
-# especially if WeeChat is running.
-#
-# Use /set or similar command to change settings in WeeChat.
-#
-# For more info, see: https://weechat.org/doc/quickstart
-#
-
-[look]
-check_license = off
-eval_keep_context = on
--- a/weechat/perl.conf	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-#
-# weechat -- perl.conf
-#
-# WARNING: It is NOT recommended to edit this file by hand,
-# especially if WeeChat is running.
-#
-# Use commands like /set or /fset to change settings in WeeChat.
-#
-# For more info, see: https://weechat.org/doc/quickstart
-#
-
-[look]
-check_license = off
-eval_keep_context = on
--- a/weechat/perl/autoload/colorize_lines.pl	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,251 +0,0 @@
-#
-# Copyright (c) 2010-2013 by Nils Görs <weechatter@arcor.de>
-# Copyleft (ɔ) 2013 by oakkitten
-#
-# colors the channel text with nick color and also highlight the whole line
-# colorize_nicks.py script will be supported
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-#
-# with version 3.0 some options were renamed or have new possible values:
-# old:                  new:
-# avail_buffer          buffer
-# blacklist_channels    blacklist_buffers
-# highlight             new values
-
-# obsolete options:
-# buffer_autoset
-# hotlist_max_level_nicks_add
-# highlight_regex
-# highlight_words
-# shuffle
-# chat                  see option highlight
-
-# history:
-# 3.0: large part of script rewritten
-#      fix: works nicely with irc colors
-#      improved: highlight_regex and highlight_words work in a natural way
-#      removed: command /colorize_lines
-#      removed: option shuffle
-# 2.2: fix: regex with [tab] in message (patch by sqrrl)
-# 2.1: fix: changing highlight color did not apply messages already displayed (reported by rafi_)
-# 2.0: fix: debugging weechat::print() removed (thanks demure)
-# 1.9: fix: display bug with nick_mode
-# 1.8  add: option "use_irc_colors" (requested by Zertap)
-#      fix: empty char for nick_mode was used, even when "irc.look.nick_mode_empty" was OFF (reported by FlashCode)
-# 1.7: fix: broken lines in dcc chat (reported by equatorping)
-# 1.6: improved: wildcard "*" can be used for server and/or nick. (requested by ldvx)
-#    : add: new value, "only", for option "own_lines" (read help!)
-# 1.5: sync: option weechat.look.nickmode changed in 0.3.9 to "irc.look.nick_mode"
-# 1.4: fix: whole ctcp message was display in prefix (reported by : Mkaysi)
-# 1.3: fix: now using weechat::buffer_get_string() instead of regex to prevent problems with dots inside server-/channelnames (reported by surfhai)
-# 1.2: add: hook_modifier("colorize_lines") to use colorize_lines with another script.
-#    : fix: regex was too greedy and also hit tag "prefix_nick_ccc"
-# 1.1: fix:  problems with temporary server (reported by nand`)
-#    : improved: using weechat_string_has_highlight()
-# 1.0: fix: irc.look.nick_prefix wasn't supported
-# 0.9: added: option "own_nick" (idea by travkin)
-#    : new value (always) for option highlight
-#    : clean up code
-# 0.8.1: fix: regex()
-# 0.8: added: option "avail_buffer" and "nicks" (please read help-page) (suggested by ldvx)
-#    : fix: blacklist_buffers wasn't load at start
-#    : fix: nick_modes wasn't displayed since v0.7
-#    : rewrote init() routine
-#    : thanks to stfn for hint with unescaped variables in regex.
-# 0.7: fix: bug when irc.look.nick_suffix was set (reported and beta-testing by: hw2) (>= weechat 0.3.4)
-#      blacklist_buffers option supports servername
-#      clean up code
-# 0.6: code optimazations.
-#      rename of script (rainbow_text.pl -> colorize_lines.pl) (suggested by xt and flashcode)
-# 0.5: support of hotlist_max_level_nicks_add and weechat.color.chat_nick_colors (>= weechat 0.3.4)
-# 0.4: support of weechat.look.highlight_regex option (>= weechat 0.3.4)
-#    : support of weechat.look.highlight option
-#    : highlighted line did not work with "." inside servername
-#    ; internal "autoset" function fixed
-# 0.3: support of colorize_nicks.py implemented.
-#    : /me text displayed wrong nick colour (colour from suffix was used)
-#    : highlight messages will be checked case insensitiv
-# 0.2: supports highlight_words_add from buffer_autoset.py script (suggested: Emralegna)
-#    : correct look_nickmode colour will be used (bug reported by: Emralegna)
-#    : /me text will be coloured, too
-# 0.1: initial release
-#
-# Development is currently hosted at
-# https://github.com/weechatter/weechat-scripts
-
-# use Data::Dumper
-# $Data::Dumper::Useqq=1;
-
-use strict;
-my $prgname	= "colorize_lines";
-my $version	= "3.0";
-my $description	= "colors text in chat area with according nick color, including highlights";
-
-my %config = ("buffers"             => "all",       # all, channel, query
-              "blacklist_buffers"   => "",          # "a,b,c"
-              "lines"               => "on",
-              "highlight"           => "on",        # on, off, nicks
-              "nicks"               => "",          # "d,e,f", "/file"
-              "own_lines"           => "on",        # on, off, only
-);
-
-my %help_desc = ("buffers"             => "buffer type affected by the script (all/channel/query, default: all)",
-                 "blacklist_buffers"   => "comma-separated list of channels to be ignored (e.g. freenode.#weechat,*.#python)",
-                 "lines"               => "apply nickname color to the non-highlighted lines (off/on/nicks). the latter will limit highlighting to nicknames in option 'nicks'",
-                 "highlight"           => "apply highlight color to the highlighted lines (off/on/nicks). the latter will limit highlighting to nicknames in option 'nicks'",
-                 "nicks"               => "comma-separater list of nicks (e.g. freenode.cat,*.dog) OR file name starting with '/' (e.g. /file.txt). in the latter case, nicknames will get loaded from that file inside weechat folder (e.g. from ~/.weechat/file.txt). nicknames in file are newline-separated (e.g. freenode.dog\\n*.cat)",
-                 "own_lines"           => "apply nickname color to own lines (off/on/only). the latter turns off all other kinds of coloring altogether",
-);
-
-#################################################################################################### config
-
-# program starts here
-sub colorize_cb {
-    my ( $data, $modifier, $modifier_data, $string ) = @_;
-
-    # quit if it's not a privmsg or ctcp
-    # or we are not supposed to
-    if ((index($modifier_data,"irc_privmsg") == -1) ||
-        (index($modifier_data,"irc_ctcp") >= 0)) {
-        return $string;
-    }
-
-    # find buffer pointer
-    $modifier_data =~ m/([^;]*);([^;]*);/;
-    my $buffer = weechat::buffer_search($1, $2);
-    return $string if ($buffer eq "");
-
-    # find buffer name, server name
-    # return if buffer is in a blacklist
-    my $buffername = weechat::buffer_get_string($buffer, "name");
-    return $string if weechat::string_has_highlight($buffername, $config{blacklist_buffers});
-    my $servername = weechat::buffer_get_string($buffer, "localvar_server");
-
-    # find stuff between \t
-    $string =~ m/^([^\t]*)\t(.*)/;
-    my $left = $1;
-    my $right = $2;
-
-    # find nick of the sender
-    # find out if we are doing an action
-    my $nick = ($modifier_data =~ m/(^|,)nick_([^,]*)/) ? $2 : weechat::string_remove_color($left, "");
-    my $action = ($modifier_data =~ m/\birc_action\b/) ? 1 : 0;
-
-    ######################################## get color
-
-    my $color = "";
-    my $my_nick = weechat::buffer_get_string($buffer, "localvar_nick");
-    if ($my_nick eq $nick) {
-        # it's our own line
-        # process only if own_lines is "on" or "only" (i.e. not "off")
-        return $string if ($config{own_lines} eq "off");
-        $color = weechat::color("chat_nick_self");
-    } else {
-        # it's someone else's line
-        # don't process is own_lines are "only"
-        # in order to get correct matching, remove colors from the string
-        return $string if ($config{own_lines} eq "only");
-        my $right_nocolor = weechat::string_remove_color($right, "");
-        if (weechat::string_has_highlight($right_nocolor, weechat::buffer_string_replace_local_var($buffer, weechat::buffer_get_string($buffer, "highlight_words"))) ||
-            weechat::string_has_highlight($right_nocolor, weechat::config_string(weechat::config_get("weechat.look.highlight"))) ||
-            weechat::string_has_highlight_regex($right_nocolor, weechat::config_string(weechat::config_get("weechat.look.highlight_regex"))) ||
-            weechat::string_has_highlight_regex($right_nocolor, weechat::buffer_get_string($buffer, "highlight_regex"))
-           ) {
-            # we have a hilight! get a hilight color
-            # and replace the first occurance of coloring, that'd be nick color
-            # process only if highlight is "on" OR "nicks" & nick's in nicks
-            return $string if ($config{highlight} eq "off" ||
-                ($config{highlight} eq "nicks" && !weechat::string_has_highlight("$servername.$nick", $config{nicks})));
-            $color = weechat::color('chat_highlight');
-            $right =~ s/\31[^\31 ]+?\Q$nick/$color$nick/ if ($action);
-        } else {
-            # that's not a highlight
-            # process only if lines is "on" OR "nicks" & nick's in nicks
-            return $string if ($config{lines} eq "off" ||
-                ($config{lines} eq "nicks" && !weechat::string_has_highlight("$servername.$nick", $config{nicks})));
-            $color = weechat::info_get('irc_nick_color', $nick);
-        }
-    }
-
-    ######################################## inject colors and go!
-
-    my $out = "";
-    if ($action) {
-        # remove the first color reset - after * nick
-        # make other resets reset to our color
-        $right =~ s/\34//;
-        $right =~ s/\34/\34$color/g;
-        $out = $left . "\t" . $right . "\34"
-    } else {
-        # make other resets reset to our color
-        $right =~ s/\34/\34$color/g;
-        $out = $left . "\t" . $color . $right . "\34"
-    }
-    #weechat::print("", ""); weechat::print("", "\$str " . Dumper($string)); weechat::print("", "\$out " . Dumper($out));
-    return $out;
-}
-
-#################################################################################################### config
-
-# read nicknames if $conf{nisks} starts with /
-# after this, $conf{nisks} is of form a,b,c,d
-# if it doesnt start with /, assume it's already a,b,c,d
-sub nicklist_read {
-    return if (substr($config{nicks}, 0, 1) ne "/");
-    my $file = weechat::info_get("weechat_dir", "") . $config{nicks};
-    return unless -e $file;
-    my $nili = "";
-    open (WL, "<", $file) || DEBUG("$file: $!");
-    while (<WL>) {
-        chomp;                                                         # kill LF
-        $nili .= $_ . ",";
-    }
-    close WL;
-    chop $nili;                                                        # remove last ","
-    $config{nicks} = $nili;
-}
-
-# called when a config option ha been changed
-# $name = plugins.var.perl.$prgname.nicks etc
-sub toggle_config_by_set {
-    my ($pointer, $name, $value) = @_;
-    $name = substr($name,length("plugins.var.perl.$prgname."),length($name));
-    $config{$name} = lc($value);
-    nicklist_read() if ($name eq "nicks");
-}
-
-# read configuration from weechat OR
-#   set default options and
-#   set dectription if weechat >= 0.3.5
-# after done, read nicklist from file if needed
-sub init_config {
-    my $weechat_version = weechat::info_get('version_number', '') || 0;
-    foreach my $option (keys %config){
-        if (!weechat::config_is_set_plugin($option)) {
-            weechat::config_set_plugin($option, $config{$option});
-            weechat::config_set_desc_plugin($option, $help_desc{$option}) if ($weechat_version >= 0x00030500); # v0.3.5
-        } else {
-            $config{$option} = lc(weechat::config_get_plugin($option));
-        }
-    }
-    nicklist_read();
-}
-
-#################################################################################################### start
-
-weechat::register($prgname, "Nils Görs <weechatter\@arcor.de>", $version, "GPL3", $description, "", "");
-weechat::hook_modifier("500|weechat_print","colorize_cb", "");
-init_config();
-weechat::hook_config("plugins.var.perl.$prgname.*", "toggle_config_by_set", "");
--- a/weechat/python.conf	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-#
-# weechat -- python.conf
-#
-# WARNING: It is NOT recommended to edit this file by hand,
-# especially if WeeChat is running.
-#
-# Use commands like /set or /fset to change settings in WeeChat.
-#
-# For more info, see: https://weechat.org/doc/quickstart/
-#
-
-[look]
-check_license = off
-eval_keep_context = on
--- a/weechat/python/autoload/autosort.py	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-../autosort.py
\ No newline at end of file
--- a/weechat/python/autoload/brows.py	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,77 +0,0 @@
-import subprocess
-import os
-
-SCRIPT_NAME = 'brows'
-SCRIPT_AUTHOR = 'Steve Losh <steve@stevelosh.com>'
-SCRIPT_VERSION = '1.0'
-SCRIPT_LICENSE = 'MIT/X11'
-SCRIPT_DESC = 'Launch brows to view URLs'
-SCRIPT_COMMAND = 'brows'
-
-import_ok = True
-
-BROWS = os.environ.get('BROWS', 'brows')
-
-try:
-    import weechat
-except ImportError:
-    print('This is a weechat script, what are you doing, run it in weechat, jesus')
-    import_ok = False
-
-weechat_version = 0
-
-def hd(fn, name, obj, attr):
-    return fn(weechat.hdata_get(name), obj, attr)
-
-def hdp(name, obj, attr):
-    return hd(weechat.hdata_pointer, name, obj, attr)
-
-def hds(name, obj, attr):
-    return hd(weechat.hdata_string, name, obj, attr)
-
-def get_lines(buffer, numlines):
-    lines = hdp("buffer", buffer, "own_lines")
-    if not lines:
-        # null pointer wat do
-        return None
-
-    last_lines = []
-
-    line = hdp("lines", lines, "last_line")
-    for _ in range(numlines):
-        if not line:
-            # shit we're at the top of the buffer
-            break
-
-        data = hdp("line", line, "data")
-        msg = hds("line_data", data, "message")
-        msg = weechat.string_remove_color(msg, "")
-
-        last_lines.append(msg.strip())
-
-        line = hdp("line", line, "prev_line")
-
-    return last_lines
-
-def brows(data, buffer, args, numlines=100):
-    lines = get_lines(buffer, numlines)
-
-    proc = subprocess.Popen([BROWS], stdin=subprocess.PIPE)
-    proc.communicate(input='\n'.join(lines))
-    weechat.command("", "/window refresh")
-
-    return weechat.WEECHAT_RC_OK
-
-
-if __name__ == '__main__' and import_ok:
-    if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION,
-                        SCRIPT_LICENSE, SCRIPT_DESC, '', ''):
-        weechat_version = weechat.info_get('version_number', '') or 0
-        weechat.hook_command(
-            SCRIPT_COMMAND,
-            'Launch brows to view URLs',
-            '',
-            '',
-            '',
-            'brows',
-            '')
--- a/weechat/python/autoload/editor.py	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +0,0 @@
-import subprocess
-import os
-import tempfile
-
-SCRIPT_NAME = 'editor'
-SCRIPT_AUTHOR = 'Steve Losh <steve@stevelosh.com>'
-SCRIPT_VERSION = '1.0'
-SCRIPT_LICENSE = 'MIT/X11'
-SCRIPT_DESC = 'Launch an external editor to compose a message'
-SCRIPT_COMMAND = 'editor'
-
-import_ok = True
-
-EDITOR = os.environ.get('EDITOR','vim')
-
-try:
-    import weechat
-except ImportError:
-    print('This is a weechat script, what are you doing, run it in weechat, jesus')
-    import_ok = False
-
-weechat_version = 0
-
-
-def get_data(suffix, initial_data):
-    with tempfile.NamedTemporaryFile(suffix=".%s" % suffix, mode="w+") as tf:
-        tf.write(initial_data)
-        tf.flush()
-
-        if subprocess.call([EDITOR, tf.name]) != 0:
-            return None
-
-        # Reopen, because most editors do atomic write-tmp+rename saves which
-        # fucks with Python here.
-        tf.file.close()
-        with open(tf.name) as tf2:
-            return tf2.read()
-
-def editor(data, buffer, args):
-    suffix = args or "tmp"
-
-    line = weechat.buffer_get_string(buffer, "input")
-
-    data = get_data(suffix, line)
-    if data:
-        weechat.command(buffer, "/input delete_line")
-        weechat.command(buffer, data.strip())
-
-    weechat.command("", "/window refresh")
-
-    return weechat.WEECHAT_RC_OK
-
-
-if __name__ == '__main__' and import_ok:
-    if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION,
-                        SCRIPT_LICENSE, SCRIPT_DESC, '', ''):
-        weechat_version = weechat.info_get('version_number', '') or 0
-        weechat.hook_command(
-            SCRIPT_COMMAND,
-            'Open $EDITOR to compose a message',
-            '[file-extension]',
-            'If an argument is given, it will be used as the extension for the temporary file.',
-            '',
-            'editor',
-            '')
--- a/weechat/python/autoload/notify.py	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,29 +0,0 @@
-import weechat, subprocess
-
-SCRIPT_NAME = 'notify'
-SCRIPT_AUTHOR = 'Steve Losh <steve@stevelosh.com>'
-SCRIPT_VERSION = '0.0.1'
-SCRIPT_LICENSE = 'MIT'
-SCRIPT_DESC = 'notify-send for weechat'
-
-weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, '', '')
-
-weechat.hook_print('', 'irc_privmsg', '', 1, 'notify', '')
-
-def _notify(text):
-    subprocess.call(['notify-send', text])
-
-def notify(data, buffer, date, tags, displayed, highlight, prefix, message):
-    # ignore if it's yourself
-    own_nick = weechat.buffer_get_string(buffer, 'localvar_nick')
-
-    if prefix == own_nick or prefix == ('@%s' % own_nick):
-        return weechat.WEECHAT_RC_OK
-
-    if int(highlight):
-        channel = weechat.buffer_get_string(buffer, 'localvar_channel')
-        _notify('%s %s\n%s' % (prefix, channel, message))
-    elif 'notify_private' in tags:
-        _notify('%s [PM]\n%s' % (prefix, message))
-
-    return weechat.WEECHAT_RC_OK
--- a/weechat/python/autoload/quotes.py	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,124 +0,0 @@
-import subprocess, os
-
-SCRIPT_NAME = 'quotes'
-SCRIPT_AUTHOR = 'Steve Losh <steve@stevelosh.com>'
-SCRIPT_VERSION = '1.0'
-SCRIPT_LICENSE = 'MIT/X11'
-SCRIPT_DESC = 'Grab quotes and shove them into a text file.'
-SCRIPT_COMMAND = 'quo'
-SCRIPT_COMMAND_LONG = 'quoo'
-SCRIPT_COMMAND_COPY = 'cop'
-
-HOME = os.getenv("HOME")
-QUOTE_FILE = '%s/Dropbox/quotes.txt' % HOME
-COPY_FILE = '%s/.ircopy.irc' % HOME
-
-import_ok = True
-
-try:
-    import weechat
-except ImportError:
-    print('This is a weechat script, what are you doing, run it in weechat, jesus')
-    import_ok = False
-
-weechat_version = 0
-
-def hd(fn, name, obj, attr):
-    return fn(weechat.hdata_get(name), obj, attr)
-
-def hdp(name, obj, attr):
-    return hd(weechat.hdata_pointer, name, obj, attr)
-
-def hds(name, obj, attr):
-    return hd(weechat.hdata_string, name, obj, attr)
-
-def get_lines(buffer, numlines):
-    lines = hdp("buffer", buffer, "own_lines")
-    if not lines:
-        # null pointer wat do
-        return None
-
-    last_lines = []
-
-    line = hdp("lines", lines, "last_line")
-    for _ in range(numlines):
-        if not line:
-            # shit we're at the top of the buffer
-            break
-
-        data = hdp("line", line, "data")
-        msg = hds("line_data", data, "message")
-        pre = hds("line_data", data, "prefix")
-
-        msg = weechat.string_remove_color(msg, "")
-        pre = weechat.string_remove_color(pre, "")
-
-        last_lines.append("<%s> %s" % (pre.strip(), msg.strip()))
-
-        line = hdp("line", line, "prev_line")
-
-    last_lines.reverse()
-    return last_lines
-
-def quote_grab(data, buffer, args, numlines=15):
-    lines = get_lines(buffer, numlines)
-
-    with open(QUOTE_FILE, 'a') as f:
-        f.write("\n---\n")
-        f.write('\n'.join(lines))
-
-    subprocess.call(["nvim", QUOTE_FILE,
-                     # start at the bottom of the file
-                     "+",
-                     # move up N lines, where N is how many we appended
-                     "-c", "normal! %dk" % len(lines)])
-    weechat.command("", "/window refresh")
-
-    return weechat.WEECHAT_RC_OK
-
-def quote_grab_long(data, buffer, args):
-    return quote_grab(data, buffer, args, 75)
-
-def quote_grab_copy(data, buffer, args):
-    lines = get_lines(buffer, 1000)
-
-    with open(COPY_FILE, 'w') as f:
-        f.write('\n'.join(lines))
-
-    subprocess.call(["nvim", COPY_FILE,
-                     # start at the bottom of the file
-                     "+"])
-    weechat.command("", "/window refresh")
-
-    return weechat.WEECHAT_RC_OK
-
-if __name__ == '__main__' and import_ok:
-    if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION,
-                        SCRIPT_LICENSE, SCRIPT_DESC, '', ''):
-        weechat_version = weechat.info_get('version_number', '') or 0
-        weechat.hook_command(
-            SCRIPT_COMMAND,
-            'Appends the last 15 lines of the current buffer to your quotes '
-            'file and opens it in Vim so you can trim it.',
-            '',
-            '',
-            '',
-            'quote_grab',
-            '')
-        weechat.hook_command(
-            SCRIPT_COMMAND_LONG,
-            'Appends the last 75 lines of the current buffer to your quotes '
-            'file and opens it in Vim so you can trim it.',
-            '',
-            '',
-            '',
-            'quote_grab_long',
-            '')
-        weechat.hook_command(
-            SCRIPT_COMMAND_COPY,
-            'Open the last 1000 lines of the file in Vim so you can copy.',
-            '',
-            '',
-            '',
-            'quote_grab_copy',
-            '')
--- a/weechat/python/autoload/urlgrab.py	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,699 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# UrlGrab, for weechat version >= 0.3.0
-#
-#   Listens to all channels for URLs, collects them in a list, and launches
-#   them in your favourite web server on the local host or a remote server.
-#   Copies url to X11 clipboard via xsel
-#      (http://www.vergenet.net/~conrad/software/xsel)
-#
-# Usage:
-#
-#   The /url command provides access to all UrlGrab functions.  Run
-#   '/help url' for complete command usage.
-#
-#   In general, use '/url list' to list the entire url list for the current
-#   channel, and '/url <n>' to launch the nth url in the list.  For
-#   example, to launch the first (and most-recently added) url in the list,
-#   you would run '/url 1'
-#
-#   From the server window, you must specify a specific channel for the
-#   list and launch commands, for example:
-#     /url list weechat
-#     /url 3 weechat
-#
-# Configuration:
-#
-#   The '/url set' command lets you get and set the following options:
-#
-#   historysize
-#     The maximum number of URLs saved per channel.  Default is 10
-#
-#   method
-#     Must be one of 'local' or 'remote' - Defines how URLs are launched by
-#     the script.  If 'local', the script will run 'localcmd' on the host.
-#     If 'remote', the script will run 'remotessh remotehost remotecmd' on
-#     the local host which should normally use ssh to connect to another
-#     host and run the browser command there.
-#
-#   localcmd
-#     The command to run on the local host to launch URLs in 'local' mode.
-#     The string '%s' will be replaced with the URL.  The default is
-#     'firefox %s'.
-#
-#   remotessh
-#     The command (and arguments) used to connect to the remote host for
-#     'remote' mode.  The default is 'ssh -x' which will connect as the
-#     current username via ssh and disable X11 forwarding.
-#
-#   remotehost
-#     The remote host to which we will connect in 'remote' mode.  For ssh,
-#     this can just be a hostname or 'user@host' to specify a username
-#     other than your current login name.  The default is 'localhost'.
-#
-#   remotecmd
-#     The command to execute on the remote host for 'remote' mode.  The
-#     default is 'bash -c "DISPLAY=:0.0 firefox '%s'"'  Which runs bash, sets
-#     up the environment to display on the remote host's main X display,
-#     and runs firefox.  As with 'localcmd', the string '%s' will be
-#     replaced with the URL.
-#
-#   cmdoutput
-#     The file where the command output (if any) is saved.  Overwritten
-#     each time you launch a new URL.  Default is ~/.weechat/urllaunch.log
-#
-#   default
-#     The command that will be run if no arguemnts to /url are given.
-#     Default is show
-#
-# Requirements:
-#
-#  - Designed to run with weechat version 0.3 or better.
-#      http://www.weechat.org/
-#
-# Acknowlegements:
-#
-#  - Based on an earlier version called 'urlcollector.py' by 'kolter' of
-#    irc.freenode.net/#weechat Honestly, I just cleaned up the code a bit and
-#    made the settings a little more useful (to me).
-#
-#  - With changes by Leonid Evdokimov (weechat at darkk dot net another dot ru):
-#    http://darkk.net.ru/weechat/urlgrab.py
-#    v1.1:  added better handling of dead zombie-childs
-#           added parsing of private messages
-#           added default command setting
-#           added parsing of scrollback buffers on load
-#    v1.2:  `historysize` was ignored
-#
-#  - With changes by ExclusivE (exclusive_tm at mail dot ru):
-#    v1.3: X11 clipboard support
-#
-#  - V1.4 Just ported it over to weechat 0.2.7  drubin AT smartcube dot co dot za
-#  - V1.5  1) I created a logging feature for urls, Time, Date, buffer, and url.
-#           2) Added selectable urls support, similar to the iset plugin (Thanks FlashCode)
-#           3) Colors/formats are configuarable.
-#           4) browser now uses hook_process (Please test with remote clients)
-#           5) Added /url open http://url.com functionality
-#           6) Changed urls detection to use regexpressions so should be much better
-#                Thanks to xt of #weechat bassed on on urlbar.py
-#  - V1.6 FlashCode <flashcode@flashtux.org>: Increase timeout for hook_process
-#         (from 1 second to 1 minute)
-#  - V1.7 FlashCode <flashcode@flashtux.org>: Update WeeChat site
-#  - V1.8 drubin <drubin [at] smartcube . co.za>:
-#           - Changed remote cmd to be single option
-#           - Added scrolling on up and down arrow keys for /url show
-#           - Changed remotecmd to include options with public/private keys password auth doesn't work
-#  - V1.9 Specimen <spinifer [at] gmail . com>:
-#           - Changed the default command when /url is run with no arguments to 'show'
-#           - Removed '/url help' command, because /help <command> is the standard way
-#  - V2.0 Xilov: replace "/url help" by "/help url"
-#  - V2.1 nand: Changed default: firefox %s to firefox '%s' (localcmd)
-#  - V2.2 Sébastien Helleu <flashcode@flashtux.org>: fix reload of config file
-#  - V2.3 nand: Allowed trailing )s for unmatched (s in URLs
-#  - V2.4 nand: Escaped URLs via URL-encoding instead of shell escaping, fixes '
-#  - V2.5 nand: Fixed some URLs that got incorrectly mangled by escaping
-#  - V2.6 nesthib: Fixed escaping of "="
-#                  Added missing quotes in default parameter (firefox '%s')
-#                  Removed the mix of tabs and spaces in the file indentation
-#  - V2.7 dobbymoodge <john.w.lamb [at] gmail . com>
-#                     ( https://github.com/dobbymoodge/ ):
-#           - Added 'copycmd' setting, users can set command to pipe into
-#             for '/url copy'
-#  - V2.8 Simmo Saan <simmo.saan@gmail.com>:
-#           - Changed print hook to ignore filtered lines
-#  - V2.9 Dominik Heidler <dominik@heidler.eu>:
-#           - Updated script for python3 support (now python2 and 3 are both supported)
-#  - V3.0 Sébastien Helleu <flashcode@flashtux.org>:
-#           - Fix python 3 compatibility (replace "has_key" by "in")
-#
-# Copyright (C) 2005 David Rubin <drubin AT smartcube dot co dot za>
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
-# USA.
-#
-
-from __future__ import print_function
-import sys
-import os
-try:
-    import weechat
-    import_ok = True
-except:
-    print("This script must be run under WeeChat.")
-    print("Get WeeChat now at: http://www.weechat.org/")
-    import_ok = False
-import subprocess
-import time
-try:
-    from urllib import quote
-except ImportError:
-    from urllib.parse import quote
-import re
-try:
-    from UserDict import UserDict
-except ImportError:
-    from collections import UserDict
-
-
-octet = r'(?:2(?:[0-4]\d|5[0-5])|1\d\d|\d{1,2})'
-ipAddr = r'%s(?:\.%s){3}' % (octet, octet)
-# Base domain regex off RFC 1034 and 1738
-label = r'[0-9a-z][-0-9a-z]*[0-9a-z]?'
-domain = r'%s(?:\.%s)*\.[a-z][-0-9a-z]*[a-z]?' % (label, label)
-urlRe = re.compile(r'(\w+://(?:%s|%s)(?::\d+)?(?:/[^\]>\s]*)?)' % (domain, ipAddr), re.I)
-
-
-SCRIPT_NAME    = "urlgrab"
-SCRIPT_AUTHOR  = "David Rubin <drubin [At] smartcube [dot] co [dot] za>"
-SCRIPT_VERSION = "3.0"
-SCRIPT_LICENSE = "GPL"
-SCRIPT_DESC    = "Url functionality Loggin, opening of browser, selectable links"
-CONFIG_FILE_NAME= "urlgrab"
-SCRIPT_COMMAND = "url"
-
-
-def stripParens(url):
-    return dropChar(')', url.count(')') - url.count('('), url[::-1])[::-1]
-
-def dropChar(c, n, xs):
-    if n == 0 or xs == []:
-        return xs
-    elif xs[0] == c:
-        return dropChar(c, n-1, xs[1:])
-    else:
-        return xs
-
-def urlGrabPrint(message):
-    bufferd=weechat.current_buffer()
-    if urlGrabSettings['output_main_buffer'] == 1 :
-        weechat.prnt("","[%s] %s" % ( SCRIPT_NAME, message ) )
-    else :
-        weechat.prnt(bufferd,"[%s] %s" % ( SCRIPT_NAME, message ) )
-
-def hashBufferName(bufferp):
-    if not weechat.buffer_get_string(bufferp, "short_name"):
-        bufferd = weechat.buffer_get_string(bufferp, "name")
-    else:
-        bufferd = weechat.buffer_get_string(bufferp, "short_name")
-    return bufferd
-
-def ug_config_reload_cb(data, config_file):
-    """ Reload configuration file. """
-    return weechat.config_reload(config_file)
-
-class UrlGrabSettings(UserDict):
-    def __init__(self):
-        UserDict.__init__(self)
-        self.data = {}
-        self.config_file = weechat.config_new(CONFIG_FILE_NAME,
-                                        "ug_config_reload_cb", "")
-        if not self.config_file:
-            return
-
-        section_color = weechat.config_new_section(
-            self.config_file, "color", 0, 0, "", "", "", "", "", "",
-                     "", "", "", "")
-        section_default = weechat.config_new_section(
-            self.config_file, "default", 0, 0, "", "", "", "", "", "",
-                     "", "", "", "")
-
-        self.data['color_buffer']=weechat.config_new_option(
-            self.config_file, section_color,
-            "color_buffer", "color", "Color to display buffer name", "", 0, 0,
-            "red", "red", 0, "", "", "", "", "", "")
-
-        self.data['color_url']=weechat.config_new_option(
-            self.config_file, section_color,
-            "color_url", "color", "Color to display urls", "", 0, 0,
-            "blue", "blue", 0, "", "", "", "", "", "")
-
-        self.data['color_time']=weechat.config_new_option(
-            self.config_file, section_color,
-            "color_time", "color", "Color to display time", "", 0, 0,
-            "cyan", "cyan", 0, "", "", "", "", "", "")
-
-        self.data['color_buffer_selected']=weechat.config_new_option(
-            self.config_file, section_color,
-            "color_buffer_selected", "color",
-            "Color to display buffer selected name", "", 0, 0, "red", "red",
-            0, "", "", "", "", "", "")
-
-        self.data['color_url_selected']=weechat.config_new_option(
-            self.config_file, section_color,
-            "color_url_selected", "color", "Color to display url selected",
-             "", 0, 0, "blue", "blue", 0, "", "", "", "", "", "")
-
-        self.data['color_time_selected']=weechat.config_new_option(
-            self.config_file, section_color,
-            "color_time_selected", "color", "Color to display tim selected",
-            "", 0, 0, "cyan", "cyan", 0, "", "", "", "", "", "")
-
-        self.data['color_bg_selected']=weechat.config_new_option(
-            self.config_file, section_color,
-            "color_bg_selected", "color", "Background for selected row", "", 0, 0,
-            "green", "green", 0, "", "", "", "", "", "")
-
-        self.data['historysize']=weechat.config_new_option(
-            self.config_file, section_default,
-            "historysize", "integer", "Max number of urls to store per buffer",
-            "", 0, 999, "10", "10", 0, "", "", "", "", "", "")
-
-        self.data['method']=weechat.config_new_option(
-            self.config_file, section_default,
-            "method", "string", """Where to launch URLs
-            If 'local', runs %localcmd%.
-            If 'remote' runs the following command:
-            '%remodecmd%'""", "", 0, 0,
-            "local", "local", 0, "", "", "", "", "", "")
-
-        self.data['copycmd']=weechat.config_new_option(
-            self.config_file, section_default,
-            "copycmd", "string",
-            "Command to pipe into for 'url copy'. "
-            "E.g. to copy into the CLIPBOARD buffer "
-            "instead of PRIMARY, you can use 'xsel -b "
-            "-i' here.", "", 0, 0,
-            "xsel -i", "xsel -i", 0, "", "", "", "", "", "")
-
-        self.data['localcmd']=weechat.config_new_option(
-            self.config_file, section_default,
-            "localcmd", "string", """Local command to execute""", "", 0, 0,
-            "firefox '%s'", "firefox '%s'", 0, "", "", "", "", "", "")
-
-        remotecmd="ssh -x localhost -i ~/.ssh/id_rsa -C \"export DISPLAY=\":0.0\" &&  firefox '%s'\""
-        self.data['remotecmd']=weechat.config_new_option(
-            self.config_file, section_default,
-            "remotecmd", "string", remotecmd, "", 0, 0,
-            remotecmd, remotecmd, 0, "", "", "", "", "", "")
-
-        self.data['url_log']=weechat.config_new_option(
-            self.config_file, section_default,
-            "url_log", "string", """log location""", "", 0, 0,
-            "~/.weechat/urls.log", "~/.weechat/urls.log", 0, "", "", "", "", "", "")
-
-        self.data['time_format']=weechat.config_new_option(
-            self.config_file, section_default,
-            "time_format", "string", """TIme format""", "", 0, 0,
-            "%H:%M:%S", "%H:%M:%S", 0, "", "", "", "", "", "")
-
-        self.data['output_main_buffer']=weechat.config_new_option(
-            self.config_file, section_default,
-            "output_main_buffer", "boolean",
-            """Print text to main buffer or current one""", "", 0, 0, "1", "1",
-             0, "", "", "", "", "", "")
-        weechat.config_read(self.config_file)
-
-    def __getitem__(self, key):
-        if key == "historysize":
-            return weechat.config_integer(self.data[key])
-        elif key == 'output_main_buffer':
-            return weechat.config_boolean(self.data[key])
-        #elif key.startswith('color'):
-        #    return weechat.config_color(self.data[key])
-        else:
-            return weechat.config_string(self.data[key])
-
-    def prnt(self, name, verbose = True):
-        weechat.prnt( ""," %s = %s" % (name.ljust(11), self.data[name]) )
-
-    def prntall(self):
-        for key in self.names():
-            self.prnt(key, verbose = False)
-
-    def createCmd(self, url):
-        str =""
-        if self['method'] == 'remote':
-            str = self['remotecmd']  % url
-        else:
-            str =  self['localcmd']  % url
-        return str
-
-class UrlGrabber:
-    def __init__(self, historysize):
-        # init
-        self.urls = {}
-        self.globalUrls = []
-        self.historysize = 5
-        # control
-        self.setHistorysize(historysize)
-
-    def setHistorysize(self, count):
-        if count > 1:
-            self.historysize = count
-
-    def getHistorysize(self):
-        return self.historysize
-
-    def addUrl(self, bufferp,url ):
-        global urlGrabSettings
-        self.globalUrls.insert(0,{"buffer":bufferp,
-            "url":url, "time":time.strftime(urlGrabSettings["time_format"])})
-        #Log urls only if we have set a log path.
-        if urlGrabSettings['url_log']:
-            try :
-                index = self.globalUrls[0]
-                logfile = os.path.expanduser(urlGrabSettings['url_log'])
-                dout = open(logfile, "a")
-                dout.write("%s %s %s\n" % (index['time'],
-                                           index['buffer'], index['url']))
-                dout.close()
-            except :
-                print("failed to log url check that %s is valid path" % urlGrabSettings['url_log'])
-                pass
-
-        # check for buffer
-        if not bufferp in self.urls:
-            self.urls[bufferp] = []
-        # add url
-        if url in self.urls[bufferp]:
-            self.urls[bufferp].remove(url)
-        self.urls[bufferp].insert(0, url)
-        # removing old urls
-        while len(self.urls[bufferp]) > self.historysize:
-            self.urls[bufferp].pop()
-
-    def hasIndex( self, bufferp, index ):
-        return bufferp in self.urls and \
-                len(self.url[bufferp]) >= index
-
-    def hasBuffer( self, bufferp ):
-        return bufferp in self.urls
-
-
-    def getUrl(self, bufferp, index):
-        url = ""
-        if  bufferp in self.urls:
-            if len(self.urls[bufferp]) >= index:
-                    url = self.urls[bufferp][index-1]
-        return url
-
-
-    def prnt(self, buff):
-        found = True
-        if buff in self.urls:
-            if len(self.urls[buff]) > 0:
-                i = 1
-                for url in self.urls[buff]:
-                    urlGrabPrint("--> " + str(i) + " : " + url)
-                    i += 1
-            else:
-                found = False
-        elif buff == "*":
-            for b in self.urls.keys():
-              self.prnt(b)
-        else:
-            found = False
-
-        if not found:
-            urlGrabPrint(buff + ": no entries")
-
-def urlGrabCheckMsgline(bufferp, message, isdisplayed):
-    global urlGrab, max_buffer_length
-    if not message or isdisplayed == 0:
-        return
-    # Ignore output from 'tinyurl.py' and our selfs
-    if ( message.startswith( "[AKA] http://tinyurl.com" ) or
-        message.startswith("[urlgrab]") ):
-        return
-    # Check for URLs
-    for url in urlRe.findall(message):
-        urlGrab.addUrl(bufferp,stripParens(url))
-        if max_buffer_length < len(bufferp):
-            max_buffer_length = len(bufferp)
-        if urlgrab_buffer:
-            refresh()
-
-
-def urlGrabCheck(data, bufferp, uber_empty, tagsn, isdisplayed, ishilight, prefix, message):
-    urlGrabCheckMsgline(hashBufferName(bufferp), message, isdisplayed)
-    return weechat.WEECHAT_RC_OK
-
-def urlGrabCopy(bufferd, index):
-    global urlGrab
-    if bufferd == "":
-        urlGrabPrint( "No current channel, you must activate one" )
-    elif not urlGrab.hasBuffer(bufferd):
-        urlGrabPrint("No URL found - Invalid channel")
-    else:
-        if index <= 0:
-            urlGrabPrint("No URL found - Invalid index")
-            return
-        url = urlGrab.getUrl(bufferd,index)
-    if url == "":
-        urlGrabPrint("No URL found - Invalid index")
-    else:
-        try:
-            pipe = os.popen(urlGrabSettings['copycmd'],"w")
-            pipe.write(url)
-            pipe.close()
-            urlGrabPrint("Url: %s gone to clipboard." % url)
-        except:
-            urlGrabPrint("Url: %s failed to copy to clipboard." % url)
-
-def urlGrabOpenUrl(url):
-    global urlGrab, urlGrabSettings
-    argl = urlGrabSettings.createCmd( quote(url, '/:#%?&+=') )
-    weechat.hook_process(argl,60000, "ug_open_cb", "")
-
-def ug_open_cb(data, command, code, out, err):
-    # weechat.prnt("", out)
-    # weechat.prnt("", err)
-    return weechat.WEECHAT_RC_OK
-
-
-def urlGrabOpen(bufferd, index):
-    global urlGrab, urlGrabSettings
-    if bufferd == "":
-        urlGrabPrint( "No current channel, you must specify one" )
-    elif not urlGrab.hasBuffer(bufferd) :
-        urlGrabPrint("No URL found - Invalid channel")
-    else:
-        if index <= 0:
-            urlGrabPrint("No URL found - Invalid index")
-            return
-        url =  urlGrab.getUrl(bufferd,index)
-        if url == "":
-            urlGrabPrint("No URL found - Invalid index")
-        else:
-            urlGrabPrint("loading %s %sly" % (url, urlGrabSettings["method"]))
-            urlGrabOpenUrl (url)
-
-def urlGrabList( args ):
-    global urlGrab
-    if len(args) == 0:
-        buf = hashBufferName(weechat.current_buffer())
-    else:
-        buf = args[0]
-    if buf == "" or buf == "all":
-        buf = "*"
-    urlGrab.prnt(buf)
-
-
-def urlGrabMain(data, bufferp, args):
-    if args[0:2] == "**":
-        keyEvent(data, bufferp, args[2:])
-        return weechat.WEECHAT_RC_OK
-
-    bufferd = hashBufferName(bufferp)
-    largs = args.split(" ")
-    #strip spaces
-    while '' in largs:
-        largs.remove('')
-    while ' ' in largs:
-        largs.remove(' ')
-    if len(largs) == 0 or largs[0] == "show":
-        if not urlgrab_buffer:
-            init()
-        refresh()
-        weechat.buffer_set(urlgrab_buffer, "display", "1")
-    elif largs[0] == 'open' and len(largs) == 2:
-        urlGrabOpenUrl(largs[1])
-    elif largs[0] == 'list':
-        urlGrabList( largs[1:] )
-    elif largs[0] == 'copy':
-        if len(largs) > 1:
-            no = int(largs[1])
-            urlGrabCopy(bufferd, no)
-        else:
-            urlGrabCopy(bufferd,1)
-    else:
-        try:
-            no = int(largs[0])
-            if len(largs) > 1:
-                urlGrabOpen(largs[1], no)
-            else:
-                urlGrabOpen(bufferd, no)
-        except ValueError:
-            #not a valid number so try opening it as a url..
-            for url in urlRe.findall(largs[0]):
-                urlGrabOpenUrl(stripParens(url))
-            urlGrabPrint( "Unknown command '%s'.  Try '/help url' for usage" % largs[0])
-    return weechat.WEECHAT_RC_OK
-
-def buffer_input(*kwargs):
-    return weechat.WEECHAT_RC_OK
-
-def buffer_close(*kwargs):
-    global urlgrab_buffer
-    urlgrab_buffer =  None
-    return weechat.WEECHAT_RC_OK
-
-def keyEvent (data, bufferp, args):
-    global urlGrab , urlGrabSettings, urlgrab_buffer, current_line
-    if args == "refresh":
-        refresh()
-    elif args == "up":
-        if current_line > 0:
-            current_line = current_line -1
-            refresh_line (current_line + 1)
-            refresh_line (current_line)
-            ugCheckLineOutsideWindow()
-    elif args == "down":
-         if current_line < len(urlGrab.globalUrls) - 1:
-            current_line = current_line +1
-            refresh_line (current_line - 1)
-            refresh_line (current_line)
-            ugCheckLineOutsideWindow()
-    elif args == "scroll_top":
-        temp_current = current_line
-        current_line =  0
-        refresh_line (temp_current)
-        refresh_line (current_line)
-        weechat.command(urlgrab_buffer, "/window scroll_top")
-        pass
-    elif args == "scroll_bottom":
-        temp_current = current_line
-        current_line =  len(urlGrab.globalUrls)
-        refresh_line (temp_current)
-        refresh_line (current_line)
-        weechat.command(urlgrab_buffer, "/window scroll_bottom")
-    elif args == "enter":
-        if urlGrab.globalUrls[current_line]:
-            urlGrabOpenUrl (urlGrab.globalUrls[current_line]['url'])
-
-def refresh_line (y):
-    global urlGrab , urlGrabSettings, urlgrab_buffer, current_line, max_buffer_length
-    #Print format  Time(space)buffer(max4 spaces, but lined up)url
-    format = "%%s%%s %%s%%-%ds%%s%%s" % (max_buffer_length+4)
-
-    #non selected colors
-    color_buffer = urlGrabSettings["color_buffer"]
-    color_url = urlGrabSettings["color_url"]
-    color_time =urlGrabSettings["color_time"]
-    #selected colors
-    color_buffer_selected = urlGrabSettings["color_buffer_selected"]
-    color_url_selected = urlGrabSettings["color_url_selected"]
-    color_time_selected = urlGrabSettings["color_time_selected"]
-
-    color_bg_selected = urlGrabSettings["color_bg_selected"]
-
-    color1 = color_time
-    color2 = color_buffer
-    color3 = color_url
-
-    #If this line is selected we change the colors.
-    if y == current_line:
-          color1 = "%s,%s" % (color_time_selected, color_bg_selected)
-          color2 = "%s,%s" % (color_buffer_selected, color_bg_selected)
-          color3 = "%s,%s" % (color_url_selected, color_bg_selected)
-
-    color1 = weechat.color(color1)
-    color2 = weechat.color(color2)
-    color3 = weechat.color(color3)
-    text = format % (color1,
-                    urlGrab.globalUrls[y]['time'],
-                    color2,
-                    urlGrab.globalUrls[y]['buffer'],
-                    color3,
-                    urlGrab.globalUrls[y]['url'] )
-    weechat.prnt_y(urlgrab_buffer,y,text)
-
-def ugCheckLineOutsideWindow():
-    global urlGrab , urlGrabSettings, urlgrab_buffer, current_line, max_buffer_length
-    if (urlgrab_buffer):
-        infolist = weechat.infolist_get("window", "", "current")
-        if (weechat.infolist_next(infolist)):
-            start_line_y = weechat.infolist_integer(infolist, "start_line_y")
-            chat_height = weechat.infolist_integer(infolist, "chat_height")
-            if(start_line_y > current_line):
-                weechat.command(urlgrab_buffer, "/window scroll -%i" %(start_line_y - current_line))
-            elif(start_line_y <= current_line - chat_height):
-                weechat.command(urlgrab_buffer, "/window scroll +%i"%(current_line - start_line_y - chat_height + 1))
-        weechat.infolist_free(infolist)
-
-
-def refresh():
-    global urlGrab
-    y=0
-    for x in urlGrab.globalUrls:
-        refresh_line (y)
-        y += 1
-
-
-def init():
-    global urlGrab , urlGrabSettings, urlgrab_buffer
-    if not urlgrab_buffer:
-        urlgrab_buffer = weechat.buffer_new("urlgrab", "buffer_input", "", "buffer_close", "")
-    if urlgrab_buffer:
-        weechat.buffer_set(urlgrab_buffer, "type", "free")
-        weechat.buffer_set(urlgrab_buffer, "key_bind_ctrl-R",        "/url **refresh")
-        weechat.buffer_set(urlgrab_buffer, "key_bind_meta2-A",       "/url **up")
-        weechat.buffer_set(urlgrab_buffer, "key_bind_meta2-B",       "/url **down")
-        weechat.buffer_set(urlgrab_buffer, "key_bind_meta-ctrl-J",   "/url **enter")
-        weechat.buffer_set(urlgrab_buffer, "key_bind_meta-ctrl-M",   "/url **enter")
-        weechat.buffer_set(urlgrab_buffer, "key_bind_meta-meta2-1./~", "/url **scroll_top")
-        weechat.buffer_set(urlgrab_buffer, "key_bind_meta-meta2-4~", "/url **scroll_bottom")
-        weechat.buffer_set(urlgrab_buffer, "title","Lists the urls in the applications")
-        weechat.buffer_set(urlgrab_buffer, "display", "1")
-
-def completion_urls_cb(data, completion_item, bufferp, completion):
-    """ Complete with URLS, for command '/url'. """
-    global urlGrab
-    bufferd = hashBufferName( bufferp)
-    for url in urlGrab.globalUrls :
-        if url['buffer'] == bufferd:
-            weechat.hook_completion_list_add(completion, url['url'], 0, weechat.WEECHAT_LIST_POS_SORT)
-    return weechat.WEECHAT_RC_OK
-
-def ug_unload_script():
-    """ Function called when script is unloaded. """
-    global urlGrabSettings
-    weechat.config_write(urlGrabSettings.config_file)
-    return weechat.WEECHAT_RC_OK
-
-#Main stuff
-if ( import_ok and
-    weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION,
-        SCRIPT_LICENSE,SCRIPT_DESC, "ug_unload_script", "") ):
-    urlgrab_buffer = None
-    current_line = 0
-    max_buffer_length = 0
-    urlGrabSettings = UrlGrabSettings()
-    urlGrab = UrlGrabber( urlGrabSettings['historysize'])
-    weechat.hook_print("", "", "", 1, "urlGrabCheck", "")
-    weechat.hook_command(SCRIPT_COMMAND,
-                             "Url Grabber",
-                             "[open <url> | <url> | show | copy [n] | [n] | list]",
-                             "open or <url>: opens the url\n"
-                             "show: Opens the select buffer to allow for url selection\n"
-                             "copy: Copies the nth url to the system clipboard\n"
-                             "list: Lists the urls in the current buffer\n",
-                             "open %(urlgrab_urls) || %(urlgrab_urls) || "
-                             "copy || show || list",
-                             "urlGrabMain", "")
-    weechat.hook_completion("urlgrab_urls", "list of URLs",
-                                "completion_urls_cb", "")
-else:
-    print("failed to load weechat")
--- a/weechat/python/autoload/wee_slack.py	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-/home/sjl/src/wee-slack/wee_slack.py
\ No newline at end of file
--- a/weechat/python/autosort.py	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1075 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2013-2017 Maarten de Vries <maarten@de-vri.es>
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-#
-
-#
-# Autosort automatically keeps your buffers sorted and grouped by server.
-# You can define your own sorting rules. See /help autosort for more details.
-#
-# https://github.com/de-vri-es/weechat-autosort
-#
-
-#
-# Changelog:
-# 3.9:
-#   * Remove `buffers.pl` from recommended settings.
-# 3,8:
-#   * Fix relative sorting on script name in default rules.
-#   * Document a useful property of stable sort algorithms.
-# 3.7:
-#   * Make default rules work with bitlbee, matrix and slack.
-# 3.6:
-#   * Add more documentation on provided info hooks.
-# 3.5:
-#   * Add ${info:autosort_escape,...} to escape arguments for other info hooks.
-# 3.4:
-#   * Fix rate-limit of sorting to prevent high CPU load and lock-ups.
-#   * Fix bug in parsing empty arguments for info hooks.
-#   * Add debug_log option to aid with debugging.
-#   * Correct a few typos.
-# 3.3:
-#   * Fix the /autosort debug command for unicode.
-#   * Update the default rules to work better with Slack.
-# 3.2:
-#   * Fix python3 compatiblity.
-# 3.1:
-#   * Use colors to format the help text.
-# 3.0:
-#   * Switch to evaluated expressions for sorting.
-#   * Add `/autosort debug` command.
-#   * Add ${info:autosort_replace,from,to,text} to replace substrings in sort rules.
-#   * Add ${info:autosort_order,value,first,second,third} to ease writing sort rules.
-#   * Make tab completion context aware.
-# 2.8:
-#   * Fix compatibility with python 3 regarding unicode handling.
-# 2.7:
-#   * Fix sorting of buffers with spaces in their name.
-# 2.6:
-#   * Ignore case in rules when doing case insensitive sorting.
-# 2.5:
-#   * Fix handling unicode buffer names.
-#   * Add hint to set irc.look.server_buffer to independent and buffers.look.indenting to on.
-# 2.4:
-#   * Make script python3 compatible.
-# 2.3:
-#   * Fix sorting items without score last (regressed in 2.2).
-# 2.2:
-#   * Add configuration option for signals that trigger a sort.
-#   * Add command to manually trigger a sort (/autosort sort).
-#   * Add replacement patterns to apply before sorting.
-# 2.1:
-#   * Fix some minor style issues.
-# 2.0:
-#   * Allow for custom sort rules.
-#
-
-
-import json
-import math
-import re
-import sys
-import time
-import weechat
-
-SCRIPT_NAME     = 'autosort'
-SCRIPT_AUTHOR   = 'Maarten de Vries <maarten@de-vri.es>'
-SCRIPT_VERSION  = '3.9'
-SCRIPT_LICENSE  = 'GPL3'
-SCRIPT_DESC     = 'Flexible automatic (or manual) buffer sorting based on eval expressions.'
-
-
-config             = None
-hooks              = []
-signal_delay_timer = None
-sort_limit_timer   = None
-sort_queued        = False
-
-
-# Make sure that unicode, bytes and str are always available in python2 and 3.
-# For python 2, str == bytes
-# For python 3, str == unicode
-if sys.version_info[0] >= 3:
-	unicode = str
-
-def ensure_str(input):
-	'''
-	Make sure the given type if the correct string type for the current python version.
-	That means bytes for python2 and unicode for python3.
-	'''
-	if not isinstance(input, str):
-		if isinstance(input, bytes):
-			return input.encode('utf-8')
-		if isinstance(input, unicode):
-			return input.decode('utf-8')
-	return input
-
-
-if hasattr(time, 'perf_counter'):
-	perf_counter = time.perf_counter
-else:
-	perf_counter = time.clock
-
-def casefold(string):
-	if hasattr(string, 'casefold'): return string.casefold()
-	# Fall back to lowercasing for python2.
-	return string.lower()
-
-def list_swap(values, a, b):
-	values[a], values[b] = values[b], values[a]
-
-def list_move(values, old_index, new_index):
-	values.insert(new_index, values.pop(old_index))
-
-def list_find(collection, value):
-	for i, elem in enumerate(collection):
-		if elem == value: return i
-	return None
-
-class HumanReadableError(Exception):
-	pass
-
-def parse_int(arg, arg_name = 'argument'):
-	''' Parse an integer and provide a more human readable error. '''
-	arg = arg.strip()
-	try:
-		return int(arg)
-	except ValueError:
-		raise HumanReadableError('Invalid {0}: expected integer, got "{1}".'.format(arg_name, arg))
-
-def decode_rules(blob):
-	parsed = json.loads(blob)
-	if not isinstance(parsed, list):
-		log('Malformed rules, expected a JSON encoded list of strings, but got a {0}. No rules have been loaded. Please fix the setting manually.'.format(type(parsed)))
-		return []
-
-	for i, entry in enumerate(parsed):
-		if not isinstance(entry, (str, unicode)):
-			log('Rule #{0} is not a string but a {1}. No rules have been loaded. Please fix the setting manually.'.format(i, type(entry)))
-			return []
-
-	return parsed
-
-def decode_helpers(blob):
-	parsed = json.loads(blob)
-	if not isinstance(parsed, dict):
-		log('Malformed helpers, expected a JSON encoded dictionary but got a {0}. No helpers have been loaded. Please fix the setting manually.'.format(type(parsed)))
-		return {}
-
-	for key, value in parsed.items():
-		if not isinstance(value, (str, unicode)):
-			log('Helper "{0}" is not a string but a {1}. No helpers have been loaded. Please fix setting manually.'.format(key, type(value)))
-			return {}
-	return parsed
-
-class Config:
-	''' The autosort configuration. '''
-
-	default_rules = json.dumps([
-		'${core_first}',
-		'${info:autosort_order,${info:autosort_escape,${script_or_plugin}},core,*,irc,bitlbee,matrix,slack}',
-		'${script_or_plugin}',
-		'${irc_raw_first}',
-		'${server}',
-		'${info:autosort_order,${type},server,*,channel,private}',
-		'${hashless_name}',
-		'${buffer.full_name}',
-	])
-
-	default_helpers = json.dumps({
-		'core_first':       '${if:${buffer.full_name}!=core.weechat}',
-		'irc_raw_first':    '${if:${buffer.full_name}!=irc.irc_raw}',
-		'irc_raw_last':     '${if:${buffer.full_name}==irc.irc_raw}',
-		'hashless_name':    '${info:autosort_replace,#,,${info:autosort_escape,${buffer.name}}}',
-		'script_or_plugin': '${if:${script_name}?${script_name}:${plugin}}',
-	})
-
-	default_signal_delay = 5
-	default_sort_limit   = 100
-
-	default_signals = 'buffer_opened buffer_merged buffer_unmerged buffer_renamed'
-
-	def __init__(self, filename):
-		''' Initialize the configuration. '''
-
-		self.filename         = filename
-		self.config_file      = weechat.config_new(self.filename, '', '')
-		self.sorting_section  = None
-		self.v3_section       = None
-
-		self.case_sensitive   = False
-		self.rules            = []
-		self.helpers          = {}
-		self.signals          = []
-		self.signal_delay     = Config.default_signal_delay,
-		self.sort_limit       = Config.default_sort_limit,
-		self.sort_on_config   = True
-		self.debug_log        = False
-
-		self.__case_sensitive = None
-		self.__rules          = None
-		self.__helpers        = None
-		self.__signals        = None
-		self.__signal_delay   = None
-		self.__sort_limit     = None
-		self.__sort_on_config = None
-		self.__debug_log      = None
-
-		if not self.config_file:
-			log('Failed to initialize configuration file "{0}".'.format(self.filename))
-			return
-
-		self.sorting_section = weechat.config_new_section(self.config_file, 'sorting', False, False, '', '', '', '', '', '', '', '', '', '')
-		self.v3_section      = weechat.config_new_section(self.config_file, 'v3',      False, False, '', '', '', '', '', '', '', '', '', '')
-
-		if not self.sorting_section:
-			log('Failed to initialize section "sorting" of configuration file.')
-			weechat.config_free(self.config_file)
-			return
-
-		self.__case_sensitive = weechat.config_new_option(
-			self.config_file, self.sorting_section,
-			'case_sensitive', 'boolean',
-			'If this option is on, sorting is case sensitive.',
-			'', 0, 0, 'off', 'off', 0,
-			'', '', '', '', '', ''
-		)
-
-		weechat.config_new_option(
-			self.config_file, self.sorting_section,
-			'rules', 'string',
-			'Sort rules used by autosort v2.x and below. Not used by autosort anymore.',
-			'', 0, 0, '', '', 0,
-			'', '', '', '', '', ''
-		)
-
-		weechat.config_new_option(
-			self.config_file, self.sorting_section,
-			'replacements', 'string',
-			'Replacement patterns used by autosort v2.x and below. Not used by autosort anymore.',
-			'', 0, 0, '', '', 0,
-			'', '', '', '', '', ''
-		)
-
-		self.__rules = weechat.config_new_option(
-			self.config_file, self.v3_section,
-			'rules', 'string',
-			'An ordered list of sorting rules encoded as JSON. See /help autosort for commands to manipulate these rules.',
-			'', 0, 0, Config.default_rules, Config.default_rules, 0,
-			'', '', '', '', '', ''
-		)
-
-		self.__helpers = weechat.config_new_option(
-			self.config_file, self.v3_section,
-			'helpers', 'string',
-			'A dictionary helper variables to use in the sorting rules, encoded as JSON. See /help autosort for commands to manipulate these helpers.',
-			'', 0, 0, Config.default_helpers, Config.default_helpers, 0,
-			'', '', '', '', '', ''
-		)
-
-		self.__signals = weechat.config_new_option(
-			self.config_file, self.sorting_section,
-			'signals', 'string',
-			'A space separated list of signals that will cause autosort to resort your buffer list.',
-			'', 0, 0, Config.default_signals, Config.default_signals, 0,
-			'', '', '', '', '', ''
-		)
-
-		self.__signal_delay = weechat.config_new_option(
-			self.config_file, self.sorting_section,
-			'signal_delay', 'integer',
-			'Delay in milliseconds to wait after a signal before sorting the buffer list. This prevents triggering many times if multiple signals arrive in a short time. It can also be needed to wait for buffer localvars to be available.',
-			'', 0, 1000, str(Config.default_signal_delay), str(Config.default_signal_delay), 0,
-			'', '', '', '', '', ''
-		)
-
-		self.__sort_limit = weechat.config_new_option(
-			self.config_file, self.sorting_section,
-			'sort_limit', 'integer',
-			'Minimum delay in milliseconds to wait after sorting before signals can trigger a sort again. This is effectively a rate limit on sorting. Keeping signal_delay low while setting this higher can reduce excessive sorting without a long initial delay.',
-			'', 0, 1000, str(Config.default_sort_limit), str(Config.default_sort_limit), 0,
-			'', '', '', '', '', ''
-		)
-
-		self.__sort_on_config = weechat.config_new_option(
-			self.config_file, self.sorting_section,
-			'sort_on_config_change', 'boolean',
-			'Decides if the buffer list should be sorted when autosort configuration changes.',
-			'', 0, 0, 'on', 'on', 0,
-			'', '', '', '', '', ''
-		)
-
-		self.__debug_log = weechat.config_new_option(
-			self.config_file, self.sorting_section,
-			'debug_log', 'boolean',
-			'If enabled, print more debug messages. Not recommended for normal usage.',
-			'', 0, 0, 'off', 'off', 0,
-			'', '', '', '', '', ''
-		)
-
-		if weechat.config_read(self.config_file) != weechat.WEECHAT_RC_OK:
-			log('Failed to load configuration file.')
-
-		if weechat.config_write(self.config_file) != weechat.WEECHAT_RC_OK:
-			log('Failed to write configuration file.')
-
-		self.reload()
-
-	def reload(self):
-		''' Load configuration variables. '''
-
-		self.case_sensitive = weechat.config_boolean(self.__case_sensitive)
-
-		rules_blob    = weechat.config_string(self.__rules)
-		helpers_blob  = weechat.config_string(self.__helpers)
-		signals_blob  = weechat.config_string(self.__signals)
-
-		self.rules          = decode_rules(rules_blob)
-		self.helpers        = decode_helpers(helpers_blob)
-		self.signals        = signals_blob.split()
-		self.signal_delay   = weechat.config_integer(self.__signal_delay)
-		self.sort_limit     = weechat.config_integer(self.__sort_limit)
-		self.sort_on_config = weechat.config_boolean(self.__sort_on_config)
-		self.debug_log      = weechat.config_boolean(self.__debug_log)
-
-	def save_rules(self, run_callback = True):
-		''' Save the current rules to the configuration. '''
-		weechat.config_option_set(self.__rules, json.dumps(self.rules), run_callback)
-
-	def save_helpers(self, run_callback = True):
-		''' Save the current helpers to the configuration. '''
-		weechat.config_option_set(self.__helpers, json.dumps(self.helpers), run_callback)
-
-
-def pad(sequence, length, padding = None):
-	''' Pad a list until is has a certain length. '''
-	return sequence + [padding] * max(0, (length - len(sequence)))
-
-def log(message, buffer = 'NULL'):
-	weechat.prnt(buffer, 'autosort: {0}'.format(message))
-
-def debug(message, buffer = 'NULL'):
-	if config.debug_log:
-		weechat.prnt(buffer, 'autosort: debug: {0}'.format(message))
-
-def get_buffers():
-	''' Get a list of all the buffers in weechat. '''
-	hdata  = weechat.hdata_get('buffer')
-	buffer = weechat.hdata_get_list(hdata, "gui_buffers");
-
-	result = []
-	while buffer:
-		number = weechat.hdata_integer(hdata, buffer, 'number')
-		result.append((number, buffer))
-		buffer = weechat.hdata_pointer(hdata, buffer, 'next_buffer')
-	return hdata, result
-
-class MergedBuffers(list):
-	""" A list of merged buffers, possibly of size 1. """
-	def __init__(self, number):
-		super(MergedBuffers, self).__init__()
-		self.number = number
-
-def merge_buffer_list(buffers):
-	'''
-	Group merged buffers together.
-	The output is a list of MergedBuffers.
-	'''
-	if not buffers: return []
-	result = {}
-	for number, buffer in buffers:
-		if number not in result: result[number] = MergedBuffers(number)
-		result[number].append(buffer)
-	return result.values()
-
-def sort_buffers(hdata, buffers, rules, helpers, case_sensitive):
-	for merged in buffers:
-		for buffer in merged:
-			name = weechat.hdata_string(hdata, buffer, 'name')
-
-	return sorted(buffers, key=merged_sort_key(rules, helpers, case_sensitive))
-
-def buffer_sort_key(rules, helpers, case_sensitive):
-	''' Create a sort key function for a list of lists of merged buffers. '''
-	def key(buffer):
-		extra_vars = {}
-		for helper_name, helper in sorted(helpers.items()):
-			expanded = weechat.string_eval_expression(helper, {"buffer": buffer}, {}, {})
-			extra_vars[helper_name] = expanded if case_sensitive else casefold(expanded)
-		result = []
-		for rule in rules:
-			expanded = weechat.string_eval_expression(rule, {"buffer": buffer}, extra_vars, {})
-			result.append(expanded if case_sensitive else casefold(expanded))
-		return result
-
-	return key
-
-def merged_sort_key(rules, helpers, case_sensitive):
-	buffer_key = buffer_sort_key(rules, helpers, case_sensitive)
-	def key(merged):
-		best = None
-		for buffer in merged:
-			this = buffer_key(buffer)
-			if best is None or this < best: best = this
-		return best
-	return key
-
-def apply_buffer_order(buffers):
-	''' Sort the buffers in weechat according to the given order. '''
-	for i, buffer in enumerate(buffers):
-		weechat.buffer_set(buffer[0], "number", str(i + 1))
-
-def split_args(args, expected, optional = 0):
-	''' Split an argument string in the desired number of arguments. '''
-	split = args.split(' ', expected - 1)
-	if (len(split) < expected):
-		raise HumanReadableError('Expected at least {0} arguments, got {1}.'.format(expected, len(split)))
-	return split[:-1] + pad(split[-1].split(' ', optional), optional + 1, '')
-
-def do_sort(verbose = False):
-	start = perf_counter()
-
-	hdata, buffers = get_buffers()
-	buffers = merge_buffer_list(buffers)
-	buffers = sort_buffers(hdata, buffers, config.rules, config.helpers, config.case_sensitive)
-	apply_buffer_order(buffers)
-
-	elapsed = perf_counter() - start
-	if verbose:
-		log("Finished sorting buffers in {0:.4f} seconds.".format(elapsed))
-	else:
-		debug("Finished sorting buffers in {0:.4f} seconds.".format(elapsed))
-
-def command_sort(buffer, command, args):
-	''' Sort the buffers and print a confirmation. '''
-	do_sort(True)
-	return weechat.WEECHAT_RC_OK
-
-def command_debug(buffer, command, args):
-	hdata, buffers = get_buffers()
-	buffers = merge_buffer_list(buffers)
-
-	# Show evaluation results.
-	log('Individual evaluation results:')
-	start = perf_counter()
-	key = buffer_sort_key(config.rules, config.helpers, config.case_sensitive)
-	results = []
-	for merged in buffers:
-		for buffer in merged:
-			fullname = weechat.hdata_string(hdata, buffer, 'full_name')
-			results.append((fullname, key(buffer)))
-	elapsed = perf_counter() - start
-
-	for fullname, result in results:
-		fullname = ensure_str(fullname)
-		result = [ensure_str(x) for x in result]
-		log('{0}: {1}'.format(fullname, result))
-	log('Computing evaluation results took {0:.4f} seconds.'.format(elapsed))
-
-	return weechat.WEECHAT_RC_OK
-
-def command_rule_list(buffer, command, args):
-	''' Show the list of sorting rules. '''
-	output = 'Sorting rules:\n'
-	for i, rule in enumerate(config.rules):
-		output += '    {0}: {1}\n'.format(i, rule)
-	if not len(config.rules):
-		output += '    No sorting rules configured.\n'
-	log(output )
-
-	return weechat.WEECHAT_RC_OK
-
-
-def command_rule_add(buffer, command, args):
-	''' Add a rule to the rule list. '''
-	config.rules.append(args)
-	config.save_rules()
-	command_rule_list(buffer, command, '')
-
-	return weechat.WEECHAT_RC_OK
-
-
-def command_rule_insert(buffer, command, args):
-	''' Insert a rule at the desired position in the rule list. '''
-	index, rule = split_args(args, 2)
-	index = parse_int(index, 'index')
-
-	config.rules.insert(index, rule)
-	config.save_rules()
-	command_rule_list(buffer, command, '')
-	return weechat.WEECHAT_RC_OK
-
-
-def command_rule_update(buffer, command, args):
-	''' Update a rule in the rule list. '''
-	index, rule = split_args(args, 2)
-	index = parse_int(index, 'index')
-
-	config.rules[index] = rule
-	config.save_rules()
-	command_rule_list(buffer, command, '')
-	return weechat.WEECHAT_RC_OK
-
-
-def command_rule_delete(buffer, command, args):
-	''' Delete a rule from the rule list. '''
-	index = args.strip()
-	index = parse_int(index, 'index')
-
-	config.rules.pop(index)
-	config.save_rules()
-	command_rule_list(buffer, command, '')
-	return weechat.WEECHAT_RC_OK
-
-
-def command_rule_move(buffer, command, args):
-	''' Move a rule to a new position. '''
-	index_a, index_b = split_args(args, 2)
-	index_a = parse_int(index_a, 'index')
-	index_b = parse_int(index_b, 'index')
-
-	list_move(config.rules, index_a, index_b)
-	config.save_rules()
-	command_rule_list(buffer, command, '')
-	return weechat.WEECHAT_RC_OK
-
-
-def command_rule_swap(buffer, command, args):
-	''' Swap two rules. '''
-	index_a, index_b = split_args(args, 2)
-	index_a = parse_int(index_a, 'index')
-	index_b = parse_int(index_b, 'index')
-
-	list_swap(config.rules, index_a, index_b)
-	config.save_rules()
-	command_rule_list(buffer, command, '')
-	return weechat.WEECHAT_RC_OK
-
-
-def command_helper_list(buffer, command, args):
-	''' Show the list of helpers. '''
-	output = 'Helper variables:\n'
-
-	width = max(map(lambda x: len(x) if len(x) <= 30 else 0, config.helpers.keys()))
-
-	for name, expression in sorted(config.helpers.items()):
-		output += '    {0:>{width}}: {1}\n'.format(name, expression, width=width)
-	if not len(config.helpers):
-		output += '    No helper variables configured.'
-	log(output)
-
-	return weechat.WEECHAT_RC_OK
-
-
-def command_helper_set(buffer, command, args):
-	''' Add/update a helper to the helper list. '''
-	name, expression = split_args(args, 2)
-
-	config.helpers[name] = expression
-	config.save_helpers()
-	command_helper_list(buffer, command, '')
-
-	return weechat.WEECHAT_RC_OK
-
-def command_helper_delete(buffer, command, args):
-	''' Delete a helper from the helper list. '''
-	name = args.strip()
-
-	del config.helpers[name]
-	config.save_helpers()
-	command_helper_list(buffer, command, '')
-	return weechat.WEECHAT_RC_OK
-
-
-def command_helper_rename(buffer, command, args):
-	''' Rename a helper to a new position. '''
-	old_name, new_name = split_args(args, 2)
-
-	try:
-		config.helpers[new_name] = config.helpers[old_name]
-		del config.helpers[old_name]
-	except KeyError:
-		raise HumanReadableError('No such helper: {0}'.format(old_name))
-	config.save_helpers()
-	command_helper_list(buffer, command, '')
-	return weechat.WEECHAT_RC_OK
-
-
-def command_helper_swap(buffer, command, args):
-	''' Swap two helpers. '''
-	a, b = split_args(args, 2)
-	try:
-		config.helpers[b], config.helpers[a] = config.helpers[a], config.helpers[b]
-	except KeyError as e:
-		raise HumanReadableError('No such helper: {0}'.format(e.args[0]))
-
-	config.helpers.swap(index_a, index_b)
-	config.save_helpers()
-	command_helper_list(buffer, command, '')
-	return weechat.WEECHAT_RC_OK
-
-def call_command(buffer, command, args, subcommands):
-	''' Call a subcommand from a dictionary. '''
-	subcommand, tail = pad(args.split(' ', 1), 2, '')
-	subcommand = subcommand.strip()
-	if (subcommand == ''):
-		child   = subcommands.get(' ')
-	else:
-		command = command + [subcommand]
-		child   = subcommands.get(subcommand)
-
-	if isinstance(child, dict):
-		return call_command(buffer, command, tail, child)
-	elif callable(child):
-		return child(buffer, command, tail)
-
-	log('{0}: command not found'.format(' '.join(command)))
-	return weechat.WEECHAT_RC_ERROR
-
-def on_signal(data, signal, signal_data):
-	global signal_delay_timer
-	global sort_queued
-
-	# If the sort limit timeout is started, we're in the hold-off time after sorting, just queue a sort.
-	if sort_limit_timer is not None:
-		if sort_queued:
-			debug('Signal {0} ignored, sort limit timeout is active and sort is already queued.'.format(signal))
-		else:
-			debug('Signal {0} received but sort limit timeout is active, sort is now queued.'.format(signal))
-		sort_queued = True
-		return weechat.WEECHAT_RC_OK
-
-	# If the signal delay timeout is started, a signal was recently received, so ignore this signal.
-	if signal_delay_timer is not None:
-		debug('Signal {0} ignored, signal delay timeout active.'.format(signal))
-		return weechat.WEECHAT_RC_OK
-
-	# Otherwise, start the signal delay timeout.
-	debug('Signal {0} received, starting signal delay timeout of {1} ms.'.format(signal, config.signal_delay))
-	weechat.hook_timer(config.signal_delay, 0, 1, "on_signal_delay_timeout", "")
-	return weechat.WEECHAT_RC_OK
-
-def on_signal_delay_timeout(pointer, remaining_calls):
-	""" Called when the signal_delay_timer triggers. """
-	global signal_delay_timer
-	global sort_limit_timer
-	global sort_queued
-
-	signal_delay_timer = None
-
-	# If the sort limit timeout was started, we're still in the no-sort period, so just queue a sort.
-	if sort_limit_timer is not None:
-		debug('Signal delay timeout expired, but sort limit timeout is active, sort is now queued.')
-		sort_queued = True
-		return weechat.WEECHAT_RC_OK
-
-	# Time to sort!
-	debug('Signal delay timeout expired, starting sort.')
-	do_sort()
-
-	# Start the sort limit timeout if not disabled.
-	if config.sort_limit > 0:
-		debug('Starting sort limit timeout of {0} ms.'.format(config.sort_limit))
-		sort_limit_timer = weechat.hook_timer(config.sort_limit, 0, 1, "on_sort_limit_timeout", "")
-
-	return weechat.WEECHAT_RC_OK
-
-def on_sort_limit_timeout(pointer, remainin_calls):
-	""" Called when de sort_limit_timer triggers. """
-	global sort_limit_timer
-	global sort_queued
-
-	# If no signal was received during the timeout, we're done.
-	if not sort_queued:
-		debug('Sort limit timeout expired without receiving a signal.')
-		sort_limit_timer = None
-		return weechat.WEECHAT_RC_OK
-
-	# Otherwise it's time to sort.
-	debug('Signal received during sort limit timeout, starting queued sort.')
-	do_sort()
-	sort_queued = False
-
-	# Start the sort limit timeout again if not disabled.
-	if config.sort_limit > 0:
-		debug('Starting sort limit timeout of {0} ms.'.format(config.sort_limit))
-		sort_limit_timer = weechat.hook_timer(config.sort_limit, 0, 1, "on_sort_limit_timeout", "")
-
-	return weechat.WEECHAT_RC_OK
-
-
-def apply_config():
-	# Unhook all signals and hook the new ones.
-	for hook in hooks:
-		weechat.unhook(hook)
-	for signal in config.signals:
-		hooks.append(weechat.hook_signal(signal, 'on_signal', ''))
-
-	if config.sort_on_config:
-		debug('Sorting because configuration changed.')
-		do_sort()
-
-def on_config_changed(*args, **kwargs):
-	''' Called whenever the configuration changes. '''
-	config.reload()
-	apply_config()
-
-	return weechat.WEECHAT_RC_OK
-
-def parse_arg(args):
-	if not args: return '', None
-
-	result  = ''
-	escaped = False
-	for i, c in enumerate(args):
-		if not escaped:
-			if c == '\\':
-				escaped = True
-				continue
-			elif c == ',':
-				return result, args[i+1:]
-		result  += c
-		escaped  = False
-	return result, None
-
-def parse_args(args, max = None):
-	result = []
-	i = 0
-	while max is None or i < max:
-		i += 1
-		arg, args = parse_arg(args)
-		if arg is None: break
-		result.append(arg)
-		if args is None: break
-	return result, args
-
-def on_info_escape(pointer, name, arguments):
-	result = ''
-	for c in arguments:
-		if c == '\\':
-			result += '\\\\'
-		elif c == ',':
-			result += '\\,'
-		else:
-			result +=c
-	return result
-
-def on_info_replace(pointer, name, arguments):
-	arguments, rest = parse_args(arguments, 3)
-	if rest or len(arguments) < 3:
-		log('usage: ${{info:{0},old,new,text}}'.format(name))
-		return ''
-	old, new, text = arguments
-
-	return text.replace(old, new)
-
-def on_info_order(pointer, name, arguments):
-	arguments, rest = parse_args(arguments)
-	if len(arguments) < 1:
-		log('usage: ${{info:{0},value,first,second,third,...}}'.format(name))
-		return ''
-
-	value = arguments[0]
-	keys  = arguments[1:]
-	if not keys: return '0'
-
-	# Find the value in the keys (or '*' if we can't find it)
-	result = list_find(keys, value)
-	if result is None: result = list_find(keys, '*')
-	if result is None: result = len(keys)
-
-	# Pad result with leading zero to make sure string sorting works.
-	width = int(math.log10(len(keys))) + 1
-	return '{0:0{1}}'.format(result, width)
-
-
-def on_autosort_command(data, buffer, args):
-	''' Called when the autosort command is invoked. '''
-	try:
-		return call_command(buffer, ['/autosort'], args, {
-			' ':      command_sort,
-			'sort':   command_sort,
-			'debug':  command_debug,
-
-			'rules': {
-				' ':         command_rule_list,
-				'list':      command_rule_list,
-				'add':       command_rule_add,
-				'insert':    command_rule_insert,
-				'update':    command_rule_update,
-				'delete':    command_rule_delete,
-				'move':      command_rule_move,
-				'swap':      command_rule_swap,
-			},
-			'helpers': {
-				' ':      command_helper_list,
-				'list':   command_helper_list,
-				'set':    command_helper_set,
-				'delete': command_helper_delete,
-				'rename': command_helper_rename,
-				'swap':   command_helper_swap,
-			},
-		})
-	except HumanReadableError as e:
-		log(e)
-		return weechat.WEECHAT_RC_ERROR
-
-def add_completions(completion, words):
-	for word in words:
-		weechat.hook_completion_list_add(completion, word, 0, weechat.WEECHAT_LIST_POS_END)
-
-def autosort_complete_rules(words, completion):
-	if len(words) == 0:
-		add_completions(completion, ['add', 'delete', 'insert', 'list', 'move', 'swap', 'update'])
-	if len(words) == 1 and words[0] in ('delete', 'insert', 'move', 'swap', 'update'):
-		add_completions(completion, map(str, range(len(config.rules))))
-	if len(words) == 2 and words[0] in ('move', 'swap'):
-		add_completions(completion, map(str, range(len(config.rules))))
-	if len(words) == 2 and words[0] in ('update'):
-		try:
-			add_completions(completion, [config.rules[int(words[1])]])
-		except KeyError: pass
-		except ValueError: pass
-	else:
-		add_completions(completion, [''])
-	return weechat.WEECHAT_RC_OK
-
-def autosort_complete_helpers(words, completion):
-	if len(words) == 0:
-		add_completions(completion, ['delete', 'list', 'rename', 'set', 'swap'])
-	elif len(words) == 1 and words[0] in ('delete', 'rename', 'set', 'swap'):
-		add_completions(completion, sorted(config.helpers.keys()))
-	elif len(words) == 2 and words[0] == 'swap':
-		add_completions(completion, sorted(config.helpers.keys()))
-	elif len(words) == 2 and words[0] == 'rename':
-		add_completions(completion, sorted(config.helpers.keys()))
-	elif len(words) == 2 and words[0] == 'set':
-		try:
-			add_completions(completion, [config.helpers[words[1]]])
-		except KeyError: pass
-	return weechat.WEECHAT_RC_OK
-
-def on_autosort_complete(data, name, buffer, completion):
-	cmdline = weechat.buffer_get_string(buffer, "input")
-	cursor  = weechat.buffer_get_integer(buffer, "input_pos")
-	prefix  = cmdline[:cursor]
-	words   = prefix.split()[1:]
-
-	# If the current word isn't finished yet,
-	# ignore it for coming up with completion suggestions.
-	if prefix[-1] != ' ': words = words[:-1]
-
-	if len(words) == 0:
-		add_completions(completion, ['debug', 'helpers', 'rules', 'sort'])
-	elif words[0] == 'rules':
-		return autosort_complete_rules(words[1:], completion)
-	elif words[0] == 'helpers':
-		return autosort_complete_helpers(words[1:], completion)
-	return weechat.WEECHAT_RC_OK
-
-command_description = r'''{*white}# General commands{reset}
-
-{*white}/autosort {brown}sort{reset}
-Manually trigger the buffer sorting.
-
-{*white}/autosort {brown}debug{reset}
-Show the evaluation results of the sort rules for each buffer.
-
-
-{*white}# Sorting rule commands{reset}
-
-{*white}/autosort{brown} rules list{reset}
-Print the list of sort rules.
-
-{*white}/autosort {brown}rules add {cyan}<expression>{reset}
-Add a new rule at the end of the list.
-
-{*white}/autosort {brown}rules insert {cyan}<index> <expression>{reset}
-Insert a new rule at the given index in the list.
-
-{*white}/autosort {brown}rules update {cyan}<index> <expression>{reset}
-Update a rule in the list with a new expression.
-
-{*white}/autosort {brown}rules delete {cyan}<index>
-Delete a rule from the list.
-
-{*white}/autosort {brown}rules move {cyan}<index_from> <index_to>{reset}
-Move a rule from one position in the list to another.
-
-{*white}/autosort {brown}rules swap {cyan}<index_a> <index_b>{reset}
-Swap two rules in the list
-
-
-{*white}# Helper variable commands{reset}
-
-{*white}/autosort {brown}helpers list
-Print the list of helper variables.
-
-{*white}/autosort {brown}helpers set {cyan}<name> <expression>
-Add or update a helper variable with the given name.
-
-{*white}/autosort {brown}helpers delete {cyan}<name>
-Delete a helper variable.
-
-{*white}/autosort {brown}helpers rename {cyan}<old_name> <new_name>
-Rename a helper variable.
-
-{*white}/autosort {brown}helpers swap {cyan}<name_a> <name_b>
-Swap the expressions of two helper variables in the list.
-
-
-{*white}# Info hooks{reset}
-Autosort comes with a number of info hooks to add some extra functionality to regular weechat eval strings.
-Info hooks can be used in eval strings in the form of {cyan}${{info:some_hook,arguments}}{reset}.
-
-Commas and backslashes in arguments to autosort info hooks (except for {cyan}${{info:autosort_escape}}{reset}) must be escaped with a backslash.
-
-{*white}${{info:{brown}autosort_replace{white},{cyan}pattern{white},{cyan}replacement{white},{cyan}source{white}}}{reset}
-Replace all occurrences of {cyan}pattern{reset} with {cyan}replacement{reset} in the string {cyan}source{reset}.
-Can be used to ignore certain strings when sorting by replacing them with an empty string.
-
-For example: {cyan}${{info:autosort_replace,cat,dog,the dog is meowing}}{reset} expands to "the cat is meowing".
-
-{*white}${{info:{brown}autosort_order{white},{cyan}value{white},{cyan}option0{white},{cyan}option1{white},{cyan}option2{white},{cyan}...{white}}}
-Generate a zero-padded number that corresponds to the index of {cyan}value{reset} in the list of options.
-If one of the options is the special value {brown}*{reset}, then any value not explicitly mentioned will be sorted at that position.
-Otherwise, any value that does not match an option is assigned the highest number available.
-Can be used to easily sort buffers based on a manual sequence.
-
-For example: {cyan}${{info:autosort_order,${{server}},freenode,oftc,efnet}}{reset} will sort freenode before oftc, followed by efnet and then any remaining servers.
-Alternatively, {cyan}${{info:autosort_order,${{server}},freenode,oftc,*,efnet}}{reset} will sort any unlisted servers after freenode and oftc, but before efnet.
-
-{*white}${{info:{brown}autosort_escape{white},{cyan}text{white}}}{reset}
-Escape commas and backslashes in {cyan}text{reset} by prepending them with a backslash.
-This is mainly useful to pass arbitrary eval strings as arguments to other autosort info hooks.
-Otherwise, an eval string that expands to something with a comma would be interpreted as multiple arguments.
-
-For example, it can be used to safely pass buffer names to {cyan}${{info:autosort_replace}}{reset} like so:
-{cyan}${{info:autosort_replace,##,#,${{info:autosort_escape,${{buffer.name}}}}}}{reset}.
-
-
-{*white}# Description
-Autosort is a weechat script to automatically keep your buffers sorted. The sort
-order can be customized by defining your own sort rules, but the default should
-be sane enough for most people. It can also group IRC channel/private buffers
-under their server buffer if you like.
-
-Autosort uses a stable sorting algorithm, meaning that you can manually move buffers
-to change their relative order, if they sort equal with your rule set.
-
-{*white}# Sort rules{reset}
-Autosort evaluates a list of eval expressions (see {*default}/help eval{reset}) and sorts the
-buffers based on evaluated result. Earlier rules will be considered first. Only
-if earlier rules produced identical results is the result of the next rule
-considered for sorting purposes.
-
-You can debug your sort rules with the `{*default}/autosort debug{reset}` command, which will
-print the evaluation results of each rule for each buffer.
-
-{*brown}NOTE:{reset} The sort rules for version 3 are not compatible with version 2 or vice
-versa. You will have to manually port your old rules to version 3 if you have any.
-
-{*white}# Helper variables{reset}
-You may define helper variables for the main sort rules to keep your rules
-readable. They can be used in the main sort rules as variables. For example,
-a helper variable named `{cyan}foo{reset}` can be accessed in a main rule with the
-string `{cyan}${{foo}}{reset}`.
-
-{*white}# Automatic or manual sorting{reset}
-By default, autosort will automatically sort your buffer list whenever a buffer
-is opened, merged, unmerged or renamed. This should keep your buffers sorted in
-almost all situations. However, you may wish to change the list of signals that
-cause your buffer list to be sorted. Simply edit the `{cyan}autosort.sorting.signals{reset}`
-option to add or remove any signal you like.
-
-If you remove all signals you can still sort your buffers manually with the
-`{*default}/autosort sort{reset}` command. To prevent all automatic sorting, the option
-`{cyan}autosort.sorting.sort_on_config_change{reset}` should also be disabled.
-
-{*white}# Recommended settings
-For the best visual effect, consider setting the following options:
-  {*white}/set {cyan}irc.look.server_buffer{reset} {brown}independent{reset}
-
-This setting allows server buffers to be sorted independently, which is
-needed to create a hierarchical tree view of the server and channel buffers.
-
-If you are using the {*default}buflist{reset} plugin you can (ab)use Unicode to draw a tree
-structure with the following setting (modify to suit your need):
-  {*white}/set {cyan}buflist.format.indent {brown}"${{color:237}}${{if:${{buffer.next_buffer.local_variables.type}}=~^(channel|private)$?├─:└─}}"{reset}
-'''
-
-command_completion = '%(plugin_autosort) %(plugin_autosort) %(plugin_autosort) %(plugin_autosort) %(plugin_autosort)'
-
-info_replace_description = (
-	'Replace all occurrences of `pattern` with `replacement` in the string `source`. '
-	'Can be used to ignore certain strings when sorting by replacing them with an empty string. '
-	'See /help autosort for examples.'
-)
-info_replace_arguments = 'pattern,replacement,source'
-
-info_order_description = (
-	'Generate a zero-padded number that corresponds to the index of `value` in the list of options. '
-	'If one of the options is the special value `*`, then any value not explicitly mentioned will be sorted at that position. '
-	'Otherwise, any value that does not match an option is assigned the highest number available. '
-	'Can be used to easily sort buffers based on a manual sequence. '
-	'See /help autosort for examples.'
-)
-info_order_arguments = 'value,first,second,third,...'
-
-info_escape_description = (
-	'Escape commas and backslashes in `text` by prepending them with a backslash. '
-	'This is mainly useful to pass arbitrary eval strings as arguments to other autosort info hooks. '
-	'Otherwise, an eval string that expands to something with a comma would be interpreted as multiple arguments.'
-	'See /help autosort for examples.'
-)
-info_escape_arguments = 'text'
-
-
-if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", ""):
-	config = Config('autosort')
-
-	colors = {
-		'default':  weechat.color('default'),
-		'reset':    weechat.color('reset'),
-		'black':    weechat.color('black'),
-		'red':      weechat.color('red'),
-		'green':    weechat.color('green'),
-		'brown':    weechat.color('brown'),
-		'yellow':   weechat.color('yellow'),
-		'blue':     weechat.color('blue'),
-		'magenta':  weechat.color('magenta'),
-		'cyan':     weechat.color('cyan'),
-		'white':    weechat.color('white'),
-		'*default': weechat.color('*default'),
-		'*black':   weechat.color('*black'),
-		'*red':     weechat.color('*red'),
-		'*green':   weechat.color('*green'),
-		'*brown':   weechat.color('*brown'),
-		'*yellow':  weechat.color('*yellow'),
-		'*blue':    weechat.color('*blue'),
-		'*magenta': weechat.color('*magenta'),
-		'*cyan':    weechat.color('*cyan'),
-		'*white':   weechat.color('*white'),
-	}
-
-	weechat.hook_config('autosort.*', 'on_config_changed',  '')
-	weechat.hook_completion('plugin_autosort', '', 'on_autosort_complete', '')
-	weechat.hook_command('autosort', command_description.format(**colors), '', '', command_completion, 'on_autosort_command', '')
-	weechat.hook_info('autosort_escape',  info_escape_description,  info_escape_arguments,  'on_info_escape', '')
-	weechat.hook_info('autosort_replace', info_replace_description, info_replace_arguments, 'on_info_replace', '')
-	weechat.hook_info('autosort_order',   info_order_description,   info_order_arguments,   'on_info_order',   '')
-
-	apply_config()
--- a/weechat/python/colon_complete.py	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-SCRIPT_NAME='coloncomplete'
-SCRIPT_AUTHOR='Steve Losh <steve@stevelosh.com>'
-SCRIPT_VERSION='1.0'
-SCRIPT_LICENSE='MIT/X11'
-SCRIPT_DESC='Add a colon after nick completion when all the previous words in the input are also nicks.'
-
-EXTRA_NICKS = ['all', 'backend', 'clojerks', 'ops', 'support']
-
-import_ok=True
-
-try:
-    import weechat
-except ImportError:
-    print 'This script must be run under WeeChat'
-    print 'You can obtain a copy of WeeChat, for free, at http://www.weechat.org'
-    import_ok=False
-
-weechat_version=0
-
-def get_nicks(buffer, prefix=''):
-    channel = weechat.buffer_get_string(buffer, 'localvar_channel')
-    server = weechat.buffer_get_string(buffer, 'localvar_server')
-    prefix = prefix.lower()
-
-    matches = []
-
-    infolist = weechat.infolist_get('irc_nick', '', '%s,%s' % (server, channel))
-    while weechat.infolist_next(infolist):
-        nick = weechat.infolist_string(infolist, 'name')
-        if nick != 'localhost' and nick.lower().startswith(prefix):
-            matches.append(nick)
-    weechat.infolist_free(infolist)
-
-    for nick in EXTRA_NICKS:
-        if nick.lower().startswith(prefix):
-            matches.append(nick)
-
-    return matches
-
-def completer(data, buffer, command):
-    cb = weechat.current_buffer()
-    if command == "/input complete_next":
-        line = weechat.buffer_get_string(cb, "input")
-        words = line.split(' ')
-        prefix = words[-1]
-        if prefix and words and all([s.endswith(':') for s in words[:-1] if s]):
-            nicks = get_nicks(cb, prefix)
-            if len(nicks) == 1:
-                for _ in range(len(prefix)):
-                    weechat.command(buffer, "/input delete_previous_char")
-                weechat.command(buffer, "/input insert " + nicks[-1] + ":\\x20")
-            elif len(nicks) > 1:
-                l = min(len(nick) for nick in nicks)
-                for i in range(len(prefix), l):
-                    if len(set(nick[i] for nick in nicks)) > 1:
-                        break
-                    else:
-                        weechat.command(buffer, "/input insert " + nicks[0][i])
-
-                for nick in nicks:
-                    weechat.prnt(cb, "==> " + nick)
-                return weechat.WEECHAT_RC_OK_EAT
-
-    return weechat.WEECHAT_RC_OK
-
-if __name__ == "__main__" and import_ok:
-    if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", ""):
-        weechat_version = weechat.info_get("version_number", "") or 0
-        weechat.hook_command_run('/input complete*', 'completer', '')
--- a/weechat/python/listbuffer.py	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,467 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# ListBuffer, version 0.8.1 for WeeChat version 0.3
-# Latest development version: https://github.com/FiXato/listbuffer
-#
-#   Show /list results in a common buffer and interact with them.
-#
-#   This script allows you to easily join channels from the /list output.
-#   It will open a common buffer for the /list result, through which you
-#   browse with your cursor keys, and join with the meta-enter keys.
-#   Adjust sorting with meta->, meta-< and meta-/ keybindings.
-#
-## History:
-### 2011-09-08: FiXato:
-#
-# * version 0.1:  initial release.
-#     * added a common buffer for /list results
-#     * added highlighting for currently selected line
-#     * added /join support via enter key
-#     * added scroll_top and scroll_bottom support
-#
-# * version 0.2:  /list format bugfix
-#     * added support for /list results without modes
-#     * some servers don't send 321 (/list start). Taken into account.
-#
-# * version 0.3: Sorting support
-#     * Added some basic sorting support. Scroll through sort options
-#       with meta-> and meta-< (users, channel, topic, modes)
-#
-### 2011-09-19: FiXato
-#
-# * version 0.4:
-#     * Case-insensitive buffer lookup fix.
-#     * Removed default enter keybind
-#
-### 2011-12-28: troydm:
-#
-# * version 0.5: It's an upside-down-world
-#     * Added inverted sorting support provided by Dmitry "troydm" Geurkov
-#       Use meta-/ to switch between inverted and regular sorting.
-#
-### 2012-02-10: FiXato:
-#
-# * version 0.6: Stop shoving that buffer in my face!
-#     * The listbuffer should no longer pop up by itself when you load the script.
-#       It should only pop up now when you actually do a /list query.
-#
-# * version 0.7: .. but please pop it up in my current window when I ask for it
-#     * Added setting plugins.var.python.listbuffer.autofocus
-#       This will autofocus the listbuffer in the current window if another window isn't
-#       already showing it, and of course only when the user issues /list
-#
-### 2012-07-10: FiXato:
-#
-# * version 0.7.1: Forgetful bugfix
-#     * Made sure lb_curline global variable is defined
-#
-### 2013-03-19: FiXato:
-#
-# * version 0.8: Sorted out the sorting
-#     * Added automatically updating options for sorting:
-#       * plugins.var.python.listbuffer.sort_inverted
-#       * plugins.var.python.listbuffer.sort_order
-# * version 0.8.1: Pad it baby!
-#     * Channel modes are equally padded even when there are no channel modes.
-#     * Added padding options:
-#       * plugins.var.python.listbuffer.modes_min_width
-#       * plugins.var.python.listbuffer.channel_min_width
-#       * plugins.var.python.listbuffer.users_min_width
-#
-## Acknowledgements:
-# * Dmitry "troydm" Geurkov, for providing the inverse-sorting patch to the project.
-# * Sebastien "Flashcode" Helleu, for developing the kick-ass IRC client WeeChat
-#    and the iset.pl script which inspired me to this script.
-# * Nils "nils_2" Görs, for his contributions to iset.pl which served as
-#    example code.
-# * David "drubin" Rubin, for his urlgrab.py script, which also served
-#    as example code.
-# * ArZa, whose listsort.pl script helped me getting started with
-#    grabbing the /list results. Parts of his code have been shamelessly
-#    copied and ported to Python.
-# * Khaled Mardam-Bey, for making me yearn for similar /list support in
-#    WeeChat as mIRC already offered. :P
-# * mave_, for pointing out that sort orders weren't remembered.
-#
-## TODO:
-#   - Auto-scroll selected line upon window scroll.
-#   - Add option to hide already joined channels.
-#   - Improve sorting methods
-#   - Add auto-join support
-#   - Detect if channel is already in auto-join
-#   - Allow automatically switching to the listbuffer
-#   - Add support for ALIS (/squery alis LIST * -mix 100 (IRCNet)
-#   - Make colours configurable
-#   - Limit number of channels to parse
-#   - Add filter support a la iset
-#   - Allow selecting multiple channels
-#   - Add optional command redirection.
-#
-## Copyright (c) 2011,2012,2013 Filip H.F. "FiXato" Slagter,
-#   <FiXato [at] Gmail [dot] com>
-#   http://profile.fixato.org
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-#
-SCRIPT_NAME    = "listbuffer"
-SCRIPT_AUTHOR  = "Filip H.F. 'FiXato' Slagter <fixato [at] gmail [dot] com>"
-SCRIPT_VERSION = "0.8.1"
-SCRIPT_LICENSE = "MIT"
-SCRIPT_DESC    = "A common buffer for /list output."
-SCRIPT_COMMAND = "listbuffer"
-
-import_ok = True
-
-try:
-  import weechat
-except ImportError:
-  print "This script must be run under WeeChat."
-  import_ok = False
-
-import re
-
-lb_settings = (
-  ("autofocus", "on", "Focus the listbuffer in the current window if it isn't already displayed by a window."),
-  ("sort_order", "users", "Last used sort order for the channel list."),
-  ("sort_inverted", "on", "Invert the sort order for the channel list."),
-  ("modes_min_width", "8", "The minimum width used for modes in the channel list. If a channel has less modes than this amount, the column will be padded with spaces."),
-  ("channel_min_width", "25", "The minimum width used for the channel name in the channel list. If a channelname is shorter than this amount, the column will be padded with spaces."),
-  ("users_min_width", "8", "The minimum width used for the usercount in the channel list. If the usercount has less digits than this amount, the column will be padded with spaces."),
-)
-lb_buffer = None
-lb_curline = 0
-lb_channels = []
-lb_network = None
-lb_list_started = False
-lb_current_sort = None
-lb_sort_inverted = False
-lb_sort_options = (
-  'channel',
-  'users',
-  'modes',
-  'topic',
-)
-
-#                              server numeric Nick Chan  Users     Modes    Topic
-lb_channel_list_expression = '(:\S+) (\d{3}) (\S+) (\S+) (\d+) :(\[(.*?)\] )?(.*)'
-
-# Create listbuffer.
-def lb_create_buffer():
-  global lb_buffer, lb_curline
-
-  if not lb_buffer:
-    lb_buffer = weechat.buffer_new("listbuffer", "lb_input_cb", \
-                "", "lb_close_cb", "")
-    lb_set_buffer_title()
-    # Sets notify to 0 as this buffer does not need to be in hotlist.
-    weechat.buffer_set(lb_buffer, "notify", "0")
-    weechat.buffer_set(lb_buffer, "nicklist", "0")
-    weechat.buffer_set(lb_buffer, "type", "free")
-    weechat.buffer_set(lb_buffer, "key_bind_ctrl-L", "/listbuffer **refresh")
-    weechat.buffer_set(lb_buffer, "key_bind_meta2-A", "/listbuffer **up")
-    weechat.buffer_set(lb_buffer, "key_bind_meta2-B", "/listbuffer **down")
-    weechat.buffer_set(lb_buffer, "key_bind_meta2-1~", "/listbuffer **scroll_top")
-    weechat.buffer_set(lb_buffer, "key_bind_meta2-4~", "/listbuffer **scroll_bottom")
-    weechat.buffer_set(lb_buffer, "key_bind_meta-ctrl-J", "/listbuffer **enter")
-    weechat.buffer_set(lb_buffer, "key_bind_meta-ctrl-M", "/listbuffer **enter")
-    weechat.buffer_set(lb_buffer, "key_bind_meta->", "/listbuffer **sort_next")
-    weechat.buffer_set(lb_buffer, "key_bind_meta-<", "/listbuffer **sort_previous")
-    weechat.buffer_set(lb_buffer, "key_bind_meta-/", "/listbuffer **sort_invert")
-    lb_curline = 0
-  if weechat.config_get_plugin("autofocus") == "on":
-    if not weechat.window_search_with_buffer(lb_buffer):
-      weechat.command("", "/buffer " + weechat.buffer_get_string(lb_buffer,"name"))
-
-def lb_set_buffer_title():
-  global lb_buffer, lb_current_sort
-  ascdesc = '(v)' if lb_sort_inverted else '(^)'
-  weechat.buffer_set(lb_buffer, "title", lb_line_format({
-    'channel': 'Channel name%s' % (ascdesc if lb_current_sort == 'channel' else ''),
-    'users': 'Users%s' % (ascdesc if lb_current_sort == 'users' else ''),
-    'modes': 'Modes%s' % (ascdesc if lb_current_sort == 'modes' else ''),
-    'topic': 'Topic%s' % (ascdesc if lb_current_sort == 'topic' else ''),
-    'nomodes': None,
-  }))
-
-def lb_list_start(data, signal, message):
-  lb_initialise_list
-
-  return weechat.WEECHAT_RC_OK
-
-def lb_initialise_list(signal):
-  global lb_channels, lb_network, lb_list_started
-
-  lb_create_buffer()
-  lb_channels = []
-  lb_network = signal.split(',')[0]
-  lb_list_started = True
-  return
-
-
-def lb_list_chan(data, signal, message):
-  global lb_channels, lb_buffer, lb_list_started
-
-  # Work-around for IRCds which don't send 321 Numeric (/List start)
-  if not lb_list_started:
-    lb_initialise_list(signal)
-
-  for chan_data in re.findall(lb_channel_list_expression,message):
-    lb_channels.append({
-      'server':  chan_data[0][1:-1],
-      'numeric': chan_data[1],
-      'nick':    chan_data[2],
-      'channel': chan_data[3],
-      'users':   chan_data[4],
-      'nomodes': chan_data[5] == '',
-      'modes':   chan_data[6],
-      'topic':   weechat.hook_modifier_exec("irc_color_decode", "1", chan_data[7])
-    })
-  return weechat.WEECHAT_RC_OK
-
-def lb_list_end(data, signal, message):
-  global lb_list_started
-
-  # Work-around for IRCds which don't send 321 Numeric (/List start)
-  if not lb_list_started:
-    lb_initialise_list(signal)
-
-  lb_list_started = False
-  if lb_current_sort:
-    lb_sort()
-  lb_refresh()
-  return weechat.WEECHAT_RC_OK
-
-def keyEvent (data, buffer, args):
-  global lb_options
-  lb_options[args]()
-
-def lb_input_cb(data, buffer, input_data):
-  global lb_options, lb_curline
-  lb_options[input_data]()
-  return weechat.WEECHAT_RC_OK
-
-def lb_refresh():
-  global lb_channels, lb_buffer
-  weechat.buffer_clear(lb_buffer)
-
-  y = 0
-  for list_data in lb_channels:
-    lb_refresh_line(y)
-    y += 1
-  return
-
-def lb_refresh_line(y):
-  global lb_buffer, lb_curline, lb_channels
-  if y >= 0 and y < len(lb_channels):
-    formatted_line = lb_line_format(lb_channels[y], y == lb_curline)
-    weechat.prnt_y(lb_buffer, y, formatted_line)
-
-def lb_refresh_curline():
-  global lb_curline
-  lb_refresh_line(lb_curline-1)
-  lb_refresh_line(lb_curline)
-  lb_refresh_line(lb_curline+1)
-  return
-
-def lb_line_format(list_data,curr=False):
-  str = ""
-  if (curr):
-    str += weechat.color("yellow,red")
-  channel_text = list_data['channel'].ljust(int(weechat.config_get_plugin('channel_min_width')))
-  users_text = "(%s)" % list_data['users']
-  padded_users_text = users_text.rjust(int(weechat.config_get_plugin('users_min_width')) + 2)
-  str += "%s%s %s " % (weechat.color("bold"), channel_text, padded_users_text)
-  if not list_data['nomodes']:
-    modes = "[%s]" % list_data['modes']
-  else:
-    modes = "[]"
-  str += "%s: " % modes.rjust(int(weechat.config_get_plugin('modes_min_width')) + 2)
-  str += "%s" % list_data['topic']
-  return str
-
-def lb_line_up():
-  global lb_curline
-  if lb_curline <= 0:
-    return
-  lb_curline -= 1
-  lb_refresh_curline()
-  lb_check_outside_window()
-  return
-
-def lb_line_down():
-  global lb_curline, lb_channels
-  if lb_curline+1 >= len(lb_channels):
-    return
-  lb_curline += 1
-  lb_refresh_curline()
-  lb_check_outside_window()
-  return
-
-def lb_line_run():
-  global lb_channels, lb_curline, lb_network
-  buff = weechat.info_get("irc_buffer", lb_network)
-  channel = lb_channels[lb_curline]['channel']
-  command = "/join %s" % channel
-  weechat.command(buff, command)
-  return
-
-def lb_line_select():
-  return
-
-def lb_scroll_top():
-  global lb_curline
-  old_y = lb_curline
-  lb_curline = 0
-  lb_refresh_curline()
-  lb_refresh_line(old_y)
-  weechat.command(lb_buffer, "/window scroll_top")
-  return
-
-def lb_scroll_bottom():
-  global lb_curline, lb_channels
-  old_y = lb_curline
-  lb_curline = len(lb_channels)-1
-  lb_refresh_curline()
-  lb_refresh_line(old_y)
-  weechat.command(lb_buffer, "/window scroll_bottom")
-  return
-
-def lb_check_outside_window():
-  global lb_buffer, lb_curline
-  if (lb_buffer):
-    infolist = weechat.infolist_get("window", "", "current")
-    if (weechat.infolist_next(infolist)):
-      start_line_y = weechat.infolist_integer(infolist, "start_line_y")
-      chat_height = weechat.infolist_integer(infolist, "chat_height")
-      if(start_line_y > lb_curline):
-        weechat.command(lb_buffer, "/window scroll -%i" %(start_line_y - lb_curline))
-      elif(start_line_y <= lb_curline - chat_height):
-        weechat.command(lb_buffer, "/window scroll +%i"%(lb_curline - start_line_y - chat_height + 1))
-    weechat.infolist_free(infolist)
-
-def lb_sort_next():
-  global lb_current_sort, lb_sort_options
-  if lb_current_sort:
-    new_index = lb_sort_options.index(lb_current_sort) + 1
-  else:
-    new_index = 0
-
-  if len(lb_sort_options) <= new_index:
-    new_index = 0
-
-  lb_set_current_sort_order(lb_sort_options[new_index])
-  lb_sort()
-
-def lb_set_current_sort_order(value):
-  global lb_current_sort
-  lb_current_sort = value
-  weechat.config_set_plugin('sort_order', lb_current_sort)
-
-def lb_set_invert_sort_order(value):
-  global lb_sort_inverted
-  lb_sort_inverted = value
-  weechat.config_set_plugin('sort_inverted', ('on' if lb_sort_inverted else 'off'))
-
-def lb_sort_previous():
-  global lb_current_sort, lb_sort_options
-  if lb_current_sort:
-    new_index = lb_sort_options.index(lb_current_sort) - 1
-  else:
-    new_index = 0
-
-  if new_index < 0:
-    new_index = len(lb_sort_options) - 1
-
-  lb_set_current_sort_order(lb_sort_options[new_index])
-  lb_sort()
-
-def lb_sort(sort_key=None):
-  global lb_channels, lb_current_sort, lb_sort_inverted
-  if sort_key:
-    lb_set_current_sort_order(sort_key)
-  if lb_current_sort == 'users':
-    lb_channels = sorted(lb_channels, key=lambda chan_data: int(chan_data[lb_current_sort]))
-  else:
-    lb_channels = sorted(lb_channels, key=lambda chan_data: chan_data[lb_current_sort])
-  if lb_sort_inverted:
-    lb_channels.reverse()
-  lb_set_buffer_title()
-  lb_refresh()
-
-def lb_sort_invert():
-  global lb_current_sort, lb_sort_inverted
-  if lb_current_sort:
-    lb_set_invert_sort_order(not lb_sort_inverted)
-    lb_sort()
-
-def lb_close_cb(*kwargs):
-  """ A callback for buffer closing. """
-  global lb_buffer
-
-  lb_buffer = None
-  return weechat.WEECHAT_RC_OK
-
-lb_options = {
-  'refresh'     : lb_refresh,
-  'up'          : lb_line_up,
-  'down'        : lb_line_down,
-  'enter'       : lb_line_run,
-  'space'       : lb_line_select,
-  'scroll_top'  : lb_scroll_top,
-  'scroll_bottom': lb_scroll_bottom,
-  'sort_next'   : lb_sort_next,
-  'sort_previous': lb_sort_previous,
-  'sort_invert': lb_sort_invert
-}
-
-def lb_command_main(data, buffer, args):
-  if args[0:2] == "**":
-    keyEvent(data, buffer, args[2:])
-  return weechat.WEECHAT_RC_OK
-
-def lb_set_default_settings():
-  global lb_settings
-  # Set default settings
-  for option, default_value, description in lb_settings:
-     if not weechat.config_is_set_plugin(option):
-         weechat.config_set_plugin(option, default_value)
-         version = weechat.info_get("version_number", "") or 0
-         if int(version) >= 0x00030500:
-             weechat.config_set_desc_plugin(option, description)
-
-def lb_reset_stored_sort_order():
-  global lb_current_sort, lb_sort_inverted
-  lb_current_sort = weechat.config_get_plugin('sort_order')
-  lb_sort_inverted = (True if weechat.config_get_plugin('sort_inverted') == 'on' else False)
-
-if __name__ == "__main__" and import_ok:
-  if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION,
-                      SCRIPT_LICENSE, SCRIPT_DESC, "lb_close_cb", ""):
-    lb_set_default_settings()
-    lb_reset_stored_sort_order()
-    lb_buffer = weechat.buffer_search("python", "listbuffer")
-
-    weechat.hook_signal("*,irc_in_321", "lb_list_start", "")
-    weechat.hook_signal("*,irc_in_322", "lb_list_chan", "")
-    weechat.hook_signal("*,irc_in_323", "lb_list_end", "")
-    weechat.hook_command(SCRIPT_COMMAND,
-                          "List Buffer",
-                          "", "", "",
-                          "lb_command_main", "")
--- a/weechat/python/sanitize_jira.py	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,33 +0,0 @@
-import re, weechat, subprocess
-
-SCRIPT_NAME = 'sanitize_jira'
-SCRIPT_AUTHOR = 'Steve Losh <steve@stevelosh.com>'
-SCRIPT_VERSION = '0.0.1'
-SCRIPT_LICENSE = 'MIT'
-SCRIPT_DESC = 'clean up the garbage jirabot sends to channels into something readable'
-
-weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, '', '')
-
-weechat.hook_line('*', '', 'nick_Jira_Cloud', 'sanitize_jira', '')
-
-first_line_re = re.compile(
-    r'(?P<link>https://[^/]+/browse/[^?]+)[?]atlOrigin=[^ ]+ [(](?P<title>.+)[)]'
-)
-
-detail_line_re = re.compile(
-    r'''Status: \x1a\x01[*](?P<status>[^*]+)[*]\x1b\x01.*Type: \x1a\x01[*](?P<type>[^*]+)[*]\x1b\x01.*Assignee: \x1a\x01[*](?P<assignee>[^*]+)[*]\x1b\x01.*Priority: \x1a\x01[*](?P<priority>[^*]+)[*]\x1b\x01'''
-)
-
-def sanitize_jira(data, line):
-    if 'sign up for an Atlassian account to view this link' in line['message']:
-        return {'message': ' '}
-
-    m = first_line_re.search(line['message'])
-    if m:
-        return {'message': '%s | %s' % (m.group('title'), m.group('link'))}
-
-    m = detail_line_re.search(line['message'])
-    if m:
-        return {'message': '%s / %s / %s' % (m.group('type'), m.group('status'), m.group('assignee'))}
-
-    return {}
--- a/weechat/relay.conf	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-#
-# weechat -- relay.conf
-#
-# WARNING: It is NOT recommended to edit this file by hand,
-# especially if WeeChat is running.
-#
-# Use commands like /set or /fset to change settings in WeeChat.
-#
-# For more info, see: https://weechat.org/doc/quickstart/
-#
-
-[look]
-auto_open_buffer = on
-raw_messages = 256
-
-[color]
-client = cyan
-status_active = lightblue
-status_auth_failed = lightred
-status_connecting = yellow
-status_disconnected = lightred
-status_waiting_auth = brown
-text = default
-text_bg = default
-text_selected = white
-
-[network]
-allow_empty_password = off
-allowed_ips = ""
-auth_timeout = 60
-bind_address = ""
-clients_purge_delay = 0
-compression = 20
-ipv6 = on
-max_clients = 5
-nonce_size = 16
-password = ""
-password_hash_algo = "*"
-password_hash_iterations = 100000
-ssl_cert_key = "%h/ssl/relay.pem"
-ssl_priorities = "NORMAL:-VERS-SSL3.0"
-totp_secret = ""
-totp_window = 0
-websocket_allowed_origins = ""
-
-[irc]
-backlog_max_minutes = 1440
-backlog_max_number = 256
-backlog_since_last_disconnect = on
-backlog_since_last_message = off
-backlog_tags = "irc_privmsg"
-backlog_time_format = "[%H:%M] "
-
-[weechat]
-commands = ""
-
-[port]
-
-[path]
--- a/weechat/rmodifier.conf	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-#
-# rmodifier.conf -- weechat v0.4.3
-#
-
-[look]
-hide_char = "*"
-
-[modifier]
-nickserv = "history_add,input_text_display;^(/(msg|quote) +nickserv +(identify|ghost \S+) +)(.*);1,4*"
-oper = "history_add,input_text_display;^(/oper +\S+ +)(.*);1,2*"
-set_pass = "history_add;^(/set +\S*password\S* +)(.*);1,2*"
--- a/weechat/ruby.conf	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-#
-# weechat -- ruby.conf
-#
-# WARNING: It is NOT recommended to edit this file by hand,
-# especially if WeeChat is running.
-#
-# Use commands like /set or /fset to change settings in WeeChat.
-#
-# For more info, see: https://weechat.org/doc/quickstart
-#
-
-[look]
-check_license = off
-eval_keep_context = on
--- a/weechat/ruby/autoload/challengeauth.rb	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,111 +0,0 @@
-# Copyright (c) 2013 Dominik Honnef <dominikh@fork-bomb.org>
-
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-# History:
-# 2013-04-20, Dominik Honnef
-#   version 0.0.1: initial version
-
-require "openssl"
-
-QBot = "Q@CServe.quakenet.org"
-QBotHost = "Q!TheQBot@CServe.quakenet.org"
-Request = Struct.new(:username, :hash)
-
-def weechat_init
-  @requests = {}
-
-  Weechat.register("challengeauth",
-                   "Dominik Honnef",
-                   "0.0.1",
-                   "MIT",
-                   "Securely authenticate with QuakeNet by using CHALLENGEAUTH",
-                   "",
-                   "")
-
-  Weechat.hook_command("challengeauth",
-                       "Authenticate with Q using CHALLENGEAUTH",
-                       "[username] [password]",
-                       "",
-                       "",
-                       "challengeauth",
-                       "")
-
-  Weechat.hook_modifier("irc_in_notice", "challenge_notice", "")
-
-  return Weechat::WEECHAT_RC_OK
-end
-
-def calculate_q_hash(username, hash, challenge)
-  username = username.tr("A-Z[]\\\\^", "a-z{}|~")
-
-  key = OpenSSL::Digest::SHA256.hexdigest("#{username}:#{hash}")
-  return OpenSSL::HMAC.hexdigest("SHA256", key, challenge)
-end
-
-def get_server_buffer(server)
-  Weechat.buffer_search("irc", "server." + server)
-end
-
-def challengeauth(data, buffer, args)
-  plugin = Weechat.buffer_get_string(buffer, "localvar_plugin")
-  if plugin != "irc"
-    Weechat.print(buffer, "/challengeauth only works for IRC buffers.")
-    return Weechat.WEECHAT_RC_ERROR
-  end
-
-  server = Weechat.buffer_get_string(buffer, "localvar_server")
-  args = args.split(" ", 2)
-  username = args[0]
-  password = args[1]
-  hash = OpenSSL::Digest::SHA256.hexdigest(password[0, 10])
-
-  @requests[server] = Request.new(username, hash)
-  server_buffer = get_server_buffer(server)
-  Weechat.print(server_buffer, "Authenticating as #{username}...")
-  Weechat.command(server_buffer, "/quote PRIVMSG #{QBot} :CHALLENGE")
-
-  return Weechat::WEECHAT_RC_OK
-end
-
-def challenge_notice(modifier, data, server, line)
-  return line unless @requests.has_key?(server)
-
-  parts = line.split(" ")
-  return line unless parts.size > 5
-
-  host = parts[0][1..-1]
-  command = parts[3][1..-1]
-  challenge = parts[4]
-
-  return line if host != QBotHost || command != "CHALLENGE"
-
-  request = @requests[server]
-  response = calculate_q_hash(request.username, request.hash, challenge)
-  server_buffer = get_server_buffer(server)
-
-  Weechat.print(server_buffer, "Sending challengeauth for #{request.username}...")
-  Weechat.command(server_buffer,
-                  "/quote PRIVMSG %s :CHALLENGEAUTH %s %s HMAC-SHA-256" % [QBot, request.username, response])
-
-  @requests.delete(server)
-
-  return line
-end
--- a/weechat/script.conf	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-#
-# weechat -- script.conf
-#
-# WARNING: It is NOT recommended to edit this file by hand,
-# especially if WeeChat is running.
-#
-# Use commands like /set or /fset to change settings in WeeChat.
-#
-# For more info, see: https://weechat.org/doc/quickstart/
-#
-
-[look]
-columns = "%s %n %V %v %u | %d | %t"
-diff_color = on
-diff_command = "auto"
-display_source = on
-quiet_actions = on
-sort = "p,n"
-translate_description = on
-use_keys = on
-
-[color]
-status_autoloaded = cyan
-status_held = white
-status_installed = lightcyan
-status_obsolete = lightmagenta
-status_popular = yellow
-status_running = lightgreen
-status_unknown = lightred
-text = default
-text_bg = default
-text_bg_selected = red
-text_date = default
-text_date_selected = white
-text_delimiters = darkgray
-text_description = default
-text_description_selected = white
-text_extension = default
-text_extension_selected = white
-text_name = cyan
-text_name_selected = lightcyan
-text_selected = white
-text_tags = brown
-text_tags_selected = yellow
-text_version = magenta
-text_version_loaded = default
-text_version_loaded_selected = white
-text_version_selected = lightmagenta
-
-[scripts]
-autoload = on
-cache_expire = 60
-download_enabled = on
-download_timeout = 30
-hold = ""
-path = "%h/script"
-url = "http://www.weechat.org/files/plugins.xml.gz"
--- a/weechat/spell.conf	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,33 +0,0 @@
-#
-# weechat -- spell.conf
-#
-# WARNING: It is NOT recommended to edit this file by hand,
-# especially if WeeChat is running.
-#
-# Use commands like /set or /fset to change settings in WeeChat.
-#
-# For more info, see: https://weechat.org/doc/quickstart
-#
-
-[color]
-misspelled = lightred
-suggestion = default
-suggestion_delimiter_dict = cyan
-suggestion_delimiter_word = cyan
-
-[check]
-commands = "away,command,cycle,kick,kickban,me,msg,notice,part,query,quit,topic"
-default_dict = ""
-during_search = off
-enabled = off
-real_time = off
-suggestions = -1
-word_min_length = 2
-
-[dict]
-
-[look]
-suggestion_delimiter_dict = " / "
-suggestion_delimiter_word = ","
-
-[option]
--- a/weechat/tcl.conf	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-#
-# weechat -- tcl.conf
-#
-# WARNING: It is NOT recommended to edit this file by hand,
-# especially if WeeChat is running.
-#
-# Use /set or similar command to change settings in WeeChat.
-#
-# For more info, see: https://weechat.org/doc/quickstart
-#
-
-[look]
-check_license = off
-eval_keep_context = on
--- a/weechat/trigger.conf	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,83 +0,0 @@
-#
-# weechat -- trigger.conf
-#
-# WARNING: It is NOT recommended to edit this file by hand,
-# especially if WeeChat is running.
-#
-# Use commands like /set or /fset to change settings in WeeChat.
-#
-# For more info, see: https://weechat.org/doc/quickstart/
-#
-
-[look]
-enabled = on
-monitor_strip_colors = off
-
-[color]
-flag_command = lightgreen
-flag_conditions = yellow
-flag_post_action = lightblue
-flag_regex = lightcyan
-flag_return_code = lightmagenta
-regex = white
-replace = cyan
-trigger = green
-trigger_disabled = red
-
-[trigger]
-beep.arguments = ""
-beep.command = "/print -beep"
-beep.conditions = "${tg_highlight} || ${tg_msg_pv}"
-beep.enabled = on
-beep.hook = print
-beep.post_action = none
-beep.regex = ""
-beep.return_code = ok
-cmd_pass.arguments = "5000|input_text_display;5000|history_add;5000|irc_command_auth"
-cmd_pass.command = ""
-cmd_pass.conditions = ""
-cmd_pass.enabled = on
-cmd_pass.hook = modifier
-cmd_pass.post_action = none
-cmd_pass.regex = "==^((/(msg|quote) +nickserv +(id|identify|register|ghost +[^ ]+|release +[^ ]+|regain +[^ ]+) +)|/oper +[^ ]+ +|/quote +pass +|/set +[^ ]*password[^ ]* +|/secure +(passphrase|decrypt|set +[^ ]+) +)(.*)==$1$.*+"
-cmd_pass.return_code = ok
-dumbass_buffer.arguments = "4000|input_text_for_buffer;4000|history_add"
-dumbass_buffer.command = ""
-dumbass_buffer.conditions = ""
-dumbass_buffer.enabled = on
-dumbass_buffer.hook = modifier
-dumbass_buffer.post_action = none
-dumbass_buffer.regex = "==^ +/?b (.+)==/b ${re:1}"
-dumbass_buffer.return_code = ok
-idiot_buffer.arguments = "4000|input_text_for_buffer;4000|history_add"
-idiot_buffer.command = ""
-idiot_buffer.conditions = ""
-idiot_buffer.enabled = on
-idiot_buffer.hook = modifier
-idiot_buffer.post_action = none
-idiot_buffer.regex = "==^b (.+)==/b ${re:1}"
-idiot_buffer.return_code = ok
-msg_auth.arguments = "5000|irc_message_auth"
-msg_auth.command = ""
-msg_auth.conditions = ""
-msg_auth.enabled = on
-msg_auth.hook = modifier
-msg_auth.post_action = none
-msg_auth.regex = "==^(.*(id|identify|register|ghost +[^ ]+|release +[^ ]+) +)(.*)==$1$.*+"
-msg_auth.return_code = ok
-server_pass.arguments = "5000|input_text_display;5000|history_add"
-server_pass.command = ""
-server_pass.conditions = ""
-server_pass.enabled = on
-server_pass.hook = modifier
-server_pass.post_action = none
-server_pass.regex = "==^(/(server|connect) .*-(sasl_)?password=)([^ ]+)(.*)==$1$.*4$5"
-server_pass.return_code = ok
-uncc.arguments = "weechat_print"
-uncc.command = ""
-uncc.conditions = "${tg_tag_nick}"
-uncc.enabled = on
-uncc.hook = modifier
-uncc.post_action = none
-uncc.regex = "== \[cc: [^ ]+\]===="
-uncc.return_code = ok
--- a/weechat/typing.conf	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,19 +0,0 @@
-#
-# weechat -- typing.conf
-#
-# WARNING: It is NOT recommended to edit this file by hand,
-# especially if WeeChat is running.
-#
-# Use commands like /set or /fset to change settings in WeeChat.
-#
-# For more info, see: https://weechat.org/doc/quickstart/
-#
-
-[look]
-delay_purge_paused = 30
-delay_purge_typing = 6
-delay_set_paused = 10
-enabled_nicks = off
-enabled_self = off
-input_min_chars = 4
-item_max_length = 0
--- a/weechat/urlgrab.conf	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,29 +0,0 @@
-#
-# weechat -- urlgrab.conf
-#
-# WARNING: It is NOT recommended to edit this file by hand,
-# especially if WeeChat is running.
-#
-# Use commands like /set or /fset to change settings in WeeChat.
-#
-# For more info, see: https://weechat.org/doc/quickstart/
-#
-
-[color]
-color_bg_selected = green
-color_buffer = red
-color_buffer_selected = red
-color_time = cyan
-color_time_selected = cyan
-color_url = blue
-color_url_selected = blue
-
-[default]
-copycmd = "xsel -i"
-historysize = 20
-localcmd = "xdg-open %s"
-method = "local"
-output_main_buffer = off
-remotecmd = "ssh -x localhost -i ~/.ssh/id_rsa -C "export DISPLAY=":0.0" &&  firefox %s""
-time_format = "%H:%M:%S"
-url_log = "~/.weechat/urls.log"
--- a/weechat/weechat.conf	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,693 +0,0 @@
-#
-# WeeChat -- weechat.conf
-#
-# WARNING: It is NOT recommended to edit this file by hand,
-# especially if WeeChat is running.
-#
-# Use commands like /set or /fset to change settings in WeeChat.
-#
-# For more info, see: https://weechat.org/doc/quickstart
-#
-
-[debug]
-
-[startup]
-command_after_plugins = ""
-command_before_plugins = ""
-display_logo = on
-display_version = on
-sys_rlimit = ""
-
-[look]
-align_end_of_lines = message
-align_multiline_words = on
-bar_more_down = "++"
-bar_more_left = "<<"
-bar_more_right = ">>"
-bar_more_up = "--"
-bare_display_exit_on_input = on
-bare_display_time_format = "%H:%M"
-buffer_auto_renumber = on
-buffer_notify_default = all
-buffer_position = end
-buffer_search_case_sensitive = off
-buffer_search_force_default = off
-buffer_search_regex = off
-buffer_search_where = prefix_message
-buffer_time_format = "%H:%M"
-buffer_time_same = ""
-color_basic_force_bold = off
-color_inactive_buffer = off
-color_inactive_message = on
-color_inactive_prefix = on
-color_inactive_prefix_buffer = on
-color_inactive_time = off
-color_inactive_window = off
-color_nick_offline = off
-color_pairs_auto_reset = 5
-color_real_white = off
-command_chars = ""
-command_incomplete = off
-confirm_quit = off
-confirm_upgrade = off
-day_change = on
-day_change_message_1date = "-- %a, %d %b %Y --"
-day_change_message_2dates = "-- %%a, %%d %%b %%Y (%a, %d %b %Y) --"
-eat_newline_glitch = off
-emphasized_attributes = ""
-highlight = "sjl,slosh,slj,horrifying,steve.losh,@steve.losh,stevelosh"
-highlight_regex = "(steve losh|rob ford|(jesus )?fucking christ|(horse|mouse|clown)fuckers?|((mother)?fuck([ie]ng?|er)?|(god?)?damn(ed)?|dammit|(bull|horse)?shite?){3,})"
-highlight_tags = ""
-hotlist_add_conditions = "${away} || ${buffer.num_displayed} == 0"
-hotlist_buffer_separator = ", "
-hotlist_count_max = 2
-hotlist_count_min_msg = 2
-hotlist_names_count = 3
-hotlist_names_length = 0
-hotlist_names_level = 12
-hotlist_names_merged_buffers = off
-hotlist_prefix = "H: "
-hotlist_remove = merged
-hotlist_short_names = on
-hotlist_sort = group_time_asc
-hotlist_suffix = ""
-hotlist_unique_numbers = on
-hotlist_update_on_buffer_switch = on
-input_cursor_scroll = 20
-input_share = none
-input_share_overwrite = off
-input_undo_max = 32
-item_away_message = on
-item_buffer_filter = "*"
-item_buffer_zoom = "!"
-item_mouse_status = "M"
-item_time_format = "%H:%M"
-jump_current_to_previous_buffer = on
-jump_previous_buffer_when_closing = on
-jump_smart_back_to_buffer = on
-key_bind_safe = on
-key_grab_delay = 800
-mouse = off
-mouse_timer_delay = 100
-nick_color_force = ""
-nick_color_hash = djb2
-nick_color_hash_salt = ""
-nick_color_stop_chars = "_|["
-nick_prefix = ""
-nick_suffix = ""
-paste_auto_add_newline = on
-paste_bracketed = off
-paste_bracketed_timer_delay = 10
-paste_max_lines = 3
-prefix_action = " *"
-prefix_align = right
-prefix_align_max = 15
-prefix_align_min = 0
-prefix_align_more = "+"
-prefix_align_more_after = on
-prefix_buffer_align = right
-prefix_buffer_align_max = 0
-prefix_buffer_align_more = "+"
-prefix_buffer_align_more_after = on
-prefix_error = "=!="
-prefix_join = "✔"
-prefix_network = "--"
-prefix_quit = "✘"
-prefix_same_nick = ""
-prefix_same_nick_middle = ""
-prefix_suffix = "|"
-quote_nick_prefix = "<"
-quote_nick_suffix = ">"
-quote_time_format = "%H:%M:%S"
-read_marker = line
-read_marker_always_show = on
-read_marker_string = "─"
-read_marker_update_on_buffer_switch = on
-save_config_on_exit = off
-save_config_with_fsync = off
-save_layout_on_exit = all
-scroll_amount = 3
-scroll_bottom_after_switch = off
-scroll_page_percent = 100
-search_text_not_found_alert = on
-separator_horizontal = "-"
-separator_vertical = ""
-tab_width = 1
-time_format = "%a, %d %b %Y %T"
-window_auto_zoom = off
-window_separator_horizontal = on
-window_separator_vertical = on
-window_title = ""
-word_chars_highlight = "!\u00A0,-,_,|,@,.,alnum"
-word_chars_input = "!\u00A0,-,_,|,alnum"
-
-[palette]
-
-[color]
-bar_more = magenta
-chat = default
-chat_bg = default
-chat_buffer = white
-chat_channel = white
-chat_day_change = cyan
-chat_delimiters = green
-chat_highlight = 207
-chat_highlight_bg = default
-chat_host = cyan
-chat_inactive_buffer = darkgray
-chat_inactive_window = darkgray
-chat_nick = lightcyan
-chat_nick_colors = "027,048,068,028,081,082,099,112,129,136,169,178,208,226,113,196,161,23,59,222"
-chat_nick_offline = darkgray
-chat_nick_offline_highlight = default
-chat_nick_offline_highlight_bg = darkgray
-chat_nick_other = cyan
-chat_nick_prefix = green
-chat_nick_self = white
-chat_nick_suffix = green
-chat_prefix_action = white
-chat_prefix_buffer = brown
-chat_prefix_buffer_inactive_buffer = darkgray
-chat_prefix_error = yellow
-chat_prefix_join = lightgreen
-chat_prefix_more = lightmagenta
-chat_prefix_network = magenta
-chat_prefix_quit = lightred
-chat_prefix_suffix = green
-chat_read_marker = green
-chat_read_marker_bg = default
-chat_server = brown
-chat_tags = red
-chat_text_found = yellow
-chat_text_found_bg = lightmagenta
-chat_time = 238
-chat_time_delimiters = 236
-chat_value = cyan
-chat_value_null = blue
-emphasized = yellow
-emphasized_bg = magenta
-input_actions = lightgreen
-input_text_not_found = red
-item_away = yellow
-nicklist_away = cyan
-nicklist_group = green
-separator = green
-status_count_highlight = magenta
-status_count_msg = brown
-status_count_other = 16
-status_count_private = green
-status_data_highlight = lightmagenta
-status_data_msg = yellow
-status_data_other = 16
-status_data_private = lightgreen
-status_filter = green
-status_more = 16
-status_mouse = green
-status_name = *16
-status_name_ssl = *16
-status_nicklist_count = default
-status_number = 16
-status_time = *16
-
-[completion]
-base_word_until_cursor = on
-command_inline = on
-default_template = "%(nicks)|%(irc_channels)"
-nick_add_space = on
-nick_case_sensitive = off
-nick_completer = ":"
-nick_first_only = off
-nick_ignore_chars = "[]`_-^"
-partial_completion_alert = on
-partial_completion_command = off
-partial_completion_command_arg = off
-partial_completion_count = on
-partial_completion_other = off
-partial_completion_templates = "config_options"
-
-[history]
-display_default = 5
-max_buffer_lines_minutes = 0
-max_buffer_lines_number = 4096
-max_commands = 100
-max_visited_buffers = 50
-
-[proxy]
-
-[network]
-connection_timeout = 60
-gnutls_ca_system = on
-gnutls_ca_user = ""
-gnutls_handshake_timeout = 30
-proxy_curl = ""
-
-[plugin]
-autoload = "*"
-debug = off
-extension = ".so"
-path = "%h/plugins"
-save_config_on_unload = on
-
-[signal]
-sighup = "${if:${info:weechat_headless}?/reload:/quit -yes}"
-sigquit = "/quit -yes"
-sigterm = "/quit -yes"
-sigusr1 = ""
-sigusr2 = ""
-
-[bar]
-buffers.color_bg = default
-buffers.color_bg_inactive = default
-buffers.color_delim = default
-buffers.color_fg = default
-buffers.conditions = ""
-buffers.filling_left_right = vertical
-buffers.filling_top_bottom = horizontal
-buffers.hidden = on
-buffers.items = "buffers"
-buffers.position = left
-buffers.priority = 0
-buffers.separator = on
-buffers.size = 0
-buffers.size_max = 0
-buffers.type = root
-buflist.color_bg = default
-buflist.color_bg_inactive = default
-buflist.color_delim = default
-buflist.color_fg = default
-buflist.conditions = ""
-buflist.filling_left_right = vertical
-buflist.filling_top_bottom = columns_vertical
-buflist.hidden = off
-buflist.items = "buflist"
-buflist.position = left
-buflist.priority = 0
-buflist.separator = on
-buflist.size = 0
-buflist.size_max = 25
-buflist.type = root
-fset.color_bg = default
-fset.color_bg_inactive = default
-fset.color_delim = cyan
-fset.color_fg = default
-fset.conditions = "${buffer.full_name} == fset.fset"
-fset.filling_left_right = vertical
-fset.filling_top_bottom = horizontal
-fset.hidden = off
-fset.items = "fset"
-fset.position = top
-fset.priority = 0
-fset.separator = on
-fset.size = 3
-fset.size_max = 3
-fset.type = window
-input.color_bg = default
-input.color_bg_inactive = default
-input.color_delim = green
-input.color_fg = default
-input.conditions = ""
-input.filling_left_right = vertical
-input.filling_top_bottom = horizontal
-input.hidden = off
-input.items = "[input_prompt]+(away),[input_search],[input_paste],input_text"
-input.position = bottom
-input.priority = 1000
-input.separator = off
-input.size = 1
-input.size_max = 0
-input.type = window
-nicklist.color_bg = default
-nicklist.color_bg_inactive = default
-nicklist.color_delim = cyan
-nicklist.color_fg = default
-nicklist.conditions = "nicklist"
-nicklist.filling_left_right = vertical
-nicklist.filling_top_bottom = columns_vertical
-nicklist.hidden = on
-nicklist.items = "buffer_nicklist"
-nicklist.position = right
-nicklist.priority = 200
-nicklist.separator = on
-nicklist.size = 0
-nicklist.size_max = 0
-nicklist.type = window
-status.color_bg = green
-status.color_bg_inactive = default
-status.color_delim = 0
-status.color_fg = 0
-status.conditions = ""
-status.filling_left_right = vertical
-status.filling_top_bottom = horizontal
-status.hidden = off
-status.items = "[time],buffer_number+:+buffer_name,buffer_title"
-status.position = bottom
-status.priority = 500
-status.separator = off
-status.size = 1
-status.size_max = 0
-status.type = window
-title.color_bg = green
-title.color_bg_inactive = default
-title.color_delim = cyan
-title.color_fg = 16
-title.conditions = ""
-title.filling_left_right = vertical
-title.filling_top_bottom = horizontal
-title.hidden = on
-title.items = "buffer_title"
-title.position = top
-title.priority = 500
-title.separator = off
-title.size = 1
-title.size_max = 0
-title.type = window
-
-[layout]
-
-[notify]
-python.slack.10xgenomics.&cloud-alerts-pagerduty = highlight
-python.slack.10xgenomics.&cloud-sumo-prod-support-alerts = highlight
-python.slack.10xgenomics.&lacework-10xdev = highlight
-python.slack.10xgenomics.&lacework-10xprod = highlight
-python.slack.10xgenomics.&testing1234 = highlight
-
-[filter]
-irc_smart = on;*;irc_smart_filter;*
-nicks = on;*;irc_366;*
-
-[key]
-ctrl-? = "/input delete_previous_char"
-ctrl-A = "/input move_beginning_of_line"
-ctrl-B = "/brows"
-ctrl-Cb = "/input insert \x02"
-ctrl-Cc = "/input insert \x03"
-ctrl-Ci = "/input insert \x1D"
-ctrl-Co = "/input insert \x0F"
-ctrl-Cr = "/input insert \x12"
-ctrl-Cu = "/input insert \x15"
-ctrl-D = "/buffer close"
-ctrl-E = "/input move_end_of_line"
-ctrl-F = "/input move_next_char"
-ctrl-H = "/input delete_previous_char"
-ctrl-I = "/input complete_next"
-ctrl-J = "/input jump_smart"
-ctrl-K = "/input delete_end_of_line"
-ctrl-L = "/window refresh"
-ctrl-M = "/input return"
-ctrl-N = "/buffer +1"
-ctrl-O = "/editor"
-ctrl-P = "/buffer -1"
-ctrl-R = "/input search_text"
-ctrl-Sctrl-U = "/input set_unread"
-ctrl-T = "/input transpose_chars"
-ctrl-U = "/url 1"
-ctrl-W = "/input delete_previous_word"
-ctrl-X = "/input switch_active_buffer"
-ctrl-Y = "/input clipboard_paste"
-meta-meta-OP = "/bar scroll buflist * b"
-meta-meta-OQ = "/bar scroll buflist * e"
-meta-meta2-11~ = "/bar scroll buflist * b"
-meta-meta2-12~ = "/bar scroll buflist * e"
-meta-meta2-1~ = "/window scroll_top"
-meta-meta2-23~ = "/bar scroll nicklist * yb"
-meta-meta2-24~ = "/bar scroll nicklist * ye"
-meta-meta2-4~ = "/window scroll_bottom"
-meta-meta2-5~ = "/window scroll_up"
-meta-meta2-6~ = "/window scroll_down"
-meta-meta2-7~ = "/window scroll_top"
-meta-meta2-8~ = "/window scroll_bottom"
-meta-meta2-A = "/buffer move -1"
-meta-meta2-B = "/buffer move +1"
-meta-meta2-C = "/buffer +1"
-meta-meta2-D = "/buffer -1"
-meta-0 = "/buffer *10"
-meta-1 = "/buffer *1"
-meta-2 = "/buffer *2"
-meta-3 = "/buffer *3"
-meta-4 = "/buffer *4"
-meta-5 = "/buffer *5"
-meta-6 = "/buffer *6"
-meta-7 = "/buffer *7"
-meta-8 = "/buffer *8"
-meta-9 = "/buffer *9"
-meta-< = "/input jump_previously_visited_buffer"
-meta-= = "/filter toggle"
-meta-> = "/input jump_next_visited_buffer"
-meta-B = "/buflist toggle"
-meta-OA = "/input history_global_previous"
-meta-OB = "/input history_global_next"
-meta-OC = "/input move_next_word"
-meta-OD = "/input move_previous_word"
-meta-OF = "/input move_end_of_line"
-meta-OH = "/input move_beginning_of_line"
-meta-OP = "/bar scroll buflist * -100%"
-meta-OQ = "/bar scroll buflist * +100%"
-meta-Oa = "/input history_global_previous"
-meta-Ob = "/input history_global_next"
-meta-Oc = "/input move_next_word"
-meta-Od = "/input move_previous_word"
-meta2-11^ = "/bar scroll buflist * -100%"
-meta2-11~ = "/bar scroll buflist * -100%"
-meta2-12^ = "/bar scroll buflist * +100%"
-meta2-12~ = "/bar scroll buflist * +100%"
-meta2-15~ = "/bar scroll nicklist * y-100%"
-meta2-17~ = "/bar scroll nicklist * y+100%"
-meta2-18~ = "/window -1"
-meta2-19~ = "/window +1"
-meta2-1;3A = "/buffer -1"
-meta2-1;3B = "/buffer +1"
-meta2-1;3C = "/buffer +1"
-meta2-1;3D = "/buffer -1"
-meta2-1;3P = "/bar scroll buflist * b"
-meta2-1;3Q = "/bar scroll buflist * e"
-meta2-1;5A = "/input history_global_previous"
-meta2-1;5B = "/input history_global_next"
-meta2-1;5P = "/bar scroll buflist * -100%"
-meta2-1;5Q = "/bar scroll buflist * +100%"
-meta2-1;9A = "/buffer move -1"
-meta2-1;9B = "/buffer move +1"
-meta2-1~ = "/input move_beginning_of_line"
-meta2-20~ = "/bar scroll title * x-50%"
-meta2-21~ = "/bar scroll title * x+50%"
-meta2-23~ = "/bar scroll nicklist * y-100%"
-meta2-24~ = "/bar scroll nicklist * y+100%"
-meta2-3~ = "/input delete_next_char"
-meta2-4~ = "/input move_end_of_line"
-meta2-5;3~ = "/window scroll_up"
-meta2-5~ = "/window page_up"
-meta2-6;3~ = "/window scroll_down"
-meta2-6~ = "/window page_down"
-meta2-7~ = "/input move_beginning_of_line"
-meta2-8~ = "/input move_end_of_line"
-meta2-A = "/input history_previous"
-meta2-B = "/input history_next"
-meta2-C = "/input move_next_char"
-meta2-D = "/input move_previous_char"
-meta2-F = "/input move_end_of_line"
-meta2-G = "/window page_down"
-meta2-H = "/input move_beginning_of_line"
-meta2-I = "/window page_up"
-meta2-Z = "/input complete_previous"
-meta-_ = "/input redo"
-meta-a = "/input jump_smart"
-meta-b = "/input move_previous_word"
-meta-d = "/input delete_next_word"
-meta-f = "/input move_next_word"
-meta-h = "/input hotlist_clear"
-meta-jmeta-l = "/input jump_last_buffer"
-meta-jmeta-r = "/server raw"
-meta-jmeta-s = "/server jump"
-meta-j01 = "/buffer 1"
-meta-j02 = "/buffer 2"
-meta-j03 = "/buffer 3"
-meta-j04 = "/buffer 4"
-meta-j05 = "/buffer 5"
-meta-j06 = "/buffer 6"
-meta-j07 = "/buffer 7"
-meta-j08 = "/buffer 8"
-meta-j09 = "/buffer 9"
-meta-j10 = "/buffer 10"
-meta-j11 = "/buffer 11"
-meta-j12 = "/buffer 12"
-meta-j13 = "/buffer 13"
-meta-j14 = "/buffer 14"
-meta-j15 = "/buffer 15"
-meta-j16 = "/buffer 16"
-meta-j17 = "/buffer 17"
-meta-j18 = "/buffer 18"
-meta-j19 = "/buffer 19"
-meta-j20 = "/buffer 20"
-meta-j21 = "/buffer 21"
-meta-j22 = "/buffer 22"
-meta-j23 = "/buffer 23"
-meta-j24 = "/buffer 24"
-meta-j25 = "/buffer 25"
-meta-j26 = "/buffer 26"
-meta-j27 = "/buffer 27"
-meta-j28 = "/buffer 28"
-meta-j29 = "/buffer 29"
-meta-j30 = "/buffer 30"
-meta-j31 = "/buffer 31"
-meta-j32 = "/buffer 32"
-meta-j33 = "/buffer 33"
-meta-j34 = "/buffer 34"
-meta-j35 = "/buffer 35"
-meta-j36 = "/buffer 36"
-meta-j37 = "/buffer 37"
-meta-j38 = "/buffer 38"
-meta-j39 = "/buffer 39"
-meta-j40 = "/buffer 40"
-meta-j41 = "/buffer 41"
-meta-j42 = "/buffer 42"
-meta-j43 = "/buffer 43"
-meta-j44 = "/buffer 44"
-meta-j45 = "/buffer 45"
-meta-j46 = "/buffer 46"
-meta-j47 = "/buffer 47"
-meta-j48 = "/buffer 48"
-meta-j49 = "/buffer 49"
-meta-j50 = "/buffer 50"
-meta-j51 = "/buffer 51"
-meta-j52 = "/buffer 52"
-meta-j53 = "/buffer 53"
-meta-j54 = "/buffer 54"
-meta-j55 = "/buffer 55"
-meta-j56 = "/buffer 56"
-meta-j57 = "/buffer 57"
-meta-j58 = "/buffer 58"
-meta-j59 = "/buffer 59"
-meta-j60 = "/buffer 60"
-meta-j61 = "/buffer 61"
-meta-j62 = "/buffer 62"
-meta-j63 = "/buffer 63"
-meta-j64 = "/buffer 64"
-meta-j65 = "/buffer 65"
-meta-j66 = "/buffer 66"
-meta-j67 = "/buffer 67"
-meta-j68 = "/buffer 68"
-meta-j69 = "/buffer 69"
-meta-j70 = "/buffer 70"
-meta-j71 = "/buffer 71"
-meta-j72 = "/buffer 72"
-meta-j73 = "/buffer 73"
-meta-j74 = "/buffer 74"
-meta-j75 = "/buffer 75"
-meta-j76 = "/buffer 76"
-meta-j77 = "/buffer 77"
-meta-j78 = "/buffer 78"
-meta-j79 = "/buffer 79"
-meta-j80 = "/buffer 80"
-meta-j81 = "/buffer 81"
-meta-j82 = "/buffer 82"
-meta-j83 = "/buffer 83"
-meta-j84 = "/buffer 84"
-meta-j85 = "/buffer 85"
-meta-j86 = "/buffer 86"
-meta-j87 = "/buffer 87"
-meta-j88 = "/buffer 88"
-meta-j89 = "/buffer 89"
-meta-j90 = "/buffer 90"
-meta-j91 = "/buffer 91"
-meta-j92 = "/buffer 92"
-meta-j93 = "/buffer 93"
-meta-j94 = "/buffer 94"
-meta-j95 = "/buffer 95"
-meta-j96 = "/buffer 96"
-meta-j97 = "/buffer 97"
-meta-j98 = "/buffer 98"
-meta-j99 = "/buffer 99"
-meta-k = "/input grab_key_command"
-meta-n = "/window scroll_next_highlight"
-meta-p = "/window scroll_previous_highlight"
-meta-r = "/input delete_line"
-meta-u = "/input scroll_unread"
-meta-wmeta-meta2-A = "/window up"
-meta-wmeta-meta2-B = "/window down"
-meta-wmeta-meta2-C = "/window right"
-meta-wmeta-meta2-D = "/window left"
-meta-wmeta2-1;3A = "/window up"
-meta-wmeta2-1;3B = "/window down"
-meta-wmeta2-1;3C = "/window right"
-meta-wmeta2-1;3D = "/window left"
-meta-wmeta-b = "/window balance"
-meta-wmeta-s = "/window swap"
-meta-x = "/bar toggle nicklist"
-meta-z = "/window zoom"
-ctrl-_ = "/input undo"
-
-[key_search]
-ctrl-J = "/input search_stop"
-ctrl-M = "/input search_stop"
-ctrl-R = "/input search_switch_case"
-meta2-A = "/input search_previous"
-meta2-B = "/input search_next"
-
-[key_cursor]
-ctrl-J = "/cursor stop"
-ctrl-M = "/cursor stop"
-meta-meta2-A = "/cursor move area_up"
-meta-meta2-B = "/cursor move area_down"
-meta-meta2-C = "/cursor move area_right"
-meta-meta2-D = "/cursor move area_left"
-meta2-1;3A = "/cursor move area_up"
-meta2-1;3B = "/cursor move area_down"
-meta2-1;3C = "/cursor move area_right"
-meta2-1;3D = "/cursor move area_left"
-meta2-A = "/cursor move up"
-meta2-B = "/cursor move down"
-meta2-C = "/cursor move right"
-meta2-D = "/cursor move left"
-@chat(python.*):D = "hsignal:slack_cursor_delete"
-@chat(python.*):L = "hsignal:slack_cursor_linkarchive"
-@chat(python.*):M = "hsignal:slack_cursor_message"
-@chat(python.*):R = "hsignal:slack_cursor_reply"
-@chat(python.*):T = "hsignal:slack_cursor_thread"
-@item(buffer_nicklist):K = "/window ${_window_number};/kickban ${nick}"
-@item(buffer_nicklist):b = "/window ${_window_number};/ban ${nick}"
-@item(buffer_nicklist):k = "/window ${_window_number};/kick ${nick}"
-@item(buffer_nicklist):q = "/window ${_window_number};/query ${nick};/cursor stop"
-@item(buffer_nicklist):w = "/window ${_window_number};/whois ${nick}"
-@chat:Q = "hsignal:chat_quote_time_prefix_message;/cursor stop"
-@chat:m = "hsignal:chat_quote_message;/cursor stop"
-@chat:q = "hsignal:chat_quote_prefix_message;/cursor stop"
-
-[key_mouse]
-@bar(buflist):ctrl-wheeldown = "hsignal:buflist_mouse"
-@bar(buflist):ctrl-wheelup = "hsignal:buflist_mouse"
-@bar(input):button2 = "/input grab_mouse_area"
-@bar(nicklist):button1-gesture-down = "/bar scroll nicklist ${_window_number} +100%"
-@bar(nicklist):button1-gesture-down-long = "/bar scroll nicklist ${_window_number} e"
-@bar(nicklist):button1-gesture-up = "/bar scroll nicklist ${_window_number} -100%"
-@bar(nicklist):button1-gesture-up-long = "/bar scroll nicklist ${_window_number} b"
-@chat(fset.fset):button1 = "/window ${_window_number};/fset -go ${_chat_line_y}"
-@chat(fset.fset):button2* = "hsignal:fset_mouse"
-@chat(fset.fset):wheeldown = "/fset -down 5"
-@chat(fset.fset):wheelup = "/fset -up 5"
-@chat(python.*):button2 = "hsignal:slack_mouse"
-@chat(script.scripts):button1 = "/window ${_window_number};/script go ${_chat_line_y}"
-@chat(script.scripts):button2 = "/window ${_window_number};/script go ${_chat_line_y};/script installremove -q ${script_name_with_extension}"
-@chat(script.scripts):wheeldown = "/script down 5"
-@chat(script.scripts):wheelup = "/script up 5"
-@item(buffer_nicklist):button1 = "/window ${_window_number};/query ${nick}"
-@item(buffer_nicklist):button1-gesture-left = "/window ${_window_number};/kick ${nick}"
-@item(buffer_nicklist):button1-gesture-left-long = "/window ${_window_number};/kickban ${nick}"
-@item(buffer_nicklist):button2 = "/window ${_window_number};/whois ${nick}"
-@item(buffer_nicklist):button2-gesture-left = "/window ${_window_number};/ban ${nick}"
-@item(buffers):button1* = "hsignal:buffers_mouse"
-@item(buffers):button2* = "hsignal:buffers_mouse"
-@item(buflist):button1* = "hsignal:buflist_mouse"
-@item(buflist):button2* = "hsignal:buflist_mouse"
-@item(buflist2):button1* = "hsignal:buflist_mouse"
-@item(buflist2):button2* = "hsignal:buflist_mouse"
-@item(buflist3):button1* = "hsignal:buflist_mouse"
-@item(buflist3):button2* = "hsignal:buflist_mouse"
-@bar:wheeldown = "/bar scroll ${_bar_name} ${_window_number} +20%"
-@bar:wheelup = "/bar scroll ${_bar_name} ${_window_number} -20%"
-@chat:button1 = "/window ${_window_number}"
-@chat:button1-gesture-left = "/window ${_window_number};/buffer -1"
-@chat:button1-gesture-left-long = "/window ${_window_number};/buffer 1"
-@chat:button1-gesture-right = "/window ${_window_number};/buffer +1"
-@chat:button1-gesture-right-long = "/window ${_window_number};/input jump_last_buffer"
-@chat:wheeldown = "/window scroll_down -window ${_window_number}"
-@chat:wheelup = "/window scroll_up -window ${_window_number}"
-@*:button3 = "/cursor go ${_x},${_y}"
--- a/weechat/xfer.conf	Sun May 18 14:59:11 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-#
-# weechat -- xfer.conf
-#
-# WARNING: It is NOT recommended to edit this file by hand,
-# especially if WeeChat is running.
-#
-# Use commands like /set or /fset to change settings in WeeChat.
-#
-# For more info, see: https://weechat.org/doc/quickstart/
-#
-
-[look]
-auto_open_buffer = on
-progress_bar_size = 20
-pv_tags = "notify_private"
-
-[color]
-status_aborted = lightred
-status_active = lightblue
-status_connecting = yellow
-status_done = lightgreen
-status_failed = lightred
-status_waiting = lightcyan
-text = default
-text_bg = default
-text_selected = white
-
-[network]
-blocksize = 65536
-fast_send = on
-own_ip = ""
-port_range = ""
-send_ack = on
-speed_limit_recv = 0
-speed_limit_send = 0
-timeout = 300
-
-[file]
-auto_accept_chats = off
-auto_accept_files = off
-auto_accept_nicks = ""
-auto_check_crc32 = off
-auto_rename = on
-auto_resume = on
-convert_spaces = on
-download_path = "%h/xfer"
-download_temporary_suffix = ".part"
-upload_path = "~"
-use_nick_in_filename = on