|
6 | 6 | import os
|
7 | 7 | import platform
|
8 | 8 | import re
|
| 9 | +import subprocess |
9 | 10 | import time
|
10 | 11 |
|
11 | 12 | import platformdirs
|
|
49 | 50 |
|
50 | 51 | def main():
|
51 | 52 | keyboard = Controller()
|
| 53 | + history = None |
52 | 54 |
|
53 |
| - # Save clipboard |
54 |
| - clipboard = pyperclip.paste() |
| 55 | + ## SELECT ALL AND COPY METHOD |
55 | 56 |
|
56 |
| - # Select all text |
57 |
| - shortcut_key = Key.cmd if platform.system() == "Darwin" else Key.ctrl |
58 |
| - with keyboard.pressed(shortcut_key): |
59 |
| - keyboard.press("a") |
60 |
| - keyboard.release("a") |
| 57 | + if True: |
| 58 | + # Save clipboard |
| 59 | + clipboard = pyperclip.paste() |
61 | 60 |
|
62 |
| - # Copy selected text |
63 |
| - with keyboard.pressed(shortcut_key): |
64 |
| - keyboard.press("c") |
65 |
| - keyboard.release("c") |
| 61 | + # Select all text |
| 62 | + shortcut_key = Key.cmd if platform.system() == "Darwin" else Key.ctrl |
| 63 | + with keyboard.pressed(shortcut_key): |
| 64 | + keyboard.press("a") |
| 65 | + keyboard.release("a") |
66 | 66 |
|
67 |
| - # Deselect |
68 |
| - keyboard.press(Key.backspace) |
69 |
| - keyboard.release(Key.backspace) |
| 67 | + # Copy selected text |
| 68 | + with keyboard.pressed(shortcut_key): |
| 69 | + keyboard.press("c") |
| 70 | + keyboard.release("c") |
70 | 71 |
|
71 |
| - # Wait for the clipboard to update |
72 |
| - time.sleep(0.1) |
| 72 | + # Deselect |
| 73 | + keyboard.press(Key.backspace) |
| 74 | + keyboard.release(Key.backspace) |
73 | 75 |
|
74 |
| - # Get terminal history from clipboard |
75 |
| - history = pyperclip.paste() |
| 76 | + # Wait for the clipboard to update |
| 77 | + time.sleep(0.1) |
76 | 78 |
|
77 |
| - # Reset clipboard to stored one |
78 |
| - pyperclip.copy(clipboard) |
| 79 | + # Get terminal history from clipboard |
| 80 | + history = pyperclip.paste() |
| 81 | + |
| 82 | + # Reset clipboard to stored one |
| 83 | + pyperclip.copy(clipboard) |
| 84 | + |
| 85 | + ## OCR SCREENSHOT METHOD |
| 86 | + |
| 87 | + if not history: |
| 88 | + try: |
| 89 | + import pytesseract |
| 90 | + from PIL import ImageGrab |
| 91 | + |
| 92 | + # Get active window coordinates using platform-specific methods |
| 93 | + platform_name = platform.system() |
| 94 | + if platform_name == "Windows": |
| 95 | + import win32gui |
| 96 | + |
| 97 | + window = win32gui.GetForegroundWindow() |
| 98 | + left, top, right, bottom = win32gui.GetWindowRect(window) |
| 99 | + elif platform_name == "Darwin": |
| 100 | + from Quartz import ( |
| 101 | + CGWindowListCopyWindowInfo, |
| 102 | + kCGNullWindowID, |
| 103 | + kCGWindowListOptionOnScreenOnly, |
| 104 | + ) |
| 105 | + |
| 106 | + window_info = CGWindowListCopyWindowInfo( |
| 107 | + kCGWindowListOptionOnScreenOnly, kCGNullWindowID |
| 108 | + ) |
| 109 | + for window in window_info: |
| 110 | + if window["kCGWindowLayer"] == 0: |
| 111 | + window_geometry = window["kCGWindowBounds"] |
| 112 | + left = window_geometry["X"] |
| 113 | + top = window_geometry["Y"] |
| 114 | + right = int(left + window_geometry["Width"]) |
| 115 | + bottom = int(top + window_geometry["Height"]) |
| 116 | + break |
| 117 | + else: # Assume it's a Linux-based system |
| 118 | + root = subprocess.Popen( |
| 119 | + ["xprop", "-root", "_NET_ACTIVE_WINDOW"], stdout=subprocess.PIPE |
| 120 | + ) |
| 121 | + stdout, stderr = root.communicate() |
| 122 | + m = re.search(b"^_NET_ACTIVE_WINDOW.* ([\\w]+)$", stdout) |
| 123 | + if m is not None: |
| 124 | + window_id = m.group(1) |
| 125 | + window = subprocess.Popen( |
| 126 | + ["xwininfo", "-id", window_id], stdout=subprocess.PIPE |
| 127 | + ) |
| 128 | + stdout, stderr = window.communicate() |
| 129 | + match = re.search( |
| 130 | + rb"Absolute upper-left X:\s*(\d+).*Absolute upper-left Y:\s*(\d+).*Width:\s*(\d+).*Height:\s*(\d+)", |
| 131 | + stdout, |
| 132 | + re.DOTALL, |
| 133 | + ) |
| 134 | + if match is not None: |
| 135 | + left, top, width, height = map(int, match.groups()) |
| 136 | + right = left + width |
| 137 | + bottom = top + height |
| 138 | + |
| 139 | + # spinner.stop() |
| 140 | + # print("\nPermission to capture terminal commands via screenshot -> OCR?") |
| 141 | + # permission = input("(y/n) > ") |
| 142 | + # print("") |
| 143 | + # if permission.lower() != 'y': |
| 144 | + # print("Exiting...") |
| 145 | + # exit() |
| 146 | + # spinner.start() |
| 147 | + |
| 148 | + # Take screenshot of the active window |
| 149 | + screenshot = ImageGrab.grab( |
| 150 | + bbox=(int(left), int(top), int(right), int(bottom)) |
| 151 | + ) |
| 152 | + |
| 153 | + # OCR the screenshot to get the text |
| 154 | + text = pytesseract.image_to_string(screenshot) |
| 155 | + |
| 156 | + history = text |
| 157 | + |
| 158 | + if "wtf" in history: |
| 159 | + last_wtf_index = history.rindex("wtf") |
| 160 | + history = history[:last_wtf_index] |
| 161 | + except ImportError: |
| 162 | + spinner.stop() |
| 163 | + print( |
| 164 | + "To use OCR to capture terminal output (recommended) run `pip install pytesseract` or `pip3 install pytesseract`." |
| 165 | + ) |
| 166 | + spinner.start() |
| 167 | + |
| 168 | + ## TERMINAL HISTORY METHOD |
| 169 | + |
| 170 | + if not history: |
| 171 | + try: |
| 172 | + shell = os.environ.get("SHELL", "/bin/bash") |
| 173 | + command = [shell, "-ic", "fc -ln -10"] # Get just the last command |
| 174 | + |
| 175 | + output = subprocess.check_output(command, stderr=subprocess.STDOUT).decode( |
| 176 | + "utf-8" |
| 177 | + ) |
| 178 | + |
| 179 | + # Split the output into lines |
| 180 | + lines = output.strip().split("\n") |
| 181 | + |
| 182 | + # Filter out lines that look like the "saving session" message |
| 183 | + history = [ |
| 184 | + line |
| 185 | + for line in lines |
| 186 | + if not line.startswith("...") |
| 187 | + and "saving" not in line |
| 188 | + and "Saving session..." not in line |
| 189 | + ] |
| 190 | + history = [l.strip() for l in history if l.strip()][-10:] |
| 191 | + |
| 192 | + # Split the history into individual commands |
| 193 | + |
| 194 | + # Get the last command |
| 195 | + last_command = history[-1] |
| 196 | + spinner.start() |
| 197 | + print( |
| 198 | + f"\nRunning the last command again to collect its output: {last_command}\n" |
| 199 | + ) |
| 200 | + spinner.stop() |
| 201 | + # Run the last command and collect its output |
| 202 | + try: |
| 203 | + last_command_output = subprocess.check_output( |
| 204 | + last_command, shell=True, stderr=subprocess.STDOUT |
| 205 | + ).decode("utf-8") |
| 206 | + except subprocess.CalledProcessError as e: |
| 207 | + last_command_output = e.output.decode("utf-8") |
| 208 | + except Exception as e: |
| 209 | + last_command_output = str(e) |
| 210 | + |
| 211 | + # Format the history |
| 212 | + history = "The user tried to run the following commands:\n" + "\n".join( |
| 213 | + history |
| 214 | + ) |
| 215 | + history += f"\nThe last command, {last_command}, resulted in this output:\n{last_command_output}" |
| 216 | + |
| 217 | + except Exception as e: |
| 218 | + raise |
| 219 | + print( |
| 220 | + "Failed to retrieve and run the last command from terminal history. Exiting." |
| 221 | + ) |
| 222 | + return |
79 | 223 |
|
80 | 224 | # Trim history
|
81 | 225 | history = history[-9000:].strip()
|
|
0 commit comments