import Cocoa import Foundation // MARK: - Configuration & Settings class TerminalSettings { static let shared = TerminalSettings() var theme: Theme = .dracula var fontSize: CGFloat = 14 var fontName: String = "Menlo" var opacity: CGFloat = 1.0 var aiProvider: AIProvider = .none var openAIKey: String = "" var ollamaURL: String = "http://localhost:11434" var ollamaModel: String = "llama2" enum AIProvider: String { case none = "None" case openai = "OpenAI" case ollama = "Ollama" } struct Theme { let name: String let background: NSColor let foreground: NSColor let cursor: NSColor let selection: NSColor static let dracula = Theme( name: "Dracula", background: NSColor(red: 0.16, green: 0.16, blue: 0.21, alpha: 1.0), foreground: NSColor(red: 0.95, green: 0.95, blue: 0.95, alpha: 1.0), cursor: NSColor(red: 0.97, green: 0.97, blue: 0.95, alpha: 1.0), selection: NSColor(red: 0.27, green: 0.28, blue: 0.35, alpha: 1.0) ) static let solarizedDark = Theme( name: "Solarized Dark", background: NSColor(red: 0.0, green: 0.17, blue: 0.21, alpha: 1.0), foreground: NSColor(red: 0.51, green: 0.58, blue: 0.59, alpha: 1.0), cursor: NSColor(red: 0.51, green: 0.58, blue: 0.59, alpha: 1.0), selection: NSColor(red: 0.03, green: 0.21, blue: 0.26, alpha: 1.0) ) static let nord = Theme( name: "Nord", background: NSColor(red: 0.18, green: 0.20, blue: 0.25, alpha: 1.0), foreground: NSColor(red: 0.85, green: 0.87, blue: 0.91, alpha: 1.0), cursor: NSColor(red: 0.85, green: 0.87, blue: 0.91, alpha: 1.0), selection: NSColor(red: 0.26, green: 0.30, blue: 0.37, alpha: 1.0) ) static let classic = Theme( name: "Classic", background: .black, foreground: NSColor(red: 0.0, green: 1.0, blue: 0.0, alpha: 1.0), cursor: NSColor(red: 0.0, green: 1.0, blue: 0.0, alpha: 1.0), selection: NSColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1.0) ) static let allThemes: [Theme] = [.classic, .dracula, .solarizedDark, .nord] } } // MARK: - Command Snippet Manager class SnippetManager { static let shared = SnippetManager() private var snippets: [Snippet] = [] struct Snippet: Codable { let id: UUID var name: String var command: String var description: String var tags: [String] var category: String init(name: String, command: String, description: String = "", tags: [String] = [], category: String = "General") { self.id = UUID() self.name = name self.command = command self.description = description self.tags = tags self.category = category } } init() { loadSnippets() if snippets.isEmpty { addDefaultSnippets() } } func addSnippet(_ snippet: Snippet) { snippets.append(snippet) saveSnippets() } func removeSnippet(_ id: UUID) { snippets.removeAll { $0.id == id } saveSnippets() } func searchSnippets(_ query: String) -> [Snippet] { if query.isEmpty { return snippets } return snippets.filter { $0.name.localizedCaseInsensitiveContains(query) || $0.command.localizedCaseInsensitiveContains(query) || $0.tags.contains { $0.localizedCaseInsensitiveContains(query) } } } private func addDefaultSnippets() { snippets = [ Snippet(name: "List files", command: "ls -lah", description: "List all files with details", tags: ["files", "list"], category: "Files"), Snippet(name: "Find large files", command: "find . -type f -size +100M", description: "Find files larger than 100MB", tags: ["find", "files"], category: "Files"), Snippet(name: "Git status", command: "git status", description: "Show git repository status", tags: ["git"], category: "Git"), Snippet(name: "Git commit", command: "git add . && git commit -m \"\"", description: "Stage and commit changes", tags: ["git"], category: "Git"), Snippet(name: "Docker ps", command: "docker ps -a", description: "List all containers", tags: ["docker"], category: "Docker"), Snippet(name: "Kill port", command: "lsof -ti:8080 | xargs kill -9", description: "Kill process on port 8080", tags: ["network", "kill"], category: "Network") ] saveSnippets() } private func saveSnippets() { if let data = try? JSONEncoder().encode(snippets) { UserDefaults.standard.set(data, forKey: "terminal_snippets") } } private func loadSnippets() { if let data = UserDefaults.standard.data(forKey: "terminal_snippets"), let loaded = try? JSONDecoder().decode([Snippet].self, from: data) { snippets = loaded } } func getAllSnippets() -> [Snippet] { return snippets } } // MARK: - Command History Manager class HistoryManager { static let shared = HistoryManager() private var history: [String] = [] private let maxHistory = 1000 init() { loadHistory() } func addCommand(_ command: String) { guard !command.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return } history.append(command) if history.count > maxHistory { history.removeFirst() } saveHistory() } func searchHistory(_ query: String) -> [String] { if query.isEmpty { return Array(history.reversed().prefix(50)) } return history.reversed().filter { $0.localizedCaseInsensitiveContains(query) } } func getRecentCommands(_ count: Int = 10) -> [String] { return Array(history.reversed().prefix(count)) } private func saveHistory() { UserDefaults.standard.set(history, forKey: "terminal_history") } private func loadHistory() { history = UserDefaults.standard.stringArray(forKey: "terminal_history") ?? [] } } // MARK: - AI Service protocol AIService { func generateCommand(from prompt: String, completion: @escaping (Result) -> Void) func explainError(_ error: String, completion: @escaping (Result) -> Void) func suggestNextCommand(history: [String], completion: @escaping (Result) -> Void) } class OpenAIService: AIService { private let apiKey: String init(apiKey: String) { self.apiKey = apiKey } func generateCommand(from prompt: String, completion: @escaping (Result) -> Void) { let systemPrompt = "You are a terminal command expert. Generate only the command, no explanations. Respond with just the command." makeRequest(system: systemPrompt, user: prompt, completion: completion) } func explainError(_ error: String, completion: @escaping (Result) -> Void) { let systemPrompt = "You are a terminal expert. Explain this error briefly and suggest a fix." makeRequest(system: systemPrompt, user: error, completion: completion) } func suggestNextCommand(history: [String], completion: @escaping (Result) -> Void) { let systemPrompt = "Based on command history, suggest the next logical command. Respond with just the command." let historyText = history.suffix(5).joined(separator: "\n") makeRequest(system: systemPrompt, user: "Recent commands:\n\(historyText)", completion: completion) } private func makeRequest(system: String, user: String, completion: @escaping (Result) -> Void) { guard let url = URL(string: "https://api.openai.com/v1/chat/completions") else { return } var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") let body: [String: Any] = [ "model": "gpt-3.5-turbo", "messages": [ ["role": "system", "content": system], ["role": "user", "content": user] ], "max_tokens": 150, "temperature": 0.7 ] request.httpBody = try? JSONSerialization.data(withJSONObject: body) URLSession.shared.dataTask(with: request) { data, response, error in if let error = error { completion(.failure(error)) return } guard let data = data, let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], let choices = json["choices"] as? [[String: Any]], let firstChoice = choices.first, let message = firstChoice["message"] as? [String: Any], let content = message["content"] as? String else { completion(.failure(NSError(domain: "OpenAI", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid response"]))) return } completion(.success(content.trimmingCharacters(in: .whitespacesAndNewlines))) }.resume() } } class OllamaService: AIService { private let baseURL: String private let model: String init(baseURL: String, model: String) { self.baseURL = baseURL self.model = model } func generateCommand(from prompt: String, completion: @escaping (Result) -> Void) { let fullPrompt = "Generate only the terminal command for: \(prompt). Respond with just the command, no explanation." makeRequest(prompt: fullPrompt, completion: completion) } func explainError(_ error: String, completion: @escaping (Result) -> Void) { let fullPrompt = "Explain this terminal error briefly and suggest a fix:\n\(error)" makeRequest(prompt: fullPrompt, completion: completion) } func suggestNextCommand(history: [String], completion: @escaping (Result) -> Void) { let historyText = history.suffix(5).joined(separator: "\n") let fullPrompt = "Based on these recent commands, suggest the next logical command:\n\(historyText)\nRespond with just the command." makeRequest(prompt: fullPrompt, completion: completion) } private func makeRequest(prompt: String, completion: @escaping (Result) -> Void) { guard let url = URL(string: "\(baseURL)/api/generate") else { return } var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") let body: [String: Any] = [ "model": model, "prompt": prompt, "stream": false ] request.httpBody = try? JSONSerialization.data(withJSONObject: body) URLSession.shared.dataTask(with: request) { data, response, error in if let error = error { completion(.failure(error)) return } guard let data = data, let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], let response = json["response"] as? String else { completion(.failure(NSError(domain: "Ollama", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid response"]))) return } completion(.success(response.trimmingCharacters(in: .whitespacesAndNewlines))) }.resume() } } class AIManager { static let shared = AIManager() private var service: AIService? func updateService() { let settings = TerminalSettings.shared switch settings.aiProvider { case .openai: if !settings.openAIKey.isEmpty { service = OpenAIService(apiKey: settings.openAIKey) } case .ollama: service = OllamaService(baseURL: settings.ollamaURL, model: settings.ollamaModel) case .none: service = nil } } func isAvailable() -> Bool { return service != nil } func generateCommand(from prompt: String, completion: @escaping (Result) -> Void) { service?.generateCommand(from: prompt, completion: completion) } func explainError(_ error: String, completion: @escaping (Result) -> Void) { service?.explainError(error, completion: completion) } func suggestNextCommand(history: [String], completion: @escaping (Result) -> Void) { service?.suggestNextCommand(history: history, completion: completion) } }