pass-cli for you and me

I've become one of those people who sits in cafes and tinkers on their laptop. Seems fitting since I'm sitting here in Bali, Indonesia and wondering what the fuss is all about. I am eating really healthy for pretty cheap, but I think the overall appeal of Bali is kind of lost on me. It likely has more to do with the fact that the habits I formed over the past 10+ years are no longer serving me and I am aggressively changing and also aggressively pushing back against those changes. I am more convinced each passing day that my issue isn't with the place I am at, my issue is that I need to painfully go through this new metamorphosis and sit with my discomfort every time I feel it.
And I am moving forward with the terminal project. Slowly chipping away at building some cli tools to assist me with my somewhat regular day to day activities. I am a Proton user which I have mixed feelings about but not enough mixed feelings to change right now. I was pretty excited to see they added a cli tool for their password store option but also disappointed to discover it is not as user friendly as their password store gui tools. I guess the pass-cli is designed to be used with CI/CD jobs because people use the proton password store for that now? Or maybe its an AI thing that I remain blissfully unaware of. And I got a bit frustrated when I pointed out to support that automating the pass-cli login flow using environment variables triggers their Sentinel product to block the login entirely. The synergy between their products is just kind of not well thought out at the moment. So I've got one of their personal access tokens because they, um, told me to.
The cli itself needed a bit of work to be more user friendly. So I wrote a wrapper for it with some bash, pass, jq, fzf, and pinentry-curses. I'll go over each...
The unix pass tool is my go-to for storing my machine passwords. My website and general user passwords live in the proton password store. But when I use pass, its for things like git credentials, ssh keys, and the proton pass personal access token. Those passwords are protected by a gpg key loaded onto my laptop/vps. When I use pass, I am prompted for the passphrase of my gpg key. I'll write more about all the cool ways I use pass in a future post.
My last tech post featured some jq shenanigans and this post is no different. The proton pass-cli output can be set to json making it easy for jq to parse this output and get to the necessary data pretty easily.
I use fzf here to easily search through the list of items in my vault and select the one I want. The fzf tool is a fuzzy finder which allows me to type a short substring to narrow down the list to the items that only contain that substring. Since I have terrible digital organizational skills and cannot remember the title of each proton pass item I create, fzf presents a nice terminal prompt to help me find what I need pretty quick.
And finally, pinentry-curses is the tool gpg uses to prompt the user for their key passphrase. Recently I discovered it can also be programmed for a wide variety of uses. Here, I am using it to pop up a box on top of my terminal to present my proton pass item's credentials. The nice thing about this is I added a timeout so the box automatically disappears after 30 seconds (or by clicking the OK button) and the credentials don't ever appear in the terminal's history scrollback.
So putting it all together, the result looks something like...
#!/usr/bin/env bash
# pass-cli wrapper print login item
set -euo pipefail
if ! $(pass-cli info > /dev/null 2>&1); then
PROTON_PASS_PERSONAL_ACCESS_TOKEN=$(pass pat) pass-cli login
fi
items=$(pass-cli item list --filter-type login --filter-state active --sort-by alphabetic-asc --output json <vault name>)
select=$(jq -r '.[][].title' <<< "${items}"| fzf --height=20 --border --border-label="╢ select the item ╟")
id=$(jq -r --arg ttle "${select}" '.[][] | select(.title == $ttle) | .id' <<< "${items}")
item=$(pass-cli item view --item-id "${id}" --vault-name <vault name> --output json)
text="--${select}--"
while read -r line; do
# pinentry -- a line feed is encoded as '%0A'
# pinentry -- the percent sign itself is encoded as '%25'
key=$(jq -r '.[0]' <<< "${line}" | sed 's/%/%25/g')
val=$(jq -r '.[1]' <<< "${line}" | sed 's/%/%25/g')
case "${key}" in
"passkeys")
# not using passkeys atm
continue
;;
"totp_uri")
if [[ -n "${val}" ]]; then
tpt=$(pass-cli item totp --item-id "${id}" --vault-name <vault name> --field totp_uri --output json)
text+="%0Atotp: $(jq -r '.totp_uri' <<< "${tpt}")"
fi
;;
"urls")
if [[ -n "${val}" ]]; then
text+="%0A${key}"
for ul in $(jq -r '.[]' <<< "${val}"); do
text+="%0A - ${ul}"
done
fi
;;
*)
if [[ -n "${val}" ]] && [[ "${val}" != "[]" ]]; then text+="%0A${key}: ${val}"; fi
;;
esac
done <<< "$(jq -c -r '.item.content.content.Login | to_entries[] | [.key, .value]' <<< "${item}")"
## pinentry expects strings to be encoded UTF-8
conv=$(echo "${text}" | iconv -t UTF-8)
pinentry-curses --ttyname $(tty) --lc-ctype "${LANG}" > /dev/null 2>&1 <<EOF
SETTIMEOUT 30
SETDESC ${conv}
SETOK OK
MESSAGE
BYE
EOF
It is the beginning of what will eventually grow into a more extensible script, but for now, it does one simple thing very well - which is to spit out the credentials of the proton pass item I choose in the terminal.