import Cocoa import Foundation // MARK: - Settings & Configuration class Settings { static let shared = Settings() var theme: Theme = .dracula var fontSize: CGFloat = 14 var aiProvider: AIProvider = .none var openAIKey: String = "" var ollamaURL: String = "http://localhost:11434" var ollamaModel: String = "llama3.2:latest" enum AIProvider: String { case none, openai, ollama } struct Theme { let background: NSColor let foreground: NSColor static let dracula = Theme( 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) ) static let classic = Theme( background: .black, foreground: NSColor(red: 0.0, green: 1.0, blue: 0.0, alpha: 1.0) ) static let solarized = Theme( 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) ) } } // MARK: - Snippet Manager class SnippetManager { static let shared = SnippetManager() private var snippets: [(name: String, command: String)] = [] init() { snippets = [ ("List files", "ls -lah"), ("Git status", "git status"), ("Find large files", "find . -type f -size +100M"), ("Docker ps", "docker ps -a"), ("Kill port 8080", "lsof -ti:8080 | xargs kill -9"), ("Disk usage", "du -sh * | sort -h") ] } func getSnippets() -> [(name: String, command: String)] { return snippets } func addSnippet(name: String, command: String) { snippets.append((name, command)) } } // MARK: - AI Service class AIService { static let shared = AIService() func generateCommand(from prompt: String, completion: @escaping (String?) -> Void) { let settings = Settings.shared switch settings.aiProvider { case .openai: generateWithOpenAI(prompt: prompt, completion: completion) case .ollama: generateWithOllama(prompt: prompt, completion: completion) case .none: completion(nil) } } private func generateWithOpenAI(prompt: String, completion: @escaping (String?) -> Void) { guard let url = URL(string: "https://api.openai.com/v1/chat/completions") else { completion(nil) return } var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("Bearer \(Settings.shared.openAIKey)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") let body: [String: Any] = [ "model": "gpt-3.5-turbo", "messages": [ ["role": "system", "content": "You are a terminal command expert. Generate only the command, no explanations."], ["role": "user", "content": prompt] ], "max_tokens": 100 ] request.httpBody = try? JSONSerialization.data(withJSONObject: body) URLSession.shared.dataTask(with: request) { data, _, _ in guard let data = data, let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], let choices = json["choices"] as? [[String: Any]], let message = choices.first?["message"] as? [String: Any], let content = message["content"] as? String else { completion(nil) return } completion(content.trimmingCharacters(in: .whitespacesAndNewlines)) }.resume() } private func generateWithOllama(prompt: String, completion: @escaping (String?) -> Void) { guard let url = URL(string: "\(Settings.shared.ollamaURL)/api/generate") else { completion(nil) return } var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") let body: [String: Any] = [ "model": Settings.shared.ollamaModel, "prompt": "Generate only the terminal command for: \(prompt). Respond with just the command.", "stream": false ] request.httpBody = try? JSONSerialization.data(withJSONObject: body) URLSession.shared.dataTask(with: request) { data, _, _ in guard let data = data, let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], let response = json["response"] as? String else { completion(nil) return } completion(response.trimmingCharacters(in: .whitespacesAndNewlines)) }.resume() } } // MARK: - Terminal View Controller class TerminalViewController: NSViewController, NSTextViewDelegate { var textView: NSTextView! var shellTask: Process? var inputPipe: Pipe? var outputPipe: Pipe? var promptPosition = 0 override func loadView() { let scrollView = NSScrollView(frame: NSRect(x: 0, y: 0, width: 800, height: 600)) scrollView.autoresizingMask = [.width, .height] scrollView.hasVerticalScroller = true textView = NSTextView(frame: scrollView.bounds) textView.autoresizingMask = [.width, .height] applyTheme() textView.isEditable = true textView.delegate = self scrollView.documentView = textView self.view = scrollView } func applyTheme() { let theme = Settings.shared.theme textView.backgroundColor = theme.background textView.textColor = theme.foreground textView.font = NSFont.monospacedSystemFont(ofSize: Settings.shared.fontSize, weight: .regular) } override func viewDidLoad() { super.viewDidLoad() appendText("Enhanced Terminal v2.0\n") appendText("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n") appendText("Shortcuts:\n") appendText(" Cmd+K - AI Command Generator (describe what you want)\n") appendText(" Cmd+P - Quick Snippets (common commands)\n") appendText(" Cmd+, - Settings (themes, AI config)\n") appendText("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n") startShell() } func startShell() { shellTask = Process() inputPipe = Pipe() outputPipe = Pipe() shellTask?.executableURL = URL(fileURLWithPath: "/bin/zsh") shellTask?.arguments = ["-i"] shellTask?.environment = [ "TERM": "dumb", "PS1": "$ ", "PATH": ProcessInfo.processInfo.environment["PATH"] ?? "/usr/bin:/bin:/usr/sbin:/sbin", "HOME": ProcessInfo.processInfo.environment["HOME"] ?? "" ] shellTask?.standardInput = inputPipe shellTask?.standardOutput = outputPipe shellTask?.standardError = outputPipe outputPipe?.fileHandleForReading.readabilityHandler = { [weak self] handle in let data = handle.availableData if data.count > 0, let output = String(data: data, encoding: .utf8) { DispatchQueue.main.async { self?.appendText(output) } } } do { try shellTask?.run() } catch { appendText("Error starting shell: \(error)\n") } showPrompt() } func showPrompt() { appendText("$ ") promptPosition = textView.string.count } func appendText(_ text: String) { let cleaned = text .replacingOccurrences(of: "\u{1B}\\[[0-9;]*[a-zA-Z]", with: "", options: .regularExpression) .replacingOccurrences(of: "\u{1B}\\][0-9];[^\u{07}]*\u{07}", with: "", options: .regularExpression) .replacingOccurrences(of: "\\]633;[^\u{07}]*\u{07}", with: "", options: .regularExpression) .replacingOccurrences(of: "\\]697;[^\u{07}]*\u{07}", with: "", options: .regularExpression) .replacingOccurrences(of: "\\]1;[^\u{07}]*\u{07}", with: "", options: .regularExpression) .replacingOccurrences(of: "\\]2;[^\u{07}]*\u{07}", with: "", options: .regularExpression) .replacingOccurrences(of: "\\]7;[^\u{07}]*\\\\", with: "", options: .regularExpression) let theme = Settings.shared.theme let attr = NSAttributedString(string: cleaned, attributes: [ .foregroundColor: theme.foreground, .font: NSFont.monospacedSystemFont(ofSize: Settings.shared.fontSize, weight: .regular) ]) textView.textStorage?.append(attr) textView.scrollToEndOfDocument(nil) } func textView(_ textView: NSTextView, shouldChangeTextIn range: NSRange, replacementString: String?) -> Bool { if range.location < promptPosition { return false } if replacementString == "\n" { let command = String(textView.string.suffix(from: textView.string.index(textView.string.startIndex, offsetBy: promptPosition))) executeCommand(command) return false } return true } func executeCommand(_ command: String) { appendText("\n") let trimmed = command.trimmingCharacters(in: .whitespacesAndNewlines) if trimmed == "exit" { NSApp.terminate(nil) return } if trimmed == "clear" { textView.string = "" showPrompt() return } if !trimmed.isEmpty { if let data = (trimmed + "\n").data(using: .utf8) { inputPipe?.fileHandleForWriting.write(data) } } DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in self?.showPrompt() } } func insertCommand(_ command: String) { // Insert command at current cursor position textView.insertText(command, replacementRange: NSRange(location: textView.string.count, length: 0)) // Show a hint DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in self?.textView.window?.makeFirstResponder(self?.textView) } } deinit { shellTask?.terminate() } } // MARK: - Snippet Panel class SnippetPanel: NSPanel { var completion: ((String) -> Void)? init() { super.init( contentRect: NSRect(x: 0, y: 0, width: 400, height: 300), styleMask: [.titled, .closable], backing: .buffered, defer: false ) title = "Snippets" setupUI() } func setupUI() { let container = NSView(frame: contentView!.bounds) var y: CGFloat = container.bounds.height - 40 for snippet in SnippetManager.shared.getSnippets() { let button = NSButton(frame: NSRect(x: 20, y: y, width: 360, height: 30)) button.title = "\(snippet.name): \(snippet.command)" button.bezelStyle = .rounded button.target = self button.action = #selector(snippetSelected(_:)) button.tag = SnippetManager.shared.getSnippets().firstIndex(where: { $0.name == snippet.name }) ?? 0 container.addSubview(button) y -= 35 } contentView = container } @objc func snippetSelected(_ sender: NSButton) { let snippets = SnippetManager.shared.getSnippets() if sender.tag < snippets.count { completion?(snippets[sender.tag].command) close() } } func show(completion: @escaping (String) -> Void) { self.completion = completion center() makeKeyAndOrderFront(nil) } } // MARK: - AI Panel class AIPanel: NSPanel { var promptField: NSTextField! var resultField: NSTextField! var completion: ((String) -> Void)? init() { super.init( contentRect: NSRect(x: 0, y: 0, width: 500, height: 200), styleMask: [.titled, .closable], backing: .buffered, defer: false ) title = "AI Command Generator" setupUI() } func setupUI() { let container = NSView(frame: contentView!.bounds) let label = NSTextField(labelWithString: "Describe what you want to do:") label.frame = NSRect(x: 20, y: 140, width: 460, height: 20) promptField = NSTextField(frame: NSRect(x: 20, y: 110, width: 460, height: 24)) promptField.placeholderString = "e.g., find all PDF files" let generateBtn = NSButton(frame: NSRect(x: 20, y: 70, width: 100, height: 30)) generateBtn.title = "Generate" generateBtn.bezelStyle = .rounded generateBtn.target = self generateBtn.action = #selector(generate) resultField = NSTextField(labelWithString: "") resultField.frame = NSRect(x: 20, y: 40, width: 460, height: 24) let useBtn = NSButton(frame: NSRect(x: 380, y: 70, width: 100, height: 30)) useBtn.title = "Use" useBtn.bezelStyle = .rounded useBtn.target = self useBtn.action = #selector(useCommand) let hintLabel = NSTextField(labelWithString: "Tip: After clicking 'Use', press Enter to execute the command") hintLabel.frame = NSRect(x: 20, y: 10, width: 460, height: 20) hintLabel.textColor = .secondaryLabelColor hintLabel.font = NSFont.systemFont(ofSize: 11) container.addSubview(label) container.addSubview(promptField) container.addSubview(generateBtn) container.addSubview(resultField) container.addSubview(useBtn) container.addSubview(hintLabel) contentView = container } @objc func generate() { let prompt = promptField.stringValue guard !prompt.isEmpty else { return } resultField.stringValue = "Generating..." AIService.shared.generateCommand(from: prompt) { [weak self] command in DispatchQueue.main.async { self?.resultField.stringValue = command ?? "Error: Could not generate command" } } } @objc func useCommand() { let command = resultField.stringValue guard !command.isEmpty && !command.hasPrefix("Error") && !command.hasPrefix("Generating") else { return } completion?(command) close() } func show(completion: @escaping (String) -> Void) { self.completion = completion center() makeKeyAndOrderFront(nil) } } // MARK: - Settings Panel class SettingsPanel: NSPanel { init() { super.init( contentRect: NSRect(x: 0, y: 0, width: 450, height: 350), styleMask: [.titled, .closable], backing: .buffered, defer: false ) title = "Settings" setupUI() } func setupUI() { let container = NSView(frame: contentView!.bounds) var y: CGFloat = container.bounds.height - 40 // Theme let themeLabel = NSTextField(labelWithString: "Theme:") themeLabel.frame = NSRect(x: 20, y: y, width: 100, height: 20) let themePopup = NSPopUpButton(frame: NSRect(x: 130, y: y - 5, width: 200, height: 30)) themePopup.addItems(withTitles: ["Classic", "Dracula", "Solarized"]) themePopup.target = self themePopup.action = #selector(themeChanged(_:)) container.addSubview(themeLabel) container.addSubview(themePopup) y -= 50 // AI Provider let aiLabel = NSTextField(labelWithString: "AI Provider:") aiLabel.frame = NSRect(x: 20, y: y, width: 100, height: 20) let aiPopup = NSPopUpButton(frame: NSRect(x: 130, y: y - 5, width: 200, height: 30)) aiPopup.addItems(withTitles: ["None", "OpenAI", "Ollama"]) aiPopup.target = self aiPopup.action = #selector(aiChanged(_:)) container.addSubview(aiLabel) container.addSubview(aiPopup) y -= 50 // OpenAI Key let keyLabel = NSTextField(labelWithString: "OpenAI Key:") keyLabel.frame = NSRect(x: 20, y: y, width: 100, height: 20) let keyField = NSSecureTextField(frame: NSRect(x: 130, y: y, width: 300, height: 24)) keyField.placeholderString = "sk-..." keyField.target = self keyField.action = #selector(keyChanged(_:)) container.addSubview(keyLabel) container.addSubview(keyField) y -= 50 // Ollama URL let urlLabel = NSTextField(labelWithString: "Ollama URL:") urlLabel.frame = NSRect(x: 20, y: y, width: 100, height: 20) let urlField = NSTextField(frame: NSRect(x: 130, y: y, width: 300, height: 24)) urlField.placeholderString = "http://localhost:11434" urlField.stringValue = Settings.shared.ollamaURL urlField.target = self urlField.action = #selector(urlChanged(_:)) container.addSubview(urlLabel) container.addSubview(urlField) y -= 50 // Ollama Model let modelLabel = NSTextField(labelWithString: "Ollama Model:") modelLabel.frame = NSRect(x: 20, y: y, width: 100, height: 20) let modelField = NSTextField(frame: NSRect(x: 130, y: y, width: 300, height: 24)) modelField.placeholderString = "llama3.2:latest" modelField.stringValue = Settings.shared.ollamaModel modelField.target = self modelField.action = #selector(modelChanged(_:)) container.addSubview(modelLabel) container.addSubview(modelField) contentView = container } @objc func themeChanged(_ sender: NSPopUpButton) { switch sender.indexOfSelectedItem { case 0: Settings.shared.theme = .classic case 1: Settings.shared.theme = .dracula case 2: Settings.shared.theme = .solarized default: break } } @objc func aiChanged(_ sender: NSPopUpButton) { switch sender.indexOfSelectedItem { case 0: Settings.shared.aiProvider = .none case 1: Settings.shared.aiProvider = .openai case 2: Settings.shared.aiProvider = .ollama default: break } } @objc func keyChanged(_ sender: NSTextField) { Settings.shared.openAIKey = sender.stringValue } @objc func urlChanged(_ sender: NSTextField) { Settings.shared.ollamaURL = sender.stringValue } @objc func modelChanged(_ sender: NSTextField) { Settings.shared.ollamaModel = sender.stringValue } } // MARK: - App Delegate class AppDelegate: NSObject, NSApplicationDelegate { var window: NSWindow! var terminalVC: TerminalViewController! func applicationDidFinishLaunching(_ notification: Notification) { window = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 900, height: 650), styleMask: [.titled, .closable, .miniaturizable, .resizable], backing: .buffered, defer: false ) window.center() window.title = "Enhanced Terminal" terminalVC = TerminalViewController() window.contentViewController = terminalVC setupMenu() window.makeKeyAndOrderFront(nil) NSApp.activate(ignoringOtherApps: true) } func setupMenu() { let mainMenu = NSMenu() // App menu let appMenuItem = NSMenuItem() mainMenu.addItem(appMenuItem) let appMenu = NSMenu() appMenuItem.submenu = appMenu appMenu.addItem(NSMenuItem(title: "Settings", action: #selector(openSettings), keyEquivalent: ",")) appMenu.addItem(NSMenuItem.separator()) appMenu.addItem(NSMenuItem(title: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")) // Tools menu let toolsMenuItem = NSMenuItem(title: "Tools", action: nil, keyEquivalent: "") mainMenu.addItem(toolsMenuItem) let toolsMenu = NSMenu(title: "Tools") toolsMenuItem.submenu = toolsMenu toolsMenu.addItem(NSMenuItem(title: "AI Generator", action: #selector(showAI), keyEquivalent: "k")) toolsMenu.addItem(NSMenuItem(title: "Snippets", action: #selector(showSnippets), keyEquivalent: "p")) NSApp.mainMenu = mainMenu } @objc func openSettings() { let panel = SettingsPanel() panel.center() panel.makeKeyAndOrderFront(nil) } @objc func showAI() { guard Settings.shared.aiProvider != .none else { let alert = NSAlert() alert.messageText = "Please configure AI provider in Settings first" alert.runModal() return } let panel = AIPanel() panel.show { [weak self] command in self?.terminalVC.insertCommand(command) } } @objc func showSnippets() { let panel = SnippetPanel() panel.show { [weak self] command in self?.terminalVC.insertCommand(command) } } func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } } // MARK: - Main Entry Point let app = NSApplication.shared let delegate = AppDelegate() app.delegate = delegate app.run()