#!/usr/bin/env bash # credbridge.sh — resolve credctl credentials into env vars for steev's # personal-flow tools, then exec the CLI. Secrets NEVER echoed, logged, or # written to disk. # # Usage: credbridge.sh [args...] # tools: google-workspace | proton-bridge | perplexity # # Per PROFILE-DISTRIBUTION-PROTOCOL §3 (shared core, "credbridge" row) and §6 # (Conventions → Secrets), every profile distribution exposes credentials via # this verb chain: credctl set → credenv → exec, with secrets per-call only. # # This is the personal-assistant variant of the credbridge pattern. Steev's # cred surface is narrow by design: # # - google-workspace: Gmail + Calendar + Contacts (OAuth blob from credctl) # - proton-bridge: IMAP/SMTP password for the local Proton Bridge T6 # sidecar — gives Steev access to JP's Proton mail via # himalaya (cleartext on 127.0.0.1 only) # - perplexity: Perplexity API key for WebSearch toolset (lightweight # — most Steev work uses the perplexity MCP instead) # # Plan B marketing platforms (WooCommerce, Mailchimp, Meta, GA4, etc.) are OUT # OF SCOPE here — that's cmo-planb's credbridge. Steev MUST NEVER resolve a # marketing platform credential. The CLAUDE.md "no access to Plan B marketing # platform credentials" rule is enforced by omission from this case statement. # # Design notes (same as cmo/credbridge.sh — shared core): # - credctl values read into local vars, exported straight to the child env # - JSON-valued creds (google-workspace OAuth) parsed via `node -e` reading # from stdin so the value never lands on argv / process list # - No `echo $secret`. set +x stays off. set -euo pipefail # Paths env-overridable for portability; defaults match the JP install. CREDCTL="${CREDCTL:-/home/svrnty/workspaces/cortex/L6-svrnty.core-credentials/credctl}" STEEV_LIB="${STEEV_LIB:-/home/svrnty/.hermes/steev}" die() { printf '{"error":"%s"}\n' "$1" >&2; exit 1; } [ $# -ge 1 ] || die "usage: credbridge.sh [args...]" TOOL="$1"; shift [ -x "$CREDCTL" ] || die "credctl not found/executable at $CREDCTL" # cred_raw — print unmasked credential value to stdout. Caller captures # into a var; value never displayed. Strips the "Value:" header from credctl. cred_raw() { "$CREDCTL" get "$1" --unmask 2>/dev/null \ | sed -n '/^Value:/,$p' | sed '1s/^Value:[[:space:]]*//' } # json_field — extract a string field via node; value never on argv. json_field() { printf '%s' "$1" | node -e ' let s="";process.stdin.on("data",d=>s+=d); process.stdin.on("end",()=>{try{const o=JSON.parse(s); const v=o[process.argv[1]];process.stdout.write(v==null?"":String(v)); }catch(e){process.stdout.write("");}});' "$2" } case "$TOOL" in google-workspace) # Gmail Data API + Calendar API + People API all expect a bearer token # minted from this service-account / OAuth blob. The blob is JSON; we # export the whole document so the child CLI can introspect scope. GW_JSON="$(cred_raw google-workspace)" [ -n "$GW_JSON" ] || die "credctl: google-workspace not set" export GOOGLE_WORKSPACE_CREDENTIALS_JSON="$GW_JSON" exec "$@" ;; proton-bridge) # Steev reads JP's Proton inbox via the local Proton Bridge IMAP daemon # (T6 sidecar — see PROFILE-DISTRIBUTION-PROTOCOL §4.T6). credctl stores # the bridge password (rotates when JP rotates the bridge). PB_PASS="$(cred_raw proton-bridge-imap)" [ -n "$PB_PASS" ] || die "credctl: proton-bridge-imap not set" export PROTON_BRIDGE_IMAP_PASSWORD="$PB_PASS" exec "$@" ;; perplexity) # Lightweight WebSearch path. Most Steev research goes through the # perplexity MCP server (which holds its own key); this credbridge entry # exists for scripts that need a raw key (rare). PPL_KEY="$(cred_raw perplexity-api)" [ -n "$PPL_KEY" ] || die "credctl: perplexity-api not set" export PERPLEXITY_API_KEY="$PPL_KEY" exec "$@" ;; *) die "unknown tool: $TOOL (allowed: google-workspace|proton-bridge|perplexity)" ;; esac