claude-pro-minmax 1.0.2 → 1.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/README.ko.md +79 -52
- package/README.md +79 -52
- package/bin/cpmm.js +2 -168
- package/install.sh +244 -106
- package/lib/cli.js +244 -0
- package/package.json +13 -12
- package/.claude/sessions/2025-01-27-auth-jwt-refresh.md +0 -32
- package/.claude/sessions/README.ko.md +0 -195
- package/.claude/sessions/README.md +0 -195
- package/.claude/skills/learned/README.ko.md +0 -64
- package/.claude/skills/learned/README.md +0 -64
- package/scripts/claude_command_smoke.sh +0 -116
package/install.sh
CHANGED
|
@@ -1,146 +1,280 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# Claude Pro MinMax - Installation Script
|
|
3
3
|
set -e
|
|
4
|
-
echo "🚀 Installing Claude Pro MinMax (CPMM)"
|
|
5
4
|
|
|
6
|
-
#
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
# Error handler: print failure line and exit message
|
|
6
|
+
_on_error() { echo ""; echo "❌ Installation failed at line $1. Check output above."; }
|
|
7
|
+
trap '_on_error $LINENO' ERR
|
|
8
|
+
|
|
9
|
+
# Guard unsupported invocations (`curl | bash`, process substitution)
|
|
10
|
+
if [[ -z "${BASH_SOURCE[0]}" || "${BASH_SOURCE[0]}" == "bash" ]]; then
|
|
11
|
+
echo "❌ This script cannot be run via 'curl | bash'."
|
|
12
|
+
echo " Please clone the repository first:"
|
|
13
|
+
echo " git clone https://github.com/move-hoon/claude-pro-minmax.git && cd claude-pro-minmax && bash install.sh"
|
|
14
|
+
exit 1
|
|
15
|
+
fi
|
|
16
|
+
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
17
|
+
if [ ! -f "$SCRIPT_DIR/.claude/CLAUDE.md" ] || [ ! -f "$SCRIPT_DIR/package.json" ]; then
|
|
18
|
+
echo "❌ This script must be run from a cloned CPMM repository directory."
|
|
19
|
+
echo " Please run:"
|
|
20
|
+
echo " git clone https://github.com/move-hoon/claude-pro-minmax.git && cd claude-pro-minmax && bash install.sh"
|
|
21
|
+
exit 1
|
|
10
22
|
fi
|
|
11
23
|
|
|
12
|
-
#
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
24
|
+
# Marker-based detection for Install vs Update
|
|
25
|
+
CPMM_MARKER="$HOME/.claude/.cpmm-version"
|
|
26
|
+
IS_UPDATE=false
|
|
27
|
+
if [ -f "$CPMM_MARKER" ]; then IS_UPDATE=true; fi
|
|
28
|
+
|
|
29
|
+
# Legacy CPMM detection (marker was introduced after early releases)
|
|
30
|
+
LEGACY_CPMM=false
|
|
31
|
+
if [ "$IS_UPDATE" = false ] && [ -d "$HOME/.claude" ]; then
|
|
32
|
+
if [ -f "$HOME/.claude/CLAUDE.md" ] &&
|
|
33
|
+
[ -f "$HOME/.claude/settings.json" ] &&
|
|
34
|
+
[ -f "$HOME/.claude/commands/do.md" ] &&
|
|
35
|
+
[ -f "$HOME/.claude/commands/do-opus.md" ]; then
|
|
36
|
+
LEGACY_CPMM=true
|
|
37
|
+
fi
|
|
16
38
|
fi
|
|
17
39
|
|
|
18
|
-
#
|
|
19
|
-
if [
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
40
|
+
# Header message: show Install vs Update mode
|
|
41
|
+
if [ "$IS_UPDATE" = true ]; then
|
|
42
|
+
echo "🔄 Updating Claude Pro MinMax (CPMM)"
|
|
43
|
+
INSTALLED_VERSION=$(head -1 "$CPMM_MARKER" 2>/dev/null || echo "unknown")
|
|
44
|
+
echo " Current version: $INSTALLED_VERSION"
|
|
45
|
+
else
|
|
46
|
+
echo "🚀 Installing Claude Pro MinMax (CPMM)"
|
|
24
47
|
fi
|
|
25
48
|
|
|
26
|
-
#
|
|
27
|
-
|
|
49
|
+
# Backup existing ~/.claude (Fresh Install Only)
|
|
50
|
+
if [ "$IS_UPDATE" = false ] && [ -d "$HOME/.claude" ]; then
|
|
51
|
+
if [ -d "$HOME/.claude.pre-cpmm" ]; then
|
|
52
|
+
echo "⚠️ ~/.claude.pre-cpmm already exists, skipping backup."
|
|
53
|
+
echo " User data preserved. Reinstalling CPMM files in-place..."
|
|
54
|
+
# No rm -rf — user data (learned/, plans/, projects/, sessions/) is safe
|
|
55
|
+
elif [ "$LEGACY_CPMM" = true ]; then
|
|
56
|
+
echo "⚠️ Detected legacy CPMM installation without marker, skipping backup."
|
|
57
|
+
echo " User data preserved. Reinstalling CPMM files in-place..."
|
|
58
|
+
# No rm -rf — user data (learned/, plans/, projects/, sessions/) is safe
|
|
59
|
+
else
|
|
60
|
+
mv "$HOME/.claude" "$HOME/.claude.pre-cpmm"
|
|
61
|
+
echo "📦 Backed up ~/.claude → ~/.claude.pre-cpmm"
|
|
62
|
+
fi
|
|
63
|
+
fi
|
|
28
64
|
|
|
29
|
-
#
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
65
|
+
# Create required directories (user-owned dirs are never rm-rf'd)
|
|
66
|
+
# NITPICK #11: only create dirs not managed by rm-rf below
|
|
67
|
+
mkdir -p "$HOME/.claude/rules" "$HOME/.claude/skills/learned"
|
|
68
|
+
mkdir -p "$HOME/.claude/"{sessions,plans,projects}
|
|
69
|
+
|
|
70
|
+
# Copy core configurations
|
|
71
|
+
cp "$SCRIPT_DIR/.claude/CLAUDE.md" "$HOME/.claude/"
|
|
72
|
+
cp "$SCRIPT_DIR/.claude/settings.json" "$HOME/.claude/"
|
|
73
|
+
# Copy settings.local.json from example template (only if not already present)
|
|
74
|
+
if [ ! -f "$HOME/.claude/settings.local.json" ] && [ -f "$SCRIPT_DIR/.claude/settings.local.example.json" ]; then
|
|
75
|
+
cp "$SCRIPT_DIR/.claude/settings.local.example.json" "$HOME/.claude/settings.local.json"
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
shopt -s nullglob
|
|
79
|
+
|
|
80
|
+
# agents/, commands/, contexts/ — fully CPMM managed: rm-rf to remove stale files
|
|
81
|
+
# EDGE CASE #6: these dirs are CPMM-only; user files should not be placed here
|
|
82
|
+
rm -rf "$HOME/.claude/agents" && mkdir -p "$HOME/.claude/agents"
|
|
83
|
+
_agent_files=("$SCRIPT_DIR/.claude/agents/"*.md)
|
|
84
|
+
if [ ${#_agent_files[@]} -gt 0 ]; then
|
|
85
|
+
for agent in "${_agent_files[@]}"; do
|
|
86
|
+
filename=$(basename "$agent")
|
|
87
|
+
[[ "$filename" == README* ]] && continue
|
|
88
|
+
[[ "$filename" == USER-MANUAL* ]] && continue
|
|
89
|
+
cp "$agent" "$HOME/.claude/agents/"
|
|
90
|
+
done
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
rm -rf "$HOME/.claude/commands" && mkdir -p "$HOME/.claude/commands"
|
|
94
|
+
_cmd_files=("$SCRIPT_DIR/.claude/commands/"*.md)
|
|
95
|
+
if [ ${#_cmd_files[@]} -gt 0 ]; then
|
|
96
|
+
for cmd in "${_cmd_files[@]}"; do
|
|
97
|
+
filename=$(basename "$cmd")
|
|
98
|
+
[[ "$filename" == README* ]] && continue
|
|
99
|
+
[[ "$filename" == USER-MANUAL* ]] && continue
|
|
100
|
+
cp "$cmd" "$HOME/.claude/commands/"
|
|
101
|
+
done
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
rm -rf "$HOME/.claude/contexts" && mkdir -p "$HOME/.claude/contexts"
|
|
105
|
+
_ctx_files=("$SCRIPT_DIR/.claude/contexts/"*.md)
|
|
106
|
+
if [ ${#_ctx_files[@]} -gt 0 ]; then
|
|
107
|
+
for ctx in "${_ctx_files[@]}"; do
|
|
108
|
+
filename=$(basename "$ctx")
|
|
109
|
+
[[ "$filename" == README* ]] && continue
|
|
110
|
+
[[ "$filename" == USER-MANUAL* ]] && continue
|
|
111
|
+
cp "$ctx" "$HOME/.claude/contexts/"
|
|
112
|
+
done
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
# rules/ — language.md is user-owned: remove stale CPMM rules, preserve language.md
|
|
116
|
+
# BUG #2: rules loop is now inside nullglob block to prevent literal glob expansion
|
|
117
|
+
find "$HOME/.claude/rules/" -name "*.md" ! -name "language.md" -delete 2>/dev/null || true
|
|
39
118
|
for rule in "$SCRIPT_DIR/.claude/rules/"*.md; do
|
|
119
|
+
[[ -f "$rule" ]] || continue # nullglob guard (no-op with nullglob, safety net)
|
|
40
120
|
filename=$(basename "$rule")
|
|
41
121
|
[ "$filename" = "language.md" ] && continue
|
|
42
|
-
|
|
122
|
+
[[ "$filename" == README* ]] && continue
|
|
123
|
+
[[ "$filename" == USER-MANUAL* ]] && continue
|
|
124
|
+
cp "$rule" "$HOME/.claude/rules/"
|
|
43
125
|
done
|
|
44
|
-
cp -R "$SCRIPT_DIR/.claude/skills/"* ~/.claude/skills/
|
|
45
|
-
cp "$SCRIPT_DIR/.claude/contexts/"*.md ~/.claude/contexts/
|
|
46
|
-
# sessions/ dir created above; example files stay in repo only (not installed)
|
|
47
|
-
cp -R "$SCRIPT_DIR/scripts/"* ~/.claude/scripts/
|
|
48
126
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
127
|
+
shopt -u nullglob
|
|
128
|
+
|
|
129
|
+
# cli-wrappers/ and scripts/ — fully CPMM managed: rm-rf to remove stale files
|
|
130
|
+
if [ -d "$SCRIPT_DIR/.claude/skills/cli-wrappers" ]; then
|
|
131
|
+
rm -rf "$HOME/.claude/skills/cli-wrappers"
|
|
132
|
+
cp -R "$SCRIPT_DIR/.claude/skills/cli-wrappers" "$HOME/.claude/skills/"
|
|
133
|
+
fi
|
|
134
|
+
# sessions/ dir created above; example files stay in repo only (not installed)
|
|
135
|
+
if [ -d "$SCRIPT_DIR/scripts" ]; then
|
|
136
|
+
rm -rf "$HOME/.claude/scripts"
|
|
137
|
+
cp -R "$SCRIPT_DIR/scripts" "$HOME/.claude/scripts"
|
|
138
|
+
fi
|
|
53
139
|
|
|
54
|
-
# Copy MCP Configuration
|
|
140
|
+
# Copy MCP Configuration
|
|
141
|
+
MCP_CONFIG_PRESENT=false
|
|
55
142
|
if [ -f "$SCRIPT_DIR/.claude.json" ]; then
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
143
|
+
MCP_CONFIG_PRESENT=true
|
|
144
|
+
|
|
145
|
+
if [ "$IS_UPDATE" = false ]; then
|
|
146
|
+
if [ -f "$HOME/.claude.json" ]; then
|
|
147
|
+
echo "📦 Backing up existing ~/.claude.json → ~/.claude.json.bak"
|
|
148
|
+
cp "$HOME/.claude.json" "$HOME/.claude.json.bak"
|
|
59
149
|
fi
|
|
60
|
-
cp "$SCRIPT_DIR/.claude.json"
|
|
150
|
+
cp "$SCRIPT_DIR/.claude.json" "$HOME/.claude.json"
|
|
61
151
|
echo "✅ Installed .claude.json to ~/.claude.json (User Scope)"
|
|
152
|
+
elif [ ! -f "$HOME/.claude.json" ]; then
|
|
153
|
+
cp "$SCRIPT_DIR/.claude.json" "$HOME/.claude.json"
|
|
154
|
+
echo "✅ Restored missing ~/.claude.json from repository template"
|
|
155
|
+
fi
|
|
62
156
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
echo "✅ Created .mcp.json → .claude.json symlink (Ensured Link)"
|
|
69
|
-
# Interactive Perplexity Setup (Read from /dev/tty for curl support)
|
|
70
|
-
if [ -t 0 ] || [ -c /dev/tty ]; then
|
|
71
|
-
if ! command -v jq &> /dev/null; then
|
|
72
|
-
echo "⚠️ Skipping Perplexity setup (jq not installed). Install jq and re-run to configure."
|
|
73
|
-
else
|
|
74
|
-
echo ""
|
|
75
|
-
echo "🔍 Perplexity API Setup (Recommended for /dplan)"
|
|
76
|
-
echo -n " Enter your API Key (Press Enter to skip): "
|
|
77
|
-
read -rs PERPLEXITY_KEY < /dev/tty || PERPLEXITY_KEY=""
|
|
78
|
-
echo "" # Newline for silent read
|
|
79
|
-
|
|
80
|
-
if [ -n "$PERPLEXITY_KEY" ]; then
|
|
81
|
-
# Enable Perplexity (Rename key and inject API Key)
|
|
82
|
-
jq --arg key "$PERPLEXITY_KEY" \
|
|
83
|
-
'.mcpServers.perplexity = .mcpServers._perplexity_disabled_by_default |
|
|
84
|
-
.mcpServers.perplexity.env.PERPLEXITY_API_KEY = $key |
|
|
85
|
-
del(.mcpServers._perplexity_disabled_by_default)' \
|
|
86
|
-
~/.claude.json > ~/.claude.json.tmp && mv ~/.claude.json.tmp ~/.claude.json
|
|
87
|
-
echo "✅ Perplexity API Key configured!"
|
|
88
|
-
else
|
|
89
|
-
# Skip: Completely remove the disabled block to keep config clean
|
|
90
|
-
echo "⚠️ Skipping Perplexity setup. Disabling feature..."
|
|
91
|
-
jq 'del(.mcpServers._perplexity_disabled_by_default)' ~/.claude.json > ~/.claude.json.tmp && mv ~/.claude.json.tmp ~/.claude.json
|
|
92
|
-
echo " (Feature removed from config. Add manually to functionality if needed)"
|
|
93
|
-
fi
|
|
94
|
-
fi
|
|
157
|
+
# Fresh install: enforce symlink. Update: restore only when missing (do not overwrite existing file/link).
|
|
158
|
+
if [ "$IS_UPDATE" = false ]; then
|
|
159
|
+
if [ ! -L "$HOME/.mcp.json" ] || [ "$(readlink "$HOME/.mcp.json" 2>/dev/null)" != "$HOME/.claude.json" ]; then
|
|
160
|
+
ln -sf "$HOME/.claude.json" "$HOME/.mcp.json"
|
|
161
|
+
echo "✅ Ensured .mcp.json → .claude.json symlink"
|
|
95
162
|
fi
|
|
163
|
+
elif [ ! -e "$HOME/.mcp.json" ] && [ ! -L "$HOME/.mcp.json" ]; then
|
|
164
|
+
ln -s "$HOME/.claude.json" "$HOME/.mcp.json"
|
|
165
|
+
echo "✅ Restored missing .mcp.json → .claude.json symlink"
|
|
166
|
+
fi
|
|
96
167
|
fi
|
|
97
168
|
|
|
98
|
-
#
|
|
99
|
-
if [
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
169
|
+
# Perplexity setup + language selection (Fresh Install Only)
|
|
170
|
+
if [ "$IS_UPDATE" = false ]; then
|
|
171
|
+
# EDGE CASE #8: check stdin is truly a terminal (not /dev/tty char-device trick)
|
|
172
|
+
# [ -c /dev/tty ] is always true on Linux/macOS — use [ -t 0 ] alone
|
|
173
|
+
if [ "$MCP_CONFIG_PRESENT" = true ] && [ -t 0 ]; then
|
|
174
|
+
if ! command -v jq &> /dev/null; then
|
|
175
|
+
echo "⚠️ Skipping Perplexity setup (jq not installed). Install jq and re-run to configure."
|
|
176
|
+
else
|
|
177
|
+
echo ""
|
|
178
|
+
echo "🔍 Perplexity API Setup (Recommended for /dplan)"
|
|
179
|
+
echo -n " Enter your API Key (Press Enter to skip): "
|
|
180
|
+
read -rs PERPLEXITY_KEY < /dev/tty || PERPLEXITY_KEY=""
|
|
181
|
+
echo "" # Newline for silent read
|
|
182
|
+
|
|
183
|
+
if [ -n "$PERPLEXITY_KEY" ]; then
|
|
184
|
+
# Enable Perplexity (use env var to avoid key exposure in process list)
|
|
185
|
+
# EDGE CASE #10: write to tmp then atomically rename; trap cleans up on failure
|
|
186
|
+
PERPLEXITY_API_KEY="$PERPLEXITY_KEY" jq \
|
|
187
|
+
'.mcpServers.perplexity = .mcpServers._perplexity_disabled_by_default |
|
|
188
|
+
.mcpServers.perplexity.env.PERPLEXITY_API_KEY = env.PERPLEXITY_API_KEY |
|
|
189
|
+
del(.mcpServers._perplexity_disabled_by_default)' \
|
|
190
|
+
"$HOME/.claude.json" > "$HOME/.claude.json.tmp"
|
|
191
|
+
mv "$HOME/.claude.json.tmp" "$HOME/.claude.json"
|
|
192
|
+
# BUG #5: unset both variables
|
|
193
|
+
unset PERPLEXITY_KEY PERPLEXITY_API_KEY
|
|
194
|
+
echo "✅ Perplexity API Key configured!"
|
|
195
|
+
else
|
|
196
|
+
# Skip: Completely remove the disabled block to keep config clean
|
|
197
|
+
echo "⚠️ Skipping Perplexity setup. Disabling feature..."
|
|
198
|
+
jq 'del(.mcpServers._perplexity_disabled_by_default)' \
|
|
199
|
+
"$HOME/.claude.json" > "$HOME/.claude.json.tmp"
|
|
200
|
+
mv "$HOME/.claude.json.tmp" "$HOME/.claude.json"
|
|
201
|
+
echo " (Feature removed from config. Add manually to functionality if needed)"
|
|
202
|
+
fi
|
|
203
|
+
fi
|
|
204
|
+
fi
|
|
205
|
+
|
|
206
|
+
# Language Selection (Interactive - Fresh Install Only)
|
|
207
|
+
if [ -t 0 ]; then
|
|
208
|
+
echo ""
|
|
209
|
+
echo "🌍 Output Language"
|
|
210
|
+
echo " 1) English (default)"
|
|
211
|
+
echo " 2) 한국어 (Korean)"
|
|
212
|
+
echo " 3) 日本語 (Japanese)"
|
|
213
|
+
echo " 4) 中文 (Chinese)"
|
|
214
|
+
echo -n " Select [1-4]: "
|
|
215
|
+
read -r LANG_CHOICE < /dev/tty || LANG_CHOICE="1"
|
|
216
|
+
|
|
217
|
+
case $LANG_CHOICE in
|
|
218
|
+
2)
|
|
219
|
+
cat > "$HOME/.claude/rules/language.md" <<'LANGEOF'
|
|
112
220
|
# Language Policy
|
|
113
221
|
Respond in Korean (한국어). Code, commands, technical terms in English.
|
|
114
222
|
LANGEOF
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
223
|
+
echo "✅ Output language: Korean"
|
|
224
|
+
;;
|
|
225
|
+
3)
|
|
226
|
+
cat > "$HOME/.claude/rules/language.md" <<'LANGEOF'
|
|
119
227
|
# Language Policy
|
|
120
228
|
Respond in Japanese (日本語). Code, commands, technical terms in English.
|
|
121
229
|
LANGEOF
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
230
|
+
echo "✅ Output language: Japanese"
|
|
231
|
+
;;
|
|
232
|
+
4)
|
|
233
|
+
cat > "$HOME/.claude/rules/language.md" <<'LANGEOF'
|
|
126
234
|
# Language Policy
|
|
127
235
|
Respond in Chinese (中文). Code, commands, technical terms in English.
|
|
128
236
|
LANGEOF
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
237
|
+
echo "✅ Output language: Chinese"
|
|
238
|
+
;;
|
|
239
|
+
*)
|
|
240
|
+
# English: no language.md needed (Claude defaults to English)
|
|
241
|
+
rm -f "$HOME/.claude/rules/language.md"
|
|
242
|
+
echo "✅ Output language: English"
|
|
243
|
+
;;
|
|
244
|
+
esac
|
|
245
|
+
fi
|
|
137
246
|
fi
|
|
138
247
|
|
|
248
|
+
# EDGE CASE #9: guard find against missing dir (scripts/ may not exist on minimal installs)
|
|
139
249
|
# Make scripts executable (Recursive)
|
|
140
|
-
|
|
141
|
-
find
|
|
250
|
+
if [ -d "$HOME/.claude/scripts" ]; then
|
|
251
|
+
find "$HOME/.claude/scripts" -name "*.sh" -exec chmod +x {} \;
|
|
252
|
+
find "$HOME/.claude/scripts" -name "*.js" -exec chmod +x {} \;
|
|
253
|
+
fi
|
|
254
|
+
|
|
255
|
+
# Write installation marker
|
|
256
|
+
# SEC #4: avoid node -p with interpolated $SCRIPT_DIR (injection risk); use node -e with argument
|
|
257
|
+
PROJECT_VERSION=$(node -e "try{process.stdout.write(require(process.argv[1]).version)}catch(e){process.exit(1)}" \
|
|
258
|
+
"$SCRIPT_DIR/package.json" 2>/dev/null || \
|
|
259
|
+
grep '"version"' "$SCRIPT_DIR/package.json" 2>/dev/null | head -1 | sed 's/.*"version": *"\([^"]*\)".*/\1/' || \
|
|
260
|
+
echo "unknown")
|
|
261
|
+
printf '%s\ninstalled:%s\n' "$PROJECT_VERSION" "$(date)" > "$CPMM_MARKER"
|
|
142
262
|
|
|
143
|
-
|
|
263
|
+
# Old backup cleanup hint
|
|
264
|
+
OLD_BACKUPS=$(find "$HOME" -maxdepth 1 -name ".claude-backup-*" -type d 2>/dev/null | wc -l | tr -d ' ')
|
|
265
|
+
if [ "$OLD_BACKUPS" -gt 0 ]; then
|
|
266
|
+
echo ""
|
|
267
|
+
echo "💡 Old backups detected: $OLD_BACKUPS directory(ies) named ~/.claude-backup-*"
|
|
268
|
+
echo " Review and remove: rm -rf ~/.claude-backup-*"
|
|
269
|
+
fi
|
|
270
|
+
|
|
271
|
+
# Final success message showing mode
|
|
272
|
+
echo ""
|
|
273
|
+
if [ "$IS_UPDATE" = true ]; then
|
|
274
|
+
echo "✅ Update complete!"
|
|
275
|
+
else
|
|
276
|
+
echo "✅ Installation complete!"
|
|
277
|
+
fi
|
|
144
278
|
echo ""
|
|
145
279
|
echo "Quick Start:"
|
|
146
280
|
echo " claude"
|
|
@@ -148,6 +282,10 @@ echo " > /plan Design a new feature"
|
|
|
148
282
|
echo " > /dplan Analyze complex architecture"
|
|
149
283
|
echo " > /do Implement the login page"
|
|
150
284
|
echo ""
|
|
285
|
+
echo "Dependency Check:"
|
|
286
|
+
echo " cpmm setup # install missing deps (jq, mgrep, tmux)"
|
|
287
|
+
echo " cpmm doctor # check status only"
|
|
288
|
+
echo ""
|
|
151
289
|
echo "Language:"
|
|
152
290
|
echo " To change language: edit ~/.claude/rules/language.md"
|
|
153
291
|
echo " To use English: rm ~/.claude/rules/language.md"
|
package/lib/cli.js
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { execSync, spawnSync } = require("node:child_process");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
const fs = require("node:fs");
|
|
6
|
+
const pkg = require("../package.json");
|
|
7
|
+
|
|
8
|
+
const DEPS = [
|
|
9
|
+
{
|
|
10
|
+
key: "claude",
|
|
11
|
+
command: "claude",
|
|
12
|
+
required: false,
|
|
13
|
+
installKind: "skip",
|
|
14
|
+
description: "Claude Code CLI (assumed pre-installed)",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
key: "jq",
|
|
18
|
+
command: "jq",
|
|
19
|
+
required: true,
|
|
20
|
+
installKind: "system",
|
|
21
|
+
systemPackage: "jq",
|
|
22
|
+
description: "JSON processor for CLI workflows",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
key: "mgrep",
|
|
26
|
+
command: "mgrep",
|
|
27
|
+
required: true,
|
|
28
|
+
installKind: "npm",
|
|
29
|
+
npmPackage: "@mixedbread/mgrep",
|
|
30
|
+
postInstall: ["mgrep", "install-claude-code"],
|
|
31
|
+
description: "Fast code search tool",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
key: "tmux",
|
|
35
|
+
command: "tmux",
|
|
36
|
+
required: true,
|
|
37
|
+
installKind: "system",
|
|
38
|
+
systemPackage: "tmux",
|
|
39
|
+
description: "Terminal multiplexer for background agents",
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
function commandExists(cmd) {
|
|
44
|
+
try {
|
|
45
|
+
execSync(`command -v ${cmd}`, { stdio: "ignore" });
|
|
46
|
+
return true;
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function detectSystemInstaller() {
|
|
53
|
+
if (commandExists("brew")) return { ok: true, installer: "brew", sudo: false };
|
|
54
|
+
if (commandExists("apt-get")) return { ok: true, installer: "apt-get", sudo: true };
|
|
55
|
+
if (commandExists("dnf")) return { ok: true, installer: "dnf", sudo: true };
|
|
56
|
+
if (commandExists("pacman")) return { ok: true, installer: "pacman", sudo: true };
|
|
57
|
+
if (commandExists("apk")) return { ok: true, installer: "apk", sudo: true };
|
|
58
|
+
return { ok: false };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function buildInstallCmd(installer, pkg, sudo) {
|
|
62
|
+
const prefix = sudo ? "sudo " : "";
|
|
63
|
+
switch (installer) {
|
|
64
|
+
case "brew": return `brew install ${pkg}`;
|
|
65
|
+
case "apt-get": return `${prefix}apt-get install -y ${pkg}`;
|
|
66
|
+
case "dnf": return `${prefix}dnf install -y ${pkg}`;
|
|
67
|
+
case "pacman": return `${prefix}pacman -S --noconfirm ${pkg}`;
|
|
68
|
+
case "apk": return `${prefix}apk add ${pkg}`;
|
|
69
|
+
default: return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function runCmd(cmd, label) {
|
|
74
|
+
console.log(` $ ${cmd}`);
|
|
75
|
+
const result = spawnSync("sh", ["-c", cmd], { stdio: "inherit" });
|
|
76
|
+
if (result.status !== 0) {
|
|
77
|
+
console.error(` FAIL: ${label} (exit ${result.status})`);
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function installDep(dep) {
|
|
84
|
+
if (dep.installKind === "skip") return true;
|
|
85
|
+
|
|
86
|
+
if (dep.installKind === "npm") {
|
|
87
|
+
if (!commandExists("npm")) {
|
|
88
|
+
console.error(` npm not found. Install Node.js first, then: npm i -g ${dep.npmPackage}`);
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
if (!runCmd(`npm install -g ${dep.npmPackage}`, dep.key)) return false;
|
|
92
|
+
if (dep.postInstall) {
|
|
93
|
+
return runCmd(dep.postInstall.join(" "), `${dep.key} post-install`);
|
|
94
|
+
}
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (dep.installKind === "system") {
|
|
99
|
+
const sys = detectSystemInstaller();
|
|
100
|
+
if (!sys.ok) {
|
|
101
|
+
const hint = process.platform === "darwin"
|
|
102
|
+
? `\n Install Homebrew first: /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"`
|
|
103
|
+
: "";
|
|
104
|
+
console.error(` No supported package manager found. Install ${dep.systemPackage} manually.${hint}`);
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
const cmd = buildInstallCmd(sys.installer, dep.systemPackage, sys.sudo);
|
|
108
|
+
return runCmd(cmd, dep.key);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function checkDeps() {
|
|
115
|
+
return DEPS.map((dep) => ({
|
|
116
|
+
...dep,
|
|
117
|
+
installed: commandExists(dep.command),
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function printStatus(results) {
|
|
122
|
+
console.log("");
|
|
123
|
+
for (const r of results) {
|
|
124
|
+
const icon = r.installed ? "OK" : (r.required ? "MISSING" : "SKIP");
|
|
125
|
+
const tag = r.required ? "[required]" : "[optional]";
|
|
126
|
+
console.log(` ${icon} ${r.key.padEnd(8)} ${tag} ${r.description}`);
|
|
127
|
+
}
|
|
128
|
+
console.log("");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function runSetup() {
|
|
132
|
+
console.log(`CPMM v${pkg.version} - Setup`);
|
|
133
|
+
|
|
134
|
+
const results = checkDeps();
|
|
135
|
+
const missing = results.filter((r) => r.required && !r.installed);
|
|
136
|
+
|
|
137
|
+
if (missing.length > 0) {
|
|
138
|
+
console.log(`\nInstalling ${missing.length} missing dependency(ies)...\n`);
|
|
139
|
+
|
|
140
|
+
for (const dep of missing) {
|
|
141
|
+
console.log(`[${dep.key}] ${dep.description}`);
|
|
142
|
+
if (!installDep(dep)) {
|
|
143
|
+
// continue to install others
|
|
144
|
+
} else {
|
|
145
|
+
console.log(` Done.\n`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const after = checkDeps();
|
|
150
|
+
printStatus(after);
|
|
151
|
+
|
|
152
|
+
const stillMissing = after.filter((r) => r.required && !r.installed);
|
|
153
|
+
if (stillMissing.length > 0) {
|
|
154
|
+
console.error(`${stillMissing.length} required dep(s) still missing.`);
|
|
155
|
+
return 1;
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
console.log("\nAll dependencies installed.");
|
|
159
|
+
printStatus(results);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Run install.sh for config files, language, and Perplexity setup
|
|
163
|
+
if (!runInstallScript()) {
|
|
164
|
+
return 1;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
console.log("Setup complete.");
|
|
168
|
+
return 0;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function runInstallScript() {
|
|
172
|
+
const scriptPath = path.resolve(__dirname, "..", "install.sh");
|
|
173
|
+
if (!fs.existsSync(scriptPath)) {
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
console.log("Configuring CPMM...\n");
|
|
177
|
+
const result = spawnSync("bash", [scriptPath], {
|
|
178
|
+
stdio: "inherit",
|
|
179
|
+
env: { ...process.env },
|
|
180
|
+
});
|
|
181
|
+
if (result.status !== 0) {
|
|
182
|
+
console.error("Config setup had issues. Run 'bash install.sh' manually if needed.");
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function runDoctor() {
|
|
189
|
+
console.log(`CPMM v${pkg.version} - Doctor`);
|
|
190
|
+
|
|
191
|
+
const results = checkDeps();
|
|
192
|
+
printStatus(results);
|
|
193
|
+
|
|
194
|
+
const missing = results.filter((r) => r.required && !r.installed);
|
|
195
|
+
if (missing.length > 0) {
|
|
196
|
+
console.log(`Fix: cpmm setup`);
|
|
197
|
+
return 1;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
console.log("All checks passed.");
|
|
201
|
+
return 0;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function printHelp() {
|
|
205
|
+
console.log(`CPMM v${pkg.version}
|
|
206
|
+
|
|
207
|
+
Usage:
|
|
208
|
+
cpmm setup Install deps + configure CPMM (language, Perplexity)
|
|
209
|
+
cpmm doctor Check dependency status
|
|
210
|
+
cpmm --help Show this help
|
|
211
|
+
cpmm --version Show version
|
|
212
|
+
`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function runCli(argv) {
|
|
216
|
+
const cmd = argv[0] || "setup";
|
|
217
|
+
|
|
218
|
+
if (cmd === "--help" || cmd === "-h" || cmd === "help") {
|
|
219
|
+
printHelp();
|
|
220
|
+
return 0;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (cmd === "--version" || cmd === "-v" || cmd === "version") {
|
|
224
|
+
console.log(pkg.version);
|
|
225
|
+
return 0;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (process.platform === "win32") {
|
|
229
|
+
console.error("CPMM requires macOS/Linux. Windows users: use WSL.");
|
|
230
|
+
return 1;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
switch (cmd) {
|
|
234
|
+
case "setup":
|
|
235
|
+
return runSetup();
|
|
236
|
+
case "doctor":
|
|
237
|
+
return runDoctor();
|
|
238
|
+
default:
|
|
239
|
+
console.error(`Unknown command: ${cmd}\nRun 'cpmm --help' for usage.`);
|
|
240
|
+
return 2;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
module.exports = { runCli };
|