--- /dev/null
+#!/bin/sh
+# spass - simple, secure password storage and retrieval
+
+[ -z "$XDG_DATA_HOME" ] && XDG_DATA_HOME="$HOME/.local/share"
+[ -z "$PASSDIR" ] && PASSDIR="$XDG_DATA_HOME/pass"
+
+all() { basename -s ".gpg" -a "$PASSDIR"/*.gpg; }
+error() { echo "$(basename "$0"): error: $1" 1>&2; stty echo; exit; }
+format() { sed -n '/[^\S\r\n]/ { 1s/^/password: /; 2s/^/username: /; 3s/^/totp: /; 4s/^/notes: /; p }'; }
+
+if [ "$1" = "init" ]; then
+ if [ -z "$2" ]
+ then error "gpg key not specified"
+ elif gpg -K "$2" >/dev/null 2>&1
+ then mkdir -p "$PASSDIR"; echo "$2" > "$PASSDIR/.gpg-id"
+ else error "gpg key doesn't exist"
+ fi; exit
+elif [ -f "$PASSDIR/.gpg-id" ]
+ then GPG_ID="$(cat "$PASSDIR/.gpg-id")"
+ else error "password store not initialised!"
+fi;
+
+show() {
+ [ ! -f "$PASSDIR/$1.gpg" ] && error "no such entry: $1"
+ gpg -dq "$PASSDIR/$1.gpg" | case "$2" in
+ all) format;;
+ notes) sed -n 4p;;
+ totp) sed -n 3p | oathtool --base32 --totp -;;
+ name) sed -n 2p;;
+ *) sed -n 1p;;
+ esac
+}
+
+dump() {
+ for f in $(all)
+ do printf "# %s\n%s\n\n" "$f" "$(show "$f" all)"
+ done | head -n -1;
+}
+
+add() {
+ [ -z "$1" ] && error "entry name not specified"
+ printf "username: "; read -r u
+ stty -echo
+ printf "password: "; read -r p1; printf "\npassword (again): "; read -r p2
+ printf "\ntotp: "; read -r t1; printf "\ntotp (again): "; read -r t2
+ stty echo
+ printf "\nnotes: "; read -r n
+ [ "$p1" != "$p2" ] && error "passwords don't match"
+ [ "$t1" != "$t2" ] && error "totps don't match"
+ printf "%s\n%s\n%s\n%s" "$p1" "$u" "$t1" "$n" | gpg -eqr "$GPG_ID" -o "$PASSDIR/$1.gpg"
+}
+
+move() {
+ [ $# -lt 2 ] && error "source and destination not specified"
+ mv "$PASSDIR/$1.gpg" "$PASSDIR/$2.gpg"
+}
+
+remove () {
+ [ ! -f "$PASSDIR/$1.gpg" ] && error "entry does not exist: $1"
+ printf "are you sure you want to remove %s? [y/N] " $1; read -r c
+ [ "$c" = "y" ] || [ "$c" = "Y" ] && rm "$PASSDIR/$1.gpg"
+}
+
+sel() {
+ [ -z "$1" ] && error "no menu program specified"
+ ENTRY="$(all | $@)"; [ -z "$ENTRY" ] && exit
+ INFO="$(printf "pass\nname\ntotp\nnotes" | "$@")"; [ -z "$INFO" ] && exit
+ for p in $(pgrep "$(basename "$0")" | head -n -2); do kill "$p"; done
+ show "$ENTRY" "$INFO" | tr -d "\n" | xclip -selection clipboard
+ notify-send "$(basename "$0")" "$INFO copied to clipboard\nclearing in 10s"
+ sleep 10; xclip -selection clipboard < /dev/null
+}
+
+case "$1" in
+ mv) shift; move "$@";;
+ rm) shift; remove "$@";;
+ sel) shift; sel "$@";;
+ add ) add "$2";;
+ dump) dump;;
+ *) if [ -z "$1" ]; then all; else show "$@"; fi;;
+esac