UNPKG

tidyai-ts

Version:

AI-powered cross-platform file organizer using OpenRouter API

313 lines (312 loc) 13.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.organizeFolder = organizeFolder; exports.deleteUnnecessaryFiles = deleteUnnecessaryFiles; exports.undoOrganize = undoOrganize; const fs = __importStar(require("fs/promises")); const path = __importStar(require("path")); const openrouter_1 = require("./openrouter"); const cli_utils_1 = require("./cli-utils"); const readline = __importStar(require("readline")); const HISTORY_FILE = '.tidyai/history.json'; const DELETE_HISTORY_FILE = '.tidyai/delete-history.json'; function promptUser(message) { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); return new Promise((resolve) => { rl.question(`${message} (y/N): `, (answer) => { rl.close(); resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes'); }); }); } async function organizeFolder(folderPath) { (0, cli_utils_1.displaySectionHeader)('Organization Process'); // Check if already organized const historyPath = path.join(folderPath, HISTORY_FILE); try { await fs.access(historyPath); (0, cli_utils_1.displayInfo)('This folder appears to be already organized. Use --undo to revert changes.'); // Create a log entry await (0, cli_utils_1.createLogEntry)(folderPath, 'Folder already organized, no action taken'); return; } catch (error) { // History file doesn't exist, which is what we expect } // Create history directory const historyDir = path.dirname(historyPath); await fs.mkdir(historyDir, { recursive: true }); // Read files in the directory (0, cli_utils_1.displayInfo)('Scanning folder contents...'); const files = await fs.readdir(folderPath); // Filter out directories and history file const fileNames = []; for (const file of files) { const filePath = path.join(folderPath, file); const stat = await fs.stat(filePath); if (stat.isFile() && filePath !== historyPath) { fileNames.push(file); } } if (fileNames.length === 0) { (0, cli_utils_1.displayInfo)('No files to organize in this directory.'); // Create a log entry await (0, cli_utils_1.createLogEntry)(folderPath, 'No files to organize in directory'); return; } (0, cli_utils_1.displayInfo)(`Found ${fileNames.length} files to organize`); // Get folder suggestions from AI (0, cli_utils_1.displayInfo)('Getting folder suggestions from AI...'); const client = new openrouter_1.OpenRouterClient(); const suggestions = await client.getSuggestions(fileNames); // Create folders and move files (0, cli_utils_1.displaySectionHeader)('Moving Files'); const moves = []; // Progress tracking let processedFiles = 0; const totalFiles = fileNames.length; for (const fileName of fileNames) { const suggestedFolder = suggestions[fileName] || 'Other'; const targetDir = path.join(folderPath, suggestedFolder); // Create target directory if it doesn't exist await fs.mkdir(targetDir, { recursive: true }); // Move file const originalPath = path.join(folderPath, fileName); const newPath = path.join(targetDir, fileName); await fs.rename(originalPath, newPath); moves.push({ fileName, originalPath, newPath }); // Update progress processedFiles++; const percentage = processedFiles / totalFiles; const progressBar = (0, cli_utils_1.displayProgressBar)(percentage); (0, cli_utils_1.updateProgress)(`Moving files: [${progressBar}] ${Math.round(percentage * 100)}% (${processedFiles}/${totalFiles}) ${fileName}`); } // Clear the progress line and show completion (0, cli_utils_1.clearProgressLine)(); (0, cli_utils_1.displaySuccess)(`Moved ${moves.length} files into folders`); // Save history (0, cli_utils_1.displayInfo)('Saving organization history...'); const history = { timestamp: new Date().toISOString(), moves }; await fs.writeFile(historyPath, JSON.stringify(history, null, 2)); // Create a log entry await (0, cli_utils_1.createLogEntry)(folderPath, `Organized ${moves.length} files into folders`, '.tidyai/logs'); (0, cli_utils_1.displaySuccess)('Organization complete!'); } async function deleteUnnecessaryFiles(folderPath) { (0, cli_utils_1.displaySectionHeader)('Delete Unnecessary Files'); // First organize the folder await organizeFolder(folderPath); // Find potentially unnecessary files (0, cli_utils_1.displayInfo)('Scanning for unnecessary files...'); const unnecessaryFiles = []; // Walk through all subdirectories to find files async function walkDir(dir) { const files = await fs.readdir(dir); for (const file of files) { // Skip .tidyai directory if (file === '.tidyai') continue; const filePath = path.join(dir, file); const stat = await fs.stat(filePath); if (stat.isDirectory()) { await walkDir(filePath); } else { // Check if file is potentially unnecessary const fileName = path.basename(file).toLowerCase(); if (fileName === 'thumbs.db' || fileName === '.ds_store' || fileName.endsWith('.tmp') || fileName.endsWith('.log') || fileName === 'desktop.ini') { unnecessaryFiles.push(filePath); } } } } await walkDir(folderPath); if (unnecessaryFiles.length === 0) { (0, cli_utils_1.displayInfo)('No unnecessary files found.'); await (0, cli_utils_1.createLogEntry)(folderPath, 'No unnecessary files found during deletion process', '.tidyai/logs'); return; } (0, cli_utils_1.displayWarning)(`Found ${unnecessaryFiles.length} potentially unnecessary files:`); for (const file of unnecessaryFiles) { console.log(` - ${file}`); } // Prompt user for confirmation const confirmDelete = await promptUser('Are you sure you want to delete these files? This action cannot be undone without a backup.'); if (!confirmDelete) { (0, cli_utils_1.displayInfo)('File deletion cancelled by user.'); await (0, cli_utils_1.createLogEntry)(folderPath, 'File deletion cancelled by user', '.tidyai/logs'); return; } // Save delete history for potential undo const deleteHistoryPath = path.join(folderPath, DELETE_HISTORY_FILE); const deleteHistory = { timestamp: new Date().toISOString(), deletedFiles: [] }; // Create delete history directory const deleteHistoryDir = path.dirname(deleteHistoryPath); await fs.mkdir(deleteHistoryDir, { recursive: true }); // Delete files and save their paths for undo (0, cli_utils_1.displaySectionHeader)('Deleting Files'); let deletedCount = 0; const totalFiles = unnecessaryFiles.length; for (const filePath of unnecessaryFiles) { try { // Save file content for potential recovery deleteHistory.deletedFiles.push(filePath); // Delete file await fs.rm(filePath); deletedCount++; // Update progress const percentage = deletedCount / totalFiles; const progressBar = (0, cli_utils_1.displayProgressBar)(percentage); (0, cli_utils_1.updateProgress)(`Deleting files: [${progressBar}] ${Math.round(percentage * 100)}% (${deletedCount}/${totalFiles}) ${path.basename(filePath)}`); } catch (error) { (0, cli_utils_1.displayWarning)(`Failed to delete ${filePath}: ${error.message}`); } } // Clear the progress line and show completion (0, cli_utils_1.clearProgressLine)(); // Save delete history await fs.writeFile(deleteHistoryPath, JSON.stringify(deleteHistory, null, 2)); (0, cli_utils_1.displaySuccess)(`Deleted ${deletedCount} unnecessary files.`); await (0, cli_utils_1.createLogEntry)(folderPath, `Deleted ${deletedCount} unnecessary files`, '.tidyai/logs'); } async function undoOrganize(folderPath) { (0, cli_utils_1.displaySectionHeader)('Undo Process'); const historyPath = path.join(folderPath, HISTORY_FILE); const deleteHistoryPath = path.join(folderPath, DELETE_HISTORY_FILE); // Check if we have delete history to undo let hasDeleteHistory = false; try { await fs.access(deleteHistoryPath); hasDeleteHistory = true; } catch (error) { // No delete history, that's fine } // First undo file deletions if they exist if (hasDeleteHistory) { try { const deleteHistoryData = await fs.readFile(deleteHistoryPath, 'utf-8'); const deleteHistory = JSON.parse(deleteHistoryData); (0, cli_utils_1.displayWarning)('Undoing file deletions is not possible as we do not store file contents.'); (0, cli_utils_1.displayInfo)('However, we can remove the delete history record.'); // Remove delete history file await fs.rm(deleteHistoryPath); await (0, cli_utils_1.createLogEntry)(folderPath, 'Removed delete history during undo process', '.tidyai/logs'); } catch (error) { (0, cli_utils_1.displayWarning)(`Error processing delete history: ${error.message}`); } } // Then undo file organization try { const historyData = await fs.readFile(historyPath, 'utf-8'); const history = JSON.parse(historyData); (0, cli_utils_1.displayInfo)(`Found history for ${history.moves.length} files`); // Move files back to their original locations (0, cli_utils_1.displaySectionHeader)('Restoring Files'); // Progress tracking let processedFiles = 0; const totalFiles = history.moves.length; for (const move of history.moves) { // Create directory for original path if needed const originalDir = path.dirname(move.originalPath); await fs.mkdir(originalDir, { recursive: true }); // Move file back await fs.rename(move.newPath, move.originalPath); // Update progress processedFiles++; const percentage = processedFiles / totalFiles; const progressBar = (0, cli_utils_1.displayProgressBar)(percentage); (0, cli_utils_1.updateProgress)(`Restoring files: [${progressBar}] ${Math.round(percentage * 100)}% (${processedFiles}/${totalFiles}) ${move.fileName}`); } // Clear the progress line and show completion (0, cli_utils_1.clearProgressLine)(); (0, cli_utils_1.displaySuccess)(`Restored ${history.moves.length} files to their original locations`); // Remove empty folders (0, cli_utils_1.displayInfo)('Cleaning up empty folders...'); const folders = [...new Set(history.moves.map(move => path.dirname(move.newPath)))]; let removedFolders = 0; for (const folder of folders) { try { const files = await fs.readdir(folder); if (files.length === 0) { await fs.rmdir(folder); removedFolders++; } } catch (error) { // Ignore errors when trying to remove folders } } if (removedFolders > 0) { (0, cli_utils_1.displaySuccess)(`Removed ${removedFolders} empty folders`); } // Remove history file await fs.rm(historyPath); // Create a log entry await (0, cli_utils_1.createLogEntry)(folderPath, `Undid organization of ${history.moves.length} files`, '.tidyai/logs'); (0, cli_utils_1.displaySuccess)('Undo complete!'); } catch (error) { if (error.code === 'ENOENT') { (0, cli_utils_1.displayInfo)('No organization history found for this folder.'); // Create a log entry await (0, cli_utils_1.createLogEntry)(folderPath, 'No organization history found for folder', '.tidyai/logs'); } else { (0, cli_utils_1.displayError)(`Error during undo: ${error.message}`); throw error; } } }