Source of truth so far has been c:\Users\smoido\projects\cli on the Windows box, which has no git history of its own. This is that tree copied verbatim over SSH, minus dist/ - the PowerShell build, the POSIX port under linux/, and the presets both share. Recorded as its own commit so that everything after it is a reviewable diff rather than an undifferentiated first drop.
96 lines
3.4 KiB
Bash
96 lines
3.4 KiB
Bash
#!/usr/bin/env bash
|
|
# Secret storage for claude-mode (POSIX port), sourced by claude-mode and by the
|
|
# key helper.
|
|
#
|
|
# Windows uses DPAPI, which binds ciphertext to one Windows user on one machine.
|
|
# There is no single equivalent here, so this picks the best available backend:
|
|
#
|
|
# macOS security login Keychain, unlocked with the session
|
|
# Linux secret-tool libsecret / GNOME Keyring, same idea
|
|
# Linux pass gpg-backed, agent-cached
|
|
# any file 0600 in ~/.claude-mode/vault - PLAINTEXT
|
|
#
|
|
# The file backend is the honest fallback: it is no worse than the API keys
|
|
# people already keep in .bashrc, but it is not encrypted and claude-mode says so
|
|
# out loud rather than implying protection it does not provide.
|
|
|
|
CM_VAULT_DIR="${CM_ROOT:-$HOME/.claude-mode}/vault"
|
|
CM_VAULT_SERVICE="claude-mode"
|
|
|
|
cm_vault_backend() {
|
|
if [ -n "${CLAUDE_MODE_VAULT:-}" ]; then printf '%s\n' "$CLAUDE_MODE_VAULT"; return; fi
|
|
if [ "$(uname -s)" = "Darwin" ] && command -v security >/dev/null 2>&1; then
|
|
printf 'security\n'; return
|
|
fi
|
|
if command -v secret-tool >/dev/null 2>&1; then printf 'secret-tool\n'; return; fi
|
|
if command -v pass >/dev/null 2>&1; then printf 'pass\n'; return; fi
|
|
printf 'file\n'
|
|
}
|
|
|
|
cm_vault_backend_label() {
|
|
case "$(cm_vault_backend)" in
|
|
security) printf 'macOS Keychain\n' ;;
|
|
secret-tool) printf 'libsecret (GNOME Keyring)\n' ;;
|
|
pass) printf 'pass (gpg)\n' ;;
|
|
file) printf 'plain file, 0600 (NOT encrypted)\n' ;;
|
|
esac
|
|
}
|
|
|
|
# cm_vault_set <ref> - reads the secret from stdin
|
|
cm_vault_set() {
|
|
local ref="$1" secret
|
|
IFS= read -r secret || true
|
|
[ -n "$secret" ] || { echo "empty key, aborted" >&2; return 1; }
|
|
|
|
case "$(cm_vault_backend)" in
|
|
security)
|
|
security add-generic-password -U -a "$ref" -s "$CM_VAULT_SERVICE" -w "$secret" >/dev/null
|
|
;;
|
|
secret-tool)
|
|
printf '%s' "$secret" | secret-tool store --label="claude-mode $ref" \
|
|
service "$CM_VAULT_SERVICE" ref "$ref" >/dev/null
|
|
;;
|
|
pass)
|
|
printf '%s\n' "$secret" | pass insert -m -f "$CM_VAULT_SERVICE/$ref" >/dev/null
|
|
;;
|
|
file)
|
|
mkdir -p "$CM_VAULT_DIR"
|
|
chmod 700 "$CM_VAULT_DIR" 2>/dev/null || true
|
|
local f="$CM_VAULT_DIR/$ref.key"
|
|
( umask 077; printf '%s' "$secret" > "$f" )
|
|
chmod 600 "$f" 2>/dev/null || true
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# cm_vault_get <ref> - prints the secret, or nothing (exit 1) if absent
|
|
cm_vault_get() {
|
|
local ref="$1" out=""
|
|
case "$(cm_vault_backend)" in
|
|
security)
|
|
out="$(security find-generic-password -a "$ref" -s "$CM_VAULT_SERVICE" -w 2>/dev/null)" || return 1
|
|
;;
|
|
secret-tool)
|
|
out="$(secret-tool lookup service "$CM_VAULT_SERVICE" ref "$ref" 2>/dev/null)" || return 1
|
|
;;
|
|
pass)
|
|
out="$(pass show "$CM_VAULT_SERVICE/$ref" 2>/dev/null | head -n1)" || return 1
|
|
;;
|
|
file)
|
|
[ -f "$CM_VAULT_DIR/$ref.key" ] || return 1
|
|
out="$(cat "$CM_VAULT_DIR/$ref.key")"
|
|
;;
|
|
esac
|
|
[ -n "$out" ] || return 1
|
|
printf '%s' "$out"
|
|
}
|
|
|
|
cm_vault_has() { cm_vault_get "$1" >/dev/null 2>&1; }
|
|
|
|
cm_vault_mask() {
|
|
local k="$1"
|
|
if [ -z "$k" ]; then printf '(none)\n'; return; fi
|
|
if [ "${#k}" -le 12 ]; then printf '****\n'; return; fi
|
|
printf '%s...%s\n' "${k:0:8}" "${k: -4}"
|
|
}
|