UNPKG

claude-code-conversation-search-mcp

Version:

Never lose your Claude Code conversations again. Search across all projects, find old chats, and resume where you left off.

247 lines (216 loc) 7.18 kB
import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; export interface ShortcutConfig { name: string; target: string; type: 'symlink' | 'alias' | 'batch'; } export class ShortcutManager { private shortcutsDir: string; private shortcutsConfig: Map<string, string> = new Map(); private configFile: string; constructor() { this.shortcutsDir = this.getShortcutsDirectory(); this.configFile = path.join(this.shortcutsDir, 'shortcuts.json'); this.ensureShortcutsDirectory(); this.loadShortcutsConfig(); } /** * Get or create a shortcut for a project path * Returns the shortcut name that can be used in cd commands */ async getOrCreateShortcut(projectPath: string, projectName: string): Promise<string> { let shortcutName = this.generateShortcutName(projectName); // Check if shortcut already exists and points to the same path const existingPath = this.shortcutsConfig.get(shortcutName); if (existingPath === projectPath) { return shortcutName; } // Handle collisions by adding a number let counter = 1; let originalName = shortcutName; while (this.shortcutsConfig.has(shortcutName) && this.shortcutsConfig.get(shortcutName) !== projectPath) { shortcutName = `${originalName}${counter}`; counter++; } // Create new shortcut await this.createShortcut(shortcutName, projectPath); this.shortcutsConfig.set(shortcutName, projectPath); this.saveShortcutsConfig(); return shortcutName; } /** * Generate project-based shortcut name with 3-5 words hyphenated */ private generateShortcutName(projectName: string): string { // Convert project name to 3-5 words, hyphenated const cleanName = projectName .toLowerCase() .replace(/[^a-z0-9\s-]/g, '') // Remove special chars except spaces and hyphens .replace(/\s+/g, '-') // Replace spaces with hyphens .replace(/-+/g, '-') // Replace multiple hyphens with single .replace(/^-|-$/g, ''); // Remove leading/trailing hyphens // Limit to 3-5 words (take first 5 parts separated by hyphens) const words = cleanName.split('-').slice(0, 5); const shortName = words.join('-'); return shortName; } /** * Create OS-specific shortcut */ private async createShortcut(name: string, targetPath: string): Promise<void> { const platform = os.platform(); try { switch (platform) { case 'win32': await this.createWindowsShortcut(name, targetPath); break; case 'darwin': case 'linux': await this.createUnixSymlink(name, targetPath); break; default: throw new Error(`Unsupported platform: ${platform}`); } } catch { // Don't throw - fallback to original path // Error logged silently to avoid spamming output } } /** * Create Windows directory shortcut using mklink */ private async createWindowsShortcut(name: string, targetPath: string): Promise<void> { const linkPath = path.join(this.shortcutsDir, name); // Remove existing link if it exists try { await fs.promises.unlink(linkPath); } catch { // Ignore if doesn't exist } // Windows: Create directory junction (like symlink but works without admin rights) const { spawn } = await import('child_process'); const mklink = spawn('cmd', ['/c', 'mklink', '/J', `"${linkPath}"`, `"${targetPath}"`], { stdio: 'inherit' }); return new Promise((resolve, reject) => { mklink.on('close', (code) => { if (code === 0) { resolve(); } else { // Fallback: try symlink (requires admin) const symlinkCmd = spawn('cmd', ['/c', 'mklink', '/D', `"${linkPath}"`, `"${targetPath}"`], { stdio: 'inherit' }); symlinkCmd.on('close', (symlinkCode) => { if (symlinkCode === 0) { resolve(); } else { reject(new Error(`Failed to create Windows directory shortcut`)); } }); } }); }); } /** * Create Unix symlink - actual directory shortcut */ private async createUnixSymlink(name: string, targetPath: string): Promise<void> { const linkPath = path.join(this.shortcutsDir, name); // Remove existing symlink if it exists try { await fs.promises.unlink(linkPath); } catch { // Ignore if doesn't exist } // Create actual directory symlink await fs.promises.symlink(targetPath, linkPath); } /** * Get shortcuts directory based on OS (much shorter path) */ private getShortcutsDirectory(): string { const homeDir = os.homedir(); // Use much shorter paths - 3 times shorter return path.join(homeDir, '.cs'); } /** * Ensure shortcuts directory exists */ private ensureShortcutsDirectory(): void { try { if (!fs.existsSync(this.shortcutsDir)) { fs.mkdirSync(this.shortcutsDir, { recursive: true }); } } catch { // Silently handle error - shortcuts are optional functionality } } /** * Load shortcuts configuration from file */ private loadShortcutsConfig(): void { try { if (fs.existsSync(this.configFile)) { const configData = fs.readFileSync(this.configFile, 'utf8'); const config = JSON.parse(configData); this.shortcutsConfig = new Map(Object.entries(config)); } } catch { // Silently reset to empty config if load fails this.shortcutsConfig = new Map(); } } /** * Save shortcuts configuration to file */ private saveShortcutsConfig(): void { try { const config = Object.fromEntries(this.shortcutsConfig); fs.writeFileSync(this.configFile, JSON.stringify(config, null, 2)); } catch { // Silently handle save errors - shortcuts are optional functionality } } /** * Ensure shortcuts directory is in PATH (Windows only) */ private async ensureInPath(): Promise<void> { if (os.platform() !== 'win32') return; // This is a complex operation that requires modifying Windows registry // Shortcuts will work directly if the batch files are in a PATH directory } /** * Get the command to use the shortcut based on OS */ getShortcutCommand(shortcutName: string): string { const platform = os.platform(); switch (platform) { case 'win32': // Windows uses %USERPROFILE% for home directory return `cd "%USERPROFILE%\\.cs\\${shortcutName}"`; case 'darwin': case 'linux': default: // Unix systems use ~ for home directory (no quotes for tilde expansion) return `cd ~/.cs/${shortcutName}`; } } /** * Check if a shortcut exists */ hasShortcut(shortcutName: string): boolean { return this.shortcutsConfig.has(shortcutName); } /** * Get all shortcuts */ getAllShortcuts(): Array<{name: string; path: string}> { return Array.from(this.shortcutsConfig.entries()).map(([name, path]) => ({ name, path })); } }