UNPKG

impulse-claude

Version:

Download and install Claude commands from Impulse Directory

248 lines (204 loc) 6.91 kB
#!/usr/bin/env node const fs = require('fs'); const path = require('path'); const https = require('https'); const http = require('http'); const os = require('os'); // Configuration par défaut const DEFAULT_BASE_URL = 'impulse.directory'; const RAW_PATH = '/raw/'; /** * Affiche l'aide de la commande */ function showHelp() { console.log(` Impulse Claude Command Installer Usage: npx impulse-claude <user-slug>/<command-slug> Description: Downloads a command from Impulse Directory and installs it as a Claude slash command. Arguments: user-slug/command-slug The full path to the command (user/command) Environment Variables: IMPULSE_BASE_URL Base URL for the Impulse Directory (default: impulse.directory) Examples: npx impulse-claude john/my-awesome-command IMPULSE_BASE_URL=localhost:3000 npx impulse-claude dev-user/dev-command The command will be installed in: 1. .claude/commands/ (if found in current or parent directories) 2. ~/.claude/commands/ (fallback) `); } /** * Détermine si on doit utiliser HTTPS ou HTTP */ function shouldUseHttps(baseUrl) { // Si c'est localhost, utilise HTTP par défaut if (baseUrl.includes('localhost') || baseUrl.includes('127.0.0.1')) { return false; } // Sinon, utilise HTTPS par défaut return true; } /** * Télécharge le contenu depuis l'URL */ function downloadCommand(baseUrl, fullPath) { return new Promise((resolve, reject) => { const useHttps = shouldUseHttps(baseUrl); const protocol = useHttps ? https : http; const scheme = useHttps ? 'https' : 'http'; // Construire l'URL complète const url = `${scheme}://${baseUrl}${RAW_PATH}${fullPath}`; protocol.get(url, (res) => { if (res.statusCode === 200) { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => resolve(data)); } else if (res.statusCode === 404) { reject(new Error(`Command '${fullPath}' not found (404)`)); } else if (useHttps && (res.statusCode === 500 || res.statusCode >= 400)) { // Si HTTPS échoue, essaie HTTP console.log(`HTTPS failed (${res.statusCode}), trying HTTP...`); downloadWithHttp(baseUrl, fullPath).then(resolve).catch(reject); } else { reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`)); } }).on('error', (err) => { if (useHttps) { // Si HTTPS échoue, essaie HTTP console.log(`HTTPS failed (${err.message}), trying HTTP...`); downloadWithHttp(baseUrl, fullPath).then(resolve).catch(reject); } else { reject(err); } }); }); } /** * Télécharge avec HTTP (fallback) */ function downloadWithHttp(baseUrl, fullPath) { return new Promise((resolve, reject) => { const url = `http://${baseUrl}${RAW_PATH}${fullPath}`; console.log(`Trying HTTP: ${url}`); http.get(url, (res) => { if (res.statusCode === 200) { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => resolve(data)); } else if (res.statusCode === 404) { reject(new Error(`Command '${fullPath}' not found (404)`)); } else { reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`)); } }).on('error', reject); }); } /** * Trouve le dossier .claude le plus proche */ function findClaudeDirectory() { let currentDir = process.cwd(); while (currentDir !== path.dirname(currentDir)) { const claudeDir = path.join(currentDir, '.claude'); if (fs.existsSync(claudeDir)) { return path.join(claudeDir, 'commands'); } currentDir = path.dirname(currentDir); } // Fallback vers le dossier home return path.join(os.homedir(), '.claude', 'commands'); } /** * Assure que le dossier existe */ function ensureDirectoryExists(dirPath) { if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); console.log(`Created directory: ${dirPath}`); } } /** * Vérifie si le fichier existe déjà */ function checkFileExists(commandsDir, filename) { const filePath = path.join(commandsDir, filename); return fs.existsSync(filePath); } /** * Sauvegarde la commande */ function saveCommand(commandsDir, filename, content, fullPath) { const filePath = path.join(commandsDir, filename); const targetDir = path.relative(process.cwd(), commandsDir); const displayPath = targetDir ? `${targetDir}/${filename}` : filename; console.log(`📁 Saving command to ${displayPath}`); fs.writeFileSync(filePath, content, 'utf8'); console.log(`✅ Command '${fullPath}' installed successfully`); // Extract command name for slash command usage const commandName = filename.replace('.md', ''); console.log(`\n🎉 You can now use: /${commandName}`); } /** * Parse les arguments pour extraire le chemin complet */ function parseArguments(args) { if (args.length === 1 && args[0].includes('/')) { return args[0]; } return null; } /** * Fonction principale */ async function main() { const args = process.argv.slice(2); // Vérifier les arguments if (args.length === 0 || args.includes('--help') || args.includes('-h')) { showHelp(); return; } const fullPath = parseArguments(args); if (!fullPath) { console.error('❌ Error: Invalid arguments format'); console.error('Use: npx impulse-claude user/command'); showHelp(); process.exit(1); } if (!fullPath.includes('/')) { console.error('❌ Error: Path must include user/command format'); showHelp(); process.exit(1); } try { // Récupérer l'URL de base depuis les variables d'environnement const baseUrl = process.env.IMPULSE_BASE_URL || DEFAULT_BASE_URL; // Trouver le dossier de destination const commandsDir = findClaudeDirectory(); // Créer le dossier si nécessaire ensureDirectoryExists(commandsDir); // Extraire le nom de la commande (tout après le dernier slash) const commandName = fullPath.split('/').pop(); const simpleFilename = `${commandName}.md`; const fullFilename = `${fullPath.replace('/', '-')}.md`; // Vérifier si le fichier existe déjà avec le nom simple const fileExists = checkFileExists(commandsDir, simpleFilename); // Déterminer le nom de fichier à utiliser const filename = fileExists ? fullFilename : simpleFilename; const targetDir = path.relative(process.cwd(), commandsDir); const displayPath = targetDir ? `${targetDir}/${fullPath}` : fullPath; // Télécharger la commande const content = await downloadCommand(baseUrl, fullPath); // Sauvegarder la commande saveCommand(commandsDir, filename, content, fullPath); } catch (error) { console.error(`❌ Error: ${error.message}`); process.exit(1); } } // Exécuter la fonction principale if (require.main === module) { main(); } module.exports = { main };