@redredchen01/env-manager 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Initial release: Skill Foundry v2 CLI toolkit
8
+
9
+ - cli-core: dispatcher, logger, config (XDG), version framework
10
+ - api-testing: HTTP requests, assertions, collections, environments, mock server
11
+ - dep-audit: outdated, vulnerabilities, licenses, dependency tree
12
+ - docker-mgmt: container lifecycle, compose, build, logs
13
+ - env-manager: .env management, encryption, diff, sync, validation
14
+ - git-workflow: branches, rebase, cherry-pick, merge-check, stash
15
+ - log-parser: filter, tail, stats, highlight, extract, JSON parsing
16
+
17
+ ### Patch Changes
18
+
19
+ - Updated dependencies
20
+ - @redredchen01/cli-core@0.2.0
21
+
22
+ All notable changes to @redredchen01/env-manager will be documented in this file.
23
+
24
+ Generated by [git-cliff](https://github.com/orhun/git-cliff).
package/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # @foundry/env-manager
2
+
3
+ > CLI toolkit for .env file management — diff, validate, sync, encrypt/decrypt
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@foundry/env-manager.svg)](https://www.npmjs.com/package/@foundry/env-manager)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ npm install -g @foundry/env-manager
12
+ ```
13
+
14
+ ## Quick Start
15
+
16
+ ```bash
17
+ env-manager show # view vars (secrets masked)
18
+ env-manager validate # check for missing vars
19
+ env-manager diff .env .env.prod # compare environments
20
+ env-manager sync # update .env.example
21
+ env-manager encrypt # encrypt .env → .env.enc
22
+ ```
23
+
24
+ ## Commands
25
+
26
+ | Command | Description |
27
+ |---------|-------------|
28
+ | `show` | Display .env with secret masking |
29
+ | `diff` | Compare two .env files |
30
+ | `validate` | Check against .env.example |
31
+ | `sync` | Generate .env.example from .env |
32
+ | `encrypt` | Encrypt .env (AES-256-CBC) |
33
+ | `decrypt` | Decrypt .env.enc |
34
+ | `init` | Create .env from template |
35
+
36
+ ## As Claude Skill
37
+
38
+ > "Show me the env vars in this project"
39
+ > "Am I missing any environment variables?"
40
+ > "What's different between staging and production env?"
41
+ > "Encrypt the .env file for safe storage"
42
+
43
+ ## License
44
+
45
+ MIT
package/SKILL.md ADDED
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: env-manager
3
+ description: Use when the user needs help with .env files — comparing environments, checking for missing variables, syncing .env to .env.example, encrypting secrets, or viewing env vars safely. Triggers on ".env", "environment variable", "missing env", "env diff", "encrypt secrets", "env example", "validate env", "sync env", "show env", "env config".
4
+ ---
5
+
6
+ # env-manager
7
+
8
+ CLI toolkit for .env file management. Compare environments, validate completeness, sync to .env.example, encrypt/decrypt secrets, and safely view variables.
9
+
10
+ ## Usage
11
+
12
+ When the user works with .env files or environment configuration, use these commands. Sensitive values are automatically masked unless `--reveal` is used.
13
+
14
+ ## CLI Commands
15
+
16
+ ```bash
17
+ env-manager <command> [options]
18
+ ```
19
+
20
+ | Command | Description |
21
+ |---------|-------------|
22
+ | `show` | Display .env with automatic secret masking |
23
+ | `diff` | Compare two .env files (added/removed/changed) |
24
+ | `validate` | Check .env against .env.example for missing vars |
25
+ | `sync` | Generate .env.example from .env (strip values) |
26
+ | `encrypt` | Encrypt .env with AES-256-CBC (OpenSSL) |
27
+ | `decrypt` | Decrypt .env.enc back to .env |
28
+ | `init` | Create .env from .env.example |
29
+
30
+ ## Common Workflows
31
+
32
+ ### "What env vars do I have?"
33
+ ```bash
34
+ env-manager show # masked secrets
35
+ env-manager show .env.production # specific file
36
+ ```
37
+
38
+ ### "Am I missing any env vars?"
39
+ ```bash
40
+ env-manager validate # check against .env.example
41
+ env-manager validate --no-empty # also flag empty values
42
+ ```
43
+
44
+ ### "What's different between staging and prod?"
45
+ ```bash
46
+ env-manager diff .env.staging .env.production --values
47
+ ```
48
+
49
+ ### "Update .env.example after adding new vars"
50
+ ```bash
51
+ env-manager sync # .env → .env.example (strip values)
52
+ ```
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env bash
2
+ # @foundry/env-manager — CLI entry point
3
+ # Installed via: npm install -g @foundry/env-manager
4
+ set -euo pipefail
5
+
6
+ SKILL_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
7
+ exec bash "$SKILL_ROOT/cli/main.sh" "$@"
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env bash
2
+ # Command: decrypt — 解密 .env.enc 檔案
3
+ # Part of @foundry/env-manager
4
+
5
+ # shellcheck disable=SC2034
6
+ COMMAND_NAME="decrypt"
7
+ # shellcheck disable=SC2034
8
+ COMMAND_DESC="Decrypt an encrypted .env file (AES-256-CBC)"
9
+
10
+ show_help() {
11
+ cat << 'EOF'
12
+ Usage: env-manager decrypt <file> [options]
13
+
14
+ Decrypt a .env.enc file back to .env (AES-256-CBC, OpenSSL).
15
+
16
+ Arguments:
17
+ file Encrypted file (e.g., .env.enc)
18
+
19
+ Options:
20
+ -o, --output FILE Output file (default: strip .enc suffix)
21
+ -h, --help Show this help
22
+
23
+ Examples:
24
+ env-manager decrypt .env.enc
25
+ env-manager decrypt secrets.enc -o .env.production
26
+ EOF
27
+ }
28
+
29
+ run() {
30
+ local file="" output=""
31
+
32
+ while [ $# -gt 0 ]; do
33
+ case "$1" in
34
+ --help|-h) show_help; return 0 ;;
35
+ --output|-o) shift; output="${1:?--output requires a file}" ;;
36
+ -*) echo "Error: unknown option '$1'" >&2; return 1 ;;
37
+ *) file="$1" ;;
38
+ esac
39
+ shift
40
+ done
41
+
42
+ [ -z "$file" ] && { echo "Error: encrypted file required." >&2; return 1; }
43
+ [ ! -f "$file" ] && { echo "Error: '$file' not found." >&2; return 1; }
44
+
45
+ if ! command -v openssl >/dev/null 2>&1; then
46
+ echo "Error: openssl is not installed." >&2
47
+ return 1
48
+ fi
49
+
50
+ # Auto-detect output name
51
+ if [ -z "$output" ]; then
52
+ output="${file%.enc}"
53
+ [ "$output" = "$file" ] && output="${file}.decrypted"
54
+ fi
55
+
56
+ echo "Decrypting: $file → $output"
57
+ echo "Enter passphrase:"
58
+
59
+ if openssl enc -aes-256-cbc -d -salt -pbkdf2 -in "$file" -out "$output" 2>/dev/null; then
60
+ echo ""
61
+ echo "Decrypted: $output"
62
+ local count
63
+ count="$(grep -c '=' "$output" 2>/dev/null || echo 0)"
64
+ echo "($count variables)"
65
+ else
66
+ echo "Error: decryption failed (wrong passphrase?)." >&2
67
+ [ -f "$output" ] && rm "$output"
68
+ return 1
69
+ fi
70
+ }
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env bash
2
+ # Command: diff — 比較兩個 .env 檔案
3
+ # Part of @foundry/env-manager
4
+
5
+ # shellcheck disable=SC2034
6
+ COMMAND_NAME="diff"
7
+ # shellcheck disable=SC2034
8
+ COMMAND_DESC="Compare two .env files and show differences"
9
+
10
+ show_help() {
11
+ cat << 'EOF'
12
+ Usage: env-manager diff <file1> <file2> [options]
13
+
14
+ Compare two .env files. Shows keys that are added, removed, or have different values.
15
+
16
+ Arguments:
17
+ file1 First .env file (e.g., .env)
18
+ file2 Second .env file (e.g., .env.production)
19
+
20
+ Options:
21
+ --keys-only Only compare keys (ignore values)
22
+ --values Also show differing values (masked by default)
23
+ -h, --help Show this help
24
+
25
+ Examples:
26
+ env-manager diff .env .env.example
27
+ env-manager diff .env .env.production --values
28
+ env-manager diff .env.staging .env.production --keys-only
29
+ EOF
30
+ }
31
+
32
+ run() {
33
+ local file1="" file2="" keys_only=false show_values=false
34
+
35
+ while [ $# -gt 0 ]; do
36
+ case "$1" in
37
+ --help|-h) show_help; return 0 ;;
38
+ --keys-only) keys_only=true ;;
39
+ --values) show_values=true ;;
40
+ -*) echo "Error: unknown option '$1'" >&2; return 1 ;;
41
+ *)
42
+ if [ -z "$file1" ]; then file1="$1"
43
+ elif [ -z "$file2" ]; then file2="$1"
44
+ else echo "Error: too many arguments." >&2; return 1
45
+ fi ;;
46
+ esac
47
+ shift
48
+ done
49
+
50
+ [ -z "$file1" ] || [ -z "$file2" ] && { echo "Error: two files required." >&2; return 1; }
51
+ [ ! -f "$file1" ] && { echo "Error: '$file1' not found." >&2; return 1; }
52
+ [ ! -f "$file2" ] && { echo "Error: '$file2' not found." >&2; return 1; }
53
+
54
+ echo "Comparing: $file1 ↔ $file2"
55
+ echo ""
56
+
57
+ # Extract keys from each file
58
+ local keys1 keys2
59
+ keys1="$(_extract_keys "$file1")"
60
+ keys2="$(_extract_keys "$file2")"
61
+
62
+ local only_in_1="" only_in_2="" in_both="" changed=""
63
+
64
+ # Keys only in file1
65
+ while IFS= read -r key; do
66
+ [ -z "$key" ] && continue
67
+ if ! echo "$keys2" | grep -qx "$key"; then
68
+ only_in_1="${only_in_1}${key}\n"
69
+ else
70
+ in_both="${in_both}${key}\n"
71
+ fi
72
+ done <<< "$keys1"
73
+
74
+ # Keys only in file2
75
+ while IFS= read -r key; do
76
+ [ -z "$key" ] && continue
77
+ if ! echo "$keys1" | grep -qx "$key"; then
78
+ only_in_2="${only_in_2}${key}\n"
79
+ fi
80
+ done <<< "$keys2"
81
+
82
+ # Value differences (for keys in both)
83
+ if [ "$keys_only" = false ]; then
84
+ while IFS= read -r key; do
85
+ [ -z "$key" ] && continue
86
+ local v1 v2
87
+ v1="$(_get_value "$file1" "$key")"
88
+ v2="$(_get_value "$file2" "$key")"
89
+ if [ "$v1" != "$v2" ]; then
90
+ changed="${changed}${key}\n"
91
+ fi
92
+ done <<< "$(echo -e "$in_both")"
93
+ fi
94
+
95
+ # Output
96
+ if [ -n "$only_in_1" ]; then
97
+ echo "Only in $file1:"
98
+ echo -e "$only_in_1" | while IFS= read -r k; do
99
+ [ -n "$k" ] && echo " - $k"
100
+ done
101
+ echo ""
102
+ fi
103
+
104
+ if [ -n "$only_in_2" ]; then
105
+ echo "Only in $file2:"
106
+ echo -e "$only_in_2" | while IFS= read -r k; do
107
+ [ -n "$k" ] && echo " + $k"
108
+ done
109
+ echo ""
110
+ fi
111
+
112
+ if [ -n "$changed" ]; then
113
+ echo "Different values:"
114
+ echo -e "$changed" | while IFS= read -r k; do
115
+ [ -z "$k" ] && continue
116
+ if [ "$show_values" = true ]; then
117
+ echo " ~ $k: $(_get_value "$file1" "$k") → $(_get_value "$file2" "$k")"
118
+ else
119
+ echo " ~ $k"
120
+ fi
121
+ done
122
+ echo ""
123
+ fi
124
+
125
+ if [ -z "$only_in_1" ] && [ -z "$only_in_2" ] && [ -z "$changed" ]; then
126
+ echo "Files are identical (same keys and values)."
127
+ fi
128
+ }
129
+
130
+ _extract_keys() {
131
+ grep -v '^#' "$1" | grep -v '^$' | grep '=' | sed 's/=.*//' | sort
132
+ }
133
+
134
+ _get_value() {
135
+ grep "^${2}=" "$1" 2>/dev/null | head -1 | sed "s/^${2}=//"
136
+ }
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env bash
2
+ # Command: encrypt — 加密 .env 檔案
3
+ # Part of @foundry/env-manager
4
+
5
+ # shellcheck disable=SC2034
6
+ COMMAND_NAME="encrypt"
7
+ # shellcheck disable=SC2034
8
+ COMMAND_DESC="Encrypt .env file with a passphrase (AES-256-CBC)"
9
+
10
+ show_help() {
11
+ cat << 'EOF'
12
+ Usage: env-manager encrypt [file] [options]
13
+
14
+ Encrypt a .env file using AES-256-CBC (OpenSSL). Produces a .env.enc file.
15
+
16
+ Arguments:
17
+ file File to encrypt (default: .env)
18
+
19
+ Options:
20
+ -o, --output FILE Output file (default: {input}.enc)
21
+ --delete Delete original after encryption
22
+ -h, --help Show this help
23
+
24
+ Prerequisites:
25
+ OpenSSL must be installed (pre-installed on macOS and most Linux).
26
+
27
+ Examples:
28
+ env-manager encrypt
29
+ env-manager encrypt .env.production
30
+ env-manager encrypt .env -o secrets.enc
31
+ env-manager encrypt --delete
32
+ EOF
33
+ }
34
+
35
+ run() {
36
+ local file=".env" output="" delete_original=false
37
+
38
+ while [ $# -gt 0 ]; do
39
+ case "$1" in
40
+ --help|-h) show_help; return 0 ;;
41
+ --output|-o) shift; output="${1:?--output requires a file}" ;;
42
+ --delete) delete_original=true ;;
43
+ -*) echo "Error: unknown option '$1'" >&2; return 1 ;;
44
+ *) file="$1" ;;
45
+ esac
46
+ shift
47
+ done
48
+
49
+ [ ! -f "$file" ] && { echo "Error: '$file' not found." >&2; return 1; }
50
+
51
+ if ! command -v openssl >/dev/null 2>&1; then
52
+ echo "Error: openssl is not installed." >&2
53
+ return 1
54
+ fi
55
+
56
+ [ -z "$output" ] && output="${file}.enc"
57
+
58
+ echo "Encrypting: $file → $output"
59
+ echo "Enter passphrase (you'll need this to decrypt):"
60
+
61
+ if openssl enc -aes-256-cbc -salt -pbkdf2 -in "$file" -out "$output" 2>/dev/null; then
62
+ echo ""
63
+ echo "Encrypted: $output"
64
+ if [ "$delete_original" = true ]; then
65
+ rm "$file"
66
+ echo "Deleted original: $file"
67
+ fi
68
+ echo ""
69
+ echo "To decrypt: env-manager decrypt $output"
70
+ else
71
+ echo "Error: encryption failed." >&2
72
+ [ -f "$output" ] && rm "$output"
73
+ return 1
74
+ fi
75
+ }
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env bash
2
+ # Command: init — 初始化 .env 檔案
3
+ # Part of @foundry/env-manager
4
+
5
+ # shellcheck disable=SC2034
6
+ COMMAND_NAME="init"
7
+ # shellcheck disable=SC2034
8
+ COMMAND_DESC="Initialize .env from .env.example with interactive prompts"
9
+
10
+ show_help() {
11
+ cat << 'EOF'
12
+ Usage: env-manager init [options]
13
+
14
+ Create .env from .env.example. Copies the template and optionally prompts for values.
15
+
16
+ Options:
17
+ --example FILE Template file (default: .env.example)
18
+ --output FILE Output file (default: .env)
19
+ --force Overwrite existing .env
20
+ --no-prompt Copy as-is without prompting
21
+ -h, --help Show this help
22
+
23
+ Examples:
24
+ env-manager init
25
+ env-manager init --example .env.template
26
+ env-manager init --force
27
+ env-manager init --no-prompt
28
+ EOF
29
+ }
30
+
31
+ run() {
32
+ local example=".env.example" output=".env" force=false no_prompt=false
33
+
34
+ while [ $# -gt 0 ]; do
35
+ case "$1" in
36
+ --help|-h) show_help; return 0 ;;
37
+ --example) shift; example="${1:?--example requires a file}" ;;
38
+ --output) shift; output="${1:?--output requires a file}" ;;
39
+ --force) force=true ;;
40
+ --no-prompt) no_prompt=true ;;
41
+ *) echo "Error: unknown option '$1'" >&2; return 1 ;;
42
+ esac
43
+ shift
44
+ done
45
+
46
+ [ ! -f "$example" ] && { echo "Error: '$example' not found." >&2; return 1; }
47
+
48
+ if [ -f "$output" ] && [ "$force" = false ]; then
49
+ echo "Error: '$output' already exists. Use --force to overwrite." >&2
50
+ return 1
51
+ fi
52
+
53
+ if [ "$no_prompt" = true ]; then
54
+ cp "$example" "$output"
55
+ local count
56
+ count="$(grep -c '=' "$output" 2>/dev/null || echo 0)"
57
+ echo "Created: $output ($count variables from $example)"
58
+ return 0
59
+ fi
60
+
61
+ # Interactive: copy with prompts for empty values
62
+ echo "Initializing $output from $example"
63
+ echo "(Press Enter to keep default, or type a new value)"
64
+ echo ""
65
+
66
+ local result=""
67
+ while IFS= read -r line; do
68
+ case "$line" in
69
+ ""|\#*)
70
+ result="${result}${line}\n"
71
+ continue
72
+ ;;
73
+ esac
74
+
75
+ local key="${line%%=*}"
76
+ local value="${line#*=}"
77
+
78
+ if [ -z "$value" ] || [ "$value" = '""' ] || [ "$value" = "''" ]; then
79
+ printf " %s = " "$key"
80
+ local new_value
81
+ read -r new_value < /dev/tty 2>/dev/null || new_value=""
82
+ if [ -n "$new_value" ]; then
83
+ result="${result}${key}=${new_value}\n"
84
+ else
85
+ result="${result}${line}\n"
86
+ fi
87
+ else
88
+ result="${result}${line}\n"
89
+ fi
90
+ done < "$example"
91
+
92
+ echo -e "$result" > "$output"
93
+ echo ""
94
+ echo "Created: $output"
95
+ }
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env bash
2
+ # Command: show — 顯示 .env 內容(遮蔽 secrets)
3
+ # Part of @foundry/env-manager
4
+
5
+ # shellcheck disable=SC2034
6
+ COMMAND_NAME="show"
7
+ # shellcheck disable=SC2034
8
+ COMMAND_DESC="Display .env contents with secret masking"
9
+
10
+ show_help() {
11
+ cat << 'EOF'
12
+ Usage: env-manager show [file] [options]
13
+
14
+ Display .env file contents with automatic secret masking.
15
+
16
+ Arguments:
17
+ file Path to .env file (default: .env)
18
+
19
+ Options:
20
+ --reveal Show actual values (no masking)
21
+ --keys Show only keys, no values
22
+ --sort Sort alphabetically
23
+ -h, --help Show this help
24
+
25
+ Examples:
26
+ env-manager show
27
+ env-manager show .env.production
28
+ env-manager show --reveal
29
+ env-manager show --keys
30
+ EOF
31
+ }
32
+
33
+ SECRET_PATTERNS="PASSWORD|SECRET|KEY|TOKEN|API_KEY|PRIVATE|CREDENTIAL|AUTH"
34
+
35
+ run() {
36
+ local file=".env" reveal=false keys_only=false do_sort=false
37
+
38
+ while [ $# -gt 0 ]; do
39
+ case "$1" in
40
+ --help|-h) show_help; return 0 ;;
41
+ --reveal) reveal=true ;;
42
+ --keys) keys_only=true ;;
43
+ --sort) do_sort=true ;;
44
+ -*) echo "Error: unknown option '$1'" >&2; return 1 ;;
45
+ *) file="$1" ;;
46
+ esac
47
+ shift
48
+ done
49
+
50
+ if [ ! -f "$file" ]; then
51
+ echo "Error: '$file' not found." >&2
52
+ return 1
53
+ fi
54
+
55
+ local lines
56
+ if [ "$do_sort" = true ]; then
57
+ lines="$(sort "$file")"
58
+ else
59
+ lines="$(cat "$file")"
60
+ fi
61
+
62
+ local count=0
63
+ while IFS= read -r line; do
64
+ # Skip empty lines and comments
65
+ case "$line" in
66
+ ""|\#*) echo "$line"; continue ;;
67
+ esac
68
+
69
+ local key value
70
+ key="${line%%=*}"
71
+ value="${line#*=}"
72
+ count=$((count + 1))
73
+
74
+ if [ "$keys_only" = true ]; then
75
+ echo "$key"
76
+ elif [ "$reveal" = true ]; then
77
+ echo "$line"
78
+ else
79
+ # Mask values for sensitive keys
80
+ if echo "$key" | grep -qiE "$SECRET_PATTERNS"; then
81
+ echo "${key}=********"
82
+ else
83
+ echo "$line"
84
+ fi
85
+ fi
86
+ done <<< "$lines"
87
+
88
+ echo ""
89
+ echo "($count variables in $file)"
90
+ }
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env bash
2
+ # Command: sync — 同步 .env 到 .env.example
3
+ # Part of @foundry/env-manager
4
+
5
+ # shellcheck disable=SC2034
6
+ COMMAND_NAME="sync"
7
+ # shellcheck disable=SC2034
8
+ COMMAND_DESC="Sync .env keys to .env.example (strip values)"
9
+
10
+ show_help() {
11
+ cat << 'EOF'
12
+ Usage: env-manager sync [options]
13
+
14
+ Generate or update .env.example from .env, keeping keys but stripping values.
15
+
16
+ Options:
17
+ --source FILE Source file (default: .env)
18
+ --target FILE Target file (default: .env.example)
19
+ --preserve Keep existing values in target (only add new keys)
20
+ --dry-run Show what would change without writing
21
+ -h, --help Show this help
22
+
23
+ Examples:
24
+ env-manager sync
25
+ env-manager sync --source .env.production --target .env.prod.example
26
+ env-manager sync --preserve
27
+ env-manager sync --dry-run
28
+ EOF
29
+ }
30
+
31
+ run() {
32
+ local source=".env" target=".env.example" preserve=false dry_run=false
33
+
34
+ while [ $# -gt 0 ]; do
35
+ case "$1" in
36
+ --help|-h) show_help; return 0 ;;
37
+ --source) shift; source="${1:?--source requires a file}" ;;
38
+ --target) shift; target="${1:?--target requires a file}" ;;
39
+ --preserve) preserve=true ;;
40
+ --dry-run) dry_run=true ;;
41
+ *) echo "Error: unknown option '$1'" >&2; return 1 ;;
42
+ esac
43
+ shift
44
+ done
45
+
46
+ [ ! -f "$source" ] && { echo "Error: '$source' not found." >&2; return 1; }
47
+
48
+ echo "Syncing: $source → $target"
49
+ echo ""
50
+
51
+ local output="" added=0 total=0
52
+
53
+ while IFS= read -r line; do
54
+ case "$line" in
55
+ ""|\#*)
56
+ output="${output}${line}\n"
57
+ continue
58
+ ;;
59
+ esac
60
+
61
+ local key="${line%%=*}"
62
+ total=$((total + 1))
63
+
64
+ if [ "$preserve" = true ] && [ -f "$target" ]; then
65
+ local existing
66
+ existing="$(grep "^${key}=" "$target" 2>/dev/null | head -1)"
67
+ if [ -n "$existing" ]; then
68
+ output="${output}${existing}\n"
69
+ continue
70
+ fi
71
+ fi
72
+
73
+ # Strip value, keep key with empty value
74
+ output="${output}${key}=\n"
75
+
76
+ # Check if this is a new key
77
+ if [ -f "$target" ] && ! grep -q "^${key}=" "$target" 2>/dev/null; then
78
+ added=$((added + 1))
79
+ fi
80
+ done < "$source"
81
+
82
+ if [ "$dry_run" = true ]; then
83
+ echo "Would write to $target ($total keys, $added new):"
84
+ echo ""
85
+ echo -e "$output"
86
+ echo "(Dry run — no file written)"
87
+ return 0
88
+ fi
89
+
90
+ echo -e "$output" > "$target"
91
+ echo "Written: $target ($total keys, $added new)"
92
+ }
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env bash
2
+ # Command: validate — 驗證 .env 完整性
3
+ # Part of @foundry/env-manager
4
+
5
+ # shellcheck disable=SC2034
6
+ COMMAND_NAME="validate"
7
+ # shellcheck disable=SC2034
8
+ COMMAND_DESC="Validate .env against .env.example for missing/extra variables"
9
+
10
+ show_help() {
11
+ cat << 'EOF'
12
+ Usage: env-manager validate [file] [options]
13
+
14
+ Check .env against .env.example to find missing or extra variables.
15
+
16
+ Arguments:
17
+ file Path to .env file (default: .env)
18
+
19
+ Options:
20
+ --example FILE Reference file (default: .env.example)
21
+ --strict Fail on extra variables (not just missing)
22
+ --no-empty Fail on empty values
23
+ -h, --help Show this help
24
+
25
+ Examples:
26
+ env-manager validate
27
+ env-manager validate .env.production
28
+ env-manager validate --example .env.required --strict
29
+ env-manager validate --no-empty
30
+ EOF
31
+ }
32
+
33
+ run() {
34
+ local file=".env" example=".env.example" strict=false no_empty=false
35
+
36
+ while [ $# -gt 0 ]; do
37
+ case "$1" in
38
+ --help|-h) show_help; return 0 ;;
39
+ --example) shift; example="${1:?--example requires a file}" ;;
40
+ --strict) strict=true ;;
41
+ --no-empty) no_empty=true ;;
42
+ -*) echo "Error: unknown option '$1'" >&2; return 1 ;;
43
+ *) file="$1" ;;
44
+ esac
45
+ shift
46
+ done
47
+
48
+ [ ! -f "$file" ] && { echo "Error: '$file' not found." >&2; return 1; }
49
+ [ ! -f "$example" ] && { echo "Error: '$example' not found. Create it or use --example." >&2; return 1; }
50
+
51
+ echo "Validating: $file against $example"
52
+ echo ""
53
+
54
+ local env_keys example_keys
55
+ env_keys="$(_extract_keys "$file")"
56
+ example_keys="$(_extract_keys "$example")"
57
+
58
+ local errors=0 warnings=0
59
+
60
+ # Missing keys (in example but not in env)
61
+ local missing=""
62
+ while IFS= read -r key; do
63
+ [ -z "$key" ] && continue
64
+ if ! echo "$env_keys" | grep -qx "$key"; then
65
+ missing="${missing} ✗ ${key}\n"
66
+ errors=$((errors + 1))
67
+ fi
68
+ done <<< "$example_keys"
69
+
70
+ if [ -n "$missing" ]; then
71
+ echo "Missing (required but not set):"
72
+ echo -e "$missing"
73
+ fi
74
+
75
+ # Extra keys (in env but not in example)
76
+ local extra=""
77
+ while IFS= read -r key; do
78
+ [ -z "$key" ] && continue
79
+ if ! echo "$example_keys" | grep -qx "$key"; then
80
+ extra="${extra} ? ${key}\n"
81
+ [ "$strict" = true ] && errors=$((errors + 1)) || warnings=$((warnings + 1))
82
+ fi
83
+ done <<< "$env_keys"
84
+
85
+ if [ -n "$extra" ]; then
86
+ if [ "$strict" = true ]; then
87
+ echo "Extra (not in $example — strict mode):"
88
+ else
89
+ echo "Extra (not in $example — informational):"
90
+ fi
91
+ echo -e "$extra"
92
+ fi
93
+
94
+ # Empty values check
95
+ if [ "$no_empty" = true ]; then
96
+ local empty=""
97
+ while IFS= read -r line; do
98
+ case "$line" in ""|\#*) continue ;; esac
99
+ local key="${line%%=*}" value="${line#*=}"
100
+ if [ -z "$value" ] || [ "$value" = '""' ] || [ "$value" = "''" ]; then
101
+ empty="${empty} ⚠ ${key} (empty)\n"
102
+ errors=$((errors + 1))
103
+ fi
104
+ done < "$file"
105
+
106
+ if [ -n "$empty" ]; then
107
+ echo "Empty values:"
108
+ echo -e "$empty"
109
+ fi
110
+ fi
111
+
112
+ # Summary
113
+ echo "Result: $errors errors, $warnings warnings"
114
+ [ "$errors" -gt 0 ] && return 1
115
+ echo "✓ Validation passed."
116
+ return 0
117
+ }
118
+
119
+ _extract_keys() {
120
+ grep -v '^#' "$1" | grep -v '^$' | grep '=' | sed 's/=.*//' | sort
121
+ }
package/cli/main.sh ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SKILL_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
5
+
6
+ # Source cli-core libraries
7
+ # shellcheck source=/dev/null
8
+ CLI_CORE="${SKILL_ROOT}/../cli-core/lib"
9
+ if [ -d "$CLI_CORE" ]; then
10
+ source "${CLI_CORE}/dispatcher.sh"
11
+ source "${CLI_CORE}/logger.sh"
12
+ source "${CLI_CORE}/config.sh"
13
+ source "${CLI_CORE}/version.sh"
14
+ fi
15
+
16
+ export SKILL_NAME="env-manager"
17
+ export VERSION
18
+ VERSION=$(foundry::version::get "$SKILL_ROOT" 2>/dev/null || echo "0.0.0")
19
+ export COMMANDS_DIR="${SKILL_ROOT}/cli/commands"
20
+
21
+ foundry::logger::parse_flags "$@"
22
+ foundry::dispatcher::dispatch "$@"
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bash
2
+ # Shared utility functions for this skill
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@redredchen01/env-manager",
3
+ "version": "0.2.0",
4
+ "description": "CLI toolkit for .env file management — diff, validate, sync, encrypt/decrypt",
5
+ "bin": {
6
+ "env-manager": "./bin/env-manager.sh"
7
+ },
8
+ "files": [
9
+ "bin/",
10
+ "cli/",
11
+ "scripts/",
12
+ "references/",
13
+ "assets/",
14
+ "SKILL.md",
15
+ "README.md",
16
+ "CHANGELOG.md"
17
+ ],
18
+ "keywords": [
19
+ "claude-code",
20
+ "skill",
21
+ "cli",
22
+ "env",
23
+ "dotenv",
24
+ "config",
25
+ "secrets"
26
+ ],
27
+ "license": "MIT",
28
+ "engines": {
29
+ "node": ">=18"
30
+ },
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/redredchen01/skill-foundry",
34
+ "directory": "packages/env-manager"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "dependencies": {
40
+ "@redredchen01/cli-core": "*"
41
+ },
42
+ "skillFoundry": {
43
+ "concept": ".env file management — show, diff, validate, sync, encrypt, decrypt, init",
44
+ "phase": "generate",
45
+ "capabilities": [
46
+ "show",
47
+ "diff",
48
+ "validate",
49
+ "sync",
50
+ "encrypt",
51
+ "decrypt",
52
+ "init"
53
+ ]
54
+ }
55
+ }