vibe-academy-cli 1.0.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.md +251 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +852 -0
- package/dist/index.js.map +1 -0
- package/package.json +60 -0
- package/templates/.claude/scripts/command-validator/README.md +147 -0
- package/templates/.claude/scripts/command-validator/biome.json +29 -0
- package/templates/.claude/scripts/command-validator/bun.lockb +0 -0
- package/templates/.claude/scripts/command-validator/package.json +27 -0
- package/templates/.claude/scripts/command-validator/src/__tests__/validator.test.ts +148 -0
- package/templates/.claude/scripts/command-validator/src/cli.ts +118 -0
- package/templates/.claude/scripts/command-validator/src/lib/security-rules.ts +172 -0
- package/templates/.claude/scripts/command-validator/src/lib/types.ts +33 -0
- package/templates/.claude/scripts/command-validator/src/lib/validator.ts +360 -0
- package/templates/.claude/scripts/command-validator/vitest.config.ts +7 -0
- package/templates/.claude/scripts/hook-post-file.ts +162 -0
- package/templates/.claude/scripts/statusline/CLAUDE.md +178 -0
- package/templates/.claude/scripts/statusline/README.md +138 -0
- package/templates/.claude/scripts/statusline/biome.json +34 -0
- package/templates/.claude/scripts/statusline/bun.lockb +0 -0
- package/templates/.claude/scripts/statusline/fixtures/test-input.json +25 -0
- package/templates/.claude/scripts/statusline/package.json +19 -0
- package/templates/.claude/scripts/statusline/src/index.ts +111 -0
- package/templates/.claude/scripts/statusline/src/lib/context.ts +82 -0
- package/templates/.claude/scripts/statusline/src/lib/formatters.ts +48 -0
- package/templates/.claude/scripts/statusline/src/lib/git.ts +54 -0
- package/templates/.claude/scripts/statusline/src/lib/types.ts +25 -0
- package/templates/.claude/scripts/statusline/src/lib/usage-limits.ts +105 -0
- package/templates/.claude/scripts/statusline/statusline.config.ts +25 -0
- package/templates/.claude/scripts/statusline/test.ts +20 -0
- package/templates/.claude/scripts/statusline/tsconfig.json +27 -0
- package/templates/.claude/scripts/statusline-ccusage.sh +188 -0
- package/templates/.claude/scripts/statusline.readme.md +194 -0
- package/templates/.claude/scripts/validate-command.js +707 -0
- package/templates/.claude/scripts/validate-command.readme.md +283 -0
- package/templates/.claude/settings.json.template +60 -0
- package/templates/.claude/song/finish.mp3 +0 -0
- package/templates/.claude/song/need-human.mp3 +0 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,852 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// node_modules/tsup/assets/esm_shims.js
|
|
13
|
+
import path from "path";
|
|
14
|
+
import { fileURLToPath } from "url";
|
|
15
|
+
var init_esm_shims = __esm({
|
|
16
|
+
"node_modules/tsup/assets/esm_shims.js"() {
|
|
17
|
+
"use strict";
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// src/utils/logger.ts
|
|
22
|
+
import chalk from "chalk";
|
|
23
|
+
import ora from "ora";
|
|
24
|
+
var logger;
|
|
25
|
+
var init_logger = __esm({
|
|
26
|
+
"src/utils/logger.ts"() {
|
|
27
|
+
"use strict";
|
|
28
|
+
init_esm_shims();
|
|
29
|
+
logger = {
|
|
30
|
+
welcome() {
|
|
31
|
+
console.log(chalk.cyan.bold("\n\u{1F393} Vibe Academy CLI\n"));
|
|
32
|
+
console.log("Installation des presets Claude Code pour Vibe Academy...\n");
|
|
33
|
+
},
|
|
34
|
+
success(path2) {
|
|
35
|
+
console.log(chalk.green("\n\u2713 Installation r\xE9ussie!\n"));
|
|
36
|
+
console.log(`Configuration install\xE9e dans: ${chalk.cyan(path2)}
|
|
37
|
+
`);
|
|
38
|
+
console.log("Prochaines \xE9tapes:");
|
|
39
|
+
console.log(
|
|
40
|
+
" 1. Ajouter le marketplace: /plugin marketplace add matthieucousin/vibe-academy-marketplace"
|
|
41
|
+
);
|
|
42
|
+
console.log(
|
|
43
|
+
" 2. Installer les plugins: /plugin install vibe-academy@vibe-academy-marketplace"
|
|
44
|
+
);
|
|
45
|
+
console.log(" 3. Commencer \xE0 coder! \u{1F389}\n");
|
|
46
|
+
},
|
|
47
|
+
error(message) {
|
|
48
|
+
console.error(chalk.red(`
|
|
49
|
+
\u2717 Erreur: ${message}
|
|
50
|
+
`));
|
|
51
|
+
},
|
|
52
|
+
warn(message) {
|
|
53
|
+
console.warn(chalk.yellow(`
|
|
54
|
+
\u26A0\uFE0F ${message}
|
|
55
|
+
`));
|
|
56
|
+
},
|
|
57
|
+
info(message) {
|
|
58
|
+
console.log(chalk.blue(`\u2139\uFE0F ${message}`));
|
|
59
|
+
},
|
|
60
|
+
spinner(text) {
|
|
61
|
+
return ora(text).start();
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// src/utils/files.ts
|
|
68
|
+
var files_exports = {};
|
|
69
|
+
__export(files_exports, {
|
|
70
|
+
installConfig: () => installConfig
|
|
71
|
+
});
|
|
72
|
+
import { mkdir as mkdir2, cp, writeFile as writeFile2, chmod, readFile as readFile2 } from "fs/promises";
|
|
73
|
+
import { join as join3, dirname } from "path";
|
|
74
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
75
|
+
import { existsSync as existsSync2 } from "fs";
|
|
76
|
+
import { execSync as execSync3 } from "child_process";
|
|
77
|
+
function getTemplatesDir() {
|
|
78
|
+
const templatesPath = join3(__dirname2, "../templates");
|
|
79
|
+
if (existsSync2(templatesPath)) {
|
|
80
|
+
return templatesPath;
|
|
81
|
+
}
|
|
82
|
+
throw new Error(`Templates directory not found at: ${templatesPath}`);
|
|
83
|
+
}
|
|
84
|
+
function detectRuntime() {
|
|
85
|
+
try {
|
|
86
|
+
execSync3("which bun", { stdio: "ignore" });
|
|
87
|
+
return "bun";
|
|
88
|
+
} catch {
|
|
89
|
+
try {
|
|
90
|
+
execSync3("which pnpm", { stdio: "ignore" });
|
|
91
|
+
return "pnpm";
|
|
92
|
+
} catch {
|
|
93
|
+
return "npm";
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async function installStatuslineDependencies(claudeDir, runtime) {
|
|
98
|
+
const statuslineDir = join3(claudeDir, "scripts/statusline");
|
|
99
|
+
const spinner = logger.spinner(
|
|
100
|
+
`Installation des d\xE9pendances statusline avec ${runtime}...`
|
|
101
|
+
);
|
|
102
|
+
try {
|
|
103
|
+
execSync3(`${runtime} install`, {
|
|
104
|
+
cwd: statuslineDir,
|
|
105
|
+
stdio: "ignore"
|
|
106
|
+
});
|
|
107
|
+
spinner.succeed(`D\xE9pendances statusline install\xE9es avec ${runtime}`);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
spinner.fail(`\xC9chec de l'installation des d\xE9pendances statusline`);
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async function generateSettings(claudeDir, runtime, config) {
|
|
114
|
+
const templatePath = join3(
|
|
115
|
+
getTemplatesDir(),
|
|
116
|
+
".claude/settings.json.template"
|
|
117
|
+
);
|
|
118
|
+
const template = await readFile2(templatePath, "utf-8");
|
|
119
|
+
const settingsObj = JSON.parse(template);
|
|
120
|
+
const settingsStr = JSON.stringify(settingsObj, null, 2).replace(/\/Users\/matthieucousin\/\.claude/g, claudeDir).replace(/\bbun\b/g, runtime);
|
|
121
|
+
const settings = JSON.parse(settingsStr);
|
|
122
|
+
if (!config.hooks) {
|
|
123
|
+
delete settings.hooks;
|
|
124
|
+
}
|
|
125
|
+
if (!config.statusline) {
|
|
126
|
+
delete settings.statusLine;
|
|
127
|
+
}
|
|
128
|
+
await writeFile2(
|
|
129
|
+
join3(claudeDir, "settings.json"),
|
|
130
|
+
JSON.stringify(settings, null, 2)
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
async function installConfig(targetPath, config) {
|
|
134
|
+
const templatesDir = getTemplatesDir();
|
|
135
|
+
const sourceDir = join3(templatesDir, ".claude");
|
|
136
|
+
const runtime = detectRuntime();
|
|
137
|
+
logger.info(`Runtime d\xE9tect\xE9: ${runtime}`);
|
|
138
|
+
await mkdir2(targetPath, { recursive: true });
|
|
139
|
+
if (config.hooks || config.statusline) {
|
|
140
|
+
const scriptsSpinner = logger.spinner("Copie des scripts...");
|
|
141
|
+
await cp(join3(sourceDir, "scripts"), join3(targetPath, "scripts"), {
|
|
142
|
+
recursive: true
|
|
143
|
+
});
|
|
144
|
+
scriptsSpinner.succeed("Scripts copi\xE9s");
|
|
145
|
+
if (config.hooks) {
|
|
146
|
+
await chmod(join3(targetPath, "scripts/validate-command.js"), 493);
|
|
147
|
+
await chmod(join3(targetPath, "scripts/hook-post-file.ts"), 493);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const songSpinner = logger.spinner("Copie des fichiers audio...");
|
|
151
|
+
await cp(join3(sourceDir, "song"), join3(targetPath, "song"), {
|
|
152
|
+
recursive: true
|
|
153
|
+
});
|
|
154
|
+
songSpinner.succeed("Fichiers audio copi\xE9s");
|
|
155
|
+
const settingsSpinner = logger.spinner("G\xE9n\xE9ration de settings.json...");
|
|
156
|
+
await generateSettings(targetPath, runtime, config);
|
|
157
|
+
settingsSpinner.succeed("settings.json g\xE9n\xE9r\xE9");
|
|
158
|
+
if (config.statusline) {
|
|
159
|
+
await installStatuslineDependencies(targetPath, runtime);
|
|
160
|
+
}
|
|
161
|
+
if (config.docs) {
|
|
162
|
+
const readmeSpinner = logger.spinner("Cr\xE9ation du README...");
|
|
163
|
+
const readme = generateReadme(targetPath);
|
|
164
|
+
await writeFile2(join3(targetPath, "README.md"), readme);
|
|
165
|
+
readmeSpinner.succeed("README cr\xE9\xE9");
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function generateReadme(installPath) {
|
|
169
|
+
return `# Vibe Academy Configuration
|
|
170
|
+
|
|
171
|
+
Configuration Claude Code install\xE9e avec succ\xE8s !
|
|
172
|
+
|
|
173
|
+
## Emplacement
|
|
174
|
+
|
|
175
|
+
\`${installPath}\`
|
|
176
|
+
|
|
177
|
+
## Contenu
|
|
178
|
+
|
|
179
|
+
- **settings.json** : Configuration Claude Code avec hooks et statusline
|
|
180
|
+
- **scripts/** : Scripts de validation et statusline
|
|
181
|
+
- **song/** : Notifications audio
|
|
182
|
+
- **README.md** : Ce fichier
|
|
183
|
+
|
|
184
|
+
## Hooks install\xE9s
|
|
185
|
+
|
|
186
|
+
### PreToolUse
|
|
187
|
+
- Validation des commandes Bash dangereuses
|
|
188
|
+
|
|
189
|
+
### Stop
|
|
190
|
+
- Notification audio de fin de session
|
|
191
|
+
|
|
192
|
+
### Notification
|
|
193
|
+
- Notification audio pour demandes utilisateur
|
|
194
|
+
|
|
195
|
+
### PostToolUse
|
|
196
|
+
- Hook post-\xE9dition de fichiers
|
|
197
|
+
|
|
198
|
+
## Statusline
|
|
199
|
+
|
|
200
|
+
La statusline affiche :
|
|
201
|
+
- Branche Git actuelle
|
|
202
|
+
- Style de sortie et mod\xE8le
|
|
203
|
+
- Tokens utilis\xE9s (contexte)
|
|
204
|
+
- Utilisation Anthropic (%) et temps restant (via API directe)
|
|
205
|
+
|
|
206
|
+
## Prochaines \xE9tapes
|
|
207
|
+
|
|
208
|
+
1. Ajouter le marketplace :
|
|
209
|
+
\`\`\`bash
|
|
210
|
+
/plugin marketplace add ton-username/vibe-coding-marketplace
|
|
211
|
+
\`\`\`
|
|
212
|
+
|
|
213
|
+
2. Installer les plugins :
|
|
214
|
+
\`\`\`bash
|
|
215
|
+
/plugin install vibe-coding@vibe-coding-marketplace
|
|
216
|
+
\`\`\`
|
|
217
|
+
|
|
218
|
+
3. Commencer \xE0 coder ! \u{1F680}
|
|
219
|
+
|
|
220
|
+
## Mise \xE0 jour
|
|
221
|
+
|
|
222
|
+
Pour mettre \xE0 jour cette configuration :
|
|
223
|
+
\`\`\`bash
|
|
224
|
+
vibe-academy update
|
|
225
|
+
\`\`\`
|
|
226
|
+
|
|
227
|
+
## D\xE9sinstallation
|
|
228
|
+
|
|
229
|
+
Pour d\xE9sinstaller :
|
|
230
|
+
\`\`\`bash
|
|
231
|
+
vibe-academy uninstall
|
|
232
|
+
\`\`\`
|
|
233
|
+
`;
|
|
234
|
+
}
|
|
235
|
+
var __filename2, __dirname2;
|
|
236
|
+
var init_files = __esm({
|
|
237
|
+
"src/utils/files.ts"() {
|
|
238
|
+
"use strict";
|
|
239
|
+
init_esm_shims();
|
|
240
|
+
init_logger();
|
|
241
|
+
__filename2 = fileURLToPath2(import.meta.url);
|
|
242
|
+
__dirname2 = dirname(__filename2);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// src/index.ts
|
|
247
|
+
init_esm_shims();
|
|
248
|
+
import { Command } from "commander";
|
|
249
|
+
|
|
250
|
+
// src/commands/setup.ts
|
|
251
|
+
init_esm_shims();
|
|
252
|
+
|
|
253
|
+
// src/utils/paths.ts
|
|
254
|
+
init_esm_shims();
|
|
255
|
+
import { execSync } from "child_process";
|
|
256
|
+
import { homedir } from "os";
|
|
257
|
+
import { join } from "path";
|
|
258
|
+
async function detectInstallPath(customFolder) {
|
|
259
|
+
if (customFolder) {
|
|
260
|
+
return customFolder;
|
|
261
|
+
}
|
|
262
|
+
try {
|
|
263
|
+
const gitRoot = execSync("git rev-parse --show-toplevel", {
|
|
264
|
+
encoding: "utf-8",
|
|
265
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
266
|
+
}).trim();
|
|
267
|
+
return join(gitRoot, ".claude");
|
|
268
|
+
} catch {
|
|
269
|
+
return join(homedir(), ".claude");
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// src/commands/setup.ts
|
|
274
|
+
init_logger();
|
|
275
|
+
|
|
276
|
+
// src/utils/auth.ts
|
|
277
|
+
init_esm_shims();
|
|
278
|
+
init_logger();
|
|
279
|
+
import { execSync as execSync2 } from "child_process";
|
|
280
|
+
var KEYCHAIN_SERVICE = "Claude Code-credentials";
|
|
281
|
+
var REQUIRED_SCOPES = ["user:inference", "user:profile"];
|
|
282
|
+
function isMacOS() {
|
|
283
|
+
return process.platform === "darwin";
|
|
284
|
+
}
|
|
285
|
+
function getCredentials() {
|
|
286
|
+
if (!isMacOS()) {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
try {
|
|
290
|
+
const result = execSync2(
|
|
291
|
+
`security find-generic-password -s "${KEYCHAIN_SERVICE}" -w`,
|
|
292
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
293
|
+
);
|
|
294
|
+
return JSON.parse(result.trim());
|
|
295
|
+
} catch {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function getCurrentScopes() {
|
|
300
|
+
const creds = getCredentials();
|
|
301
|
+
return creds?.claudeAiOauth?.scopes ?? [];
|
|
302
|
+
}
|
|
303
|
+
function hasRequiredScopes() {
|
|
304
|
+
const scopes = getCurrentScopes();
|
|
305
|
+
return REQUIRED_SCOPES.every((required) => scopes.includes(required));
|
|
306
|
+
}
|
|
307
|
+
function getMissingScopes() {
|
|
308
|
+
const scopes = getCurrentScopes();
|
|
309
|
+
return REQUIRED_SCOPES.filter((required) => !scopes.includes(required));
|
|
310
|
+
}
|
|
311
|
+
function deleteCredentials() {
|
|
312
|
+
if (!isMacOS()) {
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
try {
|
|
316
|
+
execSync2(`security delete-generic-password -s "${KEYCHAIN_SERVICE}"`, {
|
|
317
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
318
|
+
});
|
|
319
|
+
return true;
|
|
320
|
+
} catch {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
async function checkAndPromptAuth() {
|
|
325
|
+
if (!isMacOS()) {
|
|
326
|
+
logger.info(
|
|
327
|
+
"\u26A0\uFE0F V\xE9rification des scopes OAuth non disponible (macOS uniquement)"
|
|
328
|
+
);
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
const creds = getCredentials();
|
|
332
|
+
if (!creds?.claudeAiOauth) {
|
|
333
|
+
logger.info("\u26A0\uFE0F Pas de credentials Claude Code trouv\xE9s");
|
|
334
|
+
logger.info(" Lancez 'claude' pour vous authentifier d'abord");
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
if (hasRequiredScopes()) {
|
|
338
|
+
logger.info("\u2705 Scopes OAuth OK (user:profile disponible)");
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
const missing = getMissingScopes();
|
|
342
|
+
logger.warn(`\u26A0\uFE0F Scopes manquants: ${missing.join(", ")}`);
|
|
343
|
+
logger.info(
|
|
344
|
+
" La statusline a besoin du scope 'user:profile' pour afficher"
|
|
345
|
+
);
|
|
346
|
+
logger.info(" le temps restant et l'utilisation depuis l'API Anthropic.");
|
|
347
|
+
const inquirer2 = (await import("inquirer")).default;
|
|
348
|
+
const { shouldRefresh } = await inquirer2.prompt([
|
|
349
|
+
{
|
|
350
|
+
type: "confirm",
|
|
351
|
+
name: "shouldRefresh",
|
|
352
|
+
message: "Voulez-vous vous r\xE9-authentifier pour obtenir les bons scopes ?",
|
|
353
|
+
default: true
|
|
354
|
+
}
|
|
355
|
+
]);
|
|
356
|
+
if (!shouldRefresh) {
|
|
357
|
+
logger.info(
|
|
358
|
+
" La statusline fonctionnera sans les infos d'usage Anthropic"
|
|
359
|
+
);
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
const deleted = deleteCredentials();
|
|
363
|
+
if (!deleted) {
|
|
364
|
+
logger.error("Impossible de supprimer les credentials");
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
logger.info("\n\u{1F511} Credentials supprim\xE9s.");
|
|
368
|
+
logger.info(
|
|
369
|
+
" Veuillez lancer 'claude' dans un terminal pour vous reconnecter."
|
|
370
|
+
);
|
|
371
|
+
logger.info(" Puis relancez cette commande.\n");
|
|
372
|
+
process.exit(0);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// src/utils/vibe-auth.ts
|
|
376
|
+
init_esm_shims();
|
|
377
|
+
init_logger();
|
|
378
|
+
import { homedir as homedir2 } from "os";
|
|
379
|
+
import { join as join2 } from "path";
|
|
380
|
+
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
381
|
+
import { existsSync } from "fs";
|
|
382
|
+
var VIBE_API_URL = "https://app.vibeacademy.eu/api/user";
|
|
383
|
+
var CONFIG_DIR = join2(homedir2(), ".vibe-academy");
|
|
384
|
+
var CONFIG_FILE = join2(CONFIG_DIR, "config.json");
|
|
385
|
+
async function validateApiKey(apiKey) {
|
|
386
|
+
if (!apiKey || apiKey.trim() === "") {
|
|
387
|
+
return { valid: false, error: "Cl\xE9 API vide" };
|
|
388
|
+
}
|
|
389
|
+
try {
|
|
390
|
+
const response = await fetch(VIBE_API_URL, {
|
|
391
|
+
method: "GET",
|
|
392
|
+
headers: {
|
|
393
|
+
Authorization: `Bearer ${apiKey.trim()}`,
|
|
394
|
+
"Content-Type": "application/json"
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
if (!response.ok) {
|
|
398
|
+
if (response.status === 401) {
|
|
399
|
+
return { valid: false, error: "Cl\xE9 API invalide ou expir\xE9e" };
|
|
400
|
+
}
|
|
401
|
+
return {
|
|
402
|
+
valid: false,
|
|
403
|
+
error: `Erreur serveur (${response.status})`
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
const data = await response.json();
|
|
407
|
+
if (data?.data) {
|
|
408
|
+
return {
|
|
409
|
+
valid: true,
|
|
410
|
+
user: data.data
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
return { valid: false, error: "R\xE9ponse API invalide" };
|
|
414
|
+
} catch (error) {
|
|
415
|
+
if (error instanceof TypeError && error.message.includes("fetch")) {
|
|
416
|
+
return {
|
|
417
|
+
valid: false,
|
|
418
|
+
error: "Pas de connexion internet. V\xE9rifiez votre connexion."
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
return {
|
|
422
|
+
valid: false,
|
|
423
|
+
error: `Erreur r\xE9seau: ${error instanceof Error ? error.message : "inconnue"}`
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
async function getStoredApiKey() {
|
|
428
|
+
try {
|
|
429
|
+
if (!existsSync(CONFIG_FILE)) {
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
const content = await readFile(CONFIG_FILE, "utf-8");
|
|
433
|
+
const config = JSON.parse(content);
|
|
434
|
+
return config.apiKey || null;
|
|
435
|
+
} catch {
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
async function storeApiKey(apiKey) {
|
|
440
|
+
try {
|
|
441
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
442
|
+
await mkdir(CONFIG_DIR, { recursive: true });
|
|
443
|
+
}
|
|
444
|
+
const config = { apiKey };
|
|
445
|
+
await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
|
|
446
|
+
} catch (error) {
|
|
447
|
+
throw new Error(
|
|
448
|
+
`Impossible de sauvegarder la cl\xE9 API: ${error instanceof Error ? error.message : "erreur inconnue"}`
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
async function clearApiKey() {
|
|
453
|
+
try {
|
|
454
|
+
if (existsSync(CONFIG_FILE)) {
|
|
455
|
+
const config = {};
|
|
456
|
+
await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
|
|
457
|
+
}
|
|
458
|
+
} catch {
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
async function checkAndPromptVibeAuth() {
|
|
462
|
+
const storedKey = await getStoredApiKey();
|
|
463
|
+
if (storedKey) {
|
|
464
|
+
const spinner = logger.spinner("V\xE9rification de votre cl\xE9 Vibe Academy...");
|
|
465
|
+
const result = await validateApiKey(storedKey);
|
|
466
|
+
spinner.stop();
|
|
467
|
+
if (result.valid && result.user) {
|
|
468
|
+
const name = result.user.name || result.user.email || "Apprenant";
|
|
469
|
+
const credits = result.user.credits ?? 0;
|
|
470
|
+
console.log(
|
|
471
|
+
`
|
|
472
|
+
\u2705 Connect\xE9 en tant que ${name} (${credits} cr\xE9dit${credits > 1 ? "s" : ""})
|
|
473
|
+
`
|
|
474
|
+
);
|
|
475
|
+
return true;
|
|
476
|
+
}
|
|
477
|
+
logger.warn("Votre cl\xE9 API a expir\xE9 ou est invalide.");
|
|
478
|
+
await clearApiKey();
|
|
479
|
+
}
|
|
480
|
+
return await promptForApiKey();
|
|
481
|
+
}
|
|
482
|
+
async function promptForApiKey() {
|
|
483
|
+
const inquirer2 = (await import("inquirer")).default;
|
|
484
|
+
console.log("\n\u{1F511} Authentification Vibe Academy requise\n");
|
|
485
|
+
console.log(
|
|
486
|
+
" Pour utiliser cette CLI, vous devez \xEAtre inscrit \xE0 la formation."
|
|
487
|
+
);
|
|
488
|
+
console.log(
|
|
489
|
+
" Votre cl\xE9 API est disponible sur: https://app.vibeacademy.eu/outils\n"
|
|
490
|
+
);
|
|
491
|
+
const { apiKey } = await inquirer2.prompt([
|
|
492
|
+
{
|
|
493
|
+
type: "password",
|
|
494
|
+
name: "apiKey",
|
|
495
|
+
message: "Entrez votre cl\xE9 API Vibe Academy:",
|
|
496
|
+
mask: "*",
|
|
497
|
+
validate: (input) => {
|
|
498
|
+
if (!input || input.trim() === "") {
|
|
499
|
+
return "La cl\xE9 API est requise";
|
|
500
|
+
}
|
|
501
|
+
return true;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
]);
|
|
505
|
+
const spinner = logger.spinner("Validation de votre cl\xE9...");
|
|
506
|
+
const result = await validateApiKey(apiKey);
|
|
507
|
+
spinner.stop();
|
|
508
|
+
if (!result.valid) {
|
|
509
|
+
logger.error(result.error || "Cl\xE9 invalide");
|
|
510
|
+
console.log(
|
|
511
|
+
"\n V\xE9rifiez votre cl\xE9 sur https://app.vibeacademy.eu/outils\n"
|
|
512
|
+
);
|
|
513
|
+
const { retry } = await inquirer2.prompt([
|
|
514
|
+
{
|
|
515
|
+
type: "confirm",
|
|
516
|
+
name: "retry",
|
|
517
|
+
message: "Voulez-vous r\xE9essayer ?",
|
|
518
|
+
default: true
|
|
519
|
+
}
|
|
520
|
+
]);
|
|
521
|
+
if (retry) {
|
|
522
|
+
return promptForApiKey();
|
|
523
|
+
}
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
await storeApiKey(apiKey);
|
|
527
|
+
const name = result.user?.name || result.user?.email || "Apprenant";
|
|
528
|
+
const credits = result.user?.credits ?? 0;
|
|
529
|
+
console.log(`
|
|
530
|
+
\u2705 Bienvenue ${name}!`);
|
|
531
|
+
console.log(
|
|
532
|
+
` Vous avez ${credits} cr\xE9dit${credits > 1 ? "s" : ""} disponible${credits > 1 ? "s" : ""}.
|
|
533
|
+
`
|
|
534
|
+
);
|
|
535
|
+
return true;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// src/commands/setup.ts
|
|
539
|
+
function getDefaultConfig() {
|
|
540
|
+
return {
|
|
541
|
+
hooks: true,
|
|
542
|
+
statusline: true,
|
|
543
|
+
docs: true
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
async function setupCommand(options) {
|
|
547
|
+
try {
|
|
548
|
+
logger.welcome();
|
|
549
|
+
const vibeAuthOk = await checkAndPromptVibeAuth();
|
|
550
|
+
if (!vibeAuthOk) {
|
|
551
|
+
logger.error(
|
|
552
|
+
"Authentification Vibe Academy requise pour utiliser cette CLI."
|
|
553
|
+
);
|
|
554
|
+
process.exit(1);
|
|
555
|
+
}
|
|
556
|
+
await checkAndPromptAuth();
|
|
557
|
+
let installPath;
|
|
558
|
+
if (options.folder) {
|
|
559
|
+
installPath = options.folder;
|
|
560
|
+
} else if (options.skip) {
|
|
561
|
+
const spinner = logger.spinner("D\xE9tection du chemin d'installation...");
|
|
562
|
+
installPath = await detectInstallPath();
|
|
563
|
+
spinner.succeed(`Chemin d'installation: ${installPath}`);
|
|
564
|
+
} else {
|
|
565
|
+
installPath = await promptInstallLocation();
|
|
566
|
+
}
|
|
567
|
+
const config = options.skip ? getDefaultConfig() : await promptConfig(options);
|
|
568
|
+
logger.info(`Configuration: ${JSON.stringify(config, null, 2)}`);
|
|
569
|
+
const { installConfig: installConfig2 } = await Promise.resolve().then(() => (init_files(), files_exports));
|
|
570
|
+
await installConfig2(installPath, config);
|
|
571
|
+
logger.success(installPath);
|
|
572
|
+
} catch (error) {
|
|
573
|
+
logger.error(
|
|
574
|
+
error instanceof Error ? error.message : "Une erreur est survenue"
|
|
575
|
+
);
|
|
576
|
+
process.exit(1);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
async function promptInstallLocation() {
|
|
580
|
+
const inquirer2 = (await import("inquirer")).default;
|
|
581
|
+
const os = await import("os");
|
|
582
|
+
const path2 = await import("path");
|
|
583
|
+
const currentDir = process.cwd();
|
|
584
|
+
const localPath = path2.join(currentDir, ".claude");
|
|
585
|
+
const globalPath = path2.join(os.homedir(), ".claude");
|
|
586
|
+
console.log("\n\u{1F4CD} O\xF9 souhaitez-vous installer la configuration ?\n");
|
|
587
|
+
const { location } = await inquirer2.prompt([
|
|
588
|
+
{
|
|
589
|
+
type: "list",
|
|
590
|
+
name: "location",
|
|
591
|
+
message: "Emplacement d'installation:",
|
|
592
|
+
choices: [
|
|
593
|
+
{
|
|
594
|
+
name: `\u{1F30D} Global - pour tous les projets (${globalPath})`,
|
|
595
|
+
value: "global"
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
name: `\u{1F4C1} Local - projet actuel uniquement (${localPath})`,
|
|
599
|
+
value: "local"
|
|
600
|
+
},
|
|
601
|
+
{
|
|
602
|
+
name: "\u{1F4C2} Personnalis\xE9 - extraire le code sans rien \xE9craser",
|
|
603
|
+
value: "custom"
|
|
604
|
+
}
|
|
605
|
+
]
|
|
606
|
+
}
|
|
607
|
+
]);
|
|
608
|
+
let installPath;
|
|
609
|
+
if (location === "custom") {
|
|
610
|
+
const { customPath } = await inquirer2.prompt([
|
|
611
|
+
{
|
|
612
|
+
type: "input",
|
|
613
|
+
name: "customPath",
|
|
614
|
+
message: "Chemin du dossier:",
|
|
615
|
+
default: "./vibe-academy-config"
|
|
616
|
+
}
|
|
617
|
+
]);
|
|
618
|
+
installPath = customPath.startsWith("~") ? customPath.replace("~", os.homedir()) : customPath;
|
|
619
|
+
} else if (location === "local") {
|
|
620
|
+
installPath = localPath;
|
|
621
|
+
} else {
|
|
622
|
+
installPath = globalPath;
|
|
623
|
+
}
|
|
624
|
+
logger.info(`
|
|
625
|
+
\u{1F4C1} Installation dans: ${installPath}
|
|
626
|
+
`);
|
|
627
|
+
return installPath;
|
|
628
|
+
}
|
|
629
|
+
async function promptConfig(options) {
|
|
630
|
+
const inquirer2 = (await import("inquirer")).default;
|
|
631
|
+
console.log(
|
|
632
|
+
"\n\u2728 S\xE9lectionnez les composants \xE0 installer (utilisez espace pour s\xE9lectionner):\n"
|
|
633
|
+
);
|
|
634
|
+
const { components } = await inquirer2.prompt([
|
|
635
|
+
{
|
|
636
|
+
type: "checkbox",
|
|
637
|
+
name: "components",
|
|
638
|
+
message: "Composants \xE0 installer:",
|
|
639
|
+
choices: [
|
|
640
|
+
{
|
|
641
|
+
name: "\u{1F512} Hooks de s\xE9curit\xE9 (validation des commandes Bash)",
|
|
642
|
+
value: "hooks",
|
|
643
|
+
checked: !options.noHooks
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
name: "\u{1F4CA} Statusline personnalis\xE9e (git, tokens, usage Anthropic)",
|
|
647
|
+
value: "statusline",
|
|
648
|
+
checked: !options.noStatusline
|
|
649
|
+
},
|
|
650
|
+
{
|
|
651
|
+
name: "\u{1F514} Notifications audio (finish, need-human)",
|
|
652
|
+
value: "audio",
|
|
653
|
+
checked: true
|
|
654
|
+
},
|
|
655
|
+
{
|
|
656
|
+
name: "\u{1F4DA} Documentation (README.md)",
|
|
657
|
+
value: "docs",
|
|
658
|
+
checked: !options.noDocs
|
|
659
|
+
}
|
|
660
|
+
]
|
|
661
|
+
}
|
|
662
|
+
]);
|
|
663
|
+
return {
|
|
664
|
+
hooks: components.includes("hooks"),
|
|
665
|
+
statusline: components.includes("statusline"),
|
|
666
|
+
docs: components.includes("docs")
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// src/commands/update.ts
|
|
671
|
+
init_esm_shims();
|
|
672
|
+
init_logger();
|
|
673
|
+
init_files();
|
|
674
|
+
import { existsSync as existsSync3 } from "fs";
|
|
675
|
+
import { cp as cp2 } from "fs/promises";
|
|
676
|
+
async function backupConfig(installPath) {
|
|
677
|
+
const backupPath = `${installPath}.backup.${Date.now()}`;
|
|
678
|
+
const spinner = logger.spinner("Cr\xE9ation du backup...");
|
|
679
|
+
try {
|
|
680
|
+
await cp2(installPath, backupPath, { recursive: true });
|
|
681
|
+
spinner.succeed(`Backup cr\xE9\xE9: ${backupPath}`);
|
|
682
|
+
} catch (error) {
|
|
683
|
+
spinner.fail("\xC9chec de la cr\xE9ation du backup");
|
|
684
|
+
throw error;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
async function updateCommand(options) {
|
|
688
|
+
try {
|
|
689
|
+
logger.info("\u{1F504} Mise \xE0 jour de la configuration Vibe Academy...\n");
|
|
690
|
+
const installPath = await detectInstallPath(options.folder);
|
|
691
|
+
if (!existsSync3(installPath)) {
|
|
692
|
+
logger.error(
|
|
693
|
+
`Aucune configuration trouv\xE9e dans ${installPath}
|
|
694
|
+
Utilisez 'vibe-academy setup' pour installer.`
|
|
695
|
+
);
|
|
696
|
+
process.exit(1);
|
|
697
|
+
}
|
|
698
|
+
logger.info(`Configuration trouv\xE9e: ${installPath}`);
|
|
699
|
+
await backupConfig(installPath);
|
|
700
|
+
const config = {
|
|
701
|
+
hooks: true,
|
|
702
|
+
statusline: true,
|
|
703
|
+
docs: true
|
|
704
|
+
};
|
|
705
|
+
await installConfig(installPath, config);
|
|
706
|
+
logger.success(installPath);
|
|
707
|
+
logger.info(
|
|
708
|
+
"\u{1F4A1} Si quelque chose ne fonctionne pas, restaurez le backup cr\xE9\xE9 ci-dessus.\n"
|
|
709
|
+
);
|
|
710
|
+
} catch (error) {
|
|
711
|
+
logger.error(
|
|
712
|
+
error instanceof Error ? error.message : "Une erreur est survenue"
|
|
713
|
+
);
|
|
714
|
+
process.exit(1);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// src/commands/uninstall.ts
|
|
719
|
+
init_esm_shims();
|
|
720
|
+
init_logger();
|
|
721
|
+
import { existsSync as existsSync4 } from "fs";
|
|
722
|
+
import { rm } from "fs/promises";
|
|
723
|
+
import inquirer from "inquirer";
|
|
724
|
+
async function uninstallCommand(options) {
|
|
725
|
+
try {
|
|
726
|
+
logger.warn("\u26A0\uFE0F D\xE9sinstallation de la configuration Vibe Academy\n");
|
|
727
|
+
const installPath = await detectInstallPath(options.folder);
|
|
728
|
+
if (!existsSync4(installPath)) {
|
|
729
|
+
logger.error(`Aucune configuration trouv\xE9e dans ${installPath}`);
|
|
730
|
+
process.exit(1);
|
|
731
|
+
}
|
|
732
|
+
logger.info(`Configuration \xE0 supprimer: ${installPath}
|
|
733
|
+
`);
|
|
734
|
+
if (!options.force) {
|
|
735
|
+
const { confirm } = await inquirer.prompt([
|
|
736
|
+
{
|
|
737
|
+
type: "confirm",
|
|
738
|
+
name: "confirm",
|
|
739
|
+
message: "\xCAtes-vous s\xFBr de vouloir supprimer toute la configuration ?",
|
|
740
|
+
default: false
|
|
741
|
+
}
|
|
742
|
+
]);
|
|
743
|
+
if (!confirm) {
|
|
744
|
+
logger.info("D\xE9sinstallation annul\xE9e.\n");
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
const spinner = logger.spinner("Suppression de la configuration...");
|
|
749
|
+
try {
|
|
750
|
+
await rm(installPath, { recursive: true, force: true });
|
|
751
|
+
spinner.succeed("Configuration supprim\xE9e");
|
|
752
|
+
} catch (error) {
|
|
753
|
+
spinner.fail("\xC9chec de la suppression");
|
|
754
|
+
throw error;
|
|
755
|
+
}
|
|
756
|
+
console.log("\n\u2713 Configuration Vibe Academy d\xE9sinstall\xE9e avec succ\xE8s!\n");
|
|
757
|
+
logger.info("Pour r\xE9installer, ex\xE9cutez: vibe-academy setup\n");
|
|
758
|
+
} catch (error) {
|
|
759
|
+
logger.error(
|
|
760
|
+
error instanceof Error ? error.message : "Une erreur est survenue"
|
|
761
|
+
);
|
|
762
|
+
process.exit(1);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// src/commands/auth.ts
|
|
767
|
+
init_esm_shims();
|
|
768
|
+
init_logger();
|
|
769
|
+
async function authCommand(options) {
|
|
770
|
+
if (!isMacOS()) {
|
|
771
|
+
logger.error("Cette commande n'est disponible que sur macOS");
|
|
772
|
+
process.exit(1);
|
|
773
|
+
}
|
|
774
|
+
if (!options.refresh && !options.status) {
|
|
775
|
+
options.status = true;
|
|
776
|
+
}
|
|
777
|
+
if (options.status) {
|
|
778
|
+
await showAuthStatus();
|
|
779
|
+
}
|
|
780
|
+
if (options.refresh) {
|
|
781
|
+
await refreshAuth();
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
async function showAuthStatus() {
|
|
785
|
+
console.log("\n\u{1F510} Statut de l'authentification Claude Code\n");
|
|
786
|
+
const creds = getCredentials();
|
|
787
|
+
if (!creds?.claudeAiOauth) {
|
|
788
|
+
logger.warn("Pas de credentials trouv\xE9s");
|
|
789
|
+
logger.info("Lancez 'claude' pour vous authentifier");
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
const scopes = getCurrentScopes();
|
|
793
|
+
const oauth = creds.claudeAiOauth;
|
|
794
|
+
console.log(`\u{1F4CB} Scopes actuels: ${scopes.join(", ")}`);
|
|
795
|
+
if (oauth.subscriptionType) {
|
|
796
|
+
console.log(`\u{1F4B3} Abonnement: ${oauth.subscriptionType}`);
|
|
797
|
+
}
|
|
798
|
+
if (oauth.rateLimitTier) {
|
|
799
|
+
console.log(`\u{1F4CA} Rate limit tier: ${oauth.rateLimitTier}`);
|
|
800
|
+
}
|
|
801
|
+
console.log("");
|
|
802
|
+
if (hasRequiredScopes()) {
|
|
803
|
+
logger.info("\u2705 Tous les scopes requis sont pr\xE9sents");
|
|
804
|
+
logger.info(" La statusline peut afficher les infos d'usage Anthropic");
|
|
805
|
+
} else {
|
|
806
|
+
logger.warn("\u26A0\uFE0F Scope 'user:profile' manquant");
|
|
807
|
+
logger.info(" Lancez 'vibe-academy auth --refresh' pour corriger");
|
|
808
|
+
}
|
|
809
|
+
console.log("");
|
|
810
|
+
}
|
|
811
|
+
async function refreshAuth() {
|
|
812
|
+
console.log("\n\u{1F504} Rafra\xEEchissement de l'authentification\n");
|
|
813
|
+
const inquirer2 = (await import("inquirer")).default;
|
|
814
|
+
const { confirm } = await inquirer2.prompt([
|
|
815
|
+
{
|
|
816
|
+
type: "confirm",
|
|
817
|
+
name: "confirm",
|
|
818
|
+
message: "Cela va supprimer vos credentials et vous devrez vous reconnecter. Continuer ?",
|
|
819
|
+
default: true
|
|
820
|
+
}
|
|
821
|
+
]);
|
|
822
|
+
if (!confirm) {
|
|
823
|
+
logger.info("Annul\xE9");
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
const deleted = deleteCredentials();
|
|
827
|
+
if (!deleted) {
|
|
828
|
+
logger.error("Impossible de supprimer les credentials");
|
|
829
|
+
process.exit(1);
|
|
830
|
+
}
|
|
831
|
+
console.log("");
|
|
832
|
+
logger.info("\u{1F511} Credentials supprim\xE9s avec succ\xE8s");
|
|
833
|
+
console.log("");
|
|
834
|
+
console.log("Prochaines \xE9tapes:");
|
|
835
|
+
console.log(" 1. Lancez 'claude' dans un terminal");
|
|
836
|
+
console.log(" 2. Authentifiez-vous via le navigateur");
|
|
837
|
+
console.log(" 3. Les nouveaux credentials auront les bons scopes");
|
|
838
|
+
console.log("");
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// src/index.ts
|
|
842
|
+
var program = new Command();
|
|
843
|
+
program.name("vibe-academy").description("CLI pour installer les presets Claude Code pour Vibe Academy").version("0.1.0");
|
|
844
|
+
program.command("setup").description("Installer la configuration Vibe Academy").option("--skip", "Skip les prompts interactifs et installe tout").option("--folder <path>", "Dossier d'installation custom").option("--no-hooks", "Ne pas installer les hooks de s\xE9curit\xE9").option("--no-statusline", "Ne pas installer la statusline personnalis\xE9e").option("--no-docs", "Ne pas inclure la documentation").action(setupCommand);
|
|
845
|
+
program.command("update").description("Mettre \xE0 jour la configuration Vibe Academy").option("--folder <path>", "Dossier d'installation custom").action(updateCommand);
|
|
846
|
+
program.command("uninstall").description("D\xE9sinstaller la configuration Vibe Coding").option("--folder <path>", "Dossier d'installation custom").option("--force", "Forcer la suppression sans confirmation").action(uninstallCommand);
|
|
847
|
+
program.command("auth").description("G\xE9rer l'authentification Claude Code").option("--status", "Afficher le statut d'authentification").option(
|
|
848
|
+
"--refresh",
|
|
849
|
+
"Rafra\xEEchir l'authentification (pour obtenir les bons scopes)"
|
|
850
|
+
).action(authCommand);
|
|
851
|
+
program.parse();
|
|
852
|
+
//# sourceMappingURL=index.js.map
|