#!/usr/bin/env node import chalk from 'chalk'; import inquirer from 'inquirer'; import ora from 'ora'; import fs from 'fs'; import path from 'path'; import { execSync } from 'child_process'; import { marked } from 'marked'; import TerminalRenderer from 'marked-terminal'; // Configurer le rendu Markdown pour le terminal (styles chalk) marked.setOptions({ renderer: new TerminalRenderer({ code: chalk.yellow, blockquote: chalk.gray.italic, html: chalk.gray, heading: chalk.cyan.bold, firstHeading: chalk.cyan.bold, listitem: chalk.white, table: chalk.gray, tab: 2 }) }); const AUTHOR = "DJAKOUA KWANKAM"; const APP_NAME = "Cypher Coder"; // Global Session State & Config let chatMessages = []; let lastUserInput = ""; let lastAssistantResponse = ""; let commandHistory = []; let savedContexts = {}; let loadedFiles = []; let macros = {}; let logs = []; const sessionConfig = { model: "Qwen/Qwen2.5-Coder-32B-Instruct", temperature: 0.7, top_p: 0.9, max_tokens: 2048, stream: false, sandbox: false, verbose: false, silent: false, color: true, theme: "dark", format: "md", lang: "fr", env: {} }; function addLog(level, message) { const logItem = `[${new Date().toISOString()}] [${level}] ${message}`; logs.push(logItem); } // Banner ASCII Art pour l'interface de démarrage const BANNER = chalk.cyan.bold(` _____ _---------------+ / ____| | | ____ _ | | | _ _ _ __ | |__| _ \\| | | CYPHER CODER CLI | | | | | | '_ \\ | __ |_) | | | L'IA experte en développement local | |___| |_| | |_) || | | __/| |___ | Créé par ${AUTHOR} \\_____\\__, | .__/ |_| |_| |_____|| Institut Universitaire de Douala (IUD) __/ | | | |___/|_| +-----------------------+ `); // ----------------------------------------------------- // Définition des Outils Globaux (Capabilities) // ----------------------------------------------------- const tools = [ { type: "function", function: { name: "read_file", description: "Lit le contenu complet d'un fichier local sur la machine de l'utilisateur.", parameters: { type: "object", properties: { path: { type: "string", description: "Le chemin du fichier à lire (relatif ou absolu)." } }, required: ["path"] } } }, { type: "function", function: { name: "write_file", description: "Crée ou écrase un fichier local avec un nouveau contenu.", parameters: { type: "object", properties: { path: { type: "string", description: "Le chemin du fichier à écrire." }, content: { type: "string", description: "Le contenu textuel à écrire dans le fichier." } }, required: ["path", "content"] } } }, { type: "function", function: { name: "patch_file", description: "Modifie de manière ciblée un bloc de texte unique dans un fichier existant (Search and Replace). Utile pour modifier des gros fichiers sans les réécrire complètement.", parameters: { type: "object", properties: { path: { type: "string", description: "Le chemin du fichier à modifier." }, search: { type: "string", description: "Le bloc exact de code à remplacer (doit être unique dans le fichier)." }, replace: { type: "string", description: "Le nouveau bloc de code qui le remplace." } }, required: ["path", "search", "replace"] } } }, { type: "function", function: { name: "list_dir", description: "Liste les fichiers et dossiers dans un répertoire local. Peut être récursif.", parameters: { type: "object", properties: { path: { type: "string", description: "Le chemin du dossier à lister (par défaut '.')." }, recursive: { type: "boolean", description: "Si vrai, liste les sous-dossiers de manière récursive." } }, required: ["path"] } } }, { type: "function", function: { name: "find_files", description: "Recherche des fichiers récursivement par nom en utilisant un motif joker (ex: '*.js', 'package.json'). Permet de repérer rapidement des fichiers dans des sous-dossiers.", parameters: { type: "object", properties: { pattern: { type: "string", description: "Le motif de recherche (ex: '*.py', '*test*')." }, path: { type: "string", description: "Le chemin de départ pour la recherche (par défaut '.')." } }, required: ["pattern"] } } }, { type: "function", function: { name: "grep_search", description: "Recherche textuelle récursive dans tous les fichiers d'un dossier pour trouver les occurrences d'une chaîne de caractères spécifique (similaire à ripgrep).", parameters: { type: "object", properties: { query: { type: "string", description: "La chaîne de caractères ou le mot-clé à rechercher dans les fichiers." }, path: { type: "string", description: "Le chemin de départ pour la recherche (par défaut '.')." } }, required: ["query"] } } }, { type: "function", function: { name: "run_command", description: "Exécute une commande système ou shell dans le terminal local de l'utilisateur.", parameters: { type: "object", properties: { command: { type: "string", description: "La commande shell à exécuter." } }, required: ["command"] } } } ]; // Outil d'exécution d'API robuste via curl pour contourner les problèmes de socket de Node.js function callApiViaCurl(messages, clientTools) { const payload = JSON.stringify({ messages, tools: clientTools, model: sessionConfig.model, temperature: sessionConfig.temperature, top_p: sessionConfig.top_p, max_tokens: sessionConfig.max_tokens }); const escapedPayload = payload.replace(/'/g, "'\\''"); const command = `curl -s -X POST -H "Content-Type: application/json" -d '${escapedPayload}' https://theshellmaster-cypher-coder.hf.space/api/chat`; addLog("DEBUG", `Envoi payload API vers HF Space: modèle=${sessionConfig.model}, température=${sessionConfig.temperature}`); const output = execSync(command).toString(); try { const responseJson = JSON.parse(output); if (responseJson.error) { throw new Error(responseJson.error); } return responseJson.message; } catch (e) { throw new Error(`Erreur de connexion avec le serveur Cypher: ${e.message}\nRéponse brute: ${output}`); } } // ----------------------------------------------------- // Implémentation algorithmique des résolveurs d'outils // ----------------------------------------------------- // Helper de listing récursif propre function listFilesRecursive(dir, maxDepth = 3, currentDepth = 1) { let results = []; try { const list = fs.readdirSync(dir); for (const file of list) { const filePath = path.join(dir, file); const stat = fs.statSync(filePath); const relativePath = path.relative(".", filePath); // Ignorer les dossiers indésirables if (file === 'node_modules' || file === '.git' || file === '.venv' || file === 'env' || file === '.cache' || file === 'package-lock.json') { continue; } if (stat.isDirectory()) { results.push({ path: relativePath, type: 'dossier' }); if (currentDepth < maxDepth) { results = results.concat(listFilesRecursive(filePath, maxDepth, currentDepth + 1)); } } else { results.push({ path: relativePath, type: 'fichier', sizeBytes: stat.size }); } } } catch (e) { // Ignorer les dossiers inaccessibles } return results; } // Résolveurs locaux pour les outils système async function handleToolExecution(name, args) { const resolvedPath = path.resolve(args.path || "."); switch (name) { case 'read_file': try { const targetPath = path.resolve(args.path); if (!fs.existsSync(targetPath)) { return `Erreur: Le fichier n'existe pas à l'emplacement ${targetPath}`; } const content = fs.readFileSync(targetPath, 'utf8'); return content; } catch (err) { return `Erreur lors de la lecture du fichier: ${err.message}`; } case 'write_file': try { const targetPath = path.resolve(args.path); console.log(chalk.yellow(`\n📂 Cypher Coder veut modifier/créer le fichier : ${chalk.cyan(targetPath)}`)); const { confirmWrite } = await inquirer.prompt([ { type: 'confirm', name: 'confirmWrite', message: 'Autoriser la création ou modification de ce fichier ?', default: true } ]); if (!confirmWrite) { return "Action refusée par l'utilisateur."; } const dir = path.dirname(targetPath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } fs.writeFileSync(targetPath, args.content, 'utf8'); console.log(chalk.green(`✓ Fichier enregistré avec succès.`)); return `Fichier écrit avec succès à l'emplacement ${targetPath}`; } catch (err) { return `Erreur lors de l'écriture du fichier: ${err.message}`; } case 'patch_file': try { const targetPath = path.resolve(args.path); console.log(chalk.yellow(`\n📝 Cypher Coder veut modifier un bloc de code dans : ${chalk.cyan(targetPath)}`)); console.log(chalk.dim("--- BLOC À RECHERCHER ---")); console.log(chalk.red(args.search)); console.log(chalk.dim("--- NOUVEAU BLOC ---")); console.log(chalk.green(args.replace)); console.log(chalk.dim("------------------------")); const { confirmPatch } = await inquirer.prompt([ { type: 'confirm', name: 'confirmPatch', message: 'Autoriser ce remplacement ciblé ?', default: true } ]); if (!confirmPatch) { return "Action refusée par l'utilisateur."; } if (!fs.existsSync(targetPath)) { return `Erreur: Le fichier ${targetPath} n'existe pas.`; } const content = fs.readFileSync(targetPath, 'utf8'); const occurrences = content.split(args.search).length - 1; if (occurrences === 0) { return `Erreur: Le bloc de texte à remplacer n'a pas été trouvé. Assure-toi que les espaces et la mise en page correspondent exactement.`; } if (occurrences > 1) { return `Erreur: Le bloc à remplacer apparaît ${occurrences} fois dans le fichier. Rends la recherche plus spécifique en englobant d'autres lignes.`; } const newContent = content.replace(args.search, args.replace); fs.writeFileSync(targetPath, newContent, 'utf8'); console.log(chalk.green(`✓ Remplacement appliqué avec succès.`)); return "Remplacement appliqué avec succès de manière unique."; } catch (err) { return `Erreur lors du remplacement: ${err.message}`; } case 'list_dir': try { const targetPath = path.resolve(args.path || "."); if (!fs.existsSync(targetPath)) { return `Erreur: Le dossier n'existe pas à l'emplacement ${targetPath}`; } if (args.recursive) { const files = listFilesRecursive(targetPath, 3, 1); return JSON.stringify(files, null, 2); } else { const items = fs.readdirSync(targetPath); const list = items.map(item => { const itemPath = path.join(targetPath, item); const itemStats = fs.statSync(itemPath); return { name: item, type: itemStats.isDirectory() ? 'dossier' : 'fichier', sizeBytes: itemStats.size }; }); return JSON.stringify(list, null, 2); } } catch (err) { return `Erreur lors de la liste du dossier: ${err.message}`; } case 'find_files': try { const startDir = path.resolve(args.path || "."); const escapedPattern = args.pattern .replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') .replace(/\\\*/g, '.*') .replace(/\\\?/g, '.'); const regex = new RegExp(`^${escapedPattern}$`, 'i'); const allFiles = listFilesRecursive(startDir, 5, 1); const matched = allFiles .filter(item => item.type === 'fichier' && regex.test(path.basename(item.path))) .map(item => item.path); return JSON.stringify(matched, null, 2); } catch (err) { return `Erreur lors de la recherche de fichiers: ${err.message}`; } case 'grep_search': try { const startDir = path.resolve(args.path || "."); const query = args.query.toLowerCase(); const allFiles = listFilesRecursive(startDir, 5, 1); const matches = []; for (const item of allFiles) { if (item.type !== 'fichier') continue; if (item.sizeBytes > 1024 * 1024) continue; // max 1MB const content = fs.readFileSync(item.path, 'utf8'); if (content.includes('\u0000')) continue; // skip binary const lines = content.split('\n'); lines.forEach((line, idx) => { if (line.toLowerCase().includes(query)) { matches.push({ file: item.path, lineNumber: idx + 1, lineContent: line.trim() }); } }); if (matches.length >= 50) break; } return JSON.stringify(matches, null, 2); } catch (err) { return `Erreur lors de la recherche grep: ${err.message}`; } case 'run_command': try { console.log(chalk.yellow(`\n🖥️ Cypher Coder veut exécuter la commande suivante :`)); console.log(chalk.bgBlack.white(` $ ${args.command} `)); const { confirmCommand } = await inquirer.prompt([ { type: 'confirm', name: 'confirmCommand', message: 'Autoriser l\'exécution de cette commande ?', default: false } ]); if (!confirmCommand) { return "Action refusée par l'utilisateur."; } const stdout = execSync(args.command, { stdio: 'pipe' }).toString(); console.log(chalk.dim(stdout)); return `Commande exécutée avec succès.\nStdout:\n${stdout}`; } catch (err) { const errMsg = err.stderr ? err.stderr.toString() : err.message; console.log(chalk.red(`✖ Erreur de commande: ${errMsg}`)); return `La commande a échoué.\nErreur:\n${errMsg}`; } default: return `Erreur: Outil inconnu '${name}'`; } } // ----------------------------------------------------- // Prompt Système Dynamique & Intelligent // ----------------------------------------------------- function getCurrentDirectoryContext() { try { const files = listFilesRecursive(".", 2, 1); if (files.length === 0) return "Répertoire vide."; return files.map(f => `${f.path} (${f.type})`).join(", "); } catch (e) { return "Impossible de lire le répertoire."; } } function getSystemPrompt() { const dirContext = getCurrentDirectoryContext(); return `Tu es Cypher Coder, un agent de programmation IA autonome et ultra-intelligent fonctionnant dans un terminal (CLI). Tu as été conçu et développé par DJAKOUA KWANKAM, étudiant en informatique à l'Institut Universitaire de Douala (IUD). Tu dois toujours te présenter comme tel. Tu as accès à des outils locaux pour interagir avec le projet de l'utilisateur : - read_file : Lit le code d'un fichier. - write_file : Écrit un fichier. - patch_file : Modifie de manière ciblée un bloc (Search & Replace), à privilégier pour les fichiers existants. - list_dir : Inspecte l'arborescence. - find_files : Recherche des fichiers par nom récursivement dans les sous-dossiers. - grep_search : Recherche textuelle récursive dans le contenu des fichiers (comme grep/ripgrep). - run_command : Exécute des commandes système. Tu peux aussi utiliser search_web pour chercher des informations récentes sur internet. [CAPABILITÉS SYSTÈME ET COMMANDES CLI CLASSÉES PAR CATÉGORIES] : Tu connais et es capable d'utiliser ou de suggérer les commandes CLI suivantes selon le contexte : 1. Navigation & Système de fichiers : - ls / list : Lister les fichiers et dossiers du répertoire courant - ls -la : Lister avec permissions, taille, fichiers cachés - cd : Changer de répertoire - cd .. : Remonter d'un niveau - pwd : Afficher le chemin absolu du répertoire courant - tree : Afficher l'arborescence sous forme d'arbre - mkdir : Créer un dossier - mkdir -p : Créer des dossiers imbriqués en une commande - rmdir : Supprimer un dossier vide - touch : Créer un fichier vide - rm : Supprimer un fichier - rm -rf : Supprimer un dossier et son contenu récursivement - cp : Copier un fichier - cp -r : Copier un dossier récursivement - mv : Déplacer ou renommer un fichier/dossier - find -name "*.py" : Rechercher des fichiers par nom/pattern - locate : Recherche rapide dans la base de données de fichiers - stat : Afficher métadonnées d'un fichier (taille, dates, permissions) - du -sh : Taille d'un dossier (human readable) - df -h : Espace disque disponible sur toutes les partitions 2. Lecture & Édition de fichiers : - cat : Afficher le contenu d'un fichier - less : Afficher le contenu paginé (scrollable) - head -n 20 : Afficher les N premières lignes - tail -n 20 : Afficher les N dernières lignes - tail -f : Suivre un fichier en temps réel (logs) - grep "pattern" : Chercher un pattern dans un fichier - grep -r "pattern" : Recherche récursive dans un dossier - grep -n "pattern" : Chercher avec numéros de lignes - sed 's/old/new/g' : Remplacer du texte dans un fichier - awk '{print $1}' : Extraire/traiter des colonnes de texte - wc -l : Compter les lignes d'un fichier - diff : Comparer deux fichiers - nano : Éditer un fichier (éditeur simple) - vim : Éditer un fichier (éditeur avancé) - echo "text" > file : Écrire du texte dans un fichier (écrase) - echo "text" >> file : Ajouter du texte à la fin d'un fichier - sort : Trier les lignes d'un fichier - uniq : Supprimer les lignes dupliquées - cut -d',' -f1 : Extraire une colonne d'un CSV 3. Processus & Ressources système : - ps aux : Lister tous les processus en cours - top / htop : Moniteur de processus interactif en temps réel - kill : Terminer un processus par son PID - kill -9 : Forcer la fermeture d'un processus - killall : Tuer tous les processus par nom - jobs : Lister les tâches en arrière-plan du shell - bg : Mettre une tâche en arrière-plan - fg : Ramener une tâche en avant-plan - nohup & : Lancer un processus qui survive à la fermeture du terminal - screen / tmux : Multiplexeur de terminal (sessions persistantes) - free -h : Afficher la RAM utilisée/disponible - uptime : Durée de fonctionnement du système - lscpu : Informations sur le processeur - lsmem : Informations sur la mémoire - lspci : Lister les périphériques PCI (GPU, réseau, etc.) - uname -a : Infos kernel et architecture système - env : Afficher toutes les variables d'environnement - export VAR=value : Définir une variable d'environnement - echo $VAR : Afficher la valeur d'une variable - history : Historique des commandes exécutées - which : Trouver le chemin d'un exécutable - whereis : Trouver binaire, source et man d'une commande 4. Réseau : - ping : Tester la connectivité vers un hôte - curl : Envoyer une requête HTTP (GET par défaut) - curl -X POST -d '{}' : Requête HTTP POST avec body JSON - curl -O : Télécharger un fichier - wget : Télécharger un fichier via URL - wget -r : Télécharger récursivement - ifconfig / ip a : Afficher les interfaces réseau et IPs - ip route : Afficher la table de routage - netstat -tulnp : Lister les ports ouverts et processus associés - ss -tulnp : Alternative moderne à netstat - nmap : Scanner les ports d'une machine - traceroute : Tracer le chemin réseau vers un hôte - dig : Résolution DNS d'un domaine - host : Résolution DNS simplifiée - ssh user@host : Connexion SSH à une machine distante - ssh-keygen : Générer une paire de clés SSH - scp user@host: : Copier un fichier via SSH - rsync -avz : Synchronisation de fichiers (local ou distant) - nc -l : Écouter sur un port (netcat) - nc : Se connecter à un port distant 5. Gestion de paquets : - apt update : Mettre à jour la liste des paquets (Debian/Ubuntu) - apt upgrade : Mettre à jour les paquets installés - apt install : Installer un paquet - apt remove : Désinstaller un paquet - apt search : Chercher un paquet dans les dépôts - apt show : Afficher les détails d'un paquet - dpkg -i : Installer un fichier .deb local - dpkg -l : Lister tous les paquets installés - snap install : Installer via Snap - pip install : Installer un paquet Python - pip list : Lister les paquets Python installés - pip freeze > requirements.txt : Exporter les dépendances Python - npm install : Installer un paquet Node.js local - npm install -g : Installer un paquet Node.js global - npm list : Lister les paquets npm du projet - npm run