UNPKG

polish-cli

Version:

AI-powered file organization for Obsidian with automatic markdown conversion

313 lines 11.1 kB
import * as fs from 'fs/promises'; import * as path from 'path'; import * as os from 'os'; import { ConfigService } from './ConfigService.js'; export class ProfileManager { profilesDir; activeProfileFile; configService; constructor() { const polishDir = path.join(os.homedir(), '.polish'); this.profilesDir = path.join(polishDir, 'profiles'); this.activeProfileFile = path.join(polishDir, 'active-profile'); this.configService = new ConfigService(); } /** * Initialize the profile system */ async initialize() { await fs.mkdir(this.profilesDir, { recursive: true }); // Check if this is first time setup const hasProfiles = await this.hasAnyProfiles(); if (!hasProfiles) { await this.createDefaultProfile(); } // Ensure we have an active profile const activeProfile = await this.getActiveProfile(); if (!activeProfile) { const profiles = await this.listProfiles(); if (profiles.length > 0) { await this.setActiveProfile(profiles[0].name); } } } /** * Create a new profile */ async createProfile(name, config, description) { await this.validateProfileName(name); const profile = { name, description, config, createdAt: new Date().toISOString(), lastUsed: new Date().toISOString(), }; const profilePath = this.getProfilePath(name); await fs.writeFile(profilePath, JSON.stringify(profile, null, 2)); return profile; } /** * Get a profile by name */ async getProfile(name) { try { const profilePath = this.getProfilePath(name); const data = await fs.readFile(profilePath, 'utf-8'); return JSON.parse(data); } catch (error) { return null; } } /** * Update an existing profile */ async updateProfile(name, updates) { const profile = await this.getProfile(name); if (!profile) { throw new Error(`Profile '${name}' not found`); } const updatedProfile = { ...profile, ...updates, lastUsed: new Date().toISOString(), }; const profilePath = this.getProfilePath(name); await fs.writeFile(profilePath, JSON.stringify(updatedProfile, null, 2)); return updatedProfile; } /** * Delete a profile */ async deleteProfile(name) { if (name === 'default') { throw new Error('Cannot delete the default profile'); } const profile = await this.getProfile(name); if (!profile) { throw new Error(`Profile '${name}' not found`); } // If this is the active profile, switch to default const activeProfile = await this.getActiveProfile(); if (activeProfile === name) { await this.setActiveProfile('default'); } const profilePath = this.getProfilePath(name); await fs.unlink(profilePath); } /** * List all profiles */ async listProfiles() { try { const files = await fs.readdir(this.profilesDir); const activeProfile = await this.getActiveProfile(); const profiles = []; for (const file of files) { if (file.endsWith('.json')) { const name = path.basename(file, '.json'); const profile = await this.getProfile(name); if (profile) { profiles.push({ name: profile.name, description: profile.description, vaultPath: profile.config.vault.path, originalsPath: profile.config.originals.path, sourceCount: profile.config.sources.length, isActive: profile.name === activeProfile, createdAt: profile.createdAt, lastUsed: profile.lastUsed, }); } } } // Sort by last used, with active profile first return profiles.sort((a, b) => { if (a.isActive) return -1; if (b.isActive) return 1; return new Date(b.lastUsed).getTime() - new Date(a.lastUsed).getTime(); }); } catch (error) { return []; } } /** * Get the currently active profile name */ async getActiveProfile() { try { const activeProfile = await fs.readFile(this.activeProfileFile, 'utf-8'); return activeProfile.trim(); } catch (error) { return null; } } /** * Set the active profile */ async setActiveProfile(name) { const profile = await this.getProfile(name); if (!profile) { throw new Error(`Profile '${name}' not found`); } await fs.writeFile(this.activeProfileFile, name); // Update last used timestamp await this.updateProfile(name, {}); } /** * Get the active profile's configuration */ async getActiveConfig() { const activeProfileName = await this.getActiveProfile(); if (!activeProfileName) { throw new Error('No active profile found. Run "polish profile create" to create one.'); } const profile = await this.getProfile(activeProfileName); if (!profile) { throw new Error(`Active profile '${activeProfileName}' not found`); } return profile.config; } /** * Clone a profile with a new name */ async cloneProfile(sourceName, targetName, description) { const sourceProfile = await this.getProfile(sourceName); if (!sourceProfile) { throw new Error(`Source profile '${sourceName}' not found`); } return await this.createProfile(targetName, sourceProfile.config, description); } /** * Rename a profile */ async renameProfile(oldName, newName) { if (oldName === 'default') { throw new Error('Cannot rename the default profile'); } await this.validateProfileName(newName); const profile = await this.getProfile(oldName); if (!profile) { throw new Error(`Profile '${oldName}' not found`); } // Create new profile with new name const updatedProfile = { ...profile, name: newName, lastUsed: new Date().toISOString(), }; const newProfilePath = this.getProfilePath(newName); await fs.writeFile(newProfilePath, JSON.stringify(updatedProfile, null, 2)); // Update active profile if needed const activeProfile = await this.getActiveProfile(); if (activeProfile === oldName) { await this.setActiveProfile(newName); } // Delete old profile const oldProfilePath = this.getProfilePath(oldName); await fs.unlink(oldProfilePath); } /** * Import profiles from a JSON file */ async importProfiles(filePath, overwrite = false) { const data = await fs.readFile(filePath, 'utf-8'); const importedProfiles = JSON.parse(data); if (!Array.isArray(importedProfiles)) { throw new Error('Invalid import file format'); } const imported = []; for (const profile of importedProfiles) { const exists = await this.getProfile(profile.name); if (exists && !overwrite) { continue; // Skip existing profiles unless overwrite is true } await this.createProfile(profile.name, profile.config, profile.description); imported.push(profile.name); } return imported; } /** * Export profiles to a JSON file */ async exportProfiles(filePath, profileNames) { const allProfiles = await this.listProfiles(); let profilesToExport = allProfiles; if (profileNames && profileNames.length > 0) { profilesToExport = allProfiles.filter(p => profileNames.includes(p.name)); } const profiles = []; for (const summary of profilesToExport) { const profile = await this.getProfile(summary.name); if (profile) { profiles.push(profile); } } await fs.writeFile(filePath, JSON.stringify(profiles, null, 2)); } /** * Migrate legacy configuration to profile system */ async migrateLegacyConfig() { try { // Check if old config exists const legacyConfigExists = await this.configService.exists(); if (!legacyConfigExists) { return false; } const legacyConfig = await this.configService.load(); // Create default profile from legacy config await this.createProfile('default', legacyConfig, 'Migrated from legacy configuration'); await this.setActiveProfile('default'); console.log('Successfully migrated legacy configuration to default profile'); return true; } catch (error) { console.warn('Failed to migrate legacy configuration:', error); return false; } } async hasAnyProfiles() { try { const files = await fs.readdir(this.profilesDir); return files.some(file => file.endsWith('.json')); } catch (error) { return false; } } async createDefaultProfile() { // Try to migrate legacy config first const migrated = await this.migrateLegacyConfig(); if (!migrated) { // Create default profile with default config const defaultConfig = this.configService.getDefaultConfig(); await this.createProfile('default', defaultConfig, 'Default profile'); await this.setActiveProfile('default'); } } getProfilePath(name) { return path.join(this.profilesDir, `${name}.json`); } async validateProfileName(name) { if (!name || name.trim().length === 0) { throw new Error('Profile name cannot be empty'); } if (!/^[a-zA-Z0-9_-]+$/.test(name)) { throw new Error('Profile name can only contain letters, numbers, hyphens, and underscores'); } if (name.length > 50) { throw new Error('Profile name cannot be longer than 50 characters'); } const exists = await this.getProfile(name); if (exists) { throw new Error(`Profile '${name}' already exists`); } } } //# sourceMappingURL=ProfileManager.js.map