6.0 KiB
<the_problem> Raw curl commands with environment variables expose credentials:
# ❌ BAD - API key visible in chat
curl -H "Authorization: Bearer $API_KEY" https://api.example.com/data
When Claude executes this, the full command with expanded $API_KEY appears in the conversation.
</the_problem>
<the_solution>
Use ~/.claude/scripts/secure-api.sh - a wrapper that loads credentials internally.
<for_supported_services>
# ✅ GOOD - No credentials visible
~/.claude/scripts/secure-api.sh <service> <operation> [args]
# Examples:
~/.claude/scripts/secure-api.sh facebook list-campaigns
~/.claude/scripts/secure-api.sh ghl search-contact "email@example.com"
</for_supported_services>
<adding_new_services> When building a new skill that requires API calls:
- Add operations to the wrapper (
~/.claude/scripts/secure-api.sh):
case "$SERVICE" in
yourservice)
case "$OPERATION" in
list-items)
curl -s -G \
-H "Authorization: Bearer $YOUR_API_KEY" \
"https://api.yourservice.com/items"
;;
get-item)
ITEM_ID=$1
curl -s -G \
-H "Authorization: Bearer $YOUR_API_KEY" \
"https://api.yourservice.com/items/$ITEM_ID"
;;
*)
echo "Unknown operation: $OPERATION" >&2
exit 1
;;
esac
;;
esac
- Add profile support to the wrapper (if service needs multiple accounts):
# In secure-api.sh, add to profile remapping section:
yourservice)
SERVICE_UPPER="YOURSERVICE"
YOURSERVICE_API_KEY=$(eval echo \$${SERVICE_UPPER}_${PROFILE_UPPER}_API_KEY)
YOURSERVICE_ACCOUNT_ID=$(eval echo \$${SERVICE_UPPER}_${PROFILE_UPPER}_ACCOUNT_ID)
;;
- Add credential placeholders to
~/.claude/.envusing profile naming:
# Check if entries already exist
grep -q "YOURSERVICE_MAIN_API_KEY=" ~/.claude/.env 2>/dev/null || \
echo -e "\n# Your Service - Main profile\nYOURSERVICE_MAIN_API_KEY=\nYOURSERVICE_MAIN_ACCOUNT_ID=" >> ~/.claude/.env
echo "Added credential placeholders to ~/.claude/.env - user needs to fill them in"
- Document profile workflow in your SKILL.md:
## Profile Selection Workflow
**CRITICAL:** Always use profile selection to prevent using wrong account credentials.
### When user requests YourService operation:
1. **Check for saved profile:**
```bash
~/.claude/scripts/profile-state get yourservice
-
If no profile saved, discover available profiles:
~/.claude/scripts/list-profiles yourservice -
If only ONE profile: Use it automatically and announce:
"Using YourService profile 'main' to list items..." -
If MULTIPLE profiles: Ask user which one:
"Which YourService profile: main, clienta, or clientb?" -
Save user's selection:
~/.claude/scripts/profile-state set yourservice <selected_profile> -
Always announce which profile before calling API:
"Using YourService profile 'main' to list items..." -
Make API call with profile:
~/.claude/scripts/secure-api.sh yourservice:<profile> list-items
Secure API Calls
All API calls use profile syntax:
~/.claude/scripts/secure-api.sh yourservice:<profile> <operation> [args]
# Examples:
~/.claude/scripts/secure-api.sh yourservice:main list-items
~/.claude/scripts/secure-api.sh yourservice:main get-item <ITEM_ID>
Profile persists for session: Once selected, use same profile for subsequent operations unless user explicitly changes it.
</adding_new_services>
</the_solution>
<pattern_guidelines>
<simple_get_requests>
```bash
curl -s -G \
-H "Authorization: Bearer $API_KEY" \
"https://api.example.com/endpoint"
</simple_get_requests>
<post_with_json_body>
ITEM_ID=$1
curl -s -X POST \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d @- \
"https://api.example.com/items/$ITEM_ID"
Usage:
echo '{"name":"value"}' | ~/.claude/scripts/secure-api.sh service create-item
</post_with_json_body>
<post_with_form_data>
curl -s -X POST \
-F "field1=value1" \
-F "field2=value2" \
-F "access_token=$API_TOKEN" \
"https://api.example.com/endpoint"
</post_with_form_data> </pattern_guidelines>
<credential_storage>
Location: ~/.claude/.env (global for all skills, accessible from any directory)
Format:
# Service credentials
SERVICE_API_KEY=your-key-here
SERVICE_ACCOUNT_ID=account-id-here
# Another service
OTHER_API_TOKEN=token-here
OTHER_BASE_URL=https://api.other.com
Loading in script:
set -a
source ~/.claude/.env 2>/dev/null || { echo "Error: ~/.claude/.env not found" >&2; exit 1; }
set +a
</credential_storage>
<best_practices>
- Never use raw curl with
$VARIABLEin skill examples - always use the wrapper - Add all operations to the wrapper - don't make users figure out curl syntax
- Auto-create credential placeholders - add empty fields to
~/.claude/.envimmediately when creating the skill - Keep credentials in
~/.claude/.env- one central location, works everywhere - Document each operation - show examples in SKILL.md
- Handle errors gracefully - check for missing env vars, show helpful error messages </best_practices>
# This command appears in chat
~/.claude/scripts/secure-api.sh facebook list-campaigns
# But API keys never appear - they're loaded inside the script
Verify credentials are loaded:
# Check .env exists
ls -la ~/.claude/.env
# Check specific variables (without showing values)
grep -q "YOUR_API_KEY=" ~/.claude/.env && echo "API key configured" || echo "API key missing"