feat: Add /core:env for environment management (#70)

This commit introduces a new command, `/core:env`, to manage environment variables. It provides a set of tools to compare and manage a local `.env` file against a `.env.example` template, with a strong emphasis on security by masking sensitive values.

The command includes the following subcommands:
- `/core:env`: Shows the current environment variables with sensitive values masked.
- `/core:env check`: Validates the local `.env` file against `.env.example`, reporting any missing or required variables.
- `/core:env diff`: Displays the differences between the `.env` and `.env.example` files, ensuring sensitive data is not exposed.
- `/core:env sync`: Adds missing variables from `.env.example` to the local `.env` file without overwriting existing values.

To prevent accidental exposure of secrets, the `.env` file is now included in `.gitignore`.
This commit is contained in:
Snider 2026-02-02 07:14:37 +00:00 committed by GitHub
parent 0e86ec4996
commit 94d9d28f4a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 246 additions and 0 deletions

16
.env.example Normal file
View file

@ -0,0 +1,16 @@
# .env.example
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
# Required, no default
JWT_SECRET=
# Required for billing
STRIPE_KEY=

1
.gitignore vendored
View file

@ -1 +1,2 @@
.idea/
.env

View file

@ -0,0 +1,24 @@
---
name: /core:env
description: Manage environment configuration
args: [check|diff|sync]
---
# Environment Management
Provides tools for managing `.env` files based on `.env.example`.
## Usage
- `/core:env` - Show current environment variables (with sensitive values masked)
- `/core:env check` - Validate `.env` against `.env.example`
- `/core:env diff` - Show differences between `.env` and `.env.example`
- `/core:env sync` - Add missing variables from `.env.example` to `.env`
## Action
This command is implemented by the following script:
```bash
"${CLAUDE_PLUGIN_ROOT}/scripts/env.sh" "$1"
```

205
claude/code/scripts/env.sh Executable file
View file

@ -0,0 +1,205 @@
#!/bin/bash
# Environment management script for /core:env command
set -e
# Function to mask sensitive values
mask_sensitive_value() {
local key="$1"
local value="$2"
if [[ "$key" =~ (_SECRET|_KEY|_PASSWORD|_TOKEN)$ ]]; then
if [ -z "$value" ]; then
echo "***not set***"
else
echo "***set***"
fi
else
echo "$value"
fi
}
# The subcommand is the first argument
SUBCOMMAND="$1"
case "$SUBCOMMAND" in
"")
# Default command: Show env vars
if [ ! -f ".env" ]; then
echo ".env file not found."
exit 1
fi
while IFS= read -r line || [[ -n "$line" ]]; do
# Skip comments and empty lines
if [[ "$line" =~ ^\s*#.*$ || -z "$line" ]]; then
continue
fi
# Extract key and value
key=$(echo "$line" | cut -d '=' -f 1)
value=$(echo "$line" | cut -d '=' -f 2-)
masked_value=$(mask_sensitive_value "$key" "$value")
echo "$key=$masked_value"
done < ".env"
;;
check)
# Subcommand: check
if [ ! -f ".env.example" ]; then
echo ".env.example file not found."
exit 1
fi
# Create an associative array of env vars
declare -A env_vars
if [ -f ".env" ]; then
while IFS= read -r line || [[ -n "$line" ]]; do
if [[ ! "$line" =~ ^\s*# && "$line" =~ = ]]; then
key=$(echo "$line" | cut -d '=' -f 1)
value=$(echo "$line" | cut -d '=' -f 2-)
env_vars["$key"]="$value"
fi
done < ".env"
fi
echo "Environment Check"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo
errors=0
warnings=0
while IFS= read -r line || [[ -n "$line" ]]; do
if [[ -z "$line" || "$line" =~ ^\s*# ]]; then
continue
fi
example_key=$(echo "$line" | cut -d '=' -f 1)
example_value=$(echo "$line" | cut -d '=' -f 2-)
if [[ ${env_vars[$example_key]+_} ]]; then
# Key exists in .env
env_value="${env_vars[$example_key]}"
if [ -n "$env_value" ]; then
echo "$example_key=$(mask_sensitive_value "$example_key" "$env_value")"
else
# Key exists but value is empty
if [ -z "$example_value" ]; then
echo "$example_key missing (required, no default)"
((errors++))
else
echo "$example_key missing (default: $example_value)"
((warnings++))
fi
fi
else
# Key does not exist in .env
if [ -z "$example_value" ]; then
echo "$example_key missing (required, no default)"
((errors++))
else
echo "$example_key missing (default: $example_value)"
((warnings++))
fi
fi
done < ".env.example"
echo
if [ "$errors" -gt 0 ] || [ "$warnings" -gt 0 ]; then
echo "$errors errors, $warnings warnings"
else
echo "✓ All checks passed."
fi
;;
diff)
# Subcommand: diff
if [ ! -f ".env.example" ]; then
echo ".env.example file not found."
exit 1
fi
# Create associative arrays for both files
declare -A env_vars
if [ -f ".env" ]; then
while IFS= read -r line || [[ -n "$line" ]]; do
if [[ ! "$line" =~ ^\s*# && "$line" =~ = ]]; then
key=$(echo "$line" | cut -d '=' -f 1)
value=$(echo "$line" | cut -d '=' -f 2-)
env_vars["$key"]="$value"
fi
done < ".env"
fi
declare -A example_vars
while IFS= read -r line || [[ -n "$line" ]]; do
if [[ ! "$line" =~ ^\s*# && "$line" =~ = ]]; then
key=$(echo "$line" | cut -d '=' -f 1)
value=$(echo "$line" | cut -d '=' -f 2-)
example_vars["$key"]="$value"
fi
done < ".env.example"
echo "Environment Diff"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo
# Check for modifications and deletions
for key in "${!example_vars[@]}"; do
example_value="${example_vars[$key]}"
if [[ ${env_vars[$key]+_} ]]; then
# Key exists in .env
env_value="${env_vars[$key]}"
if [ "$env_value" != "$example_value" ]; then
echo "~ $key: $(mask_sensitive_value "$key" "$example_value") -> $(mask_sensitive_value "$key" "$env_value")"
fi
else
# Key does not exist in .env
echo "- $key: $(mask_sensitive_value "$key" "$example_value")"
fi
done
# Check for additions
for key in "${!env_vars[@]}"; do
if [[ ! ${example_vars[$key]+_} ]]; then
echo "+ $key: $(mask_sensitive_value "$key" "${env_vars[$key]}")"
fi
done
;;
sync)
# Subcommand: sync
if [ ! -f ".env.example" ]; then
echo ".env.example file not found."
exit 1
fi
# Create an associative array of env vars
declare -A env_vars
if [ -f ".env" ]; then
while IFS= read -r line || [[ -n "$line" ]]; do
if [[ ! "$line" =~ ^\s*# && "$line" =~ = ]]; then
key=$(echo "$line" | cut -d '=' -f 1)
value=$(echo "$line" | cut -d '=' -f 2-)
env_vars["$key"]="$value"
fi
done < ".env"
fi
while IFS= read -r line || [[ -n "$line" ]]; do
if [[ -z "$line" || "$line" =~ ^\s*# ]]; then
continue
fi
example_key=$(echo "$line" | cut -d '=' -f 1)
example_value=$(echo "$line" | cut -d '=' -f 2-)
if [[ ! ${env_vars[$example_key]+_} ]]; then
# Key does not exist in .env, so add it
echo "$example_key=$example_value" >> ".env"
echo "Added: $example_key"
fi
done < ".env.example"
echo "Sync complete."
;;
*)
echo "Unknown subcommand: $SUBCOMMAND"
exit 1
;;
esac