claude-code-achievements 1.2.2 → 1.2.3
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/.claude-plugin/plugin.json +1 -1
- package/README.es.md +177 -227
- package/README.ja.md +177 -227
- package/README.ko.md +177 -227
- package/README.md +177 -227
- package/README.zh.md +177 -227
- package/bin/install.js +79 -61
- package/hooks/track-achievement.sh +2 -1
- package/package.json +1 -1
package/bin/install.js
CHANGED
|
@@ -15,7 +15,7 @@ const ITEMS_TO_COPY = ['.claude-plugin', 'commands', 'hooks', 'scripts', 'data',
|
|
|
15
15
|
const GREEN = '\x1b[32m';
|
|
16
16
|
const CYAN = '\x1b[36m';
|
|
17
17
|
const YELLOW = '\x1b[33m';
|
|
18
|
-
const
|
|
18
|
+
const MAGENTA = '\x1b[35m';
|
|
19
19
|
const DIM = '\x1b[2m';
|
|
20
20
|
const BOLD = '\x1b[1m';
|
|
21
21
|
const RESET = '\x1b[0m';
|
|
@@ -54,14 +54,11 @@ function detectLanguage() {
|
|
|
54
54
|
function checkSystemNotification(osName) {
|
|
55
55
|
try {
|
|
56
56
|
if (osName === 'macOS') {
|
|
57
|
-
// macOS always has osascript
|
|
58
57
|
return { available: true, method: 'osascript' };
|
|
59
58
|
} else if (osName === 'Linux') {
|
|
60
|
-
// Check for notify-send
|
|
61
59
|
execSync('which notify-send', { stdio: 'ignore' });
|
|
62
60
|
return { available: true, method: 'notify-send' };
|
|
63
61
|
} else if (osName === 'Windows') {
|
|
64
|
-
// Check for PowerShell
|
|
65
62
|
execSync('where powershell.exe', { stdio: 'ignore' });
|
|
66
63
|
return { available: true, method: 'PowerShell' };
|
|
67
64
|
}
|
|
@@ -71,44 +68,54 @@ function checkSystemNotification(osName) {
|
|
|
71
68
|
return { available: false, method: null };
|
|
72
69
|
}
|
|
73
70
|
|
|
74
|
-
|
|
75
|
-
async function prompt(question, options) {
|
|
71
|
+
async function selectOption(title, options, defaultIndex = 0) {
|
|
76
72
|
const rl = readline.createInterface({
|
|
77
73
|
input: process.stdin,
|
|
78
74
|
output: process.stdout
|
|
79
75
|
});
|
|
80
76
|
|
|
77
|
+
console.log(`\n${BOLD}${title}${RESET}\n`);
|
|
78
|
+
|
|
79
|
+
options.forEach((opt, i) => {
|
|
80
|
+
const marker = i === defaultIndex ? `${CYAN}▶${RESET}` : ' ';
|
|
81
|
+
const highlight = i === defaultIndex ? CYAN : DIM;
|
|
82
|
+
console.log(` ${marker} ${highlight}${i + 1}. ${opt.label}${RESET}`);
|
|
83
|
+
});
|
|
84
|
+
|
|
81
85
|
return new Promise((resolve) => {
|
|
82
|
-
|
|
83
|
-
rl.question(`${question} [${optStr}]: `, (answer) => {
|
|
86
|
+
rl.question(`\n${DIM}Enter number [1-${options.length}]:${RESET} `, (answer) => {
|
|
84
87
|
rl.close();
|
|
85
|
-
const num = parseInt(answer) || 1;
|
|
88
|
+
const num = parseInt(answer) || (defaultIndex + 1);
|
|
86
89
|
const idx = Math.max(0, Math.min(options.length - 1, num - 1));
|
|
87
|
-
resolve(options[idx].value);
|
|
90
|
+
resolve({ value: options[idx].value, label: options[idx].label });
|
|
88
91
|
});
|
|
89
92
|
});
|
|
90
93
|
}
|
|
91
94
|
|
|
92
95
|
async function install() {
|
|
93
96
|
console.log('');
|
|
94
|
-
console.log(`${CYAN}
|
|
95
|
-
console.log(`${CYAN}
|
|
96
|
-
console.log(`${CYAN}${BOLD}
|
|
97
|
+
console.log(`${CYAN}╔════════════════════════════════════════════════════════════════╗${RESET}`);
|
|
98
|
+
console.log(`${CYAN}║ ║${RESET}`);
|
|
99
|
+
console.log(`${CYAN}║${RESET} ${BOLD}🎮 Claude Code Achievements${RESET} ${CYAN}║${RESET}`);
|
|
100
|
+
console.log(`${CYAN}║${RESET} ${DIM}Level up your AI coding skills${RESET} ${CYAN}║${RESET}`);
|
|
101
|
+
console.log(`${CYAN}║ ║${RESET}`);
|
|
102
|
+
console.log(`${CYAN}╚════════════════════════════════════════════════════════════════╝${RESET}`);
|
|
97
103
|
console.log('');
|
|
98
104
|
|
|
99
105
|
const detectedOS = detectOS();
|
|
100
106
|
const detectedLang = detectLanguage();
|
|
101
107
|
const notifyCheck = checkSystemNotification(detectedOS);
|
|
102
108
|
|
|
103
|
-
|
|
109
|
+
// System info box
|
|
110
|
+
console.log(`${DIM}┌─ System Info ─────────────────────────────────────────────────┐${RESET}`);
|
|
111
|
+
console.log(`${DIM}│${RESET} Platform: ${BOLD}${detectedOS}${RESET}`);
|
|
104
112
|
if (notifyCheck.available) {
|
|
105
|
-
console.log(`${DIM}
|
|
113
|
+
console.log(`${DIM}│${RESET} Notifications: ${GREEN}✓ Available${RESET} ${DIM}(${notifyCheck.method})${RESET}`);
|
|
106
114
|
} else {
|
|
107
|
-
console.log(`${DIM}
|
|
115
|
+
console.log(`${DIM}│${RESET} Notifications: ${YELLOW}○ Terminal only${RESET}`);
|
|
108
116
|
}
|
|
109
|
-
console.log(
|
|
117
|
+
console.log(`${DIM}└───────────────────────────────────────────────────────────────┘${RESET}`);
|
|
110
118
|
|
|
111
|
-
// Check if interactive
|
|
112
119
|
const isInteractive = process.stdin.isTTY;
|
|
113
120
|
|
|
114
121
|
let language = detectedLang;
|
|
@@ -116,43 +123,49 @@ async function install() {
|
|
|
116
123
|
|
|
117
124
|
if (isInteractive) {
|
|
118
125
|
// Language selection
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
126
|
+
const langResult = await selectOption(
|
|
127
|
+
'🌍 Choose your language',
|
|
128
|
+
[
|
|
129
|
+
{ label: '🇺🇸 English', value: 'en' },
|
|
130
|
+
{ label: '🇨🇳 中文', value: 'zh' },
|
|
131
|
+
{ label: '🇪🇸 Español', value: 'es' },
|
|
132
|
+
{ label: '🇰🇷 한국어', value: 'ko' },
|
|
133
|
+
{ label: '🇯🇵 日本語', value: 'ja' }
|
|
134
|
+
],
|
|
135
|
+
['en', 'zh', 'es', 'ko', 'ja'].indexOf(detectedLang)
|
|
136
|
+
);
|
|
137
|
+
language = langResult.value;
|
|
138
|
+
console.log(`${GREEN} ✓ Selected: ${langResult.label}${RESET}`);
|
|
139
|
+
|
|
140
|
+
// Notification style
|
|
132
141
|
if (notifyCheck.available) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
142
|
+
const notifyResult = await selectOption(
|
|
143
|
+
'🔔 Achievement notifications',
|
|
144
|
+
[
|
|
145
|
+
{ label: `System popup (${notifyCheck.method})`, value: 'system' },
|
|
146
|
+
{ label: 'Terminal message', value: 'terminal' },
|
|
147
|
+
{ label: 'Both', value: 'both' }
|
|
148
|
+
],
|
|
149
|
+
0
|
|
150
|
+
);
|
|
151
|
+
notificationStyle = notifyResult.value;
|
|
152
|
+
console.log(`${GREEN} ✓ Selected: ${notifyResult.label}${RESET}`);
|
|
140
153
|
} else {
|
|
141
|
-
console.log(
|
|
142
|
-
notificationStyle = 'terminal';
|
|
154
|
+
console.log(`\n${DIM}🔔 Notifications: Terminal mode (system not available)${RESET}`);
|
|
143
155
|
}
|
|
144
|
-
console.log('');
|
|
145
156
|
}
|
|
146
157
|
|
|
158
|
+
console.log('');
|
|
159
|
+
console.log(`${DIM}───────────────────────────────────────────────────────────────${RESET}`);
|
|
160
|
+
console.log(`${YELLOW} ⏳ Installing plugin...${RESET}`);
|
|
161
|
+
|
|
147
162
|
// Copy files
|
|
148
163
|
const packageRoot = path.resolve(__dirname, '..');
|
|
149
164
|
|
|
150
165
|
if (fs.existsSync(TARGET_DIR)) {
|
|
151
|
-
console.log(`${DIM}Removing existing installation...${RESET}`);
|
|
152
166
|
fs.rmSync(TARGET_DIR, { recursive: true, force: true });
|
|
153
167
|
}
|
|
154
168
|
|
|
155
|
-
console.log(`${DIM}Installing to ${TARGET_DIR}${RESET}`);
|
|
156
169
|
fs.mkdirSync(TARGET_DIR, { recursive: true });
|
|
157
170
|
|
|
158
171
|
for (const item of ITEMS_TO_COPY) {
|
|
@@ -171,7 +184,6 @@ async function install() {
|
|
|
171
184
|
fs.mkdirSync(stateDir, { recursive: true });
|
|
172
185
|
}
|
|
173
186
|
|
|
174
|
-
// Always update settings, preserve achievements if exists
|
|
175
187
|
let existingState = null;
|
|
176
188
|
if (fs.existsSync(stateFile)) {
|
|
177
189
|
try {
|
|
@@ -192,8 +204,7 @@ async function install() {
|
|
|
192
204
|
|
|
193
205
|
fs.writeFileSync(stateFile, JSON.stringify(newState, null, 2));
|
|
194
206
|
|
|
195
|
-
// Symlink commands
|
|
196
|
-
// Plugin also provides namespaced commands (/claude-code-achievements:achievements)
|
|
207
|
+
// Symlink commands
|
|
197
208
|
const commandsDir = path.join(os.homedir(), '.claude', 'commands');
|
|
198
209
|
if (!fs.existsSync(commandsDir)) {
|
|
199
210
|
fs.mkdirSync(commandsDir, { recursive: true });
|
|
@@ -204,17 +215,14 @@ async function install() {
|
|
|
204
215
|
const linkPath = path.join(commandsDir, file);
|
|
205
216
|
const targetPath = path.join(pluginCommands, file);
|
|
206
217
|
|
|
207
|
-
// Remove existing file/symlink if exists
|
|
208
218
|
if (fs.existsSync(linkPath)) {
|
|
209
219
|
fs.unlinkSync(linkPath);
|
|
210
220
|
}
|
|
211
|
-
|
|
212
|
-
// Create symlink
|
|
213
221
|
fs.symlinkSync(targetPath, linkPath);
|
|
214
222
|
}
|
|
215
223
|
}
|
|
216
224
|
|
|
217
|
-
// Register hooks
|
|
225
|
+
// Register hooks
|
|
218
226
|
const settingsFile = path.join(os.homedir(), '.claude', 'settings.json');
|
|
219
227
|
let settings = {};
|
|
220
228
|
if (fs.existsSync(settingsFile)) {
|
|
@@ -223,11 +231,9 @@ async function install() {
|
|
|
223
231
|
} catch (e) {}
|
|
224
232
|
}
|
|
225
233
|
|
|
226
|
-
// Add plugin to enabledPlugins
|
|
227
234
|
if (!settings.enabledPlugins) settings.enabledPlugins = {};
|
|
228
235
|
settings.enabledPlugins['claude-code-achievements@local'] = true;
|
|
229
236
|
|
|
230
|
-
// Add hooks (replace any existing achievement hooks)
|
|
231
237
|
if (!settings.hooks) settings.hooks = {};
|
|
232
238
|
|
|
233
239
|
const trackScript = path.join(TARGET_DIR, 'hooks', 'track-achievement.sh');
|
|
@@ -249,19 +255,31 @@ async function install() {
|
|
|
249
255
|
|
|
250
256
|
fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2));
|
|
251
257
|
|
|
258
|
+
// Count achievements if any exist
|
|
259
|
+
const achievementCount = Object.keys(existingState?.achievements || {}).length;
|
|
260
|
+
|
|
261
|
+
console.log(`${DIM}───────────────────────────────────────────────────────────────${RESET}`);
|
|
252
262
|
console.log('');
|
|
253
|
-
console.log(`${GREEN}${BOLD}✅ Installation complete!${RESET}`);
|
|
254
|
-
console.log('');
|
|
255
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
256
|
-
console.log('');
|
|
257
|
-
console.log(`${BOLD}Ready to use:${RESET}`);
|
|
263
|
+
console.log(`${GREEN}${BOLD} ✅ Installation complete!${RESET}`);
|
|
258
264
|
console.log('');
|
|
259
|
-
|
|
260
|
-
|
|
265
|
+
|
|
266
|
+
if (achievementCount > 0) {
|
|
267
|
+
console.log(`${MAGENTA} 🏆 Welcome back! You have ${achievementCount} achievement${achievementCount > 1 ? 's' : ''} unlocked.${RESET}`);
|
|
268
|
+
console.log('');
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
console.log(`${CYAN}╭──────────────────────────────────────────────────────────────╮${RESET}`);
|
|
272
|
+
console.log(`${CYAN}│${RESET} ${BOLD}Quick Start${RESET} ${CYAN}│${RESET}`);
|
|
273
|
+
console.log(`${CYAN}│${RESET} ${CYAN}│${RESET}`);
|
|
274
|
+
console.log(`${CYAN}│${RESET} ${YELLOW}/achievements${RESET} View your achievements ${CYAN}│${RESET}`);
|
|
275
|
+
console.log(`${CYAN}│${RESET} ${YELLOW}/achievements locked${RESET} See what's left to unlock ${CYAN}│${RESET}`);
|
|
276
|
+
console.log(`${CYAN}│${RESET} ${YELLOW}/achievements-settings${RESET} Change language & notifications ${CYAN}│${RESET}`);
|
|
277
|
+
console.log(`${CYAN}│${RESET} ${CYAN}│${RESET}`);
|
|
278
|
+
console.log(`${CYAN}╰──────────────────────────────────────────────────────────────╯${RESET}`);
|
|
261
279
|
console.log('');
|
|
262
|
-
console.log(
|
|
280
|
+
console.log(`${DIM} 26 achievements await. Start coding to unlock them!${RESET}`);
|
|
263
281
|
console.log('');
|
|
264
|
-
console.log(
|
|
282
|
+
console.log(` ${BOLD}🎮 Happy coding!${RESET}`);
|
|
265
283
|
console.log('');
|
|
266
284
|
}
|
|
267
285
|
|
|
@@ -169,7 +169,8 @@ check_achievements() {
|
|
|
169
169
|
# visual_inspector (image files) - case insensitive
|
|
170
170
|
FILE_PATH=$(echo "$TOOL_INPUT" | jq -r '.file_path // empty')
|
|
171
171
|
FILE_NAME=$(basename "${FILE_PATH}")
|
|
172
|
-
|
|
172
|
+
# Use tr for POSIX compatibility (bash 4.0+ ${,,} not available everywhere)
|
|
173
|
+
FILE_PATH_LOWER=$(echo "${FILE_PATH}" | tr '[:upper:]' '[:lower:]')
|
|
173
174
|
if [[ "${FILE_PATH_LOWER}" =~ \.(png|jpg|jpeg|gif|webp|svg|bmp|ico)$ ]]; then
|
|
174
175
|
if ! is_unlocked "visual_inspector"; then
|
|
175
176
|
unlock_achievement "visual_inspector" "Analyzed image: ${FILE_NAME}"
|