Skip to content

Instantly share code, notes, and snippets.

@yanyaoer
Last active September 23, 2025 02:36
Show Gist options
  • Save yanyaoer/63b40dc2ca9737286b34e89689fc5fa7 to your computer and use it in GitHub Desktop.
Save yanyaoer/63b40dc2ca9737286b34e89689fc5fa7 to your computer and use it in GitHub Desktop.
simple app/shortcuts/cli/password/otp luncher on macos
#!/usr/bin/env bash
# brew install choose-gui pass pass-otp fd
# https://github.com/chipsenkbeil/choose?tab=readme-ov-file#open-apps-from-the-applications-directories
# bindkey with .skhdrc
# ctrl + alt + shift + cmd - r : app-choose.sh
shopt -s nullglob globstar
CACHE_PREFIX="/tmp/.launcher_cache"
CACHE_DURATION=3600
file_birthtime() {
stat -f %B "$1" 2>/dev/null || stat -c %Y "$1" 2>/dev/null
}
cache_run() {
local key="$1"
shift
local cache_file="${CACHE_PREFIX}_${key}"
if [[ -f $cache_file ]]; then
cat "$cache_file"
local created
created=$(file_birthtime "$cache_file" 2>/dev/null)
local now=$(date +%s)
if [[ ! $created =~ ^[0-9]+$ ]] || (( now - created >= CACHE_DURATION )); then
(
local tmp
tmp=$(mktemp "${cache_file}.XXXX") || exit 0
if "$@" >"$tmp"; then
mv "$tmp" "$cache_file"
else
rm -f "$tmp"
fi
) >/dev/null 2>&1 &
fi
return
fi
local tmp
tmp=$(mktemp "${cache_file}.XXXX") || return 1
if "$@" >"$tmp"; then
cat "$tmp"
mv "$tmp" "$cache_file"
else
rm -f "$tmp"
return 1
fi
}
build_candidates() {
local dirs=(
/Applications/
"$HOME"/Applications/
/Applications/Utilities/
/System/Applications/
/System/Applications/Utilities/
/System/Library/CoreServices/Applications/
)
/opt/homebrew/bin/fd . "${dirs[@]}" -e app -d 1 --format '{/}'
if command -v /usr/bin/shortcuts >/dev/null 2>&1; then
/usr/bin/shortcuts list 2>/dev/null | sed 's/^/short-/'
fi
local store=${PASSWORD_STORE_DIR:-$HOME/.password-store}
if [[ -d $store ]]; then
/opt/homebrew/bin/fd . "$store" -e gpg --format 'pass {/}' 2>/dev/null
fi
}
get_candidates() {
cache_run candidates build_candidates
}
get_password_details() {
local password_name="$1"
local content=$(pass show "$password_name" 2>/dev/null)
local password=$(echo "$content" | head -n1)
local username=$(echo "$content" | grep -i "^user\|^login\|^email" | head -n1 | cut -d: -f2- | sed 's/^ *//')
# Check if OTP is available and cache the result
local otp_code=""
if echo "$content" | grep -q "otpauth://"; then
otp_code=$(pass otp "$password_name" 2>/dev/null)
fi
if [[ -n $username ]] && [[ -n $otp_code ]]; then
printf '%s (username)\n%s (password)\n%s (otp)\n' "$username" "$password" "$otp_code"
elif [[ -n $username ]]; then
printf '%s (username)\n%s (password)\n' "$username" "$password"
elif [[ -n $otp_code ]]; then
printf '%s (password)\n%s (otp)\n' "$password" "$otp_code"
else
printf '%s (password)\n' "$password"
fi
}
run_app() {
if [[ $1 == short-* ]]; then
/usr/bin/shortcuts run ${1#short-}
elif [[ $1 == *.app ]]; then
open -a "$1"
else
# run as shell if not match app and password
export PATH="$HOME/bin/:$PATH"
eval "$1"
fi
}
run_pass() {
local password_name="$1"
# Get password details and let user choose
details=$(get_password_details "$password_name")
if [[ -n $details ]]; then
chosen_detail=$(echo "$details" | choose -m)
if [[ $chosen_detail == *"(username)" ]]; then
echo "${chosen_detail% (username)}" | pbcopy
echo "Username copied to clipboard"
elif [[ $chosen_detail == *"(password)" ]]; then
echo "${chosen_detail% (password)}" | pbcopy
echo "Password copied to clipboard"
elif [[ $chosen_detail == *"(otp)" ]]; then
echo "${chosen_detail% (otp)}" | pbcopy
echo "OTP copied to clipboard"
fi
else
# Fallback to just copying password
pass show -c "$password_name" 2>/dev/null
fi
}
main() {
while true; do
selection=$(get_candidates | choose -m)
[[ -n $selection ]] || exit
if [[ $selection == pass* ]]; then
password_name="${selection#pass }"
run_pass "$password_name"
break
else
run_app "$selection"
break
fi
done
}
main "$@"
@yanyaoer
Copy link
Author

yanyaoer commented Sep 2, 2025

$  bw export --output /tmp/bw.json --format json
$  pass import bitwarden /tmp/bw.json # for linux

$ pimport pass /tmp/bw.json --out ~/.password-store # for macOS

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment