Créez un dossier dans plugins/ avec la structure suivante :
plugins/
└── mon-plugin/
├── manifest.json
├── index.js
├── commands/ (optionnel - sous-dossier pour les commandes)
│ ├── command1.js
│ └── command2.js
├── utils/ (optionnel - sous-dossier pour les utilitaires)
│ ├── helpers.js
│ └── validators.js
├── lib/ (optionnel - sous-dossier pour les bibliothèques)
│ └── mylib.js
└── README.md (optionnel)
Utilisez la commande import-plugin dans la console :
- Tapez
import-plugin - Collez votre code JavaScript
- Tapez
END_PLUGINpour terminer - Optionnel :
save-plugin <nom>pour sauvegarder définitivement
Un plugin peut être organisé dans un dossier avec différents niveaux de complexité :
plugins/
└── mon-plugin/
├── manifest.json
├── index.js
└── README.md (optionnel)
plugins/
└── mon-plugin-avance/
├── manifest.json
├── index.js # Point d'entrée principal
├── commands/ # Dossier pour organiser les commandes
│ ├── file-commands.js # Commandes de gestion de fichiers
│ ├── sys-commands.js # Commandes système
│ └── net-commands.js # Commandes réseau
├── utils/ # Dossier pour les utilitaires
│ ├── validators.js # Fonctions de validation
│ ├── formatters.js # Fonctions de formatage
│ └── helpers.js # Fonctions d'aide
├── lib/ # Bibliothèques tierces ou personnalisées
│ ├── api-client.js # Client API
│ └── config.js # Configuration
├── data/ # Données statiques (optionnel)
│ └── templates.json
└── README.md
Le fichier manifest.json décrit votre plugin :
{
"name": "Nom du Plugin",
"version": "1.0.0",
"description": "Description de votre plugin",
"author": "Votre nom",
"main": "index.js",
"commands": ["commande1", "commande2"]
}- name (requis) : Nom affiché du plugin
- version (requis) : Version du plugin (format semver)
- description (requis) : Description courte du plugin
- author (requis) : Nom de l'auteur
- main (requis) : Fichier principal à charger (généralement index.js)
- commands (requis) : Liste des noms de commandes exportées
Le fichier index.js sert de point d'entrée et peut importer des modules depuis les sous-dossiers :
// index.js - Point d'entrée principal
const fileCommands = require('./commands/file-commands');
const sysCommands = require('./commands/sys-commands');
const { validateInput } = require('./utils/validators');
const { formatOutput } = require('./utils/formatters');
// Export de toutes les commandes
module.exports = {
...fileCommands,
...sysCommands
};// commands/file-commands.js
const fs = require('fs');
const path = require('path');
const { validatePath } = require('../utils/validators');
const { formatFileList } = require('../utils/formatters');
const listFiles = {
name: 'list-files',
description: 'Liste les fichiers d\'un répertoire',
usage: 'list-files [chemin]',
execute: (args) => {
const targetPath = args[0] || '.';
if (!validatePath(targetPath)) {
return 'Chemin invalide';
}
try {
const files = fs.readdirSync(targetPath);
return formatFileList(files);
} catch (error) {
return `Erreur: ${error.message}`;
}
}
};
const readFile = {
name: 'read-file',
description: 'Lit le contenu d\'un fichier',
usage: 'read-file <fichier>',
execute: (args) => {
if (args.length === 0) {
return 'Usage: read-file <fichier>';
}
try {
return fs.readFileSync(args[0], 'utf8');
} catch (error) {
return `Erreur: ${error.message}`;
}
}
};
module.exports = {
'list-files': listFiles,
'read-file': readFile
};// utils/validators.js
const fs = require('fs');
const path = require('path');
function validatePath(inputPath) {
try {
// Vérifie si le chemin existe
return fs.existsSync(inputPath);
} catch (error) {
return false;
}
}
function validateNumber(input) {
const num = parseFloat(input);
return !isNaN(num) && isFinite(num);
}
function validateEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
module.exports = {
validatePath,
validateNumber,
validateEmail
};// utils/formatters.js
function formatFileList(files) {
return files
.map((file, index) => `${index + 1}. ${file}`)
.join('\n');
}
function formatSize(bytes) {
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
}
function formatDate(date) {
return new Date(date).toLocaleDateString('fr-FR');
}
module.exports = {
formatFileList,
formatSize,
formatDate
};// lib/api-client.js
const https = require('https');
class ApiClient {
constructor(baseUrl) {
this.baseUrl = baseUrl;
}
async get(endpoint) {
return new Promise((resolve, reject) => {
const url = `${this.baseUrl}${endpoint}`;
https.get(url, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
resolve(JSON.parse(data));
} catch (error) {
reject(error);
}
});
}).on('error', reject);
});
}
}
module.exports = { ApiClient };// lib/config.js
const fs = require('fs');
const path = require('path');
class Config {
constructor(configPath) {
this.configPath = configPath;
this.data = this.load();
}
load() {
try {
const content = fs.readFileSync(this.configPath, 'utf8');
return JSON.parse(content);
} catch (error) {
return {};
}
}
get(key, defaultValue = null) {
return this.data[key] || defaultValue;
}
set(key, value) {
this.data[key] = value;
this.save();
}
save() {
fs.writeFileSync(this.configPath, JSON.stringify(this.data, null, 2));
}
}
module.exports = { Config };{
"name": "Gestionnaire de Fichiers Avancé",
"version": "2.0.0",
"description": "Plugin complet de gestion de fichiers avec utilitaires",
"author": "Développeur Pro",
"main": "index.js",
"commands": ["list-files", "read-file", "search-files", "file-info"]
}// Point d'entrée principal qui importe tous les modules
const fileCommands = require('./commands/file-commands');
const searchCommands = require('./commands/search-commands');
// Export de toutes les commandes
module.exports = {
...fileCommands,
...searchCommands
};const fs = require('fs');
const path = require('path');
const { validatePath } = require('../utils/validators');
const searchFiles = {
name: 'search-files',
description: 'Recherche des fichiers par nom',
usage: 'search-files <pattern> [répertoire]',
execute: (args) => {
if (args.length === 0) {
return 'Usage: search-files <pattern> [répertoire]';
}
const pattern = args[0];
const searchDir = args[1] || '.';
if (!validatePath(searchDir)) {
return 'Répertoire invalide';
}
try {
const files = fs.readdirSync(searchDir);
const matches = files.filter(file =>
file.toLowerCase().includes(pattern.toLowerCase())
);
return matches.length > 0
? `Fichiers trouvés:\n${matches.join('\n')}`
: 'Aucun fichier trouvé';
} catch (error) {
return `Erreur: ${error.message}`;
}
}
};
module.exports = {
'search-files': searchFiles
};Chaque commande doit respecter cette structure :
const maCommande = {
name: 'nom-commande', // Nom de la commande (requis)
description: 'Description', // Description courte (requis)
usage: 'usage exemple', // Exemple d'utilisation (requis)
execute: (args) => { // Fonction d'exécution (requis)
// args est un tableau des arguments
return 'Résultat à afficher';
},
version: '1.0.0', // Version de la commande (optionnel)
author: 'Auteur' // Auteur de la commande (optionnel)
};- Paramètre :
args- tableau des arguments passés à la commande - Retour :
- String : texte à afficher dans la console
- Promise : pour les opérations asynchrones
- undefined/null : n'affiche rien
Les plugins ont accès à plusieurs fonctions utilitaires de la console :
// Affichage coloré dans la console
console.log('Message normal');
console.error('Message d\'erreur en rouge');
console.warn('Message d\'avertissement en jaune');
console.info('Message d\'information en cyan');// Affiche un message avec une couleur spécifique
print('Texte en vert', 'green');
print('Texte en rouge', 'red');
print('Texte en bleu', 'blue');
print('Texte en jaune', 'yellow');
print('Texte en magenta', 'magenta');
print('Texte en cyan', 'cyan');
print('Texte en blanc', 'white');// Efface l'écran de la console
clearConsole();// Récupère une commande existante
const lsCommand = getCommand('ls');
if (lsCommand) {
// Utilise la commande
const result = lsCommand.execute([]);
}// Exécute une commande comme si elle était tapée dans la console
const result = await executeCommand('ls -la');
return result;// Récupère le répertoire de travail actuel
const currentDir = getWorkingDirectory();
return `Répertoire actuel: ${currentDir}`;// Change le répertoire de travail
try {
setWorkingDirectory('/home/user');
return 'Répertoire changé avec succès';
} catch (error) {
return `Erreur: ${error.message}`;
}// Récupère l'historique des commandes
const history = getHistory();
return history.slice(-10).join('\n'); // Affiche les 10 dernières commandes// Ajoute une commande à l'historique
addToHistory('ma-commande personnalisée');// Demande une saisie utilisateur (asynchrone)
execute: async (args) => {
const nom = await prompt('Quel est votre nom ? ');
return `Bonjour ${nom} !`;
}// Demande une confirmation oui/non (asynchrone)
execute: async (args) => {
const confirmation = await confirm('Êtes-vous sûr ? (o/n)');
return confirmation ? 'Confirmé !' : 'Annulé.';
}const monPlugin = {
name: 'demo-api',
description: 'Démontre l\'utilisation de l\'API console',
usage: 'demo-api [action]',
execute: async (args) => {
const action = args[0] || 'help';
switch (action) {
case 'clear':
clearConsole();
return 'Console effacée !';
case 'pwd':
return `Répertoire actuel: ${getWorkingDirectory()}`;
case 'history':
const history = getHistory();
return `Dernières commandes:\n${history.slice(-5).join('\n')}`;
case 'prompt':
const nom = await prompt('Votre nom: ');
return `Bonjour ${nom}!`;
case 'confirm':
const ok = await confirm('Continuer?');
return ok ? 'Vous avez confirmé' : 'Vous avez annulé';
case 'colors':
print('Rouge', 'red');
print('Vert', 'green');
print('Bleu', 'blue');
return 'Démonstration des couleurs terminée';
case 'exec':
const result = await executeCommand('ls');
return `Résultat de ls:\n${result}`;
default:
return `Actions disponibles: clear, pwd, history, prompt, confirm, colors, exec`;
}
}
};
module.exports = { 'demo-api': monPlugin };{
"name": "Utilitaires Système",
"version": "1.0.0",
"description": "Commandes utiles pour le système",
"author": "Communauté",
"main": "index.js",
"commands": ["sysinfo", "weather", "joke"]
}const sysinfo = {
name: 'sysinfo',
description: 'Affiche les informations système',
usage: 'sysinfo',
execute: () => {
const os = require('os');
return `Système: ${os.type()} ${os.release()}\n` +
`Architecture: ${os.arch()}\n` +
`Mémoire libre: ${Math.round(os.freemem() / 1024 / 1024)} MB\n` +
`Uptime: ${Math.round(os.uptime() / 60)} minutes`;
}
};
const weather = {
name: 'weather',
description: 'Affiche une météo fictive',
usage: 'weather [ville]',
execute: (args) => {
const ville = args.length > 0 ? args.join(' ') : 'Paris';
const temperatures = [15, 18, 22, 25, 28, 20, 16];
const conditions = ['Ensoleillé', 'Nuageux', 'Pluvieux', 'Orageux'];
const temp = temperatures[Math.floor(Math.random() * temperatures.length)];
const condition = conditions[Math.floor(Math.random() * conditions.length)];
return `Météo à ${ville}: ${temp}°C, ${condition} ☀️`;
}
};
const joke = {
name: 'joke',
description: 'Raconte une blague',
usage: 'joke',
execute: () => {
const jokes = [
"Pourquoi les plongeurs plongent-ils toujours en arrière ? Parce que sinon, ils tombent dans le bateau !",
"Que dit un escargot quand il croise une limace ? 'Regarde, un nudiste !'",
"Comment appelle-t-on un chat tombé dans un pot de peinture ? Un chat-mallow !",
"Que dit un informaticien quand il se noie ? F1 ! F1 ! F1 !"
];
return jokes[Math.floor(Math.random() * jokes.length)];
}
};
// Export des commandes
module.exports = {
sysinfo,
weather,
joke
};// Exemple de plugin simple à importer
const hello = {
name: 'hello',
description: 'Dit bonjour',
usage: 'hello [nom]',
execute: (args) => {
const name = args.join(' ') || 'monde';
return `Hello ${name}!`;
}
};
const time = {
name: 'time',
description: 'Affiche l\'heure actuelle',
usage: 'time',
execute: () => {
return `Il est ${new Date().toLocaleTimeString()}`;
}
};
module.exports = {
hello,
time
};plugins: Liste tous les plugins et commandesimport-plugin: Importer un plugin depuis du codesave-plugin <nom>: Sauvegarder un plugin temporaireremove-temp-plugin <nom>: Supprimer un plugin temporaire
- Test rapide : Utilisez
import-pluginpour tester votre code - Validation : Testez vos commandes
- Sauvegarde : Utilisez
save-pluginsi le plugin fonctionne bien - Structure : Organisez en sous-fichiers pour les gros plugins
- Partage : Le plugin est maintenant dans le dossier
plugins/
Les plugins importés directement ont des restrictions :
-
✅ Modules autorisés :
fs,path,os,crypto,util -
❌ Modules interdits :
child_process,cluster, etc. -
✅ Les plugins du dossier
plugins/n'ont pas ces restrictions -
⚠️ Évitezeval()sauf si absolument nécessaire et avec validation stricte -
✅ Validez toujours les entrées utilisateur
-
✅ Gérez les erreurs proprement
-
✅ Limitez l'accès aux fichiers système sensibles
Pour déboguer votre plugin :
- Ajoutez des
console.log()dans votre fonction execute - Vérifiez les erreurs dans la console
- Testez avec différents arguments
execute: (args) => {
if (args.length === 0) {
return 'Usage: ma-commande <argument>';
}
const nombre = parseInt(args[0]);
if (isNaN(nombre)) {
return 'Erreur: L\'argument doit être un nombre';
}
// Traitement...
}execute: (args) => {
try {
// Code pouvant lever une erreur
return resultat;
} catch (error) {
return `Erreur: ${error.message}`;
}
}execute: async (args) => {
try {
const result = await maFonctionAsync();
return `Résultat: ${result}`;
} catch (error) {
return `Erreur: ${error.message}`;
}
}const fs = require('fs');
const path = require('path');
const https = require('https');
const maCommande = {
name: 'lire',
description: 'Lit un fichier',
usage: 'lire <fichier>',
execute: (args) => {
if (args.length === 0) {
return 'Usage: lire <fichier>';
}
try {
const contenu = fs.readFileSync(args[0], 'utf8');
return contenu;
} catch (error) {
return `Impossible de lire le fichier: ${error.message}`;
}
}
};