vibe-academy-cli 1.0.1 → 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/dist/index.js +79 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/.claude/scripts/statusline/src/index.ts +69 -14
- package/templates/.claude/scripts/statusline/src/lib/git.ts +54 -13
- package/templates/.claude/scripts/statusline/src/lib/usage-limits.ts +109 -27
- package/templates/.claude/scripts/validate-command.js +28 -11
package/dist/index.js
CHANGED
|
@@ -74,6 +74,9 @@ import { join as join3, dirname } from "path";
|
|
|
74
74
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
75
75
|
import { existsSync as existsSync2 } from "fs";
|
|
76
76
|
import { execSync as execSync3 } from "child_process";
|
|
77
|
+
function getWhichCommand() {
|
|
78
|
+
return isWindows ? "where" : "which";
|
|
79
|
+
}
|
|
77
80
|
function getTemplatesDir() {
|
|
78
81
|
const templatesPath = join3(__dirname2, "../templates");
|
|
79
82
|
if (existsSync2(templatesPath)) {
|
|
@@ -82,12 +85,13 @@ function getTemplatesDir() {
|
|
|
82
85
|
throw new Error(`Templates directory not found at: ${templatesPath}`);
|
|
83
86
|
}
|
|
84
87
|
function detectRuntime() {
|
|
88
|
+
const which = getWhichCommand();
|
|
85
89
|
try {
|
|
86
|
-
execSync3(
|
|
90
|
+
execSync3(`${which} bun`, { stdio: "ignore" });
|
|
87
91
|
return "bun";
|
|
88
92
|
} catch {
|
|
89
93
|
try {
|
|
90
|
-
execSync3(
|
|
94
|
+
execSync3(`${which} pnpm`, { stdio: "ignore" });
|
|
91
95
|
return "pnpm";
|
|
92
96
|
} catch {
|
|
93
97
|
return "npm";
|
|
@@ -95,8 +99,9 @@ function detectRuntime() {
|
|
|
95
99
|
}
|
|
96
100
|
}
|
|
97
101
|
function isCcusageInstalled() {
|
|
102
|
+
const which = getWhichCommand();
|
|
98
103
|
try {
|
|
99
|
-
execSync3(
|
|
104
|
+
execSync3(`${which} ccusage`, { stdio: "ignore" });
|
|
100
105
|
return true;
|
|
101
106
|
} catch {
|
|
102
107
|
return false;
|
|
@@ -137,6 +142,70 @@ async function installStatuslineDependencies(claudeDir, runtime) {
|
|
|
137
142
|
throw error;
|
|
138
143
|
}
|
|
139
144
|
}
|
|
145
|
+
function isBunAvailable() {
|
|
146
|
+
const which = getWhichCommand();
|
|
147
|
+
try {
|
|
148
|
+
execSync3(`${which} bun`, { stdio: "ignore" });
|
|
149
|
+
return true;
|
|
150
|
+
} catch {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function getScriptRunner(scriptPath) {
|
|
155
|
+
const hasBun = isBunAvailable();
|
|
156
|
+
if (scriptPath.endsWith(".js")) {
|
|
157
|
+
return "node";
|
|
158
|
+
}
|
|
159
|
+
if (scriptPath.endsWith(".ts")) {
|
|
160
|
+
if (hasBun) {
|
|
161
|
+
return "bun";
|
|
162
|
+
}
|
|
163
|
+
return "npx tsx";
|
|
164
|
+
}
|
|
165
|
+
return hasBun ? "bun" : "node";
|
|
166
|
+
}
|
|
167
|
+
function getAudioCommand(audioFile) {
|
|
168
|
+
if (process.platform === "darwin") {
|
|
169
|
+
return `afplay -v 0.1 ${audioFile}`;
|
|
170
|
+
} else if (isWindows) {
|
|
171
|
+
return `powershell -c "(New-Object Media.SoundPlayer '${audioFile}').PlaySync()"`;
|
|
172
|
+
} else {
|
|
173
|
+
return `aplay -q ${audioFile} 2>/dev/null || paplay ${audioFile} 2>/dev/null || true`;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function replacePathsInObject(obj, claudeDir, _runtime) {
|
|
177
|
+
if (typeof obj === "string") {
|
|
178
|
+
if (obj.includes("afplay")) {
|
|
179
|
+
const match = obj.match(/afplay\s+-v\s+[\d.]+\s+(.+)$/);
|
|
180
|
+
if (match) {
|
|
181
|
+
const audioPath = match[1].replace(
|
|
182
|
+
/\/Users\/matthieucousin\/\.claude/g,
|
|
183
|
+
claudeDir
|
|
184
|
+
);
|
|
185
|
+
return getAudioCommand(audioPath);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
let result = obj.replace(/\/Users\/matthieucousin\/\.claude/g, claudeDir);
|
|
189
|
+
const scriptMatch = result.match(/^bun\s+(.+\.(js|ts))$/);
|
|
190
|
+
if (scriptMatch) {
|
|
191
|
+
const scriptPath = scriptMatch[1];
|
|
192
|
+
const runner = getScriptRunner(scriptPath);
|
|
193
|
+
result = `${runner} ${scriptPath}`;
|
|
194
|
+
}
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
197
|
+
if (Array.isArray(obj)) {
|
|
198
|
+
return obj.map((item) => replacePathsInObject(item, claudeDir, _runtime));
|
|
199
|
+
}
|
|
200
|
+
if (obj !== null && typeof obj === "object") {
|
|
201
|
+
const newObj = {};
|
|
202
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
203
|
+
newObj[key] = replacePathsInObject(value, claudeDir, _runtime);
|
|
204
|
+
}
|
|
205
|
+
return newObj;
|
|
206
|
+
}
|
|
207
|
+
return obj;
|
|
208
|
+
}
|
|
140
209
|
async function generateSettings(claudeDir, runtime, config) {
|
|
141
210
|
const templatePath = join3(
|
|
142
211
|
getTemplatesDir(),
|
|
@@ -144,8 +213,11 @@ async function generateSettings(claudeDir, runtime, config) {
|
|
|
144
213
|
);
|
|
145
214
|
const template = await readFile2(templatePath, "utf-8");
|
|
146
215
|
const settingsObj = JSON.parse(template);
|
|
147
|
-
const
|
|
148
|
-
|
|
216
|
+
const settings = replacePathsInObject(
|
|
217
|
+
settingsObj,
|
|
218
|
+
claudeDir,
|
|
219
|
+
runtime
|
|
220
|
+
);
|
|
149
221
|
if (!config.hooks) {
|
|
150
222
|
delete settings.hooks;
|
|
151
223
|
}
|
|
@@ -264,12 +336,13 @@ vibe-academy uninstall
|
|
|
264
336
|
\`\`\`
|
|
265
337
|
`;
|
|
266
338
|
}
|
|
267
|
-
var __filename2, __dirname2;
|
|
339
|
+
var isWindows, __filename2, __dirname2;
|
|
268
340
|
var init_files = __esm({
|
|
269
341
|
"src/utils/files.ts"() {
|
|
270
342
|
"use strict";
|
|
271
343
|
init_esm_shims();
|
|
272
344
|
init_logger();
|
|
345
|
+
isWindows = process.platform === "win32";
|
|
273
346
|
__filename2 = fileURLToPath2(import.meta.url);
|
|
274
347
|
__dirname2 = dirname(__filename2);
|
|
275
348
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../node_modules/tsup/assets/esm_shims.js","../src/utils/logger.ts","../src/utils/files.ts","../src/index.ts","../src/commands/setup.ts","../src/utils/paths.ts","../src/utils/auth.ts","../src/utils/vibe-auth.ts","../src/commands/update.ts","../src/commands/uninstall.ts","../src/commands/auth.ts"],"sourcesContent":["// Shim globals in esm bundle\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst getFilename = () => fileURLToPath(import.meta.url)\nconst getDirname = () => path.dirname(getFilename())\n\nexport const __dirname = /* @__PURE__ */ getDirname()\nexport const __filename = /* @__PURE__ */ getFilename()\n","import chalk from \"chalk\";\nimport ora, { Ora } from \"ora\";\n\nexport const logger = {\n welcome() {\n console.log(chalk.cyan.bold(\"\\n🎓 Vibe Academy CLI\\n\"));\n console.log(\"Installation des presets Claude Code pour Vibe Academy...\\n\");\n },\n\n success(path: string) {\n console.log(chalk.green(\"\\n✓ Installation réussie!\\n\"));\n console.log(`Configuration installée dans: ${chalk.cyan(path)}\\n`);\n console.log(\"Prochaines étapes:\");\n console.log(\n \" 1. Ajouter le marketplace: /plugin marketplace add matthieucousin/vibe-academy-marketplace\",\n );\n console.log(\n \" 2. Installer les plugins: /plugin install vibe-academy@vibe-academy-marketplace\",\n );\n console.log(\" 3. Commencer à coder! 🎉\\n\");\n },\n\n error(message: string) {\n console.error(chalk.red(`\\n✗ Erreur: ${message}\\n`));\n },\n\n warn(message: string) {\n console.warn(chalk.yellow(`\\n⚠️ ${message}\\n`));\n },\n\n info(message: string) {\n console.log(chalk.blue(`ℹ️ ${message}`));\n },\n\n spinner(text: string): Ora {\n return ora(text).start();\n },\n};\n","import { mkdir, cp, writeFile, chmod, readFile } from \"fs/promises\";\nimport { join, dirname } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { existsSync } from \"fs\";\nimport type { Config } from \"../types/index.js\";\nimport { logger } from \"./logger.js\";\nimport { execSync } from \"child_process\";\n\n// Get __dirname equivalent in ES modules\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n/**\n * Get the templates directory path\n * In production: package_root/templates\n * In development: cli/templates\n */\nfunction getTemplatesDir(): string {\n // tsup compiles everything into a single dist/index.js file\n // So __dirname will be dist/, and we need to go up one level to templates/\n const templatesPath = join(__dirname, \"../templates\");\n\n if (existsSync(templatesPath)) {\n return templatesPath;\n }\n\n throw new Error(`Templates directory not found at: ${templatesPath}`);\n}\n\n/**\n * Detect available runtime (bun, pnpm, npm)\n */\nfunction detectRuntime(): \"bun\" | \"pnpm\" | \"npm\" {\n try {\n execSync(\"which bun\", { stdio: \"ignore\" });\n return \"bun\";\n } catch {\n try {\n execSync(\"which pnpm\", { stdio: \"ignore\" });\n return \"pnpm\";\n } catch {\n return \"npm\";\n }\n }\n}\n\n/**\n * Check if ccusage is installed\n */\nfunction isCcusageInstalled(): boolean {\n try {\n execSync(\"which ccusage\", { stdio: \"ignore\" });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Install ccusage globally for daily cost tracking\n */\nasync function installCcusage(runtime: \"bun\" | \"pnpm\" | \"npm\"): Promise<void> {\n const spinner = logger.spinner(\n `Installation de ccusage (coût journalier) avec ${runtime}...`,\n );\n\n try {\n // ccusage package name\n const packageName = \"@erinreily/ccusage\";\n\n if (runtime === \"bun\") {\n execSync(`bun install -g ${packageName}`, { stdio: \"ignore\" });\n } else if (runtime === \"pnpm\") {\n execSync(`pnpm add -g ${packageName}`, { stdio: \"ignore\" });\n } else {\n execSync(`npm install -g ${packageName}`, { stdio: \"ignore\" });\n }\n\n spinner.succeed(`ccusage installé avec ${runtime}`);\n } catch (error) {\n spinner.fail(`Échec de l'installation de ccusage (optionnel)`);\n logger.warn(\"Le coût journalier affichera $0.00 sans ccusage\");\n }\n}\n\n/**\n * Install statusline dependencies\n */\nasync function installStatuslineDependencies(\n claudeDir: string,\n runtime: \"bun\" | \"pnpm\" | \"npm\",\n): Promise<void> {\n const statuslineDir = join(claudeDir, \"scripts/statusline\");\n\n const spinner = logger.spinner(\n `Installation des dépendances statusline avec ${runtime}...`,\n );\n\n try {\n execSync(`${runtime} install`, {\n cwd: statuslineDir,\n stdio: \"ignore\",\n });\n spinner.succeed(`Dépendances statusline installées avec ${runtime}`);\n } catch (error) {\n spinner.fail(`Échec de l'installation des dépendances statusline`);\n throw error;\n }\n}\n\n/**\n * Generate settings.json with correct paths\n */\nasync function generateSettings(\n claudeDir: string,\n runtime: \"bun\" | \"pnpm\" | \"npm\",\n config: Config,\n): Promise<void> {\n const templatePath = join(\n getTemplatesDir(),\n \".claude/settings.json.template\",\n );\n const template = await readFile(templatePath, \"utf-8\");\n const settingsObj = JSON.parse(template);\n\n // Replace paths\n const settingsStr = JSON.stringify(settingsObj, null, 2)\n .replace(/\\/Users\\/matthieucousin\\/\\.claude/g, claudeDir)\n .replace(/\\bbun\\b/g, runtime);\n\n const settings = JSON.parse(settingsStr);\n\n // Remove hooks if not enabled\n if (!config.hooks) {\n delete settings.hooks;\n }\n\n // Remove statusline if not enabled\n if (!config.statusline) {\n delete settings.statusLine;\n }\n\n await writeFile(\n join(claudeDir, \"settings.json\"),\n JSON.stringify(settings, null, 2),\n );\n}\n\n/**\n * Install all configuration files\n */\nexport async function installConfig(\n targetPath: string,\n config: Config,\n): Promise<void> {\n const templatesDir = getTemplatesDir();\n const sourceDir = join(templatesDir, \".claude\");\n\n // Detect runtime\n const runtime = detectRuntime();\n logger.info(`Runtime détecté: ${runtime}`);\n\n // Create target directory\n await mkdir(targetPath, { recursive: true });\n\n // 1. Copy scripts directory (only if hooks or statusline enabled)\n if (config.hooks || config.statusline) {\n const scriptsSpinner = logger.spinner(\"Copie des scripts...\");\n await cp(join(sourceDir, \"scripts\"), join(targetPath, \"scripts\"), {\n recursive: true,\n });\n scriptsSpinner.succeed(\"Scripts copiés\");\n\n // Make scripts executable\n if (config.hooks) {\n await chmod(join(targetPath, \"scripts/validate-command.js\"), 0o755);\n await chmod(join(targetPath, \"scripts/hook-post-file.ts\"), 0o755);\n }\n }\n\n // 2. Copy song directory (audio notifications) - always copy for now\n const songSpinner = logger.spinner(\"Copie des fichiers audio...\");\n await cp(join(sourceDir, \"song\"), join(targetPath, \"song\"), {\n recursive: true,\n });\n songSpinner.succeed(\"Fichiers audio copiés\");\n\n // 3. Generate settings.json with correct paths\n const settingsSpinner = logger.spinner(\"Génération de settings.json...\");\n await generateSettings(targetPath, runtime, config);\n settingsSpinner.succeed(\"settings.json généré\");\n\n // 4. Install statusline dependencies if enabled\n if (config.statusline) {\n await installStatuslineDependencies(targetPath, runtime);\n\n // 4b. Install ccusage for daily cost tracking (if not already installed)\n if (!isCcusageInstalled()) {\n await installCcusage(runtime);\n } else {\n logger.info(\"ccusage déjà installé ✓\");\n }\n }\n\n // 5. Create README if enabled\n if (config.docs) {\n const readmeSpinner = logger.spinner(\"Création du README...\");\n const readme = generateReadme(targetPath);\n await writeFile(join(targetPath, \"README.md\"), readme);\n readmeSpinner.succeed(\"README créé\");\n }\n}\n\n/**\n * Generate README with installation info\n */\nfunction generateReadme(installPath: string): string {\n return `# Vibe Academy Configuration\n\nConfiguration Claude Code installée avec succès !\n\n## Emplacement\n\n\\`${installPath}\\`\n\n## Contenu\n\n- **settings.json** : Configuration Claude Code avec hooks et statusline\n- **scripts/** : Scripts de validation et statusline\n- **song/** : Notifications audio\n- **README.md** : Ce fichier\n\n## Hooks installés\n\n### PreToolUse\n- Validation des commandes Bash dangereuses\n\n### Stop\n- Notification audio de fin de session\n\n### Notification\n- Notification audio pour demandes utilisateur\n\n### PostToolUse\n- Hook post-édition de fichiers\n\n## Statusline\n\nLa statusline affiche :\n- Branche Git actuelle\n- Style de sortie et modèle\n- Tokens utilisés (contexte)\n- Utilisation Anthropic (%) et temps restant (via API directe)\n\n## Prochaines étapes\n\n1. Ajouter le marketplace :\n \\`\\`\\`bash\n /plugin marketplace add ton-username/vibe-coding-marketplace\n \\`\\`\\`\n\n2. Installer les plugins :\n \\`\\`\\`bash\n /plugin install vibe-coding@vibe-coding-marketplace\n \\`\\`\\`\n\n3. Commencer à coder ! 🚀\n\n## Mise à jour\n\nPour mettre à jour cette configuration :\n\\`\\`\\`bash\nvibe-academy update\n\\`\\`\\`\n\n## Désinstallation\n\nPour désinstaller :\n\\`\\`\\`bash\nvibe-academy uninstall\n\\`\\`\\`\n`;\n}\n","#!/usr/bin/env node\n\nimport { Command } from \"commander\";\nimport { setupCommand } from \"./commands/setup.js\";\nimport { updateCommand } from \"./commands/update.js\";\nimport { uninstallCommand } from \"./commands/uninstall.js\";\nimport { authCommand } from \"./commands/auth.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"vibe-academy\")\n .description(\"CLI pour installer les presets Claude Code pour Vibe Academy\")\n .version(\"0.1.0\");\n\nprogram\n .command(\"setup\")\n .description(\"Installer la configuration Vibe Academy\")\n .option(\"--skip\", \"Skip les prompts interactifs et installe tout\")\n .option(\"--folder <path>\", \"Dossier d'installation custom\")\n .option(\"--no-hooks\", \"Ne pas installer les hooks de sécurité\")\n .option(\"--no-statusline\", \"Ne pas installer la statusline personnalisée\")\n .option(\"--no-docs\", \"Ne pas inclure la documentation\")\n .action(setupCommand);\n\nprogram\n .command(\"update\")\n .description(\"Mettre à jour la configuration Vibe Academy\")\n .option(\"--folder <path>\", \"Dossier d'installation custom\")\n .action(updateCommand);\n\nprogram\n .command(\"uninstall\")\n .description(\"Désinstaller la configuration Vibe Coding\")\n .option(\"--folder <path>\", \"Dossier d'installation custom\")\n .option(\"--force\", \"Forcer la suppression sans confirmation\")\n .action(uninstallCommand);\n\nprogram\n .command(\"auth\")\n .description(\"Gérer l'authentification Claude Code\")\n .option(\"--status\", \"Afficher le statut d'authentification\")\n .option(\n \"--refresh\",\n \"Rafraîchir l'authentification (pour obtenir les bons scopes)\",\n )\n .action(authCommand);\n\nprogram.parse();\n","import type { SetupOptions, Config } from \"../types/index.js\";\nimport { detectInstallPath } from \"../utils/paths.js\";\nimport { logger } from \"../utils/logger.js\";\nimport { checkAndPromptAuth } from \"../utils/auth.js\";\nimport { checkAndPromptVibeAuth } from \"../utils/vibe-auth.js\";\n\n/**\n * Get default configuration\n */\nfunction getDefaultConfig(): Config {\n return {\n hooks: true,\n statusline: true,\n docs: true,\n };\n}\n\n/**\n * Main setup command\n * Installs Claude Code configuration with hooks, statusline, and documentation\n */\nexport async function setupCommand(options: SetupOptions): Promise<void> {\n try {\n logger.welcome();\n\n // 0. Check Vibe Academy API key (required to use the CLI)\n const vibeAuthOk = await checkAndPromptVibeAuth();\n if (!vibeAuthOk) {\n logger.error(\n \"Authentification Vibe Academy requise pour utiliser cette CLI.\",\n );\n process.exit(1);\n }\n\n // 1. Check OAuth scopes (for statusline features)\n await checkAndPromptAuth();\n\n // 2. Determine installation path\n let installPath: string;\n\n if (options.folder) {\n // Custom folder provided via --folder\n installPath = options.folder;\n } else if (options.skip) {\n // Skip mode: auto-detect\n const spinner = logger.spinner(\"Détection du chemin d'installation...\");\n installPath = await detectInstallPath();\n spinner.succeed(`Chemin d'installation: ${installPath}`);\n } else {\n // Interactive mode: ask user\n installPath = await promptInstallLocation();\n }\n\n // 2. Get configuration (interactive or default)\n const config = options.skip\n ? getDefaultConfig()\n : await promptConfig(options);\n\n logger.info(`Configuration: ${JSON.stringify(config, null, 2)}`);\n\n // 3. Install files\n const { installConfig } = await import(\"../utils/files.js\");\n await installConfig(installPath, config);\n\n // 4. Success message\n logger.success(installPath);\n } catch (error) {\n logger.error(\n error instanceof Error ? error.message : \"Une erreur est survenue\",\n );\n process.exit(1);\n }\n}\n\n/**\n * Prompt user for installation location\n */\nasync function promptInstallLocation(): Promise<string> {\n const inquirer = (await import(\"inquirer\")).default;\n const os = await import(\"os\");\n const path = await import(\"path\");\n\n // Detect current project path\n const currentDir = process.cwd();\n const localPath = path.join(currentDir, \".claude\");\n const globalPath = path.join(os.homedir(), \".claude\");\n\n console.log(\"\\n📍 Où souhaitez-vous installer la configuration ?\\n\");\n\n const { location } = await inquirer.prompt<{\n location: \"global\" | \"local\" | \"custom\";\n }>([\n {\n type: \"list\",\n name: \"location\",\n message: \"Emplacement d'installation:\",\n choices: [\n {\n name: `🌍 Global - pour tous les projets (${globalPath})`,\n value: \"global\",\n },\n {\n name: `📁 Local - projet actuel uniquement (${localPath})`,\n value: \"local\",\n },\n {\n name: \"📂 Personnalisé - extraire le code sans rien écraser\",\n value: \"custom\",\n },\n ],\n },\n ]);\n\n let installPath: string;\n\n if (location === \"custom\") {\n const { customPath } = await inquirer.prompt<{ customPath: string }>([\n {\n type: \"input\",\n name: \"customPath\",\n message: \"Chemin du dossier:\",\n default: \"./vibe-academy-config\",\n },\n ]);\n\n // Expand ~ to home directory\n installPath = customPath.startsWith(\"~\")\n ? customPath.replace(\"~\", os.homedir())\n : customPath;\n } else if (location === \"local\") {\n installPath = localPath;\n } else {\n installPath = globalPath;\n }\n\n logger.info(`\\n📁 Installation dans: ${installPath}\\n`);\n\n return installPath;\n}\n\n/**\n * Prompt user for configuration options\n * Interactive selection with checkboxes\n */\nasync function promptConfig(options: SetupOptions): Promise<Config> {\n const inquirer = (await import(\"inquirer\")).default;\n\n console.log(\n \"\\n✨ Sélectionnez les composants à installer (utilisez espace pour sélectionner):\\n\",\n );\n\n const { components } = await inquirer.prompt<{ components: string[] }>([\n {\n type: \"checkbox\",\n name: \"components\",\n message: \"Composants à installer:\",\n choices: [\n {\n name: \"🔒 Hooks de sécurité (validation des commandes Bash)\",\n value: \"hooks\",\n checked: !options.noHooks,\n },\n {\n name: \"📊 Statusline personnalisée (git, tokens, usage Anthropic)\",\n value: \"statusline\",\n checked: !options.noStatusline,\n },\n {\n name: \"🔔 Notifications audio (finish, need-human)\",\n value: \"audio\",\n checked: true,\n },\n {\n name: \"📚 Documentation (README.md)\",\n value: \"docs\",\n checked: !options.noDocs,\n },\n ],\n },\n ]);\n\n return {\n hooks: components.includes(\"hooks\"),\n statusline: components.includes(\"statusline\"),\n docs: components.includes(\"docs\"),\n };\n}\n","import { execSync } from \"child_process\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\nimport { existsSync } from \"fs\";\n\n/**\n * Detect the installation path for .claude directory\n * Priority:\n * 1. Custom folder (if provided via --folder flag)\n * 2. Git repository root (if in a git repo)\n * 3. Home directory (~/.claude)\n */\nexport async function detectInstallPath(\n customFolder?: string,\n): Promise<string> {\n // If custom folder is provided, use it\n if (customFolder) {\n return customFolder;\n }\n\n try {\n // Try to find git repository root\n const gitRoot = execSync(\"git rev-parse --show-toplevel\", {\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"ignore\"],\n }).trim();\n\n // Install in .claude/ at the root of the git repo\n return join(gitRoot, \".claude\");\n } catch {\n // Not in a git repo, install globally in home directory\n return join(homedir(), \".claude\");\n }\n}\n\n/**\n * Check if a directory exists\n */\nexport function directoryExists(path: string): boolean {\n return existsSync(path);\n}\n\n/**\n * Get the path to embedded templates directory\n * This is the templates/ directory in the npm package\n */\nexport function getTemplatesPath(): string {\n // In production (npm package), templates are in the package root\n // In development, they're in the parent directory\n const prodPath = join(process.cwd(), \"templates\");\n const devPath = join(process.cwd(), \"..\", \"templates\");\n\n return existsSync(prodPath) ? prodPath : devPath;\n}\n","import { execSync } from \"child_process\";\nimport { logger } from \"./logger.js\";\n\nconst KEYCHAIN_SERVICE = \"Claude Code-credentials\";\nconst REQUIRED_SCOPES = [\"user:inference\", \"user:profile\"];\n\ninterface ClaudeCredentials {\n claudeAiOauth?: {\n accessToken: string;\n refreshToken: string;\n scopes: string[];\n subscriptionType?: string;\n rateLimitTier?: string;\n };\n}\n\n/**\n * Check if running on macOS\n */\nexport function isMacOS(): boolean {\n return process.platform === \"darwin\";\n}\n\n/**\n * Get Claude Code credentials from macOS Keychain\n */\nexport function getCredentials(): ClaudeCredentials | null {\n if (!isMacOS()) {\n return null;\n }\n\n try {\n const result = execSync(\n `security find-generic-password -s \"${KEYCHAIN_SERVICE}\" -w`,\n { encoding: \"utf-8\", stdio: [\"pipe\", \"pipe\", \"pipe\"] },\n );\n return JSON.parse(result.trim());\n } catch {\n return null;\n }\n}\n\n/**\n * Get current OAuth scopes\n */\nexport function getCurrentScopes(): string[] {\n const creds = getCredentials();\n return creds?.claudeAiOauth?.scopes ?? [];\n}\n\n/**\n * Check if user has all required scopes for statusline features\n */\nexport function hasRequiredScopes(): boolean {\n const scopes = getCurrentScopes();\n return REQUIRED_SCOPES.every((required) => scopes.includes(required));\n}\n\n/**\n * Get missing scopes\n */\nexport function getMissingScopes(): string[] {\n const scopes = getCurrentScopes();\n return REQUIRED_SCOPES.filter((required) => !scopes.includes(required));\n}\n\n/**\n * Delete credentials from keychain to force re-authentication\n */\nexport function deleteCredentials(): boolean {\n if (!isMacOS()) {\n return false;\n }\n\n try {\n execSync(`security delete-generic-password -s \"${KEYCHAIN_SERVICE}\"`, {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Check auth status and prompt for re-auth if needed\n * Returns true if auth is OK, false if user declined re-auth\n */\nexport async function checkAndPromptAuth(): Promise<boolean> {\n if (!isMacOS()) {\n logger.info(\n \"⚠️ Vérification des scopes OAuth non disponible (macOS uniquement)\",\n );\n return true;\n }\n\n const creds = getCredentials();\n\n if (!creds?.claudeAiOauth) {\n logger.info(\"⚠️ Pas de credentials Claude Code trouvés\");\n logger.info(\" Lancez 'claude' pour vous authentifier d'abord\");\n return true;\n }\n\n if (hasRequiredScopes()) {\n logger.info(\"✅ Scopes OAuth OK (user:profile disponible)\");\n return true;\n }\n\n const missing = getMissingScopes();\n logger.warn(`⚠️ Scopes manquants: ${missing.join(\", \")}`);\n logger.info(\n \" La statusline a besoin du scope 'user:profile' pour afficher\",\n );\n logger.info(\" le temps restant et l'utilisation depuis l'API Anthropic.\");\n\n const inquirer = (await import(\"inquirer\")).default;\n\n const { shouldRefresh } = await inquirer.prompt<{ shouldRefresh: boolean }>([\n {\n type: \"confirm\",\n name: \"shouldRefresh\",\n message:\n \"Voulez-vous vous ré-authentifier pour obtenir les bons scopes ?\",\n default: true,\n },\n ]);\n\n if (!shouldRefresh) {\n logger.info(\n \" La statusline fonctionnera sans les infos d'usage Anthropic\",\n );\n return true;\n }\n\n // Delete credentials\n const deleted = deleteCredentials();\n if (!deleted) {\n logger.error(\"Impossible de supprimer les credentials\");\n return false;\n }\n\n logger.info(\"\\n🔑 Credentials supprimés.\");\n logger.info(\n \" Veuillez lancer 'claude' dans un terminal pour vous reconnecter.\",\n );\n logger.info(\" Puis relancez cette commande.\\n\");\n\n // Exit so user can re-auth\n process.exit(0);\n}\n","import { homedir } from \"os\";\nimport { join } from \"path\";\nimport { readFile, writeFile, mkdir } from \"fs/promises\";\nimport { existsSync } from \"fs\";\nimport { logger } from \"./logger.js\";\n\nconst VIBE_API_URL = \"https://app.vibeacademy.eu/api/user\";\nconst CONFIG_DIR = join(homedir(), \".vibe-academy\");\nconst CONFIG_FILE = join(CONFIG_DIR, \"config.json\");\n\nexport interface VibeUserData {\n credits: number;\n email?: string;\n name?: string;\n [key: string]: unknown;\n}\n\nexport interface VibeAuthResult {\n valid: boolean;\n user?: VibeUserData;\n error?: string;\n}\n\ninterface VibeConfig {\n apiKey?: string;\n}\n\n/**\n * Validate a Vibe Academy API key by calling the API\n */\nexport async function validateApiKey(apiKey: string): Promise<VibeAuthResult> {\n if (!apiKey || apiKey.trim() === \"\") {\n return { valid: false, error: \"Clé API vide\" };\n }\n\n try {\n const response = await fetch(VIBE_API_URL, {\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${apiKey.trim()}`,\n \"Content-Type\": \"application/json\",\n },\n });\n\n if (!response.ok) {\n if (response.status === 401) {\n return { valid: false, error: \"Clé API invalide ou expirée\" };\n }\n return {\n valid: false,\n error: `Erreur serveur (${response.status})`,\n };\n }\n\n const data = await response.json();\n\n // The API returns { data: { credits, ... } }\n if (data?.data) {\n return {\n valid: true,\n user: data.data as VibeUserData,\n };\n }\n\n return { valid: false, error: \"Réponse API invalide\" };\n } catch (error) {\n if (error instanceof TypeError && error.message.includes(\"fetch\")) {\n return {\n valid: false,\n error: \"Pas de connexion internet. Vérifiez votre connexion.\",\n };\n }\n return {\n valid: false,\n error: `Erreur réseau: ${error instanceof Error ? error.message : \"inconnue\"}`,\n };\n }\n}\n\n/**\n * Get the stored API key from config file\n */\nexport async function getStoredApiKey(): Promise<string | null> {\n try {\n if (!existsSync(CONFIG_FILE)) {\n return null;\n }\n const content = await readFile(CONFIG_FILE, \"utf-8\");\n const config: VibeConfig = JSON.parse(content);\n return config.apiKey || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Store the API key in config file\n */\nexport async function storeApiKey(apiKey: string): Promise<void> {\n try {\n // Create config directory if it doesn't exist\n if (!existsSync(CONFIG_DIR)) {\n await mkdir(CONFIG_DIR, { recursive: true });\n }\n\n const config: VibeConfig = { apiKey };\n await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), \"utf-8\");\n } catch (error) {\n throw new Error(\n `Impossible de sauvegarder la clé API: ${error instanceof Error ? error.message : \"erreur inconnue\"}`,\n );\n }\n}\n\n/**\n * Clear the stored API key\n */\nexport async function clearApiKey(): Promise<void> {\n try {\n if (existsSync(CONFIG_FILE)) {\n const config: VibeConfig = {};\n await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), \"utf-8\");\n }\n } catch {\n // Ignore errors when clearing\n }\n}\n\n/**\n * Check if user has valid Vibe Academy auth, prompt if needed\n * Returns true if auth is valid, false if user declined\n */\nexport async function checkAndPromptVibeAuth(): Promise<boolean> {\n // Check for stored key first\n const storedKey = await getStoredApiKey();\n\n if (storedKey) {\n const spinner = logger.spinner(\"Vérification de votre clé Vibe Academy...\");\n const result = await validateApiKey(storedKey);\n spinner.stop();\n\n if (result.valid && result.user) {\n const name = result.user.name || result.user.email || \"Apprenant\";\n const credits = result.user.credits ?? 0;\n console.log(\n `\\n✅ Connecté en tant que ${name} (${credits} crédit${credits > 1 ? \"s\" : \"\"})\\n`,\n );\n return true;\n }\n\n // Key is invalid, need to re-authenticate\n logger.warn(\"Votre clé API a expiré ou est invalide.\");\n await clearApiKey();\n }\n\n // No valid key, prompt for one\n return await promptForApiKey();\n}\n\n/**\n * Prompt user for their Vibe Academy API key\n */\nasync function promptForApiKey(): Promise<boolean> {\n const inquirer = (await import(\"inquirer\")).default;\n\n console.log(\"\\n🔑 Authentification Vibe Academy requise\\n\");\n console.log(\n \" Pour utiliser cette CLI, vous devez être inscrit à la formation.\",\n );\n console.log(\n \" Votre clé API est disponible sur: https://app.vibeacademy.eu/outils\\n\",\n );\n\n const { apiKey } = await inquirer.prompt<{ apiKey: string }>([\n {\n type: \"password\",\n name: \"apiKey\",\n message: \"Entrez votre clé API Vibe Academy:\",\n mask: \"*\",\n validate: (input: string) => {\n if (!input || input.trim() === \"\") {\n return \"La clé API est requise\";\n }\n return true;\n },\n },\n ]);\n\n const spinner = logger.spinner(\"Validation de votre clé...\");\n const result = await validateApiKey(apiKey);\n spinner.stop();\n\n if (!result.valid) {\n logger.error(result.error || \"Clé invalide\");\n console.log(\n \"\\n Vérifiez votre clé sur https://app.vibeacademy.eu/outils\\n\",\n );\n\n // Ask if they want to retry\n const { retry } = await inquirer.prompt<{ retry: boolean }>([\n {\n type: \"confirm\",\n name: \"retry\",\n message: \"Voulez-vous réessayer ?\",\n default: true,\n },\n ]);\n\n if (retry) {\n return promptForApiKey();\n }\n\n return false;\n }\n\n // Valid key, store it\n await storeApiKey(apiKey);\n\n const name = result.user?.name || result.user?.email || \"Apprenant\";\n const credits = result.user?.credits ?? 0;\n\n console.log(`\\n✅ Bienvenue ${name}!`);\n console.log(\n ` Vous avez ${credits} crédit${credits > 1 ? \"s\" : \"\"} disponible${credits > 1 ? \"s\" : \"\"}.\\n`,\n );\n\n return true;\n}\n","import type { UpdateOptions } from \"../types/index.js\";\nimport { detectInstallPath } from \"../utils/paths.js\";\nimport { logger } from \"../utils/logger.js\";\nimport { installConfig } from \"../utils/files.js\";\nimport { existsSync } from \"fs\";\nimport { cp } from \"fs/promises\";\nimport { join } from \"path\";\n\n/**\n * Create backup of current configuration\n */\nasync function backupConfig(installPath: string): Promise<void> {\n const backupPath = `${installPath}.backup.${Date.now()}`;\n const spinner = logger.spinner(\"Création du backup...\");\n\n try {\n await cp(installPath, backupPath, { recursive: true });\n spinner.succeed(`Backup créé: ${backupPath}`);\n } catch (error) {\n spinner.fail(\"Échec de la création du backup\");\n throw error;\n }\n}\n\n/**\n * Update command\n * Updates the Claude Code configuration with latest templates\n */\nexport async function updateCommand(options: UpdateOptions): Promise<void> {\n try {\n logger.info(\"🔄 Mise à jour de la configuration Vibe Academy...\\n\");\n\n // 1. Determine installation path\n const installPath = await detectInstallPath(options.folder);\n\n // Check if configuration exists\n if (!existsSync(installPath)) {\n logger.error(\n `Aucune configuration trouvée dans ${installPath}\\nUtilisez 'vibe-academy setup' pour installer.`,\n );\n process.exit(1);\n }\n\n logger.info(`Configuration trouvée: ${installPath}`);\n\n // 2. Create backup\n await backupConfig(installPath);\n\n // 3. Install updated configuration (keep all features enabled)\n const config = {\n hooks: true,\n statusline: true,\n docs: true,\n };\n\n await installConfig(installPath, config);\n\n // 4. Success message\n logger.success(installPath);\n logger.info(\n \"💡 Si quelque chose ne fonctionne pas, restaurez le backup créé ci-dessus.\\n\",\n );\n } catch (error) {\n logger.error(\n error instanceof Error ? error.message : \"Une erreur est survenue\",\n );\n process.exit(1);\n }\n}\n","import type { UninstallOptions } from \"../types/index.js\";\nimport { detectInstallPath } from \"../utils/paths.js\";\nimport { logger } from \"../utils/logger.js\";\nimport { existsSync } from \"fs\";\nimport { rm } from \"fs/promises\";\nimport inquirer from \"inquirer\";\n\n/**\n * Uninstall command\n * Removes the Claude Code configuration\n */\nexport async function uninstallCommand(\n options: UninstallOptions,\n): Promise<void> {\n try {\n logger.warn(\"⚠️ Désinstallation de la configuration Vibe Academy\\n\");\n\n // 1. Determine installation path\n const installPath = await detectInstallPath(options.folder);\n\n // Check if configuration exists\n if (!existsSync(installPath)) {\n logger.error(`Aucune configuration trouvée dans ${installPath}`);\n process.exit(1);\n }\n\n logger.info(`Configuration à supprimer: ${installPath}\\n`);\n\n // 2. Ask for confirmation (unless --force)\n if (!options.force) {\n const { confirm } = await inquirer.prompt<{ confirm: boolean }>([\n {\n type: \"confirm\",\n name: \"confirm\",\n message:\n \"Êtes-vous sûr de vouloir supprimer toute la configuration ?\",\n default: false,\n },\n ]);\n\n if (!confirm) {\n logger.info(\"Désinstallation annulée.\\n\");\n return;\n }\n }\n\n // 3. Remove configuration\n const spinner = logger.spinner(\"Suppression de la configuration...\");\n\n try {\n await rm(installPath, { recursive: true, force: true });\n spinner.succeed(\"Configuration supprimée\");\n } catch (error) {\n spinner.fail(\"Échec de la suppression\");\n throw error;\n }\n\n // 4. Success message\n console.log(\"\\n✓ Configuration Vibe Academy désinstallée avec succès!\\n\");\n logger.info(\"Pour réinstaller, exécutez: vibe-academy setup\\n\");\n } catch (error) {\n logger.error(\n error instanceof Error ? error.message : \"Une erreur est survenue\",\n );\n process.exit(1);\n }\n}\n","import { logger } from \"../utils/logger.js\";\nimport {\n isMacOS,\n getCredentials,\n getCurrentScopes,\n hasRequiredScopes,\n deleteCredentials,\n} from \"../utils/auth.js\";\n\ninterface AuthOptions {\n refresh?: boolean;\n status?: boolean;\n}\n\n/**\n * Auth command - manage Claude Code authentication\n */\nexport async function authCommand(options: AuthOptions): Promise<void> {\n if (!isMacOS()) {\n logger.error(\"Cette commande n'est disponible que sur macOS\");\n process.exit(1);\n }\n\n // Default to status if no option provided\n if (!options.refresh && !options.status) {\n options.status = true;\n }\n\n if (options.status) {\n await showAuthStatus();\n }\n\n if (options.refresh) {\n await refreshAuth();\n }\n}\n\n/**\n * Show current authentication status\n */\nasync function showAuthStatus(): Promise<void> {\n console.log(\"\\n🔐 Statut de l'authentification Claude Code\\n\");\n\n const creds = getCredentials();\n\n if (!creds?.claudeAiOauth) {\n logger.warn(\"Pas de credentials trouvés\");\n logger.info(\"Lancez 'claude' pour vous authentifier\");\n return;\n }\n\n const scopes = getCurrentScopes();\n const oauth = creds.claudeAiOauth;\n\n console.log(`📋 Scopes actuels: ${scopes.join(\", \")}`);\n\n if (oauth.subscriptionType) {\n console.log(`💳 Abonnement: ${oauth.subscriptionType}`);\n }\n\n if (oauth.rateLimitTier) {\n console.log(`📊 Rate limit tier: ${oauth.rateLimitTier}`);\n }\n\n console.log(\"\");\n\n if (hasRequiredScopes()) {\n logger.info(\"✅ Tous les scopes requis sont présents\");\n logger.info(\" La statusline peut afficher les infos d'usage Anthropic\");\n } else {\n logger.warn(\"⚠️ Scope 'user:profile' manquant\");\n logger.info(\" Lancez 'vibe-academy auth --refresh' pour corriger\");\n }\n\n console.log(\"\");\n}\n\n/**\n * Refresh authentication by deleting credentials\n */\nasync function refreshAuth(): Promise<void> {\n console.log(\"\\n🔄 Rafraîchissement de l'authentification\\n\");\n\n const inquirer = (await import(\"inquirer\")).default;\n\n const { confirm } = await inquirer.prompt<{ confirm: boolean }>([\n {\n type: \"confirm\",\n name: \"confirm\",\n message:\n \"Cela va supprimer vos credentials et vous devrez vous reconnecter. Continuer ?\",\n default: true,\n },\n ]);\n\n if (!confirm) {\n logger.info(\"Annulé\");\n return;\n }\n\n const deleted = deleteCredentials();\n\n if (!deleted) {\n logger.error(\"Impossible de supprimer les credentials\");\n process.exit(1);\n }\n\n console.log(\"\");\n logger.info(\"🔑 Credentials supprimés avec succès\");\n console.log(\"\");\n console.log(\"Prochaines étapes:\");\n console.log(\" 1. Lancez 'claude' dans un terminal\");\n console.log(\" 2. Authentifiez-vous via le navigateur\");\n console.log(\" 3. Les nouveaux credentials auront les bons scopes\");\n console.log(\"\");\n}\n"],"mappings":";;;;;;;;;;;;AACA,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAF9B;AAAA;AAAA;AAAA;AAAA;;;ACAA,OAAO,WAAW;AAClB,OAAO,SAAkB;AADzB,IAGa;AAHb;AAAA;AAAA;AAAA;AAGO,IAAM,SAAS;AAAA,MACpB,UAAU;AACR,gBAAQ,IAAI,MAAM,KAAK,KAAK,gCAAyB,CAAC;AACtD,gBAAQ,IAAI,6DAA6D;AAAA,MAC3E;AAAA,MAEA,QAAQA,OAAc;AACpB,gBAAQ,IAAI,MAAM,MAAM,qCAA6B,CAAC;AACtD,gBAAQ,IAAI,oCAAiC,MAAM,KAAKA,KAAI,CAAC;AAAA,CAAI;AACjE,gBAAQ,IAAI,uBAAoB;AAChC,gBAAQ;AAAA,UACN;AAAA,QACF;AACA,gBAAQ;AAAA,UACN;AAAA,QACF;AACA,gBAAQ,IAAI,wCAA8B;AAAA,MAC5C;AAAA,MAEA,MAAM,SAAiB;AACrB,gBAAQ,MAAM,MAAM,IAAI;AAAA,iBAAe,OAAO;AAAA,CAAI,CAAC;AAAA,MACrD;AAAA,MAEA,KAAK,SAAiB;AACpB,gBAAQ,KAAK,MAAM,OAAO;AAAA,gBAAS,OAAO;AAAA,CAAI,CAAC;AAAA,MACjD;AAAA,MAEA,KAAK,SAAiB;AACpB,gBAAQ,IAAI,MAAM,KAAK,iBAAO,OAAO,EAAE,CAAC;AAAA,MAC1C;AAAA,MAEA,QAAQ,MAAmB;AACzB,eAAO,IAAI,IAAI,EAAE,MAAM;AAAA,MACzB;AAAA,IACF;AAAA;AAAA;;;ACrCA;AAAA;AAAA;AAAA;AAAA,SAAS,SAAAC,QAAO,IAAI,aAAAC,YAAW,OAAO,YAAAC,iBAAgB;AACtD,SAAS,QAAAC,OAAM,eAAe;AAC9B,SAAS,iBAAAC,sBAAqB;AAC9B,SAAS,cAAAC,mBAAkB;AAG3B,SAAS,YAAAC,iBAAgB;AAWzB,SAAS,kBAA0B;AAGjC,QAAM,gBAAgBH,MAAKI,YAAW,cAAc;AAEpD,MAAIF,YAAW,aAAa,GAAG;AAC7B,WAAO;AAAA,EACT;AAEA,QAAM,IAAI,MAAM,qCAAqC,aAAa,EAAE;AACtE;AAKA,SAAS,gBAAwC;AAC/C,MAAI;AACF,IAAAC,UAAS,aAAa,EAAE,OAAO,SAAS,CAAC;AACzC,WAAO;AAAA,EACT,QAAQ;AACN,QAAI;AACF,MAAAA,UAAS,cAAc,EAAE,OAAO,SAAS,CAAC;AAC1C,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAKA,SAAS,qBAA8B;AACrC,MAAI;AACF,IAAAA,UAAS,iBAAiB,EAAE,OAAO,SAAS,CAAC;AAC7C,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,eAAe,eAAe,SAAgD;AAC5E,QAAM,UAAU,OAAO;AAAA,IACrB,qDAAkD,OAAO;AAAA,EAC3D;AAEA,MAAI;AAEF,UAAM,cAAc;AAEpB,QAAI,YAAY,OAAO;AACrB,MAAAA,UAAS,kBAAkB,WAAW,IAAI,EAAE,OAAO,SAAS,CAAC;AAAA,IAC/D,WAAW,YAAY,QAAQ;AAC7B,MAAAA,UAAS,eAAe,WAAW,IAAI,EAAE,OAAO,SAAS,CAAC;AAAA,IAC5D,OAAO;AACL,MAAAA,UAAS,kBAAkB,WAAW,IAAI,EAAE,OAAO,SAAS,CAAC;AAAA,IAC/D;AAEA,YAAQ,QAAQ,4BAAyB,OAAO,EAAE;AAAA,EACpD,SAAS,OAAO;AACd,YAAQ,KAAK,mDAAgD;AAC7D,WAAO,KAAK,oDAAiD;AAAA,EAC/D;AACF;AAKA,eAAe,8BACb,WACA,SACe;AACf,QAAM,gBAAgBH,MAAK,WAAW,oBAAoB;AAE1D,QAAM,UAAU,OAAO;AAAA,IACrB,mDAAgD,OAAO;AAAA,EACzD;AAEA,MAAI;AACF,IAAAG,UAAS,GAAG,OAAO,YAAY;AAAA,MAC7B,KAAK;AAAA,MACL,OAAO;AAAA,IACT,CAAC;AACD,YAAQ,QAAQ,gDAA0C,OAAO,EAAE;AAAA,EACrE,SAAS,OAAO;AACd,YAAQ,KAAK,0DAAoD;AACjE,UAAM;AAAA,EACR;AACF;AAKA,eAAe,iBACb,WACA,SACA,QACe;AACf,QAAM,eAAeH;AAAA,IACnB,gBAAgB;AAAA,IAChB;AAAA,EACF;AACA,QAAM,WAAW,MAAMD,UAAS,cAAc,OAAO;AACrD,QAAM,cAAc,KAAK,MAAM,QAAQ;AAGvC,QAAM,cAAc,KAAK,UAAU,aAAa,MAAM,CAAC,EACpD,QAAQ,sCAAsC,SAAS,EACvD,QAAQ,YAAY,OAAO;AAE9B,QAAM,WAAW,KAAK,MAAM,WAAW;AAGvC,MAAI,CAAC,OAAO,OAAO;AACjB,WAAO,SAAS;AAAA,EAClB;AAGA,MAAI,CAAC,OAAO,YAAY;AACtB,WAAO,SAAS;AAAA,EAClB;AAEA,QAAMD;AAAA,IACJE,MAAK,WAAW,eAAe;AAAA,IAC/B,KAAK,UAAU,UAAU,MAAM,CAAC;AAAA,EAClC;AACF;AAKA,eAAsB,cACpB,YACA,QACe;AACf,QAAM,eAAe,gBAAgB;AACrC,QAAM,YAAYA,MAAK,cAAc,SAAS;AAG9C,QAAM,UAAU,cAAc;AAC9B,SAAO,KAAK,0BAAoB,OAAO,EAAE;AAGzC,QAAMH,OAAM,YAAY,EAAE,WAAW,KAAK,CAAC;AAG3C,MAAI,OAAO,SAAS,OAAO,YAAY;AACrC,UAAM,iBAAiB,OAAO,QAAQ,sBAAsB;AAC5D,UAAM,GAAGG,MAAK,WAAW,SAAS,GAAGA,MAAK,YAAY,SAAS,GAAG;AAAA,MAChE,WAAW;AAAA,IACb,CAAC;AACD,mBAAe,QAAQ,mBAAgB;AAGvC,QAAI,OAAO,OAAO;AAChB,YAAM,MAAMA,MAAK,YAAY,6BAA6B,GAAG,GAAK;AAClE,YAAM,MAAMA,MAAK,YAAY,2BAA2B,GAAG,GAAK;AAAA,IAClE;AAAA,EACF;AAGA,QAAM,cAAc,OAAO,QAAQ,6BAA6B;AAChE,QAAM,GAAGA,MAAK,WAAW,MAAM,GAAGA,MAAK,YAAY,MAAM,GAAG;AAAA,IAC1D,WAAW;AAAA,EACb,CAAC;AACD,cAAY,QAAQ,0BAAuB;AAG3C,QAAM,kBAAkB,OAAO,QAAQ,sCAAgC;AACvE,QAAM,iBAAiB,YAAY,SAAS,MAAM;AAClD,kBAAgB,QAAQ,+BAAsB;AAG9C,MAAI,OAAO,YAAY;AACrB,UAAM,8BAA8B,YAAY,OAAO;AAGvD,QAAI,CAAC,mBAAmB,GAAG;AACzB,YAAM,eAAe,OAAO;AAAA,IAC9B,OAAO;AACL,aAAO,KAAK,uCAAyB;AAAA,IACvC;AAAA,EACF;AAGA,MAAI,OAAO,MAAM;AACf,UAAM,gBAAgB,OAAO,QAAQ,0BAAuB;AAC5D,UAAM,SAAS,eAAe,UAAU;AACxC,UAAMF,WAAUE,MAAK,YAAY,WAAW,GAAG,MAAM;AACrD,kBAAc,QAAQ,mBAAa;AAAA,EACrC;AACF;AAKA,SAAS,eAAe,aAA6B;AACnD,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAML,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA2Df;AA1RA,IASMK,aACAD;AAVN;AAAA;AAAA;AAAA;AAKA;AAIA,IAAMC,cAAaJ,eAAc,YAAY,GAAG;AAChD,IAAMG,aAAY,QAAQC,WAAU;AAAA;AAAA;;;ACVpC;AAEA,SAAS,eAAe;;;ACFxB;;;ACAA;AAAA,SAAS,gBAAgB;AACzB,SAAS,eAAe;AACxB,SAAS,YAAY;AAUrB,eAAsB,kBACpB,cACiB;AAEjB,MAAI,cAAc;AAChB,WAAO;AAAA,EACT;AAEA,MAAI;AAEF,UAAM,UAAU,SAAS,iCAAiC;AAAA,MACxD,UAAU;AAAA,MACV,OAAO,CAAC,QAAQ,QAAQ,QAAQ;AAAA,IAClC,CAAC,EAAE,KAAK;AAGR,WAAO,KAAK,SAAS,SAAS;AAAA,EAChC,QAAQ;AAEN,WAAO,KAAK,QAAQ,GAAG,SAAS;AAAA,EAClC;AACF;;;AD/BA;;;AEFA;AACA;AADA,SAAS,YAAAC,iBAAgB;AAGzB,IAAM,mBAAmB;AACzB,IAAM,kBAAkB,CAAC,kBAAkB,cAAc;AAelD,SAAS,UAAmB;AACjC,SAAO,QAAQ,aAAa;AAC9B;AAKO,SAAS,iBAA2C;AACzD,MAAI,CAAC,QAAQ,GAAG;AACd,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,SAASA;AAAA,MACb,sCAAsC,gBAAgB;AAAA,MACtD,EAAE,UAAU,SAAS,OAAO,CAAC,QAAQ,QAAQ,MAAM,EAAE;AAAA,IACvD;AACA,WAAO,KAAK,MAAM,OAAO,KAAK,CAAC;AAAA,EACjC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKO,SAAS,mBAA6B;AAC3C,QAAM,QAAQ,eAAe;AAC7B,SAAO,OAAO,eAAe,UAAU,CAAC;AAC1C;AAKO,SAAS,oBAA6B;AAC3C,QAAM,SAAS,iBAAiB;AAChC,SAAO,gBAAgB,MAAM,CAAC,aAAa,OAAO,SAAS,QAAQ,CAAC;AACtE;AAKO,SAAS,mBAA6B;AAC3C,QAAM,SAAS,iBAAiB;AAChC,SAAO,gBAAgB,OAAO,CAAC,aAAa,CAAC,OAAO,SAAS,QAAQ,CAAC;AACxE;AAKO,SAAS,oBAA6B;AAC3C,MAAI,CAAC,QAAQ,GAAG;AACd,WAAO;AAAA,EACT;AAEA,MAAI;AACF,IAAAA,UAAS,wCAAwC,gBAAgB,KAAK;AAAA,MACpE,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,IAChC,CAAC;AACD,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,eAAsB,qBAAuC;AAC3D,MAAI,CAAC,QAAQ,GAAG;AACd,WAAO;AAAA,MACL;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,eAAe;AAE7B,MAAI,CAAC,OAAO,eAAe;AACzB,WAAO,KAAK,yDAA4C;AACxD,WAAO,KAAK,mDAAmD;AAC/D,WAAO;AAAA,EACT;AAEA,MAAI,kBAAkB,GAAG;AACvB,WAAO,KAAK,kDAA6C;AACzD,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,iBAAiB;AACjC,SAAO,KAAK,mCAAyB,QAAQ,KAAK,IAAI,CAAC,EAAE;AACzD,SAAO;AAAA,IACL;AAAA,EACF;AACA,SAAO,KAAK,8DAA8D;AAE1E,QAAMC,aAAY,MAAM,OAAO,UAAU,GAAG;AAE5C,QAAM,EAAE,cAAc,IAAI,MAAMA,UAAS,OAAmC;AAAA,IAC1E;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,SACE;AAAA,MACF,SAAS;AAAA,IACX;AAAA,EACF,CAAC;AAED,MAAI,CAAC,eAAe;AAClB,WAAO;AAAA,MACL;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAGA,QAAM,UAAU,kBAAkB;AAClC,MAAI,CAAC,SAAS;AACZ,WAAO,MAAM,yCAAyC;AACtD,WAAO;AAAA,EACT;AAEA,SAAO,KAAK,uCAA6B;AACzC,SAAO;AAAA,IACL;AAAA,EACF;AACA,SAAO,KAAK,oCAAoC;AAGhD,UAAQ,KAAK,CAAC;AAChB;;;ACtJA;AAIA;AAJA,SAAS,WAAAC,gBAAe;AACxB,SAAS,QAAAC,aAAY;AACrB,SAAS,UAAU,WAAW,aAAa;AAC3C,SAAS,kBAAkB;AAG3B,IAAM,eAAe;AACrB,IAAM,aAAaA,MAAKD,SAAQ,GAAG,eAAe;AAClD,IAAM,cAAcC,MAAK,YAAY,aAAa;AAsBlD,eAAsB,eAAe,QAAyC;AAC5E,MAAI,CAAC,UAAU,OAAO,KAAK,MAAM,IAAI;AACnC,WAAO,EAAE,OAAO,OAAO,OAAO,kBAAe;AAAA,EAC/C;AAEA,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,cAAc;AAAA,MACzC,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,eAAe,UAAU,OAAO,KAAK,CAAC;AAAA,QACtC,gBAAgB;AAAA,MAClB;AAAA,IACF,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,UAAI,SAAS,WAAW,KAAK;AAC3B,eAAO,EAAE,OAAO,OAAO,OAAO,oCAA8B;AAAA,MAC9D;AACA,aAAO;AAAA,QACL,OAAO;AAAA,QACP,OAAO,mBAAmB,SAAS,MAAM;AAAA,MAC3C;AAAA,IACF;AAEA,UAAM,OAAO,MAAM,SAAS,KAAK;AAGjC,QAAI,MAAM,MAAM;AACd,aAAO;AAAA,QACL,OAAO;AAAA,QACP,MAAM,KAAK;AAAA,MACb;AAAA,IACF;AAEA,WAAO,EAAE,OAAO,OAAO,OAAO,0BAAuB;AAAA,EACvD,SAAS,OAAO;AACd,QAAI,iBAAiB,aAAa,MAAM,QAAQ,SAAS,OAAO,GAAG;AACjE,aAAO;AAAA,QACL,OAAO;AAAA,QACP,OAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO;AAAA,MACL,OAAO;AAAA,MACP,OAAO,qBAAkB,iBAAiB,QAAQ,MAAM,UAAU,UAAU;AAAA,IAC9E;AAAA,EACF;AACF;AAKA,eAAsB,kBAA0C;AAC9D,MAAI;AACF,QAAI,CAAC,WAAW,WAAW,GAAG;AAC5B,aAAO;AAAA,IACT;AACA,UAAM,UAAU,MAAM,SAAS,aAAa,OAAO;AACnD,UAAM,SAAqB,KAAK,MAAM,OAAO;AAC7C,WAAO,OAAO,UAAU;AAAA,EAC1B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,eAAsB,YAAY,QAA+B;AAC/D,MAAI;AAEF,QAAI,CAAC,WAAW,UAAU,GAAG;AAC3B,YAAM,MAAM,YAAY,EAAE,WAAW,KAAK,CAAC;AAAA,IAC7C;AAEA,UAAM,SAAqB,EAAE,OAAO;AACpC,UAAM,UAAU,aAAa,KAAK,UAAU,QAAQ,MAAM,CAAC,GAAG,OAAO;AAAA,EACvE,SAAS,OAAO;AACd,UAAM,IAAI;AAAA,MACR,4CAAyC,iBAAiB,QAAQ,MAAM,UAAU,iBAAiB;AAAA,IACrG;AAAA,EACF;AACF;AAKA,eAAsB,cAA6B;AACjD,MAAI;AACF,QAAI,WAAW,WAAW,GAAG;AAC3B,YAAM,SAAqB,CAAC;AAC5B,YAAM,UAAU,aAAa,KAAK,UAAU,QAAQ,MAAM,CAAC,GAAG,OAAO;AAAA,IACvE;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AAMA,eAAsB,yBAA2C;AAE/D,QAAM,YAAY,MAAM,gBAAgB;AAExC,MAAI,WAAW;AACb,UAAM,UAAU,OAAO,QAAQ,iDAA2C;AAC1E,UAAM,SAAS,MAAM,eAAe,SAAS;AAC7C,YAAQ,KAAK;AAEb,QAAI,OAAO,SAAS,OAAO,MAAM;AAC/B,YAAM,OAAO,OAAO,KAAK,QAAQ,OAAO,KAAK,SAAS;AACtD,YAAM,UAAU,OAAO,KAAK,WAAW;AACvC,cAAQ;AAAA,QACN;AAAA,iCAA4B,IAAI,KAAK,OAAO,aAAU,UAAU,IAAI,MAAM,EAAE;AAAA;AAAA,MAC9E;AACA,aAAO;AAAA,IACT;AAGA,WAAO,KAAK,+CAAyC;AACrD,UAAM,YAAY;AAAA,EACpB;AAGA,SAAO,MAAM,gBAAgB;AAC/B;AAKA,eAAe,kBAAoC;AACjD,QAAMC,aAAY,MAAM,OAAO,UAAU,GAAG;AAE5C,UAAQ,IAAI,qDAA8C;AAC1D,UAAQ;AAAA,IACN;AAAA,EACF;AACA,UAAQ;AAAA,IACN;AAAA,EACF;AAEA,QAAM,EAAE,OAAO,IAAI,MAAMA,UAAS,OAA2B;AAAA,IAC3D;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,SAAS;AAAA,MACT,MAAM;AAAA,MACN,UAAU,CAAC,UAAkB;AAC3B,YAAI,CAAC,SAAS,MAAM,KAAK,MAAM,IAAI;AACjC,iBAAO;AAAA,QACT;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF,CAAC;AAED,QAAM,UAAU,OAAO,QAAQ,+BAA4B;AAC3D,QAAM,SAAS,MAAM,eAAe,MAAM;AAC1C,UAAQ,KAAK;AAEb,MAAI,CAAC,OAAO,OAAO;AACjB,WAAO,MAAM,OAAO,SAAS,iBAAc;AAC3C,YAAQ;AAAA,MACN;AAAA,IACF;AAGA,UAAM,EAAE,MAAM,IAAI,MAAMA,UAAS,OAA2B;AAAA,MAC1D;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,QACT,SAAS;AAAA,MACX;AAAA,IACF,CAAC;AAED,QAAI,OAAO;AACT,aAAO,gBAAgB;AAAA,IACzB;AAEA,WAAO;AAAA,EACT;AAGA,QAAM,YAAY,MAAM;AAExB,QAAM,OAAO,OAAO,MAAM,QAAQ,OAAO,MAAM,SAAS;AACxD,QAAM,UAAU,OAAO,MAAM,WAAW;AAExC,UAAQ,IAAI;AAAA,mBAAiB,IAAI,GAAG;AACpC,UAAQ;AAAA,IACN,gBAAgB,OAAO,aAAU,UAAU,IAAI,MAAM,EAAE,cAAc,UAAU,IAAI,MAAM,EAAE;AAAA;AAAA,EAC7F;AAEA,SAAO;AACT;;;AH1NA,SAAS,mBAA2B;AAClC,SAAO;AAAA,IACL,OAAO;AAAA,IACP,YAAY;AAAA,IACZ,MAAM;AAAA,EACR;AACF;AAMA,eAAsB,aAAa,SAAsC;AACvE,MAAI;AACF,WAAO,QAAQ;AAGf,UAAM,aAAa,MAAM,uBAAuB;AAChD,QAAI,CAAC,YAAY;AACf,aAAO;AAAA,QACL;AAAA,MACF;AACA,cAAQ,KAAK,CAAC;AAAA,IAChB;AAGA,UAAM,mBAAmB;AAGzB,QAAI;AAEJ,QAAI,QAAQ,QAAQ;AAElB,oBAAc,QAAQ;AAAA,IACxB,WAAW,QAAQ,MAAM;AAEvB,YAAM,UAAU,OAAO,QAAQ,0CAAuC;AACtE,oBAAc,MAAM,kBAAkB;AACtC,cAAQ,QAAQ,0BAA0B,WAAW,EAAE;AAAA,IACzD,OAAO;AAEL,oBAAc,MAAM,sBAAsB;AAAA,IAC5C;AAGA,UAAM,SAAS,QAAQ,OACnB,iBAAiB,IACjB,MAAM,aAAa,OAAO;AAE9B,WAAO,KAAK,kBAAkB,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC,EAAE;AAG/D,UAAM,EAAE,eAAAC,eAAc,IAAI,MAAM;AAChC,UAAMA,eAAc,aAAa,MAAM;AAGvC,WAAO,QAAQ,WAAW;AAAA,EAC5B,SAAS,OAAO;AACd,WAAO;AAAA,MACL,iBAAiB,QAAQ,MAAM,UAAU;AAAA,IAC3C;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAKA,eAAe,wBAAyC;AACtD,QAAMC,aAAY,MAAM,OAAO,UAAU,GAAG;AAC5C,QAAM,KAAK,MAAM,OAAO,IAAI;AAC5B,QAAMC,QAAO,MAAM,OAAO,MAAM;AAGhC,QAAM,aAAa,QAAQ,IAAI;AAC/B,QAAM,YAAYA,MAAK,KAAK,YAAY,SAAS;AACjD,QAAM,aAAaA,MAAK,KAAK,GAAG,QAAQ,GAAG,SAAS;AAEpD,UAAQ,IAAI,iEAAuD;AAEnE,QAAM,EAAE,SAAS,IAAI,MAAMD,UAAS,OAEjC;AAAA,IACD;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,SAAS;AAAA,MACT,SAAS;AAAA,QACP;AAAA,UACE,MAAM,6CAAsC,UAAU;AAAA,UACtD,OAAO;AAAA,QACT;AAAA,QACA;AAAA,UACE,MAAM,+CAAwC,SAAS;AAAA,UACvD,OAAO;AAAA,QACT;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AAED,MAAI;AAEJ,MAAI,aAAa,UAAU;AACzB,UAAM,EAAE,WAAW,IAAI,MAAMA,UAAS,OAA+B;AAAA,MACnE;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,QACT,SAAS;AAAA,MACX;AAAA,IACF,CAAC;AAGD,kBAAc,WAAW,WAAW,GAAG,IACnC,WAAW,QAAQ,KAAK,GAAG,QAAQ,CAAC,IACpC;AAAA,EACN,WAAW,aAAa,SAAS;AAC/B,kBAAc;AAAA,EAChB,OAAO;AACL,kBAAc;AAAA,EAChB;AAEA,SAAO,KAAK;AAAA,+BAA2B,WAAW;AAAA,CAAI;AAEtD,SAAO;AACT;AAMA,eAAe,aAAa,SAAwC;AAClE,QAAMA,aAAY,MAAM,OAAO,UAAU,GAAG;AAE5C,UAAQ;AAAA,IACN;AAAA,EACF;AAEA,QAAM,EAAE,WAAW,IAAI,MAAMA,UAAS,OAAiC;AAAA,IACrE;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,SAAS;AAAA,MACT,SAAS;AAAA,QACP;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,UACP,SAAS,CAAC,QAAQ;AAAA,QACpB;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,UACP,SAAS,CAAC,QAAQ;AAAA,QACpB;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,UACP,SAAS;AAAA,QACX;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,UACP,SAAS,CAAC,QAAQ;AAAA,QACpB;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL,OAAO,WAAW,SAAS,OAAO;AAAA,IAClC,YAAY,WAAW,SAAS,YAAY;AAAA,IAC5C,MAAM,WAAW,SAAS,MAAM;AAAA,EAClC;AACF;;;AI1LA;AAEA;AACA;AACA,SAAS,cAAAE,mBAAkB;AAC3B,SAAS,MAAAC,WAAU;AAMnB,eAAe,aAAa,aAAoC;AAC9D,QAAM,aAAa,GAAG,WAAW,WAAW,KAAK,IAAI,CAAC;AACtD,QAAM,UAAU,OAAO,QAAQ,0BAAuB;AAEtD,MAAI;AACF,UAAMA,IAAG,aAAa,YAAY,EAAE,WAAW,KAAK,CAAC;AACrD,YAAQ,QAAQ,sBAAgB,UAAU,EAAE;AAAA,EAC9C,SAAS,OAAO;AACd,YAAQ,KAAK,sCAAgC;AAC7C,UAAM;AAAA,EACR;AACF;AAMA,eAAsB,cAAc,SAAuC;AACzE,MAAI;AACF,WAAO,KAAK,gEAAsD;AAGlE,UAAM,cAAc,MAAM,kBAAkB,QAAQ,MAAM;AAG1D,QAAI,CAACD,YAAW,WAAW,GAAG;AAC5B,aAAO;AAAA,QACL,wCAAqC,WAAW;AAAA;AAAA,MAClD;AACA,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,WAAO,KAAK,6BAA0B,WAAW,EAAE;AAGnD,UAAM,aAAa,WAAW;AAG9B,UAAM,SAAS;AAAA,MACb,OAAO;AAAA,MACP,YAAY;AAAA,MACZ,MAAM;AAAA,IACR;AAEA,UAAM,cAAc,aAAa,MAAM;AAGvC,WAAO,QAAQ,WAAW;AAC1B,WAAO;AAAA,MACL;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,WAAO;AAAA,MACL,iBAAiB,QAAQ,MAAM,UAAU;AAAA,IAC3C;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;;;ACpEA;AAEA;AACA,SAAS,cAAAE,mBAAkB;AAC3B,SAAS,UAAU;AACnB,OAAO,cAAc;AAMrB,eAAsB,iBACpB,SACe;AACf,MAAI;AACF,WAAO,KAAK,qEAAwD;AAGpE,UAAM,cAAc,MAAM,kBAAkB,QAAQ,MAAM;AAG1D,QAAI,CAACA,YAAW,WAAW,GAAG;AAC5B,aAAO,MAAM,wCAAqC,WAAW,EAAE;AAC/D,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,WAAO,KAAK,iCAA8B,WAAW;AAAA,CAAI;AAGzD,QAAI,CAAC,QAAQ,OAAO;AAClB,YAAM,EAAE,QAAQ,IAAI,MAAM,SAAS,OAA6B;AAAA,QAC9D;AAAA,UACE,MAAM;AAAA,UACN,MAAM;AAAA,UACN,SACE;AAAA,UACF,SAAS;AAAA,QACX;AAAA,MACF,CAAC;AAED,UAAI,CAAC,SAAS;AACZ,eAAO,KAAK,kCAA4B;AACxC;AAAA,MACF;AAAA,IACF;AAGA,UAAM,UAAU,OAAO,QAAQ,oCAAoC;AAEnE,QAAI;AACF,YAAM,GAAG,aAAa,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACtD,cAAQ,QAAQ,4BAAyB;AAAA,IAC3C,SAAS,OAAO;AACd,cAAQ,KAAK,4BAAyB;AACtC,YAAM;AAAA,IACR;AAGA,YAAQ,IAAI,0EAA4D;AACxE,WAAO,KAAK,wDAAkD;AAAA,EAChE,SAAS,OAAO;AACd,WAAO;AAAA,MACL,iBAAiB,QAAQ,MAAM,UAAU;AAAA,IAC3C;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;;;AClEA;AAAA;AAiBA,eAAsB,YAAY,SAAqC;AACrE,MAAI,CAAC,QAAQ,GAAG;AACd,WAAO,MAAM,+CAA+C;AAC5D,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,MAAI,CAAC,QAAQ,WAAW,CAAC,QAAQ,QAAQ;AACvC,YAAQ,SAAS;AAAA,EACnB;AAEA,MAAI,QAAQ,QAAQ;AAClB,UAAM,eAAe;AAAA,EACvB;AAEA,MAAI,QAAQ,SAAS;AACnB,UAAM,YAAY;AAAA,EACpB;AACF;AAKA,eAAe,iBAAgC;AAC7C,UAAQ,IAAI,wDAAiD;AAE7D,QAAM,QAAQ,eAAe;AAE7B,MAAI,CAAC,OAAO,eAAe;AACzB,WAAO,KAAK,+BAA4B;AACxC,WAAO,KAAK,wCAAwC;AACpD;AAAA,EACF;AAEA,QAAM,SAAS,iBAAiB;AAChC,QAAM,QAAQ,MAAM;AAEpB,UAAQ,IAAI,6BAAsB,OAAO,KAAK,IAAI,CAAC,EAAE;AAErD,MAAI,MAAM,kBAAkB;AAC1B,YAAQ,IAAI,yBAAkB,MAAM,gBAAgB,EAAE;AAAA,EACxD;AAEA,MAAI,MAAM,eAAe;AACvB,YAAQ,IAAI,8BAAuB,MAAM,aAAa,EAAE;AAAA,EAC1D;AAEA,UAAQ,IAAI,EAAE;AAEd,MAAI,kBAAkB,GAAG;AACvB,WAAO,KAAK,gDAAwC;AACpD,WAAO,KAAK,4DAA4D;AAAA,EAC1E,OAAO;AACL,WAAO,KAAK,6CAAmC;AAC/C,WAAO,KAAK,uDAAuD;AAAA,EACrE;AAEA,UAAQ,IAAI,EAAE;AAChB;AAKA,eAAe,cAA6B;AAC1C,UAAQ,IAAI,yDAA+C;AAE3D,QAAMC,aAAY,MAAM,OAAO,UAAU,GAAG;AAE5C,QAAM,EAAE,QAAQ,IAAI,MAAMA,UAAS,OAA6B;AAAA,IAC9D;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,SACE;AAAA,MACF,SAAS;AAAA,IACX;AAAA,EACF,CAAC;AAED,MAAI,CAAC,SAAS;AACZ,WAAO,KAAK,WAAQ;AACpB;AAAA,EACF;AAEA,QAAM,UAAU,kBAAkB;AAElC,MAAI,CAAC,SAAS;AACZ,WAAO,MAAM,yCAAyC;AACtD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,UAAQ,IAAI,EAAE;AACd,SAAO,KAAK,mDAAsC;AAClD,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,uBAAoB;AAChC,UAAQ,IAAI,uCAAuC;AACnD,UAAQ,IAAI,0CAA0C;AACtD,UAAQ,IAAI,sDAAsD;AAClE,UAAQ,IAAI,EAAE;AAChB;;;AP3GA,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,cAAc,EACnB,YAAY,8DAA8D,EAC1E,QAAQ,OAAO;AAElB,QACG,QAAQ,OAAO,EACf,YAAY,yCAAyC,EACrD,OAAO,UAAU,+CAA+C,EAChE,OAAO,mBAAmB,+BAA+B,EACzD,OAAO,cAAc,8CAAwC,EAC7D,OAAO,mBAAmB,iDAA8C,EACxE,OAAO,aAAa,iCAAiC,EACrD,OAAO,YAAY;AAEtB,QACG,QAAQ,QAAQ,EAChB,YAAY,gDAA6C,EACzD,OAAO,mBAAmB,+BAA+B,EACzD,OAAO,aAAa;AAEvB,QACG,QAAQ,WAAW,EACnB,YAAY,8CAA2C,EACvD,OAAO,mBAAmB,+BAA+B,EACzD,OAAO,WAAW,yCAAyC,EAC3D,OAAO,gBAAgB;AAE1B,QACG,QAAQ,MAAM,EACd,YAAY,yCAAsC,EAClD,OAAO,YAAY,uCAAuC,EAC1D;AAAA,EACC;AAAA,EACA;AACF,EACC,OAAO,WAAW;AAErB,QAAQ,MAAM;","names":["path","mkdir","writeFile","readFile","join","fileURLToPath","existsSync","execSync","__dirname","__filename","execSync","inquirer","homedir","join","inquirer","installConfig","inquirer","path","existsSync","cp","existsSync","inquirer"]}
|
|
1
|
+
{"version":3,"sources":["../node_modules/tsup/assets/esm_shims.js","../src/utils/logger.ts","../src/utils/files.ts","../src/index.ts","../src/commands/setup.ts","../src/utils/paths.ts","../src/utils/auth.ts","../src/utils/vibe-auth.ts","../src/commands/update.ts","../src/commands/uninstall.ts","../src/commands/auth.ts"],"sourcesContent":["// Shim globals in esm bundle\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst getFilename = () => fileURLToPath(import.meta.url)\nconst getDirname = () => path.dirname(getFilename())\n\nexport const __dirname = /* @__PURE__ */ getDirname()\nexport const __filename = /* @__PURE__ */ getFilename()\n","import chalk from \"chalk\";\nimport ora, { Ora } from \"ora\";\n\nexport const logger = {\n welcome() {\n console.log(chalk.cyan.bold(\"\\n🎓 Vibe Academy CLI\\n\"));\n console.log(\"Installation des presets Claude Code pour Vibe Academy...\\n\");\n },\n\n success(path: string) {\n console.log(chalk.green(\"\\n✓ Installation réussie!\\n\"));\n console.log(`Configuration installée dans: ${chalk.cyan(path)}\\n`);\n console.log(\"Prochaines étapes:\");\n console.log(\n \" 1. Ajouter le marketplace: /plugin marketplace add matthieucousin/vibe-academy-marketplace\",\n );\n console.log(\n \" 2. Installer les plugins: /plugin install vibe-academy@vibe-academy-marketplace\",\n );\n console.log(\" 3. Commencer à coder! 🎉\\n\");\n },\n\n error(message: string) {\n console.error(chalk.red(`\\n✗ Erreur: ${message}\\n`));\n },\n\n warn(message: string) {\n console.warn(chalk.yellow(`\\n⚠️ ${message}\\n`));\n },\n\n info(message: string) {\n console.log(chalk.blue(`ℹ️ ${message}`));\n },\n\n spinner(text: string): Ora {\n return ora(text).start();\n },\n};\n","import { mkdir, cp, writeFile, chmod, readFile } from \"fs/promises\";\nimport { join, dirname } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { existsSync } from \"fs\";\nimport type { Config } from \"../types/index.js\";\nimport { logger } from \"./logger.js\";\nimport { execSync } from \"child_process\";\n\n/**\n * Check if running on Windows\n */\nconst isWindows = process.platform === \"win32\";\n\n/**\n * Get the command to check if a binary exists\n */\nfunction getWhichCommand(): string {\n return isWindows ? \"where\" : \"which\";\n}\n\n// Get __dirname equivalent in ES modules\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n/**\n * Get the templates directory path\n * In production: package_root/templates\n * In development: cli/templates\n */\nfunction getTemplatesDir(): string {\n // tsup compiles everything into a single dist/index.js file\n // So __dirname will be dist/, and we need to go up one level to templates/\n const templatesPath = join(__dirname, \"../templates\");\n\n if (existsSync(templatesPath)) {\n return templatesPath;\n }\n\n throw new Error(`Templates directory not found at: ${templatesPath}`);\n}\n\n/**\n * Detect available runtime (bun, pnpm, npm)\n */\nfunction detectRuntime(): \"bun\" | \"pnpm\" | \"npm\" {\n const which = getWhichCommand();\n try {\n execSync(`${which} bun`, { stdio: \"ignore\" });\n return \"bun\";\n } catch {\n try {\n execSync(`${which} pnpm`, { stdio: \"ignore\" });\n return \"pnpm\";\n } catch {\n return \"npm\";\n }\n }\n}\n\n/**\n * Check if ccusage is installed\n */\nfunction isCcusageInstalled(): boolean {\n const which = getWhichCommand();\n try {\n execSync(`${which} ccusage`, { stdio: \"ignore\" });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Install ccusage globally for daily cost tracking\n */\nasync function installCcusage(runtime: \"bun\" | \"pnpm\" | \"npm\"): Promise<void> {\n const spinner = logger.spinner(\n `Installation de ccusage (coût journalier) avec ${runtime}...`,\n );\n\n try {\n // ccusage package name\n const packageName = \"@erinreily/ccusage\";\n\n if (runtime === \"bun\") {\n execSync(`bun install -g ${packageName}`, { stdio: \"ignore\" });\n } else if (runtime === \"pnpm\") {\n execSync(`pnpm add -g ${packageName}`, { stdio: \"ignore\" });\n } else {\n execSync(`npm install -g ${packageName}`, { stdio: \"ignore\" });\n }\n\n spinner.succeed(`ccusage installé avec ${runtime}`);\n } catch (error) {\n spinner.fail(`Échec de l'installation de ccusage (optionnel)`);\n logger.warn(\"Le coût journalier affichera $0.00 sans ccusage\");\n }\n}\n\n/**\n * Install statusline dependencies\n */\nasync function installStatuslineDependencies(\n claudeDir: string,\n runtime: \"bun\" | \"pnpm\" | \"npm\",\n): Promise<void> {\n const statuslineDir = join(claudeDir, \"scripts/statusline\");\n\n const spinner = logger.spinner(\n `Installation des dépendances statusline avec ${runtime}...`,\n );\n\n try {\n execSync(`${runtime} install`, {\n cwd: statuslineDir,\n stdio: \"ignore\",\n });\n spinner.succeed(`Dépendances statusline installées avec ${runtime}`);\n } catch (error) {\n spinner.fail(`Échec de l'installation des dépendances statusline`);\n throw error;\n }\n}\n\n/**\n * Check if bun is available\n */\nfunction isBunAvailable(): boolean {\n const which = getWhichCommand();\n try {\n execSync(`${which} bun`, { stdio: \"ignore\" });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Get the script runner for the current platform\n * - .js files: use node (works everywhere)\n * - .ts files: use bun if available, otherwise npx tsx\n */\nfunction getScriptRunner(scriptPath: string): string {\n const hasBun = isBunAvailable();\n\n if (scriptPath.endsWith(\".js\")) {\n // For .js files, use node (universal)\n return \"node\";\n }\n\n if (scriptPath.endsWith(\".ts\")) {\n // For .ts files, prefer bun (faster), fallback to npx tsx\n if (hasBun) {\n return \"bun\";\n }\n // npx tsx works on all platforms with Node.js\n return \"npx tsx\";\n }\n\n // Default to the detected runtime\n return hasBun ? \"bun\" : \"node\";\n}\n\n/**\n * Get the audio player command for the current platform\n */\nfunction getAudioCommand(audioFile: string): string {\n if (process.platform === \"darwin\") {\n return `afplay -v 0.1 ${audioFile}`;\n } else if (isWindows) {\n // PowerShell command to play audio on Windows\n return `powershell -c \"(New-Object Media.SoundPlayer '${audioFile}').PlaySync()\"`;\n } else {\n // Linux - try aplay or paplay\n return `aplay -q ${audioFile} 2>/dev/null || paplay ${audioFile} 2>/dev/null || true`;\n }\n}\n\n/**\n * Recursively replace paths and runtime in an object\n * This avoids string manipulation on JSON which can break on Windows paths\n */\nfunction replacePathsInObject(\n obj: unknown,\n claudeDir: string,\n _runtime: string,\n): unknown {\n if (typeof obj === \"string\") {\n // Check if this is an audio command (afplay)\n if (obj.includes(\"afplay\")) {\n // Extract the audio file path and create platform-specific command\n const match = obj.match(/afplay\\s+-v\\s+[\\d.]+\\s+(.+)$/);\n if (match) {\n const audioPath = match[1].replace(\n /\\/Users\\/matthieucousin\\/\\.claude/g,\n claudeDir,\n );\n return getAudioCommand(audioPath);\n }\n }\n\n // Replace template path with actual path\n let result = obj.replace(/\\/Users\\/matthieucousin\\/\\.claude/g, claudeDir);\n\n // Smart runtime replacement based on file extension\n // Match pattern: \"bun /path/to/script.js\" or \"bun /path/to/script.ts\"\n const scriptMatch = result.match(/^bun\\s+(.+\\.(js|ts))$/);\n if (scriptMatch) {\n const scriptPath = scriptMatch[1];\n const runner = getScriptRunner(scriptPath);\n result = `${runner} ${scriptPath}`;\n }\n\n return result;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => replacePathsInObject(item, claudeDir, _runtime));\n }\n\n if (obj !== null && typeof obj === \"object\") {\n const newObj: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n newObj[key] = replacePathsInObject(value, claudeDir, _runtime);\n }\n return newObj;\n }\n\n return obj;\n}\n\n/**\n * Generate settings.json with correct paths\n */\nasync function generateSettings(\n claudeDir: string,\n runtime: \"bun\" | \"pnpm\" | \"npm\",\n config: Config,\n): Promise<void> {\n const templatePath = join(\n getTemplatesDir(),\n \".claude/settings.json.template\",\n );\n const template = await readFile(templatePath, \"utf-8\");\n const settingsObj = JSON.parse(template);\n\n // Replace paths by manipulating the object directly (not string replacement)\n // This properly handles Windows backslashes in paths\n const settings = replacePathsInObject(\n settingsObj,\n claudeDir,\n runtime,\n ) as Record<string, unknown>;\n\n // Remove hooks if not enabled\n if (!config.hooks) {\n delete settings.hooks;\n }\n\n // Remove statusline if not enabled\n if (!config.statusline) {\n delete settings.statusLine;\n }\n\n await writeFile(\n join(claudeDir, \"settings.json\"),\n JSON.stringify(settings, null, 2),\n );\n}\n\n/**\n * Install all configuration files\n */\nexport async function installConfig(\n targetPath: string,\n config: Config,\n): Promise<void> {\n const templatesDir = getTemplatesDir();\n const sourceDir = join(templatesDir, \".claude\");\n\n // Detect runtime\n const runtime = detectRuntime();\n logger.info(`Runtime détecté: ${runtime}`);\n\n // Create target directory\n await mkdir(targetPath, { recursive: true });\n\n // 1. Copy scripts directory (only if hooks or statusline enabled)\n if (config.hooks || config.statusline) {\n const scriptsSpinner = logger.spinner(\"Copie des scripts...\");\n await cp(join(sourceDir, \"scripts\"), join(targetPath, \"scripts\"), {\n recursive: true,\n });\n scriptsSpinner.succeed(\"Scripts copiés\");\n\n // Make scripts executable\n if (config.hooks) {\n await chmod(join(targetPath, \"scripts/validate-command.js\"), 0o755);\n await chmod(join(targetPath, \"scripts/hook-post-file.ts\"), 0o755);\n }\n }\n\n // 2. Copy song directory (audio notifications) - always copy for now\n const songSpinner = logger.spinner(\"Copie des fichiers audio...\");\n await cp(join(sourceDir, \"song\"), join(targetPath, \"song\"), {\n recursive: true,\n });\n songSpinner.succeed(\"Fichiers audio copiés\");\n\n // 3. Generate settings.json with correct paths\n const settingsSpinner = logger.spinner(\"Génération de settings.json...\");\n await generateSettings(targetPath, runtime, config);\n settingsSpinner.succeed(\"settings.json généré\");\n\n // 4. Install statusline dependencies if enabled\n if (config.statusline) {\n await installStatuslineDependencies(targetPath, runtime);\n\n // 4b. Install ccusage for daily cost tracking (if not already installed)\n if (!isCcusageInstalled()) {\n await installCcusage(runtime);\n } else {\n logger.info(\"ccusage déjà installé ✓\");\n }\n }\n\n // 5. Create README if enabled\n if (config.docs) {\n const readmeSpinner = logger.spinner(\"Création du README...\");\n const readme = generateReadme(targetPath);\n await writeFile(join(targetPath, \"README.md\"), readme);\n readmeSpinner.succeed(\"README créé\");\n }\n}\n\n/**\n * Generate README with installation info\n */\nfunction generateReadme(installPath: string): string {\n return `# Vibe Academy Configuration\n\nConfiguration Claude Code installée avec succès !\n\n## Emplacement\n\n\\`${installPath}\\`\n\n## Contenu\n\n- **settings.json** : Configuration Claude Code avec hooks et statusline\n- **scripts/** : Scripts de validation et statusline\n- **song/** : Notifications audio\n- **README.md** : Ce fichier\n\n## Hooks installés\n\n### PreToolUse\n- Validation des commandes Bash dangereuses\n\n### Stop\n- Notification audio de fin de session\n\n### Notification\n- Notification audio pour demandes utilisateur\n\n### PostToolUse\n- Hook post-édition de fichiers\n\n## Statusline\n\nLa statusline affiche :\n- Branche Git actuelle\n- Style de sortie et modèle\n- Tokens utilisés (contexte)\n- Utilisation Anthropic (%) et temps restant (via API directe)\n\n## Prochaines étapes\n\n1. Ajouter le marketplace :\n \\`\\`\\`bash\n /plugin marketplace add ton-username/vibe-coding-marketplace\n \\`\\`\\`\n\n2. Installer les plugins :\n \\`\\`\\`bash\n /plugin install vibe-coding@vibe-coding-marketplace\n \\`\\`\\`\n\n3. Commencer à coder ! 🚀\n\n## Mise à jour\n\nPour mettre à jour cette configuration :\n\\`\\`\\`bash\nvibe-academy update\n\\`\\`\\`\n\n## Désinstallation\n\nPour désinstaller :\n\\`\\`\\`bash\nvibe-academy uninstall\n\\`\\`\\`\n`;\n}\n","#!/usr/bin/env node\n\nimport { Command } from \"commander\";\nimport { setupCommand } from \"./commands/setup.js\";\nimport { updateCommand } from \"./commands/update.js\";\nimport { uninstallCommand } from \"./commands/uninstall.js\";\nimport { authCommand } from \"./commands/auth.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"vibe-academy\")\n .description(\"CLI pour installer les presets Claude Code pour Vibe Academy\")\n .version(\"0.1.0\");\n\nprogram\n .command(\"setup\")\n .description(\"Installer la configuration Vibe Academy\")\n .option(\"--skip\", \"Skip les prompts interactifs et installe tout\")\n .option(\"--folder <path>\", \"Dossier d'installation custom\")\n .option(\"--no-hooks\", \"Ne pas installer les hooks de sécurité\")\n .option(\"--no-statusline\", \"Ne pas installer la statusline personnalisée\")\n .option(\"--no-docs\", \"Ne pas inclure la documentation\")\n .action(setupCommand);\n\nprogram\n .command(\"update\")\n .description(\"Mettre à jour la configuration Vibe Academy\")\n .option(\"--folder <path>\", \"Dossier d'installation custom\")\n .action(updateCommand);\n\nprogram\n .command(\"uninstall\")\n .description(\"Désinstaller la configuration Vibe Coding\")\n .option(\"--folder <path>\", \"Dossier d'installation custom\")\n .option(\"--force\", \"Forcer la suppression sans confirmation\")\n .action(uninstallCommand);\n\nprogram\n .command(\"auth\")\n .description(\"Gérer l'authentification Claude Code\")\n .option(\"--status\", \"Afficher le statut d'authentification\")\n .option(\n \"--refresh\",\n \"Rafraîchir l'authentification (pour obtenir les bons scopes)\",\n )\n .action(authCommand);\n\nprogram.parse();\n","import type { SetupOptions, Config } from \"../types/index.js\";\nimport { detectInstallPath } from \"../utils/paths.js\";\nimport { logger } from \"../utils/logger.js\";\nimport { checkAndPromptAuth } from \"../utils/auth.js\";\nimport { checkAndPromptVibeAuth } from \"../utils/vibe-auth.js\";\n\n/**\n * Get default configuration\n */\nfunction getDefaultConfig(): Config {\n return {\n hooks: true,\n statusline: true,\n docs: true,\n };\n}\n\n/**\n * Main setup command\n * Installs Claude Code configuration with hooks, statusline, and documentation\n */\nexport async function setupCommand(options: SetupOptions): Promise<void> {\n try {\n logger.welcome();\n\n // 0. Check Vibe Academy API key (required to use the CLI)\n const vibeAuthOk = await checkAndPromptVibeAuth();\n if (!vibeAuthOk) {\n logger.error(\n \"Authentification Vibe Academy requise pour utiliser cette CLI.\",\n );\n process.exit(1);\n }\n\n // 1. Check OAuth scopes (for statusline features)\n await checkAndPromptAuth();\n\n // 2. Determine installation path\n let installPath: string;\n\n if (options.folder) {\n // Custom folder provided via --folder\n installPath = options.folder;\n } else if (options.skip) {\n // Skip mode: auto-detect\n const spinner = logger.spinner(\"Détection du chemin d'installation...\");\n installPath = await detectInstallPath();\n spinner.succeed(`Chemin d'installation: ${installPath}`);\n } else {\n // Interactive mode: ask user\n installPath = await promptInstallLocation();\n }\n\n // 2. Get configuration (interactive or default)\n const config = options.skip\n ? getDefaultConfig()\n : await promptConfig(options);\n\n logger.info(`Configuration: ${JSON.stringify(config, null, 2)}`);\n\n // 3. Install files\n const { installConfig } = await import(\"../utils/files.js\");\n await installConfig(installPath, config);\n\n // 4. Success message\n logger.success(installPath);\n } catch (error) {\n logger.error(\n error instanceof Error ? error.message : \"Une erreur est survenue\",\n );\n process.exit(1);\n }\n}\n\n/**\n * Prompt user for installation location\n */\nasync function promptInstallLocation(): Promise<string> {\n const inquirer = (await import(\"inquirer\")).default;\n const os = await import(\"os\");\n const path = await import(\"path\");\n\n // Detect current project path\n const currentDir = process.cwd();\n const localPath = path.join(currentDir, \".claude\");\n const globalPath = path.join(os.homedir(), \".claude\");\n\n console.log(\"\\n📍 Où souhaitez-vous installer la configuration ?\\n\");\n\n const { location } = await inquirer.prompt<{\n location: \"global\" | \"local\" | \"custom\";\n }>([\n {\n type: \"list\",\n name: \"location\",\n message: \"Emplacement d'installation:\",\n choices: [\n {\n name: `🌍 Global - pour tous les projets (${globalPath})`,\n value: \"global\",\n },\n {\n name: `📁 Local - projet actuel uniquement (${localPath})`,\n value: \"local\",\n },\n {\n name: \"📂 Personnalisé - extraire le code sans rien écraser\",\n value: \"custom\",\n },\n ],\n },\n ]);\n\n let installPath: string;\n\n if (location === \"custom\") {\n const { customPath } = await inquirer.prompt<{ customPath: string }>([\n {\n type: \"input\",\n name: \"customPath\",\n message: \"Chemin du dossier:\",\n default: \"./vibe-academy-config\",\n },\n ]);\n\n // Expand ~ to home directory\n installPath = customPath.startsWith(\"~\")\n ? customPath.replace(\"~\", os.homedir())\n : customPath;\n } else if (location === \"local\") {\n installPath = localPath;\n } else {\n installPath = globalPath;\n }\n\n logger.info(`\\n📁 Installation dans: ${installPath}\\n`);\n\n return installPath;\n}\n\n/**\n * Prompt user for configuration options\n * Interactive selection with checkboxes\n */\nasync function promptConfig(options: SetupOptions): Promise<Config> {\n const inquirer = (await import(\"inquirer\")).default;\n\n console.log(\n \"\\n✨ Sélectionnez les composants à installer (utilisez espace pour sélectionner):\\n\",\n );\n\n const { components } = await inquirer.prompt<{ components: string[] }>([\n {\n type: \"checkbox\",\n name: \"components\",\n message: \"Composants à installer:\",\n choices: [\n {\n name: \"🔒 Hooks de sécurité (validation des commandes Bash)\",\n value: \"hooks\",\n checked: !options.noHooks,\n },\n {\n name: \"📊 Statusline personnalisée (git, tokens, usage Anthropic)\",\n value: \"statusline\",\n checked: !options.noStatusline,\n },\n {\n name: \"🔔 Notifications audio (finish, need-human)\",\n value: \"audio\",\n checked: true,\n },\n {\n name: \"📚 Documentation (README.md)\",\n value: \"docs\",\n checked: !options.noDocs,\n },\n ],\n },\n ]);\n\n return {\n hooks: components.includes(\"hooks\"),\n statusline: components.includes(\"statusline\"),\n docs: components.includes(\"docs\"),\n };\n}\n","import { execSync } from \"child_process\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\nimport { existsSync } from \"fs\";\n\n/**\n * Detect the installation path for .claude directory\n * Priority:\n * 1. Custom folder (if provided via --folder flag)\n * 2. Git repository root (if in a git repo)\n * 3. Home directory (~/.claude)\n */\nexport async function detectInstallPath(\n customFolder?: string,\n): Promise<string> {\n // If custom folder is provided, use it\n if (customFolder) {\n return customFolder;\n }\n\n try {\n // Try to find git repository root\n const gitRoot = execSync(\"git rev-parse --show-toplevel\", {\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"ignore\"],\n }).trim();\n\n // Install in .claude/ at the root of the git repo\n return join(gitRoot, \".claude\");\n } catch {\n // Not in a git repo, install globally in home directory\n return join(homedir(), \".claude\");\n }\n}\n\n/**\n * Check if a directory exists\n */\nexport function directoryExists(path: string): boolean {\n return existsSync(path);\n}\n\n/**\n * Get the path to embedded templates directory\n * This is the templates/ directory in the npm package\n */\nexport function getTemplatesPath(): string {\n // In production (npm package), templates are in the package root\n // In development, they're in the parent directory\n const prodPath = join(process.cwd(), \"templates\");\n const devPath = join(process.cwd(), \"..\", \"templates\");\n\n return existsSync(prodPath) ? prodPath : devPath;\n}\n","import { execSync } from \"child_process\";\nimport { logger } from \"./logger.js\";\n\nconst KEYCHAIN_SERVICE = \"Claude Code-credentials\";\nconst REQUIRED_SCOPES = [\"user:inference\", \"user:profile\"];\n\ninterface ClaudeCredentials {\n claudeAiOauth?: {\n accessToken: string;\n refreshToken: string;\n scopes: string[];\n subscriptionType?: string;\n rateLimitTier?: string;\n };\n}\n\n/**\n * Check if running on macOS\n */\nexport function isMacOS(): boolean {\n return process.platform === \"darwin\";\n}\n\n/**\n * Get Claude Code credentials from macOS Keychain\n */\nexport function getCredentials(): ClaudeCredentials | null {\n if (!isMacOS()) {\n return null;\n }\n\n try {\n const result = execSync(\n `security find-generic-password -s \"${KEYCHAIN_SERVICE}\" -w`,\n { encoding: \"utf-8\", stdio: [\"pipe\", \"pipe\", \"pipe\"] },\n );\n return JSON.parse(result.trim());\n } catch {\n return null;\n }\n}\n\n/**\n * Get current OAuth scopes\n */\nexport function getCurrentScopes(): string[] {\n const creds = getCredentials();\n return creds?.claudeAiOauth?.scopes ?? [];\n}\n\n/**\n * Check if user has all required scopes for statusline features\n */\nexport function hasRequiredScopes(): boolean {\n const scopes = getCurrentScopes();\n return REQUIRED_SCOPES.every((required) => scopes.includes(required));\n}\n\n/**\n * Get missing scopes\n */\nexport function getMissingScopes(): string[] {\n const scopes = getCurrentScopes();\n return REQUIRED_SCOPES.filter((required) => !scopes.includes(required));\n}\n\n/**\n * Delete credentials from keychain to force re-authentication\n */\nexport function deleteCredentials(): boolean {\n if (!isMacOS()) {\n return false;\n }\n\n try {\n execSync(`security delete-generic-password -s \"${KEYCHAIN_SERVICE}\"`, {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Check auth status and prompt for re-auth if needed\n * Returns true if auth is OK, false if user declined re-auth\n */\nexport async function checkAndPromptAuth(): Promise<boolean> {\n if (!isMacOS()) {\n logger.info(\n \"⚠️ Vérification des scopes OAuth non disponible (macOS uniquement)\",\n );\n return true;\n }\n\n const creds = getCredentials();\n\n if (!creds?.claudeAiOauth) {\n logger.info(\"⚠️ Pas de credentials Claude Code trouvés\");\n logger.info(\" Lancez 'claude' pour vous authentifier d'abord\");\n return true;\n }\n\n if (hasRequiredScopes()) {\n logger.info(\"✅ Scopes OAuth OK (user:profile disponible)\");\n return true;\n }\n\n const missing = getMissingScopes();\n logger.warn(`⚠️ Scopes manquants: ${missing.join(\", \")}`);\n logger.info(\n \" La statusline a besoin du scope 'user:profile' pour afficher\",\n );\n logger.info(\" le temps restant et l'utilisation depuis l'API Anthropic.\");\n\n const inquirer = (await import(\"inquirer\")).default;\n\n const { shouldRefresh } = await inquirer.prompt<{ shouldRefresh: boolean }>([\n {\n type: \"confirm\",\n name: \"shouldRefresh\",\n message:\n \"Voulez-vous vous ré-authentifier pour obtenir les bons scopes ?\",\n default: true,\n },\n ]);\n\n if (!shouldRefresh) {\n logger.info(\n \" La statusline fonctionnera sans les infos d'usage Anthropic\",\n );\n return true;\n }\n\n // Delete credentials\n const deleted = deleteCredentials();\n if (!deleted) {\n logger.error(\"Impossible de supprimer les credentials\");\n return false;\n }\n\n logger.info(\"\\n🔑 Credentials supprimés.\");\n logger.info(\n \" Veuillez lancer 'claude' dans un terminal pour vous reconnecter.\",\n );\n logger.info(\" Puis relancez cette commande.\\n\");\n\n // Exit so user can re-auth\n process.exit(0);\n}\n","import { homedir } from \"os\";\nimport { join } from \"path\";\nimport { readFile, writeFile, mkdir } from \"fs/promises\";\nimport { existsSync } from \"fs\";\nimport { logger } from \"./logger.js\";\n\nconst VIBE_API_URL = \"https://app.vibeacademy.eu/api/user\";\nconst CONFIG_DIR = join(homedir(), \".vibe-academy\");\nconst CONFIG_FILE = join(CONFIG_DIR, \"config.json\");\n\nexport interface VibeUserData {\n credits: number;\n email?: string;\n name?: string;\n [key: string]: unknown;\n}\n\nexport interface VibeAuthResult {\n valid: boolean;\n user?: VibeUserData;\n error?: string;\n}\n\ninterface VibeConfig {\n apiKey?: string;\n}\n\n/**\n * Validate a Vibe Academy API key by calling the API\n */\nexport async function validateApiKey(apiKey: string): Promise<VibeAuthResult> {\n if (!apiKey || apiKey.trim() === \"\") {\n return { valid: false, error: \"Clé API vide\" };\n }\n\n try {\n const response = await fetch(VIBE_API_URL, {\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${apiKey.trim()}`,\n \"Content-Type\": \"application/json\",\n },\n });\n\n if (!response.ok) {\n if (response.status === 401) {\n return { valid: false, error: \"Clé API invalide ou expirée\" };\n }\n return {\n valid: false,\n error: `Erreur serveur (${response.status})`,\n };\n }\n\n const data = await response.json();\n\n // The API returns { data: { credits, ... } }\n if (data?.data) {\n return {\n valid: true,\n user: data.data as VibeUserData,\n };\n }\n\n return { valid: false, error: \"Réponse API invalide\" };\n } catch (error) {\n if (error instanceof TypeError && error.message.includes(\"fetch\")) {\n return {\n valid: false,\n error: \"Pas de connexion internet. Vérifiez votre connexion.\",\n };\n }\n return {\n valid: false,\n error: `Erreur réseau: ${error instanceof Error ? error.message : \"inconnue\"}`,\n };\n }\n}\n\n/**\n * Get the stored API key from config file\n */\nexport async function getStoredApiKey(): Promise<string | null> {\n try {\n if (!existsSync(CONFIG_FILE)) {\n return null;\n }\n const content = await readFile(CONFIG_FILE, \"utf-8\");\n const config: VibeConfig = JSON.parse(content);\n return config.apiKey || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Store the API key in config file\n */\nexport async function storeApiKey(apiKey: string): Promise<void> {\n try {\n // Create config directory if it doesn't exist\n if (!existsSync(CONFIG_DIR)) {\n await mkdir(CONFIG_DIR, { recursive: true });\n }\n\n const config: VibeConfig = { apiKey };\n await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), \"utf-8\");\n } catch (error) {\n throw new Error(\n `Impossible de sauvegarder la clé API: ${error instanceof Error ? error.message : \"erreur inconnue\"}`,\n );\n }\n}\n\n/**\n * Clear the stored API key\n */\nexport async function clearApiKey(): Promise<void> {\n try {\n if (existsSync(CONFIG_FILE)) {\n const config: VibeConfig = {};\n await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), \"utf-8\");\n }\n } catch {\n // Ignore errors when clearing\n }\n}\n\n/**\n * Check if user has valid Vibe Academy auth, prompt if needed\n * Returns true if auth is valid, false if user declined\n */\nexport async function checkAndPromptVibeAuth(): Promise<boolean> {\n // Check for stored key first\n const storedKey = await getStoredApiKey();\n\n if (storedKey) {\n const spinner = logger.spinner(\"Vérification de votre clé Vibe Academy...\");\n const result = await validateApiKey(storedKey);\n spinner.stop();\n\n if (result.valid && result.user) {\n const name = result.user.name || result.user.email || \"Apprenant\";\n const credits = result.user.credits ?? 0;\n console.log(\n `\\n✅ Connecté en tant que ${name} (${credits} crédit${credits > 1 ? \"s\" : \"\"})\\n`,\n );\n return true;\n }\n\n // Key is invalid, need to re-authenticate\n logger.warn(\"Votre clé API a expiré ou est invalide.\");\n await clearApiKey();\n }\n\n // No valid key, prompt for one\n return await promptForApiKey();\n}\n\n/**\n * Prompt user for their Vibe Academy API key\n */\nasync function promptForApiKey(): Promise<boolean> {\n const inquirer = (await import(\"inquirer\")).default;\n\n console.log(\"\\n🔑 Authentification Vibe Academy requise\\n\");\n console.log(\n \" Pour utiliser cette CLI, vous devez être inscrit à la formation.\",\n );\n console.log(\n \" Votre clé API est disponible sur: https://app.vibeacademy.eu/outils\\n\",\n );\n\n const { apiKey } = await inquirer.prompt<{ apiKey: string }>([\n {\n type: \"password\",\n name: \"apiKey\",\n message: \"Entrez votre clé API Vibe Academy:\",\n mask: \"*\",\n validate: (input: string) => {\n if (!input || input.trim() === \"\") {\n return \"La clé API est requise\";\n }\n return true;\n },\n },\n ]);\n\n const spinner = logger.spinner(\"Validation de votre clé...\");\n const result = await validateApiKey(apiKey);\n spinner.stop();\n\n if (!result.valid) {\n logger.error(result.error || \"Clé invalide\");\n console.log(\n \"\\n Vérifiez votre clé sur https://app.vibeacademy.eu/outils\\n\",\n );\n\n // Ask if they want to retry\n const { retry } = await inquirer.prompt<{ retry: boolean }>([\n {\n type: \"confirm\",\n name: \"retry\",\n message: \"Voulez-vous réessayer ?\",\n default: true,\n },\n ]);\n\n if (retry) {\n return promptForApiKey();\n }\n\n return false;\n }\n\n // Valid key, store it\n await storeApiKey(apiKey);\n\n const name = result.user?.name || result.user?.email || \"Apprenant\";\n const credits = result.user?.credits ?? 0;\n\n console.log(`\\n✅ Bienvenue ${name}!`);\n console.log(\n ` Vous avez ${credits} crédit${credits > 1 ? \"s\" : \"\"} disponible${credits > 1 ? \"s\" : \"\"}.\\n`,\n );\n\n return true;\n}\n","import type { UpdateOptions } from \"../types/index.js\";\nimport { detectInstallPath } from \"../utils/paths.js\";\nimport { logger } from \"../utils/logger.js\";\nimport { installConfig } from \"../utils/files.js\";\nimport { existsSync } from \"fs\";\nimport { cp } from \"fs/promises\";\nimport { join } from \"path\";\n\n/**\n * Create backup of current configuration\n */\nasync function backupConfig(installPath: string): Promise<void> {\n const backupPath = `${installPath}.backup.${Date.now()}`;\n const spinner = logger.spinner(\"Création du backup...\");\n\n try {\n await cp(installPath, backupPath, { recursive: true });\n spinner.succeed(`Backup créé: ${backupPath}`);\n } catch (error) {\n spinner.fail(\"Échec de la création du backup\");\n throw error;\n }\n}\n\n/**\n * Update command\n * Updates the Claude Code configuration with latest templates\n */\nexport async function updateCommand(options: UpdateOptions): Promise<void> {\n try {\n logger.info(\"🔄 Mise à jour de la configuration Vibe Academy...\\n\");\n\n // 1. Determine installation path\n const installPath = await detectInstallPath(options.folder);\n\n // Check if configuration exists\n if (!existsSync(installPath)) {\n logger.error(\n `Aucune configuration trouvée dans ${installPath}\\nUtilisez 'vibe-academy setup' pour installer.`,\n );\n process.exit(1);\n }\n\n logger.info(`Configuration trouvée: ${installPath}`);\n\n // 2. Create backup\n await backupConfig(installPath);\n\n // 3. Install updated configuration (keep all features enabled)\n const config = {\n hooks: true,\n statusline: true,\n docs: true,\n };\n\n await installConfig(installPath, config);\n\n // 4. Success message\n logger.success(installPath);\n logger.info(\n \"💡 Si quelque chose ne fonctionne pas, restaurez le backup créé ci-dessus.\\n\",\n );\n } catch (error) {\n logger.error(\n error instanceof Error ? error.message : \"Une erreur est survenue\",\n );\n process.exit(1);\n }\n}\n","import type { UninstallOptions } from \"../types/index.js\";\nimport { detectInstallPath } from \"../utils/paths.js\";\nimport { logger } from \"../utils/logger.js\";\nimport { existsSync } from \"fs\";\nimport { rm } from \"fs/promises\";\nimport inquirer from \"inquirer\";\n\n/**\n * Uninstall command\n * Removes the Claude Code configuration\n */\nexport async function uninstallCommand(\n options: UninstallOptions,\n): Promise<void> {\n try {\n logger.warn(\"⚠️ Désinstallation de la configuration Vibe Academy\\n\");\n\n // 1. Determine installation path\n const installPath = await detectInstallPath(options.folder);\n\n // Check if configuration exists\n if (!existsSync(installPath)) {\n logger.error(`Aucune configuration trouvée dans ${installPath}`);\n process.exit(1);\n }\n\n logger.info(`Configuration à supprimer: ${installPath}\\n`);\n\n // 2. Ask for confirmation (unless --force)\n if (!options.force) {\n const { confirm } = await inquirer.prompt<{ confirm: boolean }>([\n {\n type: \"confirm\",\n name: \"confirm\",\n message:\n \"Êtes-vous sûr de vouloir supprimer toute la configuration ?\",\n default: false,\n },\n ]);\n\n if (!confirm) {\n logger.info(\"Désinstallation annulée.\\n\");\n return;\n }\n }\n\n // 3. Remove configuration\n const spinner = logger.spinner(\"Suppression de la configuration...\");\n\n try {\n await rm(installPath, { recursive: true, force: true });\n spinner.succeed(\"Configuration supprimée\");\n } catch (error) {\n spinner.fail(\"Échec de la suppression\");\n throw error;\n }\n\n // 4. Success message\n console.log(\"\\n✓ Configuration Vibe Academy désinstallée avec succès!\\n\");\n logger.info(\"Pour réinstaller, exécutez: vibe-academy setup\\n\");\n } catch (error) {\n logger.error(\n error instanceof Error ? error.message : \"Une erreur est survenue\",\n );\n process.exit(1);\n }\n}\n","import { logger } from \"../utils/logger.js\";\nimport {\n isMacOS,\n getCredentials,\n getCurrentScopes,\n hasRequiredScopes,\n deleteCredentials,\n} from \"../utils/auth.js\";\n\ninterface AuthOptions {\n refresh?: boolean;\n status?: boolean;\n}\n\n/**\n * Auth command - manage Claude Code authentication\n */\nexport async function authCommand(options: AuthOptions): Promise<void> {\n if (!isMacOS()) {\n logger.error(\"Cette commande n'est disponible que sur macOS\");\n process.exit(1);\n }\n\n // Default to status if no option provided\n if (!options.refresh && !options.status) {\n options.status = true;\n }\n\n if (options.status) {\n await showAuthStatus();\n }\n\n if (options.refresh) {\n await refreshAuth();\n }\n}\n\n/**\n * Show current authentication status\n */\nasync function showAuthStatus(): Promise<void> {\n console.log(\"\\n🔐 Statut de l'authentification Claude Code\\n\");\n\n const creds = getCredentials();\n\n if (!creds?.claudeAiOauth) {\n logger.warn(\"Pas de credentials trouvés\");\n logger.info(\"Lancez 'claude' pour vous authentifier\");\n return;\n }\n\n const scopes = getCurrentScopes();\n const oauth = creds.claudeAiOauth;\n\n console.log(`📋 Scopes actuels: ${scopes.join(\", \")}`);\n\n if (oauth.subscriptionType) {\n console.log(`💳 Abonnement: ${oauth.subscriptionType}`);\n }\n\n if (oauth.rateLimitTier) {\n console.log(`📊 Rate limit tier: ${oauth.rateLimitTier}`);\n }\n\n console.log(\"\");\n\n if (hasRequiredScopes()) {\n logger.info(\"✅ Tous les scopes requis sont présents\");\n logger.info(\" La statusline peut afficher les infos d'usage Anthropic\");\n } else {\n logger.warn(\"⚠️ Scope 'user:profile' manquant\");\n logger.info(\" Lancez 'vibe-academy auth --refresh' pour corriger\");\n }\n\n console.log(\"\");\n}\n\n/**\n * Refresh authentication by deleting credentials\n */\nasync function refreshAuth(): Promise<void> {\n console.log(\"\\n🔄 Rafraîchissement de l'authentification\\n\");\n\n const inquirer = (await import(\"inquirer\")).default;\n\n const { confirm } = await inquirer.prompt<{ confirm: boolean }>([\n {\n type: \"confirm\",\n name: \"confirm\",\n message:\n \"Cela va supprimer vos credentials et vous devrez vous reconnecter. Continuer ?\",\n default: true,\n },\n ]);\n\n if (!confirm) {\n logger.info(\"Annulé\");\n return;\n }\n\n const deleted = deleteCredentials();\n\n if (!deleted) {\n logger.error(\"Impossible de supprimer les credentials\");\n process.exit(1);\n }\n\n console.log(\"\");\n logger.info(\"🔑 Credentials supprimés avec succès\");\n console.log(\"\");\n console.log(\"Prochaines étapes:\");\n console.log(\" 1. Lancez 'claude' dans un terminal\");\n console.log(\" 2. Authentifiez-vous via le navigateur\");\n console.log(\" 3. Les nouveaux credentials auront les bons scopes\");\n console.log(\"\");\n}\n"],"mappings":"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","names":["path","mkdir","writeFile","readFile","join","fileURLToPath","existsSync","execSync","__dirname","__filename","execSync","inquirer","homedir","join","inquirer","installConfig","inquirer","path","existsSync","cp","existsSync","inquirer"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibe-academy-cli",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "CLI pour installer les presets Claude Code pour la formation Vibe Academy. Installe statusline, hooks de sécurité et notifications audio.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { defaultConfig } from "../statusline.config";
|
|
4
4
|
import { getContextData } from "./lib/context";
|
|
@@ -6,6 +6,10 @@ import { colors, formatPath } from "./lib/formatters";
|
|
|
6
6
|
import { getGitInfo } from "./lib/git";
|
|
7
7
|
import type { HookInput } from "./lib/types";
|
|
8
8
|
import { getUsageLimits } from "./lib/usage-limits";
|
|
9
|
+
import { execSync } from "child_process";
|
|
10
|
+
|
|
11
|
+
// Check if running in Bun
|
|
12
|
+
const isBun = typeof Bun !== "undefined";
|
|
9
13
|
|
|
10
14
|
// Helper functions for formatting
|
|
11
15
|
function formatCost(cost: number): string {
|
|
@@ -24,17 +28,61 @@ function formatTokens(tokens: number): string {
|
|
|
24
28
|
return tokens.toString();
|
|
25
29
|
}
|
|
26
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Read JSON from stdin - works with both Bun and Node.js
|
|
33
|
+
*/
|
|
34
|
+
async function readStdinJson<T>(): Promise<T> {
|
|
35
|
+
if (isBun) {
|
|
36
|
+
return Bun.stdin.json();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Node.js fallback
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
let data = "";
|
|
42
|
+
process.stdin.setEncoding("utf-8");
|
|
43
|
+
process.stdin.on("data", (chunk) => {
|
|
44
|
+
data += chunk;
|
|
45
|
+
});
|
|
46
|
+
process.stdin.on("end", () => {
|
|
47
|
+
try {
|
|
48
|
+
resolve(JSON.parse(data));
|
|
49
|
+
} catch (e) {
|
|
50
|
+
reject(e);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
process.stdin.on("error", reject);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Execute a command and return stdout
|
|
59
|
+
*/
|
|
60
|
+
async function execCommand(args: string[]): Promise<string> {
|
|
61
|
+
if (isBun) {
|
|
62
|
+
const proc = Bun.spawn(args, { stderr: "ignore" });
|
|
63
|
+
return new Response(proc.stdout).text();
|
|
64
|
+
}
|
|
65
|
+
// Node.js fallback
|
|
66
|
+
try {
|
|
67
|
+
return execSync(args.join(" "), {
|
|
68
|
+
encoding: "utf-8",
|
|
69
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
70
|
+
});
|
|
71
|
+
} catch {
|
|
72
|
+
return "";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
27
76
|
async function getDailyCost(): Promise<number> {
|
|
28
77
|
try {
|
|
29
78
|
const today = new Date().toISOString().split("T")[0].replace(/-/g, "");
|
|
30
|
-
const
|
|
79
|
+
const dailyText = await execCommand([
|
|
31
80
|
"ccusage",
|
|
32
81
|
"daily",
|
|
33
82
|
"--json",
|
|
34
83
|
"--since",
|
|
35
84
|
today,
|
|
36
85
|
]);
|
|
37
|
-
const dailyText = await new Response(dailyProc.stdout).text();
|
|
38
86
|
const dailyData = JSON.parse(dailyText);
|
|
39
87
|
|
|
40
88
|
if (dailyData && dailyData.totals && dailyData.totals.totalCost) {
|
|
@@ -48,7 +96,7 @@ async function getDailyCost(): Promise<number> {
|
|
|
48
96
|
|
|
49
97
|
async function main() {
|
|
50
98
|
try {
|
|
51
|
-
const input: HookInput = await
|
|
99
|
+
const input: HookInput = await readStdinJson<HookInput>();
|
|
52
100
|
|
|
53
101
|
// Get basic info from Claude Code
|
|
54
102
|
const dirPath = formatPath(
|
|
@@ -95,16 +143,23 @@ async function main() {
|
|
|
95
143
|
`⏱️ ${colors.GRAY}(${usageLimits.utilization}%)${colors.LIGHT_GRAY}`,
|
|
96
144
|
`⏳ ${colors.GRAY}(${usageLimits.remainingTime} left)${colors.LIGHT_GRAY}`,
|
|
97
145
|
);
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
146
|
+
|
|
147
|
+
// Weekly limits (7 days)
|
|
148
|
+
// 🗓️ Global | 🧠 Opus | ⚡ Sonnet | 🔗 OAuth Apps
|
|
149
|
+
const globalUtil = usageLimits.weeklyUtilization ?? 0;
|
|
150
|
+
const opusUtil = usageLimits.opus?.utilization ?? 0;
|
|
151
|
+
const sonnetUtil = usageLimits.sonnet?.utilization ?? 0;
|
|
152
|
+
const oauthUtil = usageLimits.oauthApps?.utilization ?? 0;
|
|
153
|
+
const resetDays =
|
|
154
|
+
usageLimits.weeklyResetDays ??
|
|
155
|
+
usageLimits.opus?.resetDays ??
|
|
156
|
+
usageLimits.sonnet?.resetDays ??
|
|
157
|
+
usageLimits.oauthApps?.resetDays ??
|
|
158
|
+
7;
|
|
159
|
+
|
|
160
|
+
secondLineParts.push(
|
|
161
|
+
`🗓️ ${globalUtil}% ${colors.GRAY}(${resetDays}j)${colors.LIGHT_GRAY} ${colors.GRAY}•${colors.LIGHT_GRAY} 🧠 ${opusUtil}% ${colors.GRAY}•${colors.LIGHT_GRAY} ⚡ ${sonnetUtil}% ${colors.GRAY}•${colors.LIGHT_GRAY} 🔗 ${oauthUtil}%`,
|
|
162
|
+
);
|
|
108
163
|
}
|
|
109
164
|
|
|
110
165
|
const secondLine = `${colors.LIGHT_GRAY}${secondLineParts.join(sep)}${colors.RESET}`;
|
|
@@ -1,26 +1,67 @@
|
|
|
1
|
-
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
|
|
3
|
+
// Check if running in Bun
|
|
4
|
+
const isBun = typeof Bun !== "undefined";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Execute a command and return stdout
|
|
8
|
+
* Uses Bun.spawn if available (faster), falls back to execSync
|
|
9
|
+
*/
|
|
10
|
+
async function execCommand(args: string[]): Promise<string> {
|
|
11
|
+
if (isBun) {
|
|
12
|
+
const proc = Bun.spawn(args, { stderr: "ignore" });
|
|
13
|
+
const text = await new Response(proc.stdout).text();
|
|
14
|
+
return text;
|
|
15
|
+
}
|
|
16
|
+
// Node.js fallback
|
|
2
17
|
try {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
18
|
+
return execSync(args.join(" "), {
|
|
19
|
+
encoding: "utf-8",
|
|
20
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
6
21
|
});
|
|
7
|
-
|
|
22
|
+
} catch {
|
|
23
|
+
return "";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if a command succeeds (exit code 0)
|
|
29
|
+
*/
|
|
30
|
+
async function execCheck(args: string[]): Promise<boolean> {
|
|
31
|
+
if (isBun) {
|
|
32
|
+
const proc = Bun.spawn(args, { stderr: "ignore", stdout: "ignore" });
|
|
33
|
+
await proc.exited;
|
|
34
|
+
return proc.exitCode === 0;
|
|
35
|
+
}
|
|
36
|
+
// Node.js fallback
|
|
37
|
+
try {
|
|
38
|
+
execSync(args.join(" "), { stdio: "ignore" });
|
|
39
|
+
return true;
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
8
44
|
|
|
9
|
-
|
|
45
|
+
export async function getGitInfo(): Promise<string> {
|
|
46
|
+
try {
|
|
47
|
+
// Check if in git repo
|
|
48
|
+
const isGitRepo = await execCheck(["git", "rev-parse", "--git-dir"]);
|
|
49
|
+
if (!isGitRepo) {
|
|
10
50
|
return "no-git";
|
|
11
51
|
}
|
|
12
52
|
|
|
13
53
|
// Get current branch
|
|
14
|
-
const
|
|
15
|
-
const branchText = await new Response(branchProc.stdout).text();
|
|
54
|
+
const branchText = await execCommand(["git", "branch", "--show-current"]);
|
|
16
55
|
let branch = branchText.trim() || "detached";
|
|
17
56
|
|
|
18
57
|
// Check for changes
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
58
|
+
const statusText = await execCommand(["git", "diff", "--numstat"]);
|
|
59
|
+
const cachedText = await execCommand([
|
|
60
|
+
"git",
|
|
61
|
+
"diff",
|
|
62
|
+
"--cached",
|
|
63
|
+
"--numstat",
|
|
64
|
+
]);
|
|
24
65
|
|
|
25
66
|
let totalAdded = 0;
|
|
26
67
|
let totalDeleted = 0;
|
|
@@ -1,21 +1,46 @@
|
|
|
1
1
|
import { execSync } from "child_process";
|
|
2
|
+
import { readFileSync, existsSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
|
|
6
|
+
// Platform detection
|
|
7
|
+
const isMacOS = process.platform === "darwin";
|
|
8
|
+
const isWindows = process.platform === "win32";
|
|
9
|
+
|
|
10
|
+
interface ModelUsage {
|
|
11
|
+
utilization: number; // percentage 0-100
|
|
12
|
+
resetDays: number | null; // days until reset
|
|
13
|
+
}
|
|
2
14
|
|
|
3
15
|
interface UsageLimitsResult {
|
|
4
16
|
remainingTime: string;
|
|
5
|
-
utilization: number; // percentage 0-100
|
|
6
|
-
// weekly limits
|
|
7
|
-
|
|
8
|
-
|
|
17
|
+
utilization: number; // percentage 0-100 (5h window)
|
|
18
|
+
// Model-specific weekly limits (7 days)
|
|
19
|
+
opus: ModelUsage | null;
|
|
20
|
+
sonnet: ModelUsage | null;
|
|
21
|
+
oauthApps: ModelUsage | null; // 🔗 API/SDK usage
|
|
22
|
+
// Legacy combined weekly (fallback)
|
|
23
|
+
weeklyUtilization: number | null;
|
|
24
|
+
weeklyResetDays: number | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface UsageWindow {
|
|
28
|
+
utilization: number;
|
|
29
|
+
resets_at: string | null;
|
|
9
30
|
}
|
|
10
31
|
|
|
11
32
|
interface AnthropicUsageResponse {
|
|
12
|
-
five_hour?:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
33
|
+
five_hour?: UsageWindow | null;
|
|
34
|
+
seven_day?: UsageWindow | null;
|
|
35
|
+
// New model-specific fields (added Jan 2026)
|
|
36
|
+
seven_day_opus?: UsageWindow | null;
|
|
37
|
+
seven_day_sonnet?: UsageWindow | null;
|
|
38
|
+
seven_day_oauth_apps?: UsageWindow | null;
|
|
39
|
+
extra_usage?: {
|
|
40
|
+
is_enabled: boolean;
|
|
41
|
+
monthly_limit: number | null;
|
|
42
|
+
used_credits: number | null;
|
|
43
|
+
utilization: number | null;
|
|
19
44
|
} | null;
|
|
20
45
|
}
|
|
21
46
|
|
|
@@ -49,17 +74,52 @@ function calculateRemainingMinutes(resetsAt: string): number {
|
|
|
49
74
|
return Math.min(minutes, 300);
|
|
50
75
|
}
|
|
51
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Get OAuth token - platform specific
|
|
79
|
+
* - macOS: Uses Keychain via `security` command
|
|
80
|
+
* - Windows/Linux: Tries to read from Claude Code credentials file
|
|
81
|
+
*/
|
|
52
82
|
function getOAuthToken(): string | null {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
83
|
+
// Try macOS Keychain first (fastest on macOS)
|
|
84
|
+
if (isMacOS) {
|
|
85
|
+
try {
|
|
86
|
+
const result = execSync(
|
|
87
|
+
'security find-generic-password -s "Claude Code-credentials" -w',
|
|
88
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] },
|
|
89
|
+
);
|
|
90
|
+
const creds = JSON.parse(result.trim());
|
|
91
|
+
return creds?.claudeAiOauth?.accessToken ?? null;
|
|
92
|
+
} catch {
|
|
93
|
+
// Fall through to file-based approach
|
|
94
|
+
}
|
|
62
95
|
}
|
|
96
|
+
|
|
97
|
+
// Try reading from Claude Code credentials file (Windows/Linux or macOS fallback)
|
|
98
|
+
// Claude Code stores credentials in different locations per platform
|
|
99
|
+
const possiblePaths = isWindows
|
|
100
|
+
? [
|
|
101
|
+
join(process.env.APPDATA || "", "Claude", "credentials.json"),
|
|
102
|
+
join(homedir(), ".claude", "credentials.json"),
|
|
103
|
+
]
|
|
104
|
+
: [
|
|
105
|
+
join(homedir(), ".config", "claude", "credentials.json"),
|
|
106
|
+
join(homedir(), ".claude", "credentials.json"),
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
for (const credPath of possiblePaths) {
|
|
110
|
+
try {
|
|
111
|
+
if (existsSync(credPath)) {
|
|
112
|
+
const content = readFileSync(credPath, "utf-8");
|
|
113
|
+
const creds = JSON.parse(content);
|
|
114
|
+
const token = creds?.claudeAiOauth?.accessToken;
|
|
115
|
+
if (token) return token;
|
|
116
|
+
}
|
|
117
|
+
} catch {
|
|
118
|
+
// Continue to next path
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return null;
|
|
63
123
|
}
|
|
64
124
|
|
|
65
125
|
async function fetchAnthropicUsage(
|
|
@@ -84,6 +144,16 @@ async function fetchAnthropicUsage(
|
|
|
84
144
|
}
|
|
85
145
|
}
|
|
86
146
|
|
|
147
|
+
function parseModelUsage(
|
|
148
|
+
data: UsageWindow | null | undefined,
|
|
149
|
+
): ModelUsage | null {
|
|
150
|
+
if (!data) return null;
|
|
151
|
+
return {
|
|
152
|
+
utilization: Math.round(data.utilization),
|
|
153
|
+
resetDays: data.resets_at ? calculateRemainingDays(data.resets_at) : null,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
87
157
|
export async function getUsageLimits(): Promise<UsageLimitsResult | null> {
|
|
88
158
|
try {
|
|
89
159
|
// get oauth token from keychain
|
|
@@ -94,21 +164,30 @@ export async function getUsageLimits(): Promise<UsageLimitsResult | null> {
|
|
|
94
164
|
|
|
95
165
|
// fetch usage from anthropic api
|
|
96
166
|
const usage = await fetchAnthropicUsage(token);
|
|
97
|
-
if (!usage
|
|
167
|
+
if (!usage) {
|
|
98
168
|
return null;
|
|
99
169
|
}
|
|
100
170
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
// calculate remaining time
|
|
171
|
+
// Handle five_hour - can be null when no active window
|
|
172
|
+
let utilization = 0;
|
|
104
173
|
let remainingMinutes = 0;
|
|
105
|
-
|
|
106
|
-
|
|
174
|
+
|
|
175
|
+
if (usage.five_hour) {
|
|
176
|
+
utilization = usage.five_hour.utilization;
|
|
177
|
+
if (usage.five_hour.resets_at) {
|
|
178
|
+
remainingMinutes = calculateRemainingMinutes(usage.five_hour.resets_at);
|
|
179
|
+
}
|
|
107
180
|
}
|
|
108
181
|
|
|
109
|
-
//
|
|
182
|
+
// Parse model-specific usage (Opus, Sonnet, OAuth Apps)
|
|
183
|
+
const opus = parseModelUsage(usage.seven_day_opus);
|
|
184
|
+
const sonnet = parseModelUsage(usage.seven_day_sonnet);
|
|
185
|
+
const oauthApps = parseModelUsage(usage.seven_day_oauth_apps);
|
|
186
|
+
|
|
187
|
+
// Legacy combined weekly (fallback for old API or combined view)
|
|
110
188
|
let weeklyUtilization: number | null = null;
|
|
111
189
|
let weeklyResetDays: number | null = null;
|
|
190
|
+
|
|
112
191
|
if (usage.seven_day) {
|
|
113
192
|
weeklyUtilization = Math.round(usage.seven_day.utilization);
|
|
114
193
|
if (usage.seven_day.resets_at) {
|
|
@@ -119,6 +198,9 @@ export async function getUsageLimits(): Promise<UsageLimitsResult | null> {
|
|
|
119
198
|
return {
|
|
120
199
|
remainingTime: formatTimeRemaining(remainingMinutes),
|
|
121
200
|
utilization: Math.round(utilization),
|
|
201
|
+
opus,
|
|
202
|
+
sonnet,
|
|
203
|
+
oauthApps,
|
|
122
204
|
weeklyUtilization,
|
|
123
205
|
weeklyResetDays,
|
|
124
206
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Claude Code "Before Tools" Hook - Command Validation Script
|
|
@@ -7,9 +7,18 @@
|
|
|
7
7
|
* It receives command data via stdin and returns exit code 0 (allow) or 1 (block).
|
|
8
8
|
*
|
|
9
9
|
* Usage: Called automatically by Claude Code PreToolUse hook
|
|
10
|
-
* Manual test: echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' |
|
|
10
|
+
* Manual test: echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' | node validate-command.js
|
|
11
|
+
*
|
|
12
|
+
* Cross-platform compatible: Works on macOS, Linux, and Windows
|
|
11
13
|
*/
|
|
12
14
|
|
|
15
|
+
const fs = require("fs");
|
|
16
|
+
const path = require("path");
|
|
17
|
+
const os = require("os");
|
|
18
|
+
|
|
19
|
+
// Cross-platform log file path
|
|
20
|
+
const LOG_FILE = path.join(os.homedir(), ".claude", "security.log");
|
|
21
|
+
|
|
13
22
|
// Comprehensive dangerous command patterns database
|
|
14
23
|
const SECURITY_RULES = {
|
|
15
24
|
// Critical system destruction commands
|
|
@@ -159,13 +168,15 @@ const SECURITY_RULES = {
|
|
|
159
168
|
"/root/",
|
|
160
169
|
],
|
|
161
170
|
|
|
162
|
-
// Safe paths where rm -rf is allowed
|
|
171
|
+
// Safe paths where rm -rf is allowed (cross-platform)
|
|
163
172
|
SAFE_RM_PATHS: [
|
|
164
|
-
"
|
|
165
|
-
"
|
|
166
|
-
"
|
|
167
|
-
process.
|
|
168
|
-
|
|
173
|
+
path.join(os.homedir(), "Developer") + path.sep,
|
|
174
|
+
path.join(os.homedir(), "Projects") + path.sep,
|
|
175
|
+
path.join(os.homedir(), "dev") + path.sep,
|
|
176
|
+
process.platform === "win32" ? process.env.TEMP + path.sep : "/tmp/",
|
|
177
|
+
process.platform === "win32" ? "" : "/var/tmp/",
|
|
178
|
+
process.cwd() + path.sep, // Current working directory
|
|
179
|
+
].filter(Boolean),
|
|
169
180
|
};
|
|
170
181
|
|
|
171
182
|
// Allowlist of safe commands (when used appropriately)
|
|
@@ -205,7 +216,7 @@ const SAFE_COMMANDS = [
|
|
|
205
216
|
|
|
206
217
|
class CommandValidator {
|
|
207
218
|
constructor() {
|
|
208
|
-
this.logFile =
|
|
219
|
+
this.logFile = LOG_FILE;
|
|
209
220
|
}
|
|
210
221
|
|
|
211
222
|
/**
|
|
@@ -547,9 +558,15 @@ class CommandValidator {
|
|
|
547
558
|
};
|
|
548
559
|
|
|
549
560
|
try {
|
|
550
|
-
//
|
|
561
|
+
// Ensure log directory exists
|
|
562
|
+
const logDir = path.dirname(this.logFile);
|
|
563
|
+
if (!fs.existsSync(logDir)) {
|
|
564
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Write to log file (Node.js compatible)
|
|
551
568
|
const logLine = JSON.stringify(logEntry) + "\n";
|
|
552
|
-
|
|
569
|
+
fs.appendFileSync(this.logFile, logLine);
|
|
553
570
|
|
|
554
571
|
// Also output to stderr for immediate visibility
|
|
555
572
|
console.error(
|