Files
smoido 112068314c Import claude-code-switcher from the Windows build
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.
2026-08-30 21:05:48 +03:00

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}"
}