// yamlConfig.js - Module pour lire/écrire le fichier config.yaml de Pilot V2 // Version simple sans dépendances externes import GLib from 'gi://GLib'; import Gio from 'gi://Gio'; /** * Parser YAML simple - ne gère que les structures basiques nécessaires pour config.yaml * Format attendu: clés avec indentation de 2 espaces */ export class YamlConfig { constructor(configPath = null) { // Chemins par défaut où chercher le config.yaml this.configPath = configPath || this._findConfigPath(); this.config = null; } /** * Trouve le fichier de configuration dans les emplacements standards */ _findConfigPath() { const possiblePaths = [ GLib.build_filenamev([GLib.get_home_dir(), 'app/pilot/pilot-v2/config.yaml']), GLib.build_filenamev([GLib.get_home_dir(), '.config/pilot/config.yaml']), '/etc/pilot/config.yaml', './pilot-v2/config.yaml' ]; for (const path of possiblePaths) { if (GLib.file_test(path, GLib.FileTest.EXISTS)) { return path; } } // Par défaut return GLib.build_filenamev([GLib.get_home_dir(), 'app/pilot/pilot-v2/config.yaml']); } /** * Lit le fichier YAML et retourne un objet JavaScript */ load() { try { const file = Gio.File.new_for_path(this.configPath); const [success, contents] = file.load_contents(null); if (!success) { throw new Error(`Cannot read file: ${this.configPath}`); } const decoder = new TextDecoder('utf-8'); const text = decoder.decode(contents); this.config = this._parseYaml(text); return this.config; } catch (error) { console.error(`Error loading config: ${error.message}`); return null; } } /** * Parse simple de YAML (gère uniquement la structure de config.yaml) * Convertit YAML en objet JavaScript */ _parseYaml(text) { const lines = text.split('\n'); const result = {}; const stack = [{obj: result, indent: -1}]; for (let line of lines) { // Ignorer les commentaires et lignes vides if (line.trim().startsWith('#') || line.trim() === '') { continue; } const indent = line.search(/\S/); const trimmed = line.trim(); // Calculer le niveau d'indentation (2 espaces = 1 niveau) const level = Math.floor(indent / 2); // Remonter dans la pile si nécessaire while (stack.length > 0 && stack[stack.length - 1].indent >= level) { stack.pop(); } const parent = stack[stack.length - 1].obj; // Traiter la ligne if (trimmed.includes(':')) { const colonIndex = trimmed.indexOf(':'); const key = trimmed.substring(0, colonIndex).trim(); let value = trimmed.substring(colonIndex + 1).trim(); if (value === '') { // C'est un objet (nouvelle section) parent[key] = {}; stack.push({obj: parent[key], indent: level}); } else if (value === 'true' || value === 'false') { // Boolean parent[key] = value === 'true'; } else if (!isNaN(value) && value !== '') { // Number parent[key] = Number(value); } else { // String (enlever les quotes si présentes) parent[key] = value.replace(/^["']|["']$/g, ''); } } else if (trimmed.startsWith('- ')) { // Liste if (!Array.isArray(parent)) { // Convertir le parent en tableau si ce n'est pas déjà le cas const lastKey = Object.keys(stack[stack.length - 2].obj).pop(); stack[stack.length - 2].obj[lastKey] = []; stack[stack.length - 1].obj = stack[stack.length - 2].obj[lastKey]; } const value = trimmed.substring(2).trim(); parent.push(value.replace(/^["']|["']$/g, '')); } } return result; } /** * Sauvegarde l'objet JavaScript en YAML */ save(config = null) { const dataToSave = config || this.config; if (!dataToSave) { throw new Error('No config data to save'); } try { const yamlText = this._toYaml(dataToSave); const file = Gio.File.new_for_path(this.configPath); // Créer une sauvegarde this._createBackup(); // Écrire le nouveau fichier file.replace_contents( new TextEncoder().encode(yamlText), null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, null ); console.log(`Config saved to: ${this.configPath}`); return true; } catch (error) { console.error(`Error saving config: ${error.message}`); return false; } } /** * Convertit un objet JavaScript en YAML */ _toYaml(obj, indent = 0) { let yaml = ''; const spaces = ' '.repeat(indent); for (const [key, value] of Object.entries(obj)) { if (value === null || value === undefined) { continue; } if (typeof value === 'object' && !Array.isArray(value)) { // Objet imbriqué yaml += `${spaces}${key}:\n`; yaml += this._toYaml(value, indent + 1); } else if (Array.isArray(value)) { // Tableau yaml += `${spaces}${key}:\n`; for (const item of value) { yaml += `${spaces} - ${item}\n`; } } else { // Valeur simple const valueStr = typeof value === 'string' ? value : String(value); yaml += `${spaces}${key}: ${valueStr}\n`; } } return yaml; } /** * Crée une sauvegarde du fichier de config actuel */ _createBackup() { try { const timestamp = GLib.DateTime.new_now_local().format('%Y%m%d_%H%M%S'); const backupPath = `${this.configPath}.backup_${timestamp}`; const source = Gio.File.new_for_path(this.configPath); const dest = Gio.File.new_for_path(backupPath); if (source.query_exists(null)) { source.copy(dest, Gio.FileCopyFlags.OVERWRITE, null, null); console.log(`Backup created: ${backupPath}`); } } catch (error) { console.warn(`Could not create backup: ${error.message}`); } } /** * Obtient les métriques de télémétrie configurées */ getTelemetryMetrics() { if (!this.config?.features?.telemetry?.metrics) { return {}; } return this.config.features.telemetry.metrics; } /** * Obtient la liste des commandes autorisées */ getCommandsAllowlist() { if (!this.config?.features?.commands?.allowlist) { return []; } return this.config.features.commands.allowlist; } /** * Met à jour une métrique de télémétrie */ updateTelemetryMetric(metricName, updates) { if (!this.config?.features?.telemetry?.metrics?.[metricName]) { return false; } Object.assign(this.config.features.telemetry.metrics[metricName], updates); return true; } /** * Active/désactive la télémétrie globale */ setTelemetryEnabled(enabled) { if (!this.config?.features?.telemetry) { return false; } this.config.features.telemetry.enabled = enabled; return true; } /** * Active/désactive les commandes globales */ setCommandsEnabled(enabled) { if (!this.config?.features?.commands) { return false; } this.config.features.commands.enabled = enabled; return true; } /** * Met à jour la liste des commandes autorisées */ updateCommandsAllowlist(newAllowlist) { if (!this.config?.features?.commands) { return false; } this.config.features.commands.allowlist = newAllowlist; return true; } }