Session log: 1Password skill setup, Lonestar MDM fix, credentials migration planning
- Activated 1Password skill for Claude Code (extracted from .skill ZIP) - Resolved Lonestar Electrical MDM issue: ManageEngine was configured as third-party EMM in Google Workspace, causing persistent enrollment prompts on joser's personal phone - Scoped credentials.md migration to 1Password (op:// refs + MSP vaults) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
75
.claude/skills/1password/scripts/check_setup.sh
Executable file
75
.claude/skills/1password/scripts/check_setup.sh
Executable file
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env bash
|
||||
# check_setup.sh — Verify 1Password CLI is installed and authenticated
|
||||
# Usage: bash check_setup.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
check() {
|
||||
local label="$1"
|
||||
local cmd="$2"
|
||||
if eval "$cmd" &>/dev/null; then
|
||||
echo " ✅ $label"
|
||||
((PASS++)) || true
|
||||
else
|
||||
echo " ❌ $label"
|
||||
((FAIL++)) || true
|
||||
fi
|
||||
}
|
||||
|
||||
echo "=== 1Password CLI Setup Check ==="
|
||||
echo ""
|
||||
|
||||
# 1. CLI installed
|
||||
check "op CLI installed" "command -v op"
|
||||
|
||||
# 2. Version
|
||||
if command -v op &>/dev/null; then
|
||||
echo " ℹ️ Version: $(op --version)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "--- Authentication ---"
|
||||
|
||||
# 3. Signed in
|
||||
check "Signed in to 1Password" "op account list 2>/dev/null | grep -q '.'"
|
||||
|
||||
# 4. Can list vaults
|
||||
check "Can list vaults" "op vault list &>/dev/null"
|
||||
|
||||
# Show accounts if authenticated
|
||||
if op account list &>/dev/null 2>&1; then
|
||||
echo ""
|
||||
echo " Accounts:"
|
||||
op account list 2>/dev/null | tail -n +2 | while read -r line; do
|
||||
echo " • $line"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo " Vaults:"
|
||||
op vault list --format=json 2>/dev/null | \
|
||||
python3 -c "import sys,json; [print(f' • {v[\"name\"]} ({v[\"id\"]})') for v in json.load(sys.stdin)]" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "--- Environment ---"
|
||||
|
||||
# 5. OP_SERVICE_ACCOUNT_TOKEN (CI/CD pattern)
|
||||
if [[ -n "${OP_SERVICE_ACCOUNT_TOKEN:-}" ]]; then
|
||||
echo " ✅ OP_SERVICE_ACCOUNT_TOKEN is set (service account mode)"
|
||||
else
|
||||
echo " ℹ️ OP_SERVICE_ACCOUNT_TOKEN not set (interactive/desktop app mode)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "==================================="
|
||||
if [[ $FAIL -eq 0 ]]; then
|
||||
echo "✅ All checks passed. 1Password CLI is ready."
|
||||
else
|
||||
echo "⚠️ $FAIL check(s) failed. See above."
|
||||
echo ""
|
||||
echo "Install: https://developer.1password.com/docs/cli/get-started/"
|
||||
echo "Sign in: op signin"
|
||||
fi
|
||||
142
.claude/skills/1password/scripts/env_from_op.sh
Executable file
142
.claude/skills/1password/scripts/env_from_op.sh
Executable file
@@ -0,0 +1,142 @@
|
||||
#!/usr/bin/env bash
|
||||
# env_from_op.sh — Generate a .env file from 1Password items
|
||||
#
|
||||
# Usage:
|
||||
# bash env_from_op.sh # Interactive: prompts for vault + items
|
||||
# bash env_from_op.sh --vault Dev # Use specific vault
|
||||
# bash env_from_op.sh --item "My Project" # Export all fields from one item
|
||||
# bash env_from_op.sh --output .env # Write to file (default: .env)
|
||||
# bash env_from_op.sh --dry-run # Print without writing
|
||||
#
|
||||
# Output format:
|
||||
# FIELD_NAME=op://Vault/Item/field # Secret references (safest)
|
||||
# FIELD_NAME=actual_value # Resolved values (with --resolve)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
VAULT=""
|
||||
ITEM=""
|
||||
OUTPUT=".env"
|
||||
DRY_RUN=false
|
||||
RESOLVE=false
|
||||
|
||||
# Parse args
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--vault) VAULT="$2"; shift 2 ;;
|
||||
--item) ITEM="$2"; shift 2 ;;
|
||||
--output) OUTPUT="$2"; shift 2 ;;
|
||||
--dry-run) DRY_RUN=true; shift ;;
|
||||
--resolve) RESOLVE=true; shift ;;
|
||||
*) echo "Unknown option: $1"; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Check op is available
|
||||
if ! command -v op &>/dev/null; then
|
||||
echo "❌ 1Password CLI (op) not found. Install: https://developer.1password.com/docs/cli/get-started/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# If no item specified, list items and prompt
|
||||
if [[ -z "$ITEM" ]]; then
|
||||
echo "Available items in vault '${VAULT:-all vaults}':"
|
||||
if [[ -n "$VAULT" ]]; then
|
||||
op item list --vault "$VAULT" --format=json | \
|
||||
python3 -c "import sys,json; [print(f' {i[\"title\"]}') for i in json.load(sys.stdin)]"
|
||||
else
|
||||
op item list --format=json | \
|
||||
python3 -c "import sys,json; [print(f' [{i[\"vault\"][\"name\"]}] {i[\"title\"]}') for i in json.load(sys.stdin)]"
|
||||
fi
|
||||
echo ""
|
||||
read -rp "Enter item title: " ITEM
|
||||
fi
|
||||
|
||||
echo "Fetching '${ITEM}' from 1Password..."
|
||||
|
||||
# Get item as JSON
|
||||
if [[ -n "$VAULT" ]]; then
|
||||
ITEM_JSON=$(op item get "$ITEM" --vault "$VAULT" --format=json)
|
||||
else
|
||||
ITEM_JSON=$(op item get "$ITEM" --format=json)
|
||||
fi
|
||||
|
||||
VAULT_NAME=$(echo "$ITEM_JSON" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['vault']['name'])")
|
||||
ITEM_TITLE=$(echo "$ITEM_JSON" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['title'])")
|
||||
|
||||
# Build .env content
|
||||
ENV_CONTENT=$(echo "$ITEM_JSON" | python3 - <<'PYEOF'
|
||||
import sys, json, re
|
||||
|
||||
data = json.load(sys.stdin)
|
||||
vault = data['vault']['name']
|
||||
title = data['title']
|
||||
lines = []
|
||||
|
||||
SKIP_LABELS = {'username', 'password', 'notesPlain', 'notes'}
|
||||
SKIP_TYPES = {'CONCEALED'} if False else set() # resolved mode: don't skip
|
||||
|
||||
for field in data.get('fields', []):
|
||||
label = field.get('label', '')
|
||||
value = field.get('value', '')
|
||||
field_id = field.get('id', '')
|
||||
ftype = field.get('type', '')
|
||||
|
||||
# Skip empty, metadata, or UI-only fields
|
||||
if not value or not label:
|
||||
continue
|
||||
if label.lower() in {'username', 'notesplain', 'notes', 'password'} and ftype not in {'CONCEALED', 'URL'}:
|
||||
continue
|
||||
|
||||
# Convert label to ENV_VAR format
|
||||
env_key = re.sub(r'[^A-Z0-9_]', '_', label.upper().replace(' ', '_').replace('-', '_'))
|
||||
env_key = re.sub(r'_+', '_', env_key).strip('_')
|
||||
|
||||
# Use secret reference (safer than raw value)
|
||||
ref = f"op://{vault}/{title}/{label}"
|
||||
lines.append(f"{env_key}={ref}")
|
||||
|
||||
print('\n'.join(lines))
|
||||
PYEOF
|
||||
)
|
||||
|
||||
# Handle resolve flag — replace refs with real values
|
||||
if $RESOLVE; then
|
||||
echo "⚠️ Writing resolved values (actual secrets). Handle carefully."
|
||||
FINAL_CONTENT=""
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" =~ ^([A-Z_]+)=(op://.+)$ ]]; then
|
||||
key="${BASH_REMATCH[1]}"
|
||||
ref="${BASH_REMATCH[2]}"
|
||||
value=$(op read "$ref" 2>/dev/null || echo "ERROR_READING")
|
||||
FINAL_CONTENT+="${key}=${value}"$'\n'
|
||||
else
|
||||
FINAL_CONTENT+="$line"$'\n'
|
||||
fi
|
||||
done <<< "$ENV_CONTENT"
|
||||
ENV_CONTENT="$FINAL_CONTENT"
|
||||
fi
|
||||
|
||||
# Header
|
||||
HEADER="# Generated from 1Password: ${VAULT_NAME}/${ITEM_TITLE}
|
||||
# Generated: $(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
# Load with: op run --env-file=.env -- <command>
|
||||
# or: eval \$(op run --env-file=.env -- env | grep KEY)
|
||||
|
||||
"
|
||||
|
||||
FULL_CONTENT="${HEADER}${ENV_CONTENT}"
|
||||
|
||||
if $DRY_RUN; then
|
||||
echo ""
|
||||
echo "--- .env preview ---"
|
||||
echo "$FULL_CONTENT"
|
||||
echo "--- end ---"
|
||||
else
|
||||
echo "$FULL_CONTENT" > "$OUTPUT"
|
||||
echo "✅ Written to $OUTPUT (${#ENV_CONTENT} chars, $(echo "$ENV_CONTENT" | grep -c '=' || true) vars)"
|
||||
echo ""
|
||||
echo "To use:"
|
||||
echo " op run --env-file=$OUTPUT -- your-command"
|
||||
echo " source <(op run --env-file=$OUTPUT -- env)"
|
||||
fi
|
||||
52
.claude/skills/1password/scripts/launch-in-terminal.sh
Executable file
52
.claude/skills/1password/scripts/launch-in-terminal.sh
Executable file
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env bash
|
||||
# launch-in-terminal.sh — Open a script in a NEW Terminal.app window
|
||||
#
|
||||
# This is how the 1Password skill keeps secrets OUT of Claude Code.
|
||||
# Claude generates the script, then calls this launcher.
|
||||
# The script runs in Terminal.app — Claude never sees what you type.
|
||||
#
|
||||
# Usage:
|
||||
# bash launch-in-terminal.sh /path/to/script.sh
|
||||
# bash launch-in-terminal.sh /path/to/script.sh "window title"
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_PATH="${1:-}"
|
||||
TITLE="${2:-1Password Setup}"
|
||||
|
||||
if [[ -z "$SCRIPT_PATH" ]]; then
|
||||
echo "Usage: bash launch-in-terminal.sh /path/to/script.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$SCRIPT_PATH" ]]; then
|
||||
echo "❌ Script not found: $SCRIPT_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
chmod +x "$SCRIPT_PATH"
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo " Opening Terminal.app to collect secrets"
|
||||
echo " Script: $SCRIPT_PATH"
|
||||
echo ""
|
||||
echo " ⚠️ Type your secrets in the Terminal"
|
||||
echo " window that is about to open."
|
||||
echo " Claude Code cannot see that window."
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
osascript <<APPLESCRIPT
|
||||
tell application "Terminal"
|
||||
activate
|
||||
set newTab to do script "echo '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'; echo ' ${TITLE}'; echo ' Type secrets here — Claude Code cannot see this window'; echo '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'; echo ''; bash ${SCRIPT_PATH}"
|
||||
end tell
|
||||
APPLESCRIPT
|
||||
|
||||
echo "✅ Terminal.app opened. Complete the prompts there, then return here."
|
||||
echo " (This window will wait for you to press Enter when done)"
|
||||
echo ""
|
||||
read -rp "Press Enter once you've finished in Terminal.app... "
|
||||
echo ""
|
||||
echo "Continuing..."
|
||||
124
.claude/skills/1password/scripts/store-mcp-credentials.sh
Executable file
124
.claude/skills/1password/scripts/store-mcp-credentials.sh
Executable file
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env bash
|
||||
# store-mcp-credentials.sh — Store MCP server credentials in 1Password
|
||||
#
|
||||
# ⚠️ RUN THIS IN TERMINAL.APP — NOT IN CLAUDE CODE
|
||||
# Claude Code can see everything typed in its terminal.
|
||||
# Open Terminal.app separately, then run this script.
|
||||
#
|
||||
# Usage (Claude will generate a pre-filled version for you):
|
||||
# bash store-mcp-credentials.sh \
|
||||
# --vault Dev \
|
||||
# --item "My MCP Server" \
|
||||
# --set "url=https://api.example.com" \
|
||||
# --set "log_level=error" \
|
||||
# --secret "api_key" \
|
||||
# --secret "webhook_secret"
|
||||
#
|
||||
# Options:
|
||||
# --vault 1Password vault name (default: Dev)
|
||||
# --item Item title in 1Password
|
||||
# --set Non-secret field: key=value (pre-filled, visible)
|
||||
# --secret Secret field: prompted with hidden input
|
||||
# --update Update existing item instead of creating new
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
VAULT="Dev"
|
||||
ITEM=""
|
||||
UPDATE=false
|
||||
declare -a SET_FIELDS=()
|
||||
declare -a SECRET_FIELDS=()
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--vault) VAULT="$2"; shift 2 ;;
|
||||
--item) ITEM="$2"; shift 2 ;;
|
||||
--set) SET_FIELDS+=("$2"); shift 2 ;;
|
||||
--secret) SECRET_FIELDS+=("$2"); shift 2 ;;
|
||||
--update) UPDATE=true; shift ;;
|
||||
*) echo "Unknown option: $1"; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$ITEM" ]]; then
|
||||
read -rp "Item title in 1Password: " ITEM
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo " Storing: $ITEM"
|
||||
echo " Vault: $VAULT"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# Show pre-filled fields
|
||||
if [[ ${#SET_FIELDS[@]} -gt 0 ]]; then
|
||||
echo "Pre-filled fields:"
|
||||
for field in "${SET_FIELDS[@]}"; do
|
||||
key="${field%%=*}"
|
||||
val="${field#*=}"
|
||||
echo " $key = $val"
|
||||
done
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Prompt for secret fields
|
||||
declare -a SECRET_VALUES=()
|
||||
if [[ ${#SECRET_FIELDS[@]} -gt 0 ]]; then
|
||||
echo "Enter secret values (input is hidden):"
|
||||
for field in "${SECRET_FIELDS[@]}"; do
|
||||
read -rsp " $field: " secret_val
|
||||
echo ""
|
||||
SECRET_VALUES+=("${field}[password]=${secret_val}")
|
||||
done
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Build op field args for non-secret fields
|
||||
declare -a OP_FIELDS=()
|
||||
for field in "${SET_FIELDS[@]}"; do
|
||||
key="${field%%=*}"
|
||||
val="${field#*=}"
|
||||
OP_FIELDS+=("${key}[text]=${val}")
|
||||
done
|
||||
|
||||
# Combine all fields
|
||||
ALL_FIELDS=("${OP_FIELDS[@]+"${OP_FIELDS[@]}"}" "${SECRET_VALUES[@]+"${SECRET_VALUES[@]}"}")
|
||||
|
||||
echo "Saving to 1Password..."
|
||||
|
||||
if $UPDATE; then
|
||||
op item edit "$ITEM" --vault "$VAULT" "${ALL_FIELDS[@]}"
|
||||
echo ""
|
||||
echo "✅ Updated '$ITEM' in vault '$VAULT'"
|
||||
else
|
||||
# Try create, fall back to update if already exists
|
||||
if op item get "$ITEM" --vault "$VAULT" &>/dev/null 2>&1; then
|
||||
echo " Item already exists — updating instead..."
|
||||
op item edit "$ITEM" --vault "$VAULT" "${ALL_FIELDS[@]}"
|
||||
echo ""
|
||||
echo "✅ Updated '$ITEM' in vault '$VAULT'"
|
||||
else
|
||||
op item create \
|
||||
--category API_CREDENTIAL \
|
||||
--title "$ITEM" \
|
||||
--vault "$VAULT" \
|
||||
"${ALL_FIELDS[@]}"
|
||||
echo ""
|
||||
echo "✅ Created '$ITEM' in vault '$VAULT'"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Secret references for your config:"
|
||||
for field in "${SET_FIELDS[@]}"; do
|
||||
key="${field%%=*}"
|
||||
echo " op://${VAULT}/${ITEM}/${key}"
|
||||
done
|
||||
for field in "${SECRET_FIELDS[@]}"; do
|
||||
echo " op://${VAULT}/${ITEM}/${field}"
|
||||
done
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo " Done. You can close this terminal."
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
91
.claude/skills/1password/scripts/store_secret.sh
Executable file
91
.claude/skills/1password/scripts/store_secret.sh
Executable file
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env bash
|
||||
# store_secret.sh — Store or update a secret in 1Password
|
||||
#
|
||||
# Usage:
|
||||
# bash store_secret.sh --title "My API Key" --field "api_key" --value "sk-..."
|
||||
# bash store_secret.sh --title "Project Creds" --vault Dev --category API_CREDENTIAL
|
||||
# bash store_secret.sh --update --title "Existing Item" --field "api_key" --value "new-value"
|
||||
# bash store_secret.sh --from-env MY_VAR # Store from environment variable
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
TITLE=""
|
||||
FIELD="credential"
|
||||
VALUE=""
|
||||
VAULT=""
|
||||
CATEGORY="API_CREDENTIAL"
|
||||
UPDATE=false
|
||||
FROM_ENV=""
|
||||
GENERATE=false
|
||||
GENERATE_LENGTH=32
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--title) TITLE="$2"; shift 2 ;;
|
||||
--field) FIELD="$2"; shift 2 ;;
|
||||
--value) VALUE="$2"; shift 2 ;;
|
||||
--vault) VAULT="$2"; shift 2 ;;
|
||||
--category) CATEGORY="$2"; shift 2 ;;
|
||||
--update) UPDATE=true; shift ;;
|
||||
--from-env) FROM_ENV="$2"; shift 2 ;;
|
||||
--generate) GENERATE=true; shift ;;
|
||||
--length) GENERATE_LENGTH="$2"; shift 2 ;;
|
||||
*) echo "Unknown option: $1"; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Validate
|
||||
if [[ -z "$TITLE" ]]; then
|
||||
read -rp "Item title: " TITLE
|
||||
fi
|
||||
|
||||
# Get value from env var if requested
|
||||
if [[ -n "$FROM_ENV" ]]; then
|
||||
VALUE="${!FROM_ENV:-}"
|
||||
if [[ -z "$VALUE" ]]; then
|
||||
echo "❌ Environment variable $FROM_ENV is not set or empty"
|
||||
exit 1
|
||||
fi
|
||||
FIELD="${FROM_ENV}"
|
||||
echo "Using value from \$$FROM_ENV"
|
||||
fi
|
||||
|
||||
# Generate a secure credential if requested
|
||||
if $GENERATE; then
|
||||
VALUE=$(openssl rand -base64 "$GENERATE_LENGTH" | tr -d '=+/' | head -c "$GENERATE_LENGTH")
|
||||
echo "🔐 Generated secure credential ($GENERATE_LENGTH chars)"
|
||||
fi
|
||||
|
||||
# Prompt for value if still empty
|
||||
if [[ -z "$VALUE" ]]; then
|
||||
read -rsp "Value (hidden): " VALUE
|
||||
echo ""
|
||||
fi
|
||||
|
||||
VAULT_FLAG=""
|
||||
[[ -n "$VAULT" ]] && VAULT_FLAG="--vault $VAULT"
|
||||
|
||||
if $UPDATE; then
|
||||
echo "Updating '${FIELD}' in '${TITLE}'..."
|
||||
op item edit "$TITLE" $VAULT_FLAG "${FIELD}[password]=${VALUE}"
|
||||
echo "✅ Updated '${FIELD}' in '${TITLE}'"
|
||||
else
|
||||
echo "Creating '${TITLE}' in 1Password..."
|
||||
RESULT=$(op item create \
|
||||
--category "$CATEGORY" \
|
||||
--title "$TITLE" \
|
||||
$VAULT_FLAG \
|
||||
"${FIELD}[password]=${VALUE}" \
|
||||
--format=json)
|
||||
|
||||
ITEM_ID=$(echo "$RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
|
||||
VAULT_NAME=$(echo "$RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin)['vault']['name'])")
|
||||
|
||||
echo "✅ Created '${TITLE}' (ID: ${ITEM_ID})"
|
||||
echo ""
|
||||
echo "Secret reference:"
|
||||
echo " op://${VAULT_NAME}/${TITLE}/${FIELD}"
|
||||
echo ""
|
||||
echo "Read it back:"
|
||||
echo " op read \"op://${VAULT_NAME}/${TITLE}/${FIELD}\""
|
||||
fi
|
||||
Reference in New Issue
Block a user