impulse-claude
Version:
Download and install Claude commands from Impulse Directory
248 lines (204 loc) • 6.91 kB
JavaScript
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 };