Recently Written · git

sh

Small shell and Python scripts

git clone https://github.com/equwal/sh

Log | Files | Refs


commit 6494fde6d5bed7b499f4752ca1209ff5b16c364e
equwal <13551856+equwal@users.noreply.github.com>
2026-10-04 23:02:26 -0700

Remove bm, sbm, mpdmenu, windervolage: not this repo's code (equwal/sbm copies, Chris Down's mpdmenu, markus schnalke's wv)

 bm               | 686 -------------------------------------------------------
 git-update-repos |   0
 mpdmenu          |  76 ------
 sbm/bm-commit    |  36 ---
 sbm/bm-page      | 212 -----------------
 sbm/bm-sync      | 248 --------------------
 sbm/bm-title     |  33 ---
 windervolage     |  57 -----
 8 files changed, 1348 deletions(-)
diff --git a/bm b/bm
deleted file mode 100755
index 5b5f090..0000000
--- a/bm
+++ /dev/null
@@ -1,686 +0,0 @@
-#!/bin/sh
-
-# bm: bookmarks in a plain file, picked with dmenu (or fzf).
-#
-#     URL<tab>description<tab>tag tag tag
-#
-# bm owns that file and nothing else. Importing, link checking, the web page
-# and so on are separate bm-* filters; see README.
-
-DMENULINES=${DMENULINES:-15}
-DATADIR=${XDG_DATA_HOME:-$HOME/.local/share}/sbm
-BOOKMARKS=${BOOKMARKS:-$DATADIR/bookmarks}
-USERTAGS=${USERTAGS:-$DATADIR/usertags}
-ENGINES=${SBM_ENGINES:-$DATADIR/engines}
-SEARCH=${SBM_SEARCH:-https://duckduckgo.com/?q=%s}
-USAGE=$BOOKMARKS.usage
-WLOCK=$BOOKMARKS.lock
-locked=
-TAB=$(printf '\t')
-
-# Two URLs are the same bookmark when they differ only in scheme, a leading
-# www., trailing slashes or the case of the host.
-AWK_NORM='
-function norm(u,    host, rest, i) {
-    sub(/^[A-Za-z][A-Za-z0-9+.-]*:\/\//, "", u)
-    i = index(u, "/")
-    if (i) { host = substr(u, 1, i - 1); rest = substr(u, i) }
-    else   { host = u; rest = "" }
-    host = tolower(host)
-    sub(/^www\./, "", host)
-    sub(/\/+$/, "", rest)
-    return host rest
-}'
-
-die () {
-    printf 'bm: %s\n' "$*" >&2
-    exit 1
-}
-
-have () {
-    command -v "$1" >/dev/null 2>&1
-}
-
-usage () {
-    cat <<'EOF'
-usage: bm [-t tag] [-S order] [action]
-Without an action: browse. Pick a bookmark, then what to do with it. Text
-that is no bookmark is opened as an address, or searched for on the web.
-  -o, --open        open a bookmark, an address or a web search
-                    (-p, --plumb is the same)
-  -c, --copy        copy a bookmark's URL to the clipboard
-      --print       print a bookmark's URL
-  -a, --add [url]   add a bookmark; the url defaults to the selection
-  -e, --edit        open the bookmark file in $EDITOR at a bookmark
-  -d, --delete      delete a bookmark
-  -l, --list        print the bookmarks
-  -m, --merge       add the bookmarks on stdin, skipping the known ones
-  -t, --tag <tag>   only consider bookmarks with this tag
-  -S, --sort <o>    used (default), recent, url, desc or tag
-  -h, --help
-EOF
-}
-
-# ---- external programs: menu, clipboard, opener ----
-
-# dmenu when there is a display to draw it on and bm does not run in a
-# terminal, for example from a key of the window manager. In a terminal fzf
-# is the menu, because its preview shows the page of the bookmark under the
-# cursor. fzf also stands in when dmenu is not installed.
-pickmenu () {
-    [ -n "$SBM_MENU" ] && return
-    if [ -n "$DISPLAY$WAYLAND_DISPLAY" ] && ! { [ -t 0 ] && [ -t 2 ]; }; then
-        order='dmenu fzf'
-    else
-        order='fzf dmenu'
-    fi
-    for menu in $order; do
-        if have "$menu"; then
-            SBM_MENU=$menu
-            return
-        fi
-    done
-    # shellcheck disable=SC2016
-    die 'no menu program found: install dmenu or fzf, or set $SBM_MENU'
-}
-
-# $SBM_MENU may carry arguments, so it is split on purpose.
-custommenu () {
-    # shellcheck disable=SC2086
-    $SBM_MENU "$@"
-}
-
-fzfcmd () {
-    # shellcheck disable=SC2086
-    fzf -i --reverse --height=40% $SBM_FZF_OPTS "$@"
-}
-
-# fzfpick <fzf options>: the chosen line, or else what was typed, which is
-# what dmenu gives by itself.
-fzfpick () {
-    out=$(fzfcmd --print-query "$@")
-    chosen=$(printf '%s\n' "$out" | sed -n 2p)
-    printf '%s\n' "${chosen:-$(printf '%s\n' "$out" | sed -n 1p)}"
-}
-
-# pick <prompt>: choices on stdin; the chosen line, or the typed text.
-pick () {
-    case $SBM_MENU in
-        fzf)   fzfpick --prompt="$1 " ;;
-        dmenu) dmenu -i -l "$DMENULINES" -p "$1" ;;
-        *)     custommenu pick "$1" ;;
-    esac
-}
-
-# pickmulti <prompt>: like pick, for any number of lines.
-pickmulti () {
-    choices=$(cat)
-    case $SBM_MENU in
-        fzf)
-            while :; do
-                out=$(printf '%s\n' "$choices" | fzfcmd --multi --print-query \
-                      --prompt="$1 " --header='TAB marks, ENTER confirms. No match: ENTER creates the typed tag, ESC finishes.')
-                chosen=$(printf '%s\n' "$out" | sed 1d)
-                if [ -n "$chosen" ]; then
-                    printf '%s\n' "$chosen"
-                    break
-                fi
-                typed=$(printf '%s\n' "$out" | sed -n 1p)
-                [ -n "$typed" ] || break
-                printf '%s\n' "$typed"
-            done
-            ;;
-        dmenu)
-            while :; do
-                one=$(printf 'done | (choose when done)\n%s\n' "$choices" \
-                      | dmenu -i -l "$DMENULINES" -p "$1")
-                # An empty answer means the menu was closed.
-                [ -z "$one" ] || [ "${one%% *}" = 'done' ] && break
-                printf '%s\n' "$one"
-            done
-            ;;
-        *)  printf '%s\n' "$choices" | custommenu multi "$1" ;;
-    esac
-}
-
-# ask <prompt> [default]: free text on stdout.
-ask () {
-    case $SBM_MENU in
-        fzf)
-            printf '%s%s: ' "$1" "${2:+ [$2]}" >&2
-            IFS= read -r reply || reply=
-            printf '%s\n' "${reply:-$2}"
-            ;;
-        dmenu)
-            if [ -n "$2" ]; then printf '%s\n' "$2"; fi | dmenu -p "$1:"
-            ;;
-        *)
-            if [ -n "$2" ]; then printf '%s\n' "$2"; fi | custommenu ask "$1"
-            ;;
-    esac
-}
-
-clip_out () {
-    # shellcheck disable=SC2086,SC2016
-    if   [ -n "$SBM_COPY" ]; then $SBM_COPY
-    elif [ -n "$WAYLAND_DISPLAY" ] && have wl-copy; then wl-copy
-    elif have xclip; then xclip -i -selection clipboard
-    elif have xsel; then xsel -ib
-    else die 'no clipboard program found: set $SBM_COPY'
-    fi
-}
-
-# The primary selection first, then the clipboard. Never fatal: without
-# either there is just no URL to suggest.
-clip_in () {
-    # shellcheck disable=SC2086
-    if   [ -n "$SBM_PASTE" ]; then $SBM_PASTE
-    elif [ -n "$WAYLAND_DISPLAY" ] && have wl-paste; then
-        wl-paste -n -p; echo; wl-paste -n
-    elif have xclip; then
-        xclip -o; echo; xclip -o -selection clipboard
-    elif have xsel; then
-        xsel -op; echo; xsel -ob
-    fi 2>/dev/null
-}
-
-opener () {
-    # shellcheck disable=SC2086,SC2016
-    if   [ -n "$SBM_OPEN" ]; then $SBM_OPEN "$1"
-    elif have xdg-open; then xdg-open "$1"
-    else die 'no opener found: set $SBM_OPEN'
-    fi
-}
-
-# ---- the bookmark file ----
-
-# Bookmarks restricted to $tagfilter and ordered by $sortorder, each followed
-# by a fourth field: how often it was used.
-rows () {
-    SBM_TAG=$tagfilter SBM_ORDER=$sortorder SBM_USAGE=$USAGE awk -F'\t' '
-        BEGIN {
-            while ((getline line < ENVIRON["SBM_USAGE"]) > 0) {
-                split(line, f, "\t")
-                used[f[1]] = f[2]
-            }
-            order = ENVIRON["SBM_ORDER"]
-        }
-        NF == 0 || /^#/ { next }
-        ENVIRON["SBM_TAG"] != "" {
-            hit = 0
-            n = split($3, tags, " ")
-            for (i = 1; i <= n; i++)
-                if (tags[i] == ENVIRON["SBM_TAG"]) hit = 1
-            if (!hit) next
-        }
-        {
-            if (order == "used")        key = sprintf("%09d", 999999999 - used[$1])
-            else if (order == "recent") key = sprintf("%09d", 999999999 - NR)
-            else if (order == "desc")   key = tolower($2)
-            else if (order == "tag")    key = tolower($3)
-            else                        key = ""
-            printf "%s\t%s\t%s\t%s\t%s\t%d\n", key, tolower($1), $1, $2, $3, used[$1]
-        }' "$BOOKMARKS" | LC_ALL=C sort -t"$TAB" -k1,1 -k2,2 | cut -f3-
-}
-
-# details <url>: a bookmark in full, one field per line.
-details () {
-    rows | SBM_URL=$1 awk -F'\t' '$1 == ENVIRON["SBM_URL"] {
-        printf "%s\n%s\ntags: %s\nused: %d\n", $1, $2, $3, $4
-        exit
-    }'
-}
-
-is_bookmark () {
-    SBM_URL=$1 awk -F'\t' '$1 == ENVIRON["SBM_URL"] { found = 1; exit }
-        END { exit !found }' "$BOOKMARKS"
-}
-
-# For fzf a fifth field is added, and it is all that is listed. The first
-# four feed the preview, which follows the cursor: the bookmark in full, then
-# the text of its page when bm-page is installed. fzf stops the preview when
-# the cursor moves on, so the short sleep keeps a fast scroll from fetching
-# each page on the way. A sleep that knows no fractions fails and skips it.
-PREVIEW="printf '%s\n%s\ntags: %s\nused: %s\n\n' {1} {2} {3} {4}; sleep 0.3 2>/dev/null; bm-page {1} 2>/dev/null"
-fzfrows () {
-    awk -F'\t' 'NF {
-        desc = $2
-        if (length(desc) > 44) desc = substr(desc, 1, 42) ".."
-        printf "%s\t%-44s  %-18s  %s\n", $0, desc, $3, $1
-    }'
-}
-
-# withpreview <fzf command and options>: for the output of fzfrows.
-withpreview () {
-    "$@" --delimiter="$TAB" --with-nth=5.. \
-         --preview-window=right:50%:wrap --preview="$PREVIEW"
-}
-
-# choose <prompt>: a bookmark's URL, or whatever else the user typed.
-choose () {
-    if [ "$SBM_MENU" = fzf ]; then
-        rows | fzfrows | withpreview fzfpick --prompt="$1 "
-    else
-        rows | cut -f1-3 | pick "$1"
-    fi | cut -f1
-}
-
-# choose_url <prompt>: like choose, but only a bookmark will do.
-choose_url () {
-    if [ -z "$(rows)" ]; then
-        [ -n "$tagfilter" ] && die "no bookmarks tagged '$tagfilter'"
-        die 'no bookmarks yet; add one with: bm --add'
-    fi
-    url=$(choose "$1")
-    [ -n "$url" ] || exit 1
-    is_bookmark "$url" || die "not a bookmark: $url"
-    printf '%s\n' "$url"
-}
-
-# find_dup <url>: print the bookmark that is the same page as <url>, if any.
-find_dup () {
-    SBM_URL=$1 awk -F'\t' "$AWK_NORM"'
-        BEGIN { want = norm(ENVIRON["SBM_URL"]) }
-        NF && !/^#/ && norm($1) == want { print; exit }' "$BOOKMARKS"
-}
-
-# tmpfile <file>: create an empty temporary file and print its name. Beside
-# <file> if that directory can be written to, else in $TMPDIR. noclobber makes
-# the shell create it with O_EXCL, so a file or symlink planted under that
-# name in a shared /tmp is refused rather than followed: mktemp, in POSIX.
-tmpfile () {
-    for candidate in "$1.tmp.$$" "${TMPDIR:-/tmp}/sbm.$$.${1##*/}"; do
-        if (umask 077; set -C; : > "$candidate") 2>/dev/null; then
-            printf '%s\n' "$candidate"
-            return 0
-        fi
-    done
-    return 1
-}
-
-# rewrite <file>: replace <file> with stdin. Through cat, not mv, so that a
-# symlinked file stays a symlink and keeps its mode.
-rewrite () {
-    tmp=$(tmpfile "$1") || die "cannot create a temporary file for $1"
-    cat > "$tmp" && cat "$tmp" > "$1"
-    rc=$?
-    rm -f "$tmp"
-    return $rc
-}
-
-# lock: take the write lock of the bookmark file, a directory beside it. bm
-# holds it while it changes the file, and bm-sync while it reads or writes
-# the file, so that neither writes over a change of the other. A lock whose
-# process is gone is taken over. After $SBM_LOCK_WAIT seconds (default 10)
-# bm stops.
-lock () {
-    waited=0
-    until mkdir "$WLOCK" 2>/dev/null; do
-        pid=$(cat "$WLOCK/pid" 2>/dev/null)
-        if [ -n "$pid" ] && ! kill -0 "$pid" 2>/dev/null; then
-            [ "$(cat "$WLOCK/pid" 2>/dev/null)" != "$pid" ] || rm -rf "$WLOCK"
-            continue
-        fi
-        [ "$waited" -lt "${SBM_LOCK_WAIT:-10}" ] \
-            || die "$BOOKMARKS is in use (bm -e or bm-sync). If neither runs, remove $WLOCK"
-        sleep 1
-        waited=$((waited + 1))
-    done
-    echo $$ > "$WLOCK/pid"
-    locked=1
-    # Also in the subshell of add_inside, which does not keep these traps.
-    trap unlock EXIT
-    trap 'exit 1' HUP INT TERM
-}
-
-unlock () {
-    [ -z "$locked" ] || rm -rf "$WLOCK"
-    locked=
-}
-
-# bump <url>: count a use, for the "used" order. The counts live beside the
-# bookmark file, which stays a plain list: url, count, time of last use.
-# srand() returns the previous seed, and the first seed is the time of day:
-# that is "date +%s" for a POSIX that has no %s.
-bump () {
-    [ -e "$USAGE" ] || : > "$USAGE" || return 0
-    SBM_URL=$1 awk -F'\t' '
-        BEGIN { OFS = "\t"; srand(); now = srand() }
-        $1 == ENVIRON["SBM_URL"] { $2 = $2 + 1; $3 = now; seen = 1 }
-        { print }
-        END { if (!seen) print ENVIRON["SBM_URL"], 1, now }
-    ' "$USAGE" | rewrite "$USAGE"
-}
-
-# learn_tags: tag names on stdin; the ones $USERTAGS lacks are appended to it.
-learn_tags () {
-    [ -e "$USERTAGS" ] || : > "$USERTAGS"
-    while IFS= read -r name; do
-        [ -n "$name" ] || continue
-        sed 's/[ |].*//' "$USERTAGS" | grep -Fxq -- "$name" \
-            || printf '%s | \n' "$name" >> "$USERTAGS"
-    done
-}
-
-# changed <message>: the file was written. bm-commit, where installed, keeps
-# its history in git. bm-sync, where installed, tells your other machines; it
-# runs in the background, so that a slow network never holds the menu up.
-changed () {
-    if have bm-commit; then bm-commit "$1"; fi
-    if have bm-sync; then bm-sync -q >/dev/null 2>&1 & fi
-}
-
-# ---- actions ----
-
-copy_url () {
-    bump "$1"
-    printf '%s' "$1" | clip_out
-}
-
-open_url () {
-    bump "$1"
-    opener "$1"
-}
-
-# The editor reads the file when it starts and writes it when you save, so
-# bm holds the lock until the editor ends. bm-sync then waits, and syncs the
-# edit after it.
-edit_url () {
-    lock
-    line=$(SBM_URL=$1 awk -F'\t' \
-           '$1 == ENVIRON["SBM_URL"] { print NR; exit }' "$BOOKMARKS")
-    ${VISUAL:-${EDITOR:-vi}} "+${line:-1}" "$BOOKMARKS"
-    unlock
-    changed 'bm: edit'
-}
-
-delete_url () {
-    lock
-    for file in "$BOOKMARKS" "$USAGE"; do
-        [ -e "$file" ] || continue
-        SBM_URL=$1 awk -F'\t' '$1 != ENVIRON["SBM_URL"]' "$file" \
-            | rewrite "$file" || die "could not rewrite $file"
-    done
-    unlock
-    changed "bm: delete $1"
-}
-
-# urlencode <text>: for a query string. awk has no ord(), hence the table.
-urlencode () {
-    printf '%s' "$1" | LC_ALL=C awk '
-        BEGIN { for (i = 1; i < 256; i++) ord[sprintf("%c", i)] = i }
-        {
-            for (i = 1; i <= length($0); i++) {
-                c = substr($0, i, 1)
-                if (c ~ /[A-Za-z0-9._~-]/) out = out c
-                else if (c == " ")         out = out "+"
-                else                       out = out sprintf("%%%02X", ord[c])
-            }
-        }
-        END { printf "%s", out }'
-}
-
-# go <text>: open text that is not a bookmark. One word with "://" or a dot
-# in it is an address. Anything else is a web search: by the engine whose
-# keyword is the first word ($ENGINES holds "keyword | url with %s" lines),
-# or else by $SEARCH.
-go () {
-    case $1 in
-        *' '*) ;;
-        *://*) opener "$1"; return ;;
-        *.*)   opener "https://$1"; return ;;
-    esac
-    engine=$SEARCH
-    query=$1
-    if [ "${1%% *}" != "$1" ] && [ -r "$ENGINES" ]; then
-        found=$(SBM_KEY=${1%% *} awk -F' *[|] *' \
-                '$1 == ENVIRON["SBM_KEY"] { print $2; exit }' "$ENGINES")
-        if [ -n "$found" ]; then
-            engine=$found
-            query=${1#* }
-        fi
-    fi
-    opener "$(printf '%s\n' "$engine" | sed "s|%s|$(urlencode "$query")|")"
-}
-
-plumb () {
-    url=$(choose 'open:')
-    [ -n "$url" ] || exit 1
-    if is_bookmark "$url"; then open_url "$url"; else go "$url"; fi
-}
-
-# Exit status 3 means "already bookmarked".
-add () {
-    url=$1
-    if [ -z "$url" ]; then
-        url=$(ask 'url' "$(clip_in | grep '://' | head -n 1)")
-    fi
-    url=$(printf '%s' "$url" | tr -d '[:space:]')
-    [ -n "$url" ] || exit 1
-
-    dup=$(find_dup "$url")
-    if [ -n "$dup" ]; then
-        printf 'bm: already bookmarked:\n%s\n' "$dup" >&2
-        exit 3
-    fi
-
-    # bm-title, where installed, fetches the page's title.
-    title=
-    if have bm-title; then title=$(bm-title "$url"); fi
-    desc=$(ask 'description' "$title" | tr '\t' ' ')
-
-    [ -e "$USERTAGS" ] || : > "$USERTAGS"
-    names=$(pickmulti 'tags:' < "$USERTAGS" | sed 's/[ |].*//' | sed '/^$/d' \
-            | sort -u)
-    printf '%s\n' "$names" | learn_tags
-    tags=$(printf '%s\n' "$names" | paste -sd ' ' -)
-
-    lock
-    printf '%s\t%s\t%s\n' "$url" "$desc" "$tags" >> "$BOOKMARKS"
-    unlock
-    changed "bm: add $url"
-}
-
-# merge: bookmark lines on stdin (a bare URL per line will do); the ones not
-# known yet are appended. This is how the output of bm-import gets in.
-merge () {
-    new=$(tmpfile "$BOOKMARKS") || die 'cannot create a temporary file'
-    lock
-    SBM_FILE=$BOOKMARKS SBM_NEW=$new awk -F'\t' "$AWK_NORM"'
-        BEGIN {
-            while ((getline line < ENVIRON["SBM_FILE"]) > 0) {
-                split(line, f, "\t")
-                seen[norm(f[1])] = 1
-            }
-        }
-        NF == 0 || /^#/ { next }
-        seen[norm($1)]++ { skipped++; next }
-        { printf "%s\t%s\t%s\n", $1, $2, $3 > ENVIRON["SBM_NEW"]; added++ }
-        END { printf "added %d, skipped %d duplicates\n", added, skipped }'
-    cut -f3 "$new" | tr ' ' '\n' | sort -u | learn_tags
-    cat "$new" >> "$BOOKMARKS"
-    unlock
-    rm -f "$new"
-    changed 'bm: merge'
-}
-
-nextorder () {
-    case $sortorder in
-        used)   sortorder=recent ;;
-        recent) sortorder=url ;;
-        url)    sortorder=desc ;;
-        desc)   sortorder=tag ;;
-        *)      sortorder=used ;;
-    esac
-}
-
-# add exits, which must not end a browsing session; and when it refuses a
-# duplicate the user has to get to see that before the list comes back.
-add_inside () {
-    ( add '' )
-    if [ $? -eq 3 ]; then
-        ask 'already bookmarked; press ENTER' >/dev/null
-    fi
-}
-
-# browse_fzf: fzf can bind keys, so one list does everything. Opening and
-# copying leave it, the other keys come back to it.
-browse_fzf () {
-    while :; do
-        header="ENTER open  ^Y copy  ^A add  ^D delete  ^E edit  ^S sort [$sortorder]"
-        out=$(rows | fzfrows | withpreview fzfcmd --print-query \
-              --expect=ctrl-y,ctrl-a,ctrl-d,ctrl-e,ctrl-s \
-              --header="$header" --prompt='bookmark: ')
-        [ -n "$out" ] || exit 1
-        typed=$(printf '%s\n' "$out" | sed -n 1p)
-        key=$(printf '%s\n' "$out" | sed -n 2p)
-        url=$(printf '%s\n' "$out" | sed -n 3p | cut -f1)
-
-        case $key in
-            ctrl-a) add_inside; continue ;;
-            ctrl-s) nextorder; continue ;;
-        esac
-        if [ -z "$url" ]; then
-            [ -n "$key" ] && continue
-            [ -n "$typed" ] || exit 1
-            go "$typed"
-            exit 0
-        fi
-        case $key in
-            ctrl-y) copy_url "$url"; exit 0 ;;
-            ctrl-e) edit_url "$url" ;;
-            ctrl-d)
-                case $(ask "delete $url? (y/N)") in
-                    y|Y|yes) delete_url "$url" ;;
-                esac
-                ;;
-            *)      open_url "$url"; exit 0 ;;
-        esac
-    done
-}
-
-# browse_menu: dmenu can neither bind keys nor preview, so a second menu does
-# both: it shows the bookmark in full and asks what to do with it. Closing it
-# goes back to the list; closing the list leaves.
-browse_menu () {
-    while :; do
-        chosen=$( { printf '[add]\n'; rows | cut -f1-3; } | pick 'bookmark:')
-        [ -n "$chosen" ] || exit 1
-        if [ "$chosen" = '[add]' ]; then
-            add_inside
-            continue
-        fi
-        url=$(printf '%s\n' "$chosen" | cut -f1)
-        if ! is_bookmark "$url"; then
-            go "$chosen"
-            exit 0
-        fi
-        action=$( { printf 'open\ncopy\nedit\ndelete\n'
-                    details "$url" | sed 's/^/  /'; } | pick 'do:')
-        case $action in
-            open)   open_url "$url"; exit 0 ;;
-            copy)   copy_url "$url"; exit 0 ;;
-            edit)   edit_url "$url" ;;
-            delete) delete_url "$url" ;;
-        esac
-    done
-}
-
-# ---- main ----
-
-setaction () {
-    if [ -n "$action" ]; then
-        printf 'bm: only one action at a time\n' >&2
-        exit 2
-    fi
-    action=$1
-}
-
-# needarg <flag> <count of remaining arguments> <value>
-needarg () {
-    if [ "$2" -lt 2 ] || [ -z "$3" ]; then
-        printf 'bm: %s needs an argument\n' "$1" >&2
-        exit 2
-    fi
-}
-
-action=
-addurl=
-tagfilter=
-sortorder=${SBM_SORT:-used}
-while [ $# -gt 0 ]; do
-    case $1 in
-        -h|--help)   setaction help ;;
-        -c|--copy)   setaction copy ;;
-        --print)     setaction print ;;
-        -o|--open|-p|--plumb) setaction open ;;
-        -e|--edit)   setaction edit ;;
-        -d|--delete) setaction delete ;;
-        -l|--list)   setaction list ;;
-        -m|--merge)  setaction merge ;;
-        -a|--add)
-            setaction add
-            case ${2-} in
-                ''|-*) ;;
-                *) addurl=$2; shift ;;
-            esac
-            ;;
-        -t|--tag)
-            needarg "$1" $# "${2-}"
-            tagfilter=$2
-            shift
-            ;;
-        -S|--sort)
-            needarg "$1" $# "${2-}"
-            sortorder=$2
-            shift
-            ;;
-        *)
-            printf 'bm: unknown argument: %s\n' "$1" >&2
-            usage >&2
-            exit 2
-            ;;
-    esac
-    shift
-done
-
-case $sortorder in
-    used|recent|url|desc|tag) ;;
-    *)
-        printf 'bm: unknown sort order: %s\n' "$sortorder" >&2
-        exit 2
-        ;;
-esac
-
-if [ "$action" = help ]; then
-    usage
-    exit 0
-fi
-
-mkdir -p "$(dirname "$BOOKMARKS")" || die "cannot create directory for $BOOKMARKS"
-[ -e "$BOOKMARKS" ] || : > "$BOOKMARKS" || die "cannot create $BOOKMARKS"
-
-if awk '!/\t/ && !/^#/ && NF { found = 1; exit } END { exit !found }' "$BOOKMARKS"
-then
-    printf 'bm: %s has lines in the old format; run: bm-migrate\n' "$BOOKMARKS" >&2
-fi
-
-case $action in
-    list|merge) ;;
-    *) pickmenu ;;
-esac
-
-case $action in
-    '')
-        if [ "$SBM_MENU" = fzf ]; then browse_fzf; else browse_menu; fi
-        ;;
-    open)   plumb ;;
-    copy)   url=$(choose_url 'copy:')   && copy_url "$url" ;;
-    print)  url=$(choose_url 'print:')  && bump "$url" && printf '%s\n' "$url" ;;
-    edit)   url=$(choose_url 'edit:')   && edit_url "$url" ;;
-    delete) url=$(choose_url 'delete:') && delete_url "$url" \
-                && printf 'deleted %s\n' "$url" ;;
-    add)    add "$addurl" ;;
-    list)   rows | cut -f1-3 ;;
-    merge)  merge ;;
-esac
diff --git a/git-update-repos b/git-update-repos
old mode 100644
new mode 100755
diff --git a/mpdmenu b/mpdmenu
deleted file mode 100755
index d32d957..0000000
--- a/mpdmenu
+++ /dev/null
@@ -1,76 +0,0 @@
-#!/bin/bash
-
-all_name='[ALL]'
-mode=library
-
-d_artist() {
-    mpc list artist | sort -f | dmenu -p artist "${dmenu_args[@]}"
-}
-
-d_album() {
-    local artist="$1"
-    local albums
-
-    mapfile -t albums < <(mpc list album artist "$artist")
-    if (( ${#albums[@]} > 1 )) ; then
-        {
-            printf '%s\n' "$all_name"
-            printf '%s\n' "${albums[@]}" | sort -f
-        } | dmenu -p album "${dmenu_args[@]}"
-    else
-        # We only have one album, so just use that.
-        printf '%s\n' "${albums[0]}"
-    fi
-}
-
-d_playlist() {
-    local format="%position% %title%"
-    local extra_format="(%artist% - %album%)"
-    local track
-    local num_extras
-
-    # If all tracks are from the same artist and album, no need to display that
-    num_extras=$(mpc playlist -f "$extra_format" | sort | uniq | wc -l)
-    (( num_extras == 1 )) || format+=" $extra_format"
-
-    track=$(mpc playlist -f "$format" | dmenu -p track "${dmenu_args[@]}")
-    printf '%s' "${track%% *}"
-}
-
-i=2
-
-for arg do
-    if [[ $arg == :: ]]; then
-        dmenu_args=( "${@:$i}" )
-        break
-    fi
-
-    case "$arg" in
-        -l) mode=library ;;
-        -p) mode=playlist ;;
-    esac
-
-    let i++
-done
-
-case "$mode" in
-    library)
-        artist=$(d_artist)
-        [[ $artist ]] || exit 1
-
-        album=$(d_album "$artist")
-        [[ $album ]] || exit 2
-
-        mpc clear
-        if [[ $album == "$all_name" ]]; then
-            mpc find artist "$artist" | sort | mpc add
-        else
-            mpc find artist "$artist" album "$album" | sort | mpc add
-        fi
-
-        mpc play >/dev/null
-    ;;
-    playlist)
-        mpc play "$(d_playlist)" >/dev/null
-    ;;
-esac
diff --git a/sbm/bm-commit b/sbm/bm-commit
deleted file mode 100755
index fbefcca..0000000
--- a/sbm/bm-commit
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/bin/sh
-
-# bm-commit [message]: commit the bookmark file, and the tag file beside it,
-# when their directory is a git repository of its own. bm runs it after every
-# change when it is installed. It never pushes or pulls.
-#
-#     cd ~/.local/share/sbm && git init
-#     printf '%s\n' '*.usage' '*.bak*' > .gitignore
-#
-# A file that merely sits somewhere inside a bigger repository (your dotfiles,
-# say) is left alone, unless SBM_GIT=1. SBM_GIT=0 turns commits off.
-
-DATADIR=${XDG_DATA_HOME:-$HOME/.local/share}/sbm
-BOOKMARKS=${BOOKMARKS:-$DATADIR/bookmarks}
-USERTAGS=${USERTAGS:-$DATADIR/usertags}
-
-[ "${SBM_GIT:-auto}" != 0 ] || exit 0
-dir=$(cd "$(dirname "$BOOKMARKS")" 2>/dev/null && pwd -P) || exit 0
-top=$(git -C "$dir" rev-parse --show-toplevel 2>/dev/null) || exit 0
-[ "${SBM_GIT:-auto}" = 1 ] || [ "$top" = "$dir" ] || exit 0
-
-set -- "${1:-bm: update}" "$BOOKMARKS"
-if [ -e "$USERTAGS" ] \
-    && [ "$(cd "$(dirname "$USERTAGS")" 2>/dev/null && pwd -P)" = "$dir" ]
-then
-    set -- "$@" "$USERTAGS"
-fi
-message=$1
-shift
-
-git -C "$dir" add -- "$@" 2>/dev/null || exit 0
-git -C "$dir" diff --cached --quiet -- "$@" && exit 0
-if ! git -C "$dir" commit -q -m "$message" -- "$@" >/dev/null 2>&1; then
-    printf 'bm-commit: git commit failed in %s\n' "$dir" >&2
-    exit 1
-fi
diff --git a/sbm/bm-page b/sbm/bm-page
deleted file mode 100755
index 41d73ce..0000000
--- a/sbm/bm-page
+++ /dev/null
@@ -1,212 +0,0 @@
-#!/bin/sh
-
-# bm-page: print a web page as plain text: its title, a blank line, then its
-# text. When it is installed, the fzf list of bm shows the page of the
-# bookmark under the cursor in the preview, live. SBM_FETCH=0 keeps it off
-# the network.
-#
-#     bm-page https://suckless.org/ | less
-#
-# Only http and https pages are fetched. curl stops after 5 seconds, and dd
-# passes at most 512k of the page. Scripts, styles and comments are left
-# out. Control characters are removed, so that a page cannot send escape
-# sequences to the terminal.
-
-if [ $# -ne 1 ]; then
-    printf 'usage: bm-page <url>\n' >&2
-    exit 2
-fi
-[ "${SBM_FETCH:-1}" != 0 ] || exit 0
-case $(printf '%s' "$1" | tr '[:upper:]' '[:lower:]') in
-    http://*|https://*) ;;
-    *) exit 0 ;;
-esac
-
-# The first awk reads the page as one record (RS is a control character),
-# then splits it at each "<". Each part starts with a tag and ends with the
-# text after it, so one pass through the parts reads the page in linear
-# time. The second awk tidies the white space.
-curl -sL --max-time 5 -A 'Mozilla/5.0' -- "$1" 2>/dev/null \
-    | dd bs=1024 count=512 2>/dev/null | LC_ALL=C awk '
-    function utf8(c) {
-        if (c == 160) return " "
-        if (c < 128) return sprintf("%c", c)
-        if (c < 2048) return sprintf("%c%c", 192 + int(c / 64), 128 + c % 64)
-        if (c < 65536)
-            return sprintf("%c%c%c", 224 + int(c / 4096), 128 + int(c / 64) % 64, 128 + c % 64)
-        if (c < 1114112)
-            return sprintf("%c%c%c%c", 240 + int(c / 262144), 128 + int(c / 4096) % 64,
-                           128 + int(c / 64) % 64, 128 + c % 64)
-        return ""
-    }
-    function entity(name,    n, i) {
-        if (name ~ /^#[0-9]+$/) return utf8(substr(name, 2) + 0)
-        if (name ~ /^#[xX][0-9A-Fa-f]+$/) {
-            n = 0
-            for (i = 3; i <= length(name); i++)
-                n = n * 16 + index("0123456789abcdef", tolower(substr(name, i, 1))) - 1
-            return utf8(n)
-        }
-        if (name in named) return named[name]
-        return "&" name ";"
-    }
-    function decode(s,    out, i, j, name) {
-        out = ""
-        while ((i = index(s, "&")) > 0) {
-            out = out substr(s, 1, i - 1)
-            s = substr(s, i + 1)
-            j = index(s, ";")
-            name = substr(s, 1, j - 1)
-            if (j > 1 && j <= 12 && name ~ /^(#[0-9]+|#[xX][0-9A-Fa-f]+|[A-Za-z][A-Za-z0-9]*)$/) {
-                out = out entity(name)
-                s = substr(s, j + 1)
-            } else {
-                out = out "&"
-            }
-        }
-        return out s
-    }
-    # text: the text of the page, or of its title. As in a browser, a line
-    # break in the text is a space. The line breaks come from the tags: brk
-    # tells how many are waiting for the next text.
-    function text(s) {
-        if (intitle) {
-            title = title " " s
-            return
-        }
-        s = decode(s)
-        gsub(/[ \t\r\n]+/, " ", s)
-        if (s == "" || s == " ") {
-            if (s == " " && !brk) printf " "
-            return
-        }
-        if (brk) {
-            printf "%s", brk == 2 ? "\n\n" : "\n"
-            brk = 0
-            sub(/^ /, "", s)
-        }
-        printf "%s", s
-    }
-    BEGIN {
-        RS = "\001"
-        named["amp"] = "&"; named["lt"] = "<"; named["gt"] = ">"; named["quot"] = "\""
-        named["apos"] = "\047"; named["nbsp"] = " "
-        named["ndash"] = utf8(8211); named["mdash"] = utf8(8212); named["hellip"] = utf8(8230)
-        named["lsquo"] = utf8(8216); named["rsquo"] = utf8(8217)
-        named["ldquo"] = utf8(8220); named["rdquo"] = utf8(8221)
-        named["laquo"] = utf8(171); named["raquo"] = utf8(187); named["middot"] = utf8(183)
-        named["bull"] = utf8(8226); named["copy"] = utf8(169); named["reg"] = utf8(174)
-        named["trade"] = utf8(8482); named["deg"] = utf8(176); named["times"] = utf8(215)
-        named["euro"] = utf8(8364)
-        # Tags that start a line, and tags that start a paragraph.
-        split("br div li tr dt dd header footer nav main section article aside " \
-              "figure figcaption form details summary address hr", b, " ")
-        for (i in b) line[b[i]] = 1
-        split("p h1 h2 h3 h4 h5 h6 ul ol dl table blockquote pre", b, " ")
-        for (i in b) para[b[i]] = 1
-        # Tags whose content is no text of the page.
-        split("script style noscript template svg iframe object", b, " ")
-        for (i in b) hidden[b[i]] = 1
-    }
-    { page = page $0 }
-    END {
-        if (substr(page, 1, 5) == "%PDF-") {
-            print "(a PDF document)"
-            exit
-        }
-        n = split(page, part, "<")
-        text(part[1])
-        for (i = 2; i <= n; i++) {
-            p = part[i]
-            if (comment || substr(p, 1, 3) == "!--") {
-                k = index(p, "-->")
-                comment = !k
-                if (k && skip == "") text(substr(p, k + 3))
-                continue
-            }
-            k = index(p, ">")
-            if (!k) {
-                # A "<" in the text, or the end of a page that dd cut off.
-                if (skip == "" && i < n) text("<" p)
-                continue
-            }
-            tag = tolower(substr(p, 1, k - 1))
-            closing = substr(tag, 1, 1) == "/"
-            name = tag
-            sub(/^\//, "", name)
-            sub(/[^a-z0-9].*/, "", name)
-            rest = substr(p, k + 1)
-            if (skip != "") {
-                if (closing && name == skip) {
-                    skip = ""
-                    text(rest)
-                }
-                continue
-            }
-            if (name in hidden && !closing && substr(tag, length(tag)) != "/") {
-                skip = name
-                continue
-            }
-            if (name == "title" && !done) {
-                if (closing) {
-                    intitle = 0
-                    done = 1
-                    t = decode(title)
-                    gsub(/[ \t\r\n]+/, " ", t)
-                    printf "\n\001T%s\n", t
-                    brk = 0
-                } else {
-                    intitle = 1
-                }
-            } else if (name == "main") {
-                # Marks for the second awk: the main part of the page.
-                printf "\n\001%s\n", closing ? ">" : "<"
-                brk = 0
-            } else if (name in para) {
-                brk = 2
-            } else if (name in line) {
-                if (!brk) brk = 1
-            } else if (name == "td" || name == "th") {
-                text(" ")
-            }
-            text(rest)
-        }
-        print ""
-    }' | LC_ALL=C awk '
-    {
-        gsub(/\302\240/, " ")
-        gsub(/\302[\200-\237]/, "")
-        gsub(/[ \t\r]+/, " ")
-        sub(/^ /, "")
-        sub(/ $/, "")
-    }
-    /^\001T/ {
-        if (title == "") {
-            title = substr($0, 3)
-            sub(/^ /, "", title)
-        }
-        next
-    }
-    /^\001</ { if (!opened) { opened = 1; from = n + 1 }; next }
-    /^\001>/ { if (opened && !closed) { closed = 1; to = n }; next }
-    { line[++n] = $0 }
-    # The title, then the main part of the page when the page marks one, so
-    # that menus do not fill the preview. At most one blank line in a row.
-    END {
-        if (!opened) from = 1
-        if (!closed) to = n
-        if (title != "") {
-            print title
-            seen = blank = 1
-        }
-        for (i = from; i <= to; i++) {
-            if (line[i] == "") {
-                blank = seen
-                continue
-            }
-            if (blank) print ""
-            print line[i]
-            seen = 1
-            blank = 0
-        }
-    }' | tr -d '\000-\010\013-\037\177'
diff --git a/sbm/bm-sync b/sbm/bm-sync
deleted file mode 100755
index b4e233d..0000000
--- a/sbm/bm-sync
+++ /dev/null
@@ -1,248 +0,0 @@
-#!/bin/sh
-
-# bm-sync: keep the bookmark file the same on all your devices, through an
-# sbm-sync server (https://github.com/equwal/sbm-sync). bm runs it after each
-# change when it is installed; you can also run it yourself, or from cron.
-#
-# The server merges: bookmarks that you add or remove here are added or
-# removed there, and the other way round. The file stays a plain file.
-
-DATADIR=${XDG_DATA_HOME:-$HOME/.local/share}/sbm
-BOOKMARKS=${BOOKMARKS:-$DATADIR/bookmarks}
-CONFIG=${SBM_SYNC_CONFIG:-${XDG_CONFIG_HOME:-$HOME/.config}/sbm/sync}
-SERVER=https://sbmsync.com
-STATE=$BOOKMARKS.sync   # name of the version of the last sync
-LOCK=$BOOKMARKS.sync.lock
-WLOCK=$BOOKMARKS.lock   # the write lock of bm
-quiet=
-
-usage () {
-    cat <<'EOF'
-usage: bm-sync [-q] [login [server] | logout | status]
-Without a command: sync the bookmark file now.
-  login     sign in to the server (default https://sbmsync.com)
-            and sync; asks for your email and password
-  logout    sign out on this computer
-  status    show the server and the time of the last sync
-  -q        print nothing but errors
-EOF
-}
-
-die () {
-    printf 'bm-sync: %s\n' "$*" >&2
-    exit 1
-}
-
-say () {
-    [ -n "$quiet" ] || printf 'bm-sync: %s\n' "$*"
-}
-
-# setting <name>: a value from the config file.
-setting () {
-    sed -n "s/^$1=//p" "$CONFIG" 2>/dev/null | head -n 1
-}
-
-# private <file>: make <file> an empty file that only you can read. umask
-# sets the mode of a new file. chmod sets the mode of a file that exists
-# already, for example the config file of an earlier login.
-private () {
-    (umask 077; : > "$1") && chmod 600 "$1"
-}
-
-cleanup () {
-    rm -rf "$work"
-    [ -z "$locked" ] || rm -rf "$LOCK"
-    wunlock
-}
-
-# wlock: take the write lock of bm, as bm does. bm holds it while it changes
-# the file: while bm -e runs, that is until the editor ends. After
-# $SBM_LOCK_WAIT seconds (default 10) bm-sync stops, and the next change in
-# bm syncs again.
-wlock () {
-    waited=0
-    until mkdir "$WLOCK" 2>/dev/null; do
-        pid=$(cat "$WLOCK/pid" 2>/dev/null)
-        if [ -n "$pid" ] && ! kill -0 "$pid" 2>/dev/null; then
-            [ "$(cat "$WLOCK/pid" 2>/dev/null)" != "$pid" ] || rm -rf "$WLOCK"
-            continue
-        fi
-        [ "$waited" -lt "${SBM_LOCK_WAIT:-10}" ] \
-            || die "$BOOKMARKS is in use (bm -e?): sync again later"
-        sleep 1
-        waited=$((waited + 1))
-    done
-    echo $$ > "$WLOCK/pid"
-    wlocked=1
-}
-
-wunlock () {
-    [ -z "$wlocked" ] || rm -rf "$WLOCK"
-    wlocked=
-}
-
-login () {
-    server=${1:-$SERVER}
-    server=${server%/}
-    printf 'email: ' >&2
-    read -r email || exit 1
-    printf 'password: ' >&2
-    stty -echo 2>/dev/null
-    IFS= read -r password
-    rc=$?
-    stty echo 2>/dev/null
-    printf '\n' >&2
-    [ $rc -eq 0 ] || exit 1
-
-    # The password goes through stdin: on the command line, other users
-    # could see it with ps.
-    code=$(printf '%s' "$password" | curl -sS -o "$work/out" -w '%{http_code}' \
-        --data-urlencode "email=$email" --data-urlencode 'password@-' \
-        -- "$server/api/login") || die "cannot reach $server"
-    [ "$code" = 200 ] || die "$(cat "$work/out")"
-    token=$(tr -d '\r\n' < "$work/out")
-
-    mkdir -p "$(dirname "$CONFIG")" || exit 1
-    private "$CONFIG" || exit 1
-    printf 'server=%s\ntoken=%s\n' "$server" "$token" > "$CONFIG"
-    rm -f "$STATE"
-    say "signed in to $server"
-    sync
-}
-
-logout () {
-    if [ -e "$CONFIG" ]; then
-        private "$work/auth"
-        printf 'Authorization: Bearer %s\n' "$(setting token)" > "$work/auth"
-        curl -sS -o /dev/null -X POST -H "@$work/auth" -- "$(setting server)/api/logout" \
-            2>/dev/null
-        rm -f "$CONFIG" "$STATE"
-    fi
-    say 'signed out'
-}
-
-status () {
-    if [ ! -e "$CONFIG" ]; then
-        echo 'not signed in: run bm-sync login'
-        return
-    fi
-    echo "server: $(setting server)"
-    if [ -e "$STATE" ]; then
-        echo "last sync: $(ls -l "$STATE" | awk '{ print $6, $7, $8 }')"
-    else
-        echo 'last sync: never'
-    fi
-}
-
-# sync: send the file with the name of its last version; the server gives
-# back the merged file and the name of the new version.
-sync () {
-    [ -e "$CONFIG" ] || die 'not signed in: run bm-sync login'
-    server=$(setting server)
-    token=$(setting token)
-    [ -n "$server" ] && [ -n "$token" ] || die "$CONFIG is damaged: run bm-sync login"
-
-    # One sync at a time. A second bm-sync leaves a note and exits: the
-    # first one then syncs again, so that no change waits for the next one.
-    if ! mkdir "$LOCK" 2>/dev/null; then
-        pid=$(cat "$LOCK/pid" 2>/dev/null)
-        if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
-            : > "$LOCK/again"
-            exit 0
-        fi
-        rm -rf "$LOCK"   # left by a bm-sync that was killed
-        mkdir "$LOCK" 2>/dev/null || die "cannot lock $LOCK"
-    fi
-    locked=1
-    echo $$ > "$LOCK/pid"
-
-    private "$work/auth"
-    printf 'Authorization: Bearer %s\n' "$token" > "$work/auth"
-    if [ ! -e "$BOOKMARKS" ]; then
-        mkdir -p "$(dirname "$BOOKMARKS")" && : > "$BOOKMARKS" || exit 1
-    fi
-
-    tries=0
-    while :; do
-        rm -f "$LOCK/again"
-        # bm writes a delete as an empty file and then the new text. Without
-        # the lock, bm-sync could send the half-written file, and the server
-        # would delete the lines that it lacks.
-        wlock
-        cat "$BOOKMARKS" > "$work/sent"
-        wunlock
-        # An empty file with a version would delete all bookmarks on the
-        # server, and then on all devices. Without a version, the server
-        # deletes nothing, and sends all bookmarks back.
-        [ -s "$work/sent" ] || rm -f "$STATE"
-        base=$(cat "$STATE" 2>/dev/null)
-        code=$(curl -sS --max-time 60 -o "$work/out" -D "$work/head" -w '%{http_code}' \
-            -H "@$work/auth" -H 'Content-Type: text/plain; charset=utf-8' \
-            --data-binary "@$work/sent" -- "$server/api/sync?base=$base") \
-            || die "cannot reach $server"
-        case $code in
-            200) ;;
-            401) die 'not signed in: run bm-sync login' ;;
-            *)   die "$(cat "$work/out")" ;;
-        esac
-        version=$(tr -d '\r' < "$work/head" | sed -n 's/^[Ss][Bb][Mm]-[Vv][Ee][Rr][Ss][Ii][Oo][Nn]: *//p')
-        [ -n "$version" ] || die 'the server gave no version'
-
-        # Hold the lock from the check to the write, so that no change of bm
-        # comes between them.
-        wlock
-        # bm changed the file while it was away: send the new file. The
-        # answer to the old one is of no use.
-        if ! cmp -s "$work/sent" "$BOOKMARKS"; then
-            wunlock
-            tries=$((tries + 1))
-            [ $tries -lt 5 ] || die 'the file changes all the time: try again later'
-            continue
-        fi
-        wrote=
-        if ! cmp -s "$work/out" "$BOOKMARKS"; then
-            # Forget the version first, and keep the new one only after the
-            # write. If the write stops part way (a full disk), the next sync
-            # sends no version, so the server deletes none of the lost lines.
-            rm -f "$STATE"
-            # Through cat, not mv, as in bm: a symlinked file stays a symlink.
-            cat "$work/out" > "$BOOKMARKS" || die "cannot write $BOOKMARKS"
-            wrote=1
-        fi
-        printf '%s\n' "$version" > "$STATE"
-        wunlock
-        if [ -n "$wrote" ] && command -v bm-commit >/dev/null 2>&1; then
-            bm-commit 'bm-sync: merge'
-        fi
-        [ -e "$LOCK/again" ] || break
-    done
-    say "in sync: $(grep -c -v -e '^#' -e '^[[:space:]]*$' "$BOOKMARKS") bookmarks"
-}
-
-while [ $# -gt 0 ]; do
-    case $1 in
-        -q) quiet=1 ;;
-        -h|--help) usage; exit 0 ;;
-        -*) usage >&2; exit 2 ;;
-        *)  break ;;
-    esac
-    shift
-done
-
-command -v curl >/dev/null 2>&1 || die 'curl is not installed'
-# mkdir fails when the name exists: a directory planted in a shared /tmp is
-# refused, not used.
-work=${TMPDIR:-/tmp}/bm-sync.$$
-mkdir -m 700 "$work" || die "cannot create $work"
-locked=
-wlocked=
-trap cleanup EXIT
-trap 'exit 1' INT TERM
-
-case ${1:-sync} in
-    login)  shift; login "$@" ;;
-    logout) logout ;;
-    status) status ;;
-    sync)   sync ;;
-    *)      usage >&2; exit 2 ;;
-esac
diff --git a/sbm/bm-title b/sbm/bm-title
deleted file mode 100755
index 84a17ba..0000000
--- a/sbm/bm-title
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/sh
-
-# bm-title: print the <title> of a web page, or nothing.
-# When it is installed, "bm --add" offers the title as the description.
-# SBM_FETCH=0 keeps it off the network.
-
-if [ $# -ne 1 ]; then
-    printf 'usage: bm-title <url>\n' >&2
-    exit 2
-fi
-[ "${SBM_FETCH:-1}" != 0 ] || exit 0
-
-# Lines are gathered until the title has been closed; awk then leaves, and
-# curl stops downloading. dd caps it at 64k for pages that are one long line
-# (it may pass a little less when its reads come up short; never more).
-curl -sL --max-time 3 -A 'Mozilla/5.0' -- "$1" 2>/dev/null \
-    | dd bs=1024 count=64 2>/dev/null | LC_ALL=C awk '
-    function title(s,    start, end) {
-        start = index(tolower(s), "<title")
-        if (!start) return
-        s = substr(s, start)
-        s = substr(s, index(s, ">") + 1)
-        end = index(tolower(s), "</title")
-        if (end) s = substr(s, 1, end - 1)
-        gsub(/&lt;/, "<", s); gsub(/&gt;/, ">", s); gsub(/&quot;/, "\"", s)
-        gsub(/&#39;|&#x27;|&apos;/, "\047", s); gsub(/&nbsp;/, " ", s)
-        gsub(/&amp;/, "\\&", s)
-        gsub(/[ \t\r]+/, " ", s); sub(/^ /, "", s); sub(/ $/, "", s)
-        print s
-    }
-    { page = page " " $0 }
-    index(tolower($0), "</title") || NR >= 500 { exit }
-    END { title(page) }'
diff --git a/windervolage b/windervolage
deleted file mode 100755
index faac9fd..0000000
--- a/windervolage
+++ /dev/null
@@ -1,57 +0,0 @@
-#!/bin/sh
-#
-# wv - Wiedervorlage (bring forward file)
-# schedule a reminder mail to yourself at a given date using at(1)
-#
-# markus schnalke <meillo@marmaro.de>
-
-queue=w
-
-cd  # move to home directory
-
-listone() {
-	atq -q "$queue" | awk "/^$1\t/ {sub(/ $queue .*/,\"\");print}" 
-	at -c "$1" | sed '
-		1,/^# wv/d;
-		/^mail /{s,[^"]*",,; s,".*,,};
-		s,^,\t,
-		'
-}
-
-list() {
-	if [ $# -eq 0 ] ; then
-		atq -q "$queue" | while read id rest ; do
-			listone "$id"
-		done
-	else
-		for i in "$@" ; do
-			listone "$i"
-		done
-	fi
-}
-
-
-if [ $# -eq 0 ] ; then
-	cat >&2 <<!
-Usage: ${0##*/} timespec...
-       ${0##*/} -l [jobid...]
-!
-	exit 1
-fi
-
-if [ "$1" = "-l" ] ; then
-	shift
-	list "$@"
-	exit
-fi
-
-echo At: `date +"%Y-%m-%d %H:%M" -d "$*"`
-printf "Subject: "
-read sub
-
-at -q "$queue" "$@" <<!
-# wv
-mail -s "Wv: $sub" `id -un` <<EOF
-`cat`
-EOF
-!