├── .python-version ├── icons ├── feedback.png └── attribution.txt ├── .github ├── feedback-ui.png └── cline-installation.png ├── .gitignore ├── pyproject.toml ├── LICENSE ├── server.py ├── README.md ├── feedback_ui.py └── uv.lock /.python-version: -------------------------------------------------------------------------------- 1 | 3.11 2 | -------------------------------------------------------------------------------- /icons/feedback.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrexodia/user-feedback-mcp/HEAD/icons/feedback.png -------------------------------------------------------------------------------- /.github/feedback-ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrexodia/user-feedback-mcp/HEAD/.github/feedback-ui.png -------------------------------------------------------------------------------- /.github/cline-installation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrexodia/user-feedback-mcp/HEAD/.github/cline-installation.png -------------------------------------------------------------------------------- /icons/attribution.txt: -------------------------------------------------------------------------------- 1 | Good icons created by Freepik - Flaticon 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python-generated files 2 | __pycache__/ 3 | *.py[oc] 4 | build/ 5 | dist/ 6 | wheels/ 7 | *.egg-info 8 | 9 | # Virtual environments 10 | .venv*/ 11 | venv*/ 12 | 13 | # Logs 14 | *.log 15 | .user-feedback.json -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "user-feedback-mcp" 3 | version = "0.1.0" 4 | description = "Add your description here" 5 | readme = "README.md" 6 | requires-python = ">=3.11" 7 | dependencies = [ 8 | "fastmcp>=0.4.1", 9 | "psutil>=7.0.0", 10 | "pyside6>=6.8.2.1", 11 | ] 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Duncan Ogilvie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | import tempfile 5 | import subprocess 6 | 7 | from typing import Annotated, Dict 8 | 9 | from fastmcp import FastMCP 10 | from pydantic import Field 11 | 12 | # The log_level is necessary for Cline to work: https://github.com/jlowin/fastmcp/issues/81 13 | mcp = FastMCP("User Feedback", log_level="ERROR") 14 | 15 | def launch_feedback_ui(project_directory: str, summary: str) -> dict[str, str]: 16 | # Create a temporary file for the feedback result 17 | with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as tmp: 18 | output_file = tmp.name 19 | 20 | try: 21 | # Get the path to feedback_ui.py relative to this script 22 | script_dir = os.path.dirname(os.path.abspath(__file__)) 23 | feedback_ui_path = os.path.join(script_dir, "feedback_ui.py") 24 | 25 | # Run feedback_ui.py as a separate process 26 | # NOTE: There appears to be a bug in uv, so we need 27 | # to pass a bunch of special flags to make this work 28 | args = [ 29 | sys.executable, 30 | "-u", 31 | feedback_ui_path, 32 | "--project-directory", project_directory, 33 | "--prompt", summary, 34 | "--output-file", output_file 35 | ] 36 | result = subprocess.run( 37 | args, 38 | check=False, 39 | shell=False, 40 | stdout=subprocess.DEVNULL, 41 | stderr=subprocess.DEVNULL, 42 | stdin=subprocess.DEVNULL, 43 | close_fds=True 44 | ) 45 | if result.returncode != 0: 46 | raise Exception(f"Failed to launch feedback UI: {result.returncode}") 47 | 48 | # Read the result from the temporary file 49 | with open(output_file, 'r') as f: 50 | result = json.load(f) 51 | os.unlink(output_file) 52 | return result 53 | except Exception as e: 54 | if os.path.exists(output_file): 55 | os.unlink(output_file) 56 | raise e 57 | 58 | def first_line(text: str) -> str: 59 | return text.split("\n")[0].strip() 60 | 61 | @mcp.tool() 62 | def user_feedback( 63 | project_directory: Annotated[str, Field(description="Full path to the project directory")], 64 | summary: Annotated[str, Field(description="Short, one-line summary of the changes")], 65 | ) -> Dict[str, str]: 66 | """Request user feedback for a given project directory and summary""" 67 | return launch_feedback_ui(first_line(project_directory), first_line(summary)) 68 | 69 | if __name__ == "__main__": 70 | mcp.run(transport="stdio") 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # User Feedback MCP 2 | 3 | Simple [MCP Server](https://modelcontextprotocol.io/introduction) to enable a human-in-the-loop workflow in tools like [Cline](https://cline.bot) and [Cursor](https://www.cursor.com). This is especially useful for developing desktop applications that require complex user interactions to test. 4 | 5 | ![Screenshot showing the feedback UI](https://github.com/mrexodia/user-feedback-mcp/blob/main/.github/feedback-ui.png?raw=true) 6 | 7 | ## Prompt Engineering 8 | 9 | For the best results, add the following to your custom prompt: 10 | 11 | > Before completing the task, use the user_feedback MCP tool to ask the user for feedback. 12 | 13 | This will ensure Cline uses this MCP server to request user feedback before marking the task as completed. 14 | 15 | ## `.user-feedback.json` 16 | 17 | Hitting _Save Configuration_ creates a `.user-feedback.json` file in your project directory that looks like this: 18 | 19 | ```json 20 | { 21 | "command": "npm run dev", 22 | "execute_automatically": false 23 | } 24 | ``` 25 | 26 | This configuration will be loaded on startup and if `execute_automatically` is enabled your `command` will be instantly executed (you will not have to click _Run_ manually). For multi-step commands you should use something like [Task](https://taskfile.dev). 27 | 28 | ## Installation (Cline) 29 | 30 | To install the MCP server in Cline, follow these steps (see screenshot): 31 | 32 | ![Screenshot showing installation steps](https://github.com/mrexodia/user-feedback-mcp/blob/main/.github/cline-installation.png?raw=true) 33 | 34 | 1. Install [uv](https://github.com/astral-sh/uv) globally: 35 | - Windows: `pip install uv` 36 | - Linux/Mac: `curl -LsSf https://astral.sh/uv/install.sh | sh` 37 | 2. Clone this repository, for this example `C:\MCP\user-feedback-mcp`. 38 | 3. Navigate to the Cline _MCP Servers_ configuration (see screenshot). 39 | 4. Click on the _Installed_ tab. 40 | 5. Click on _Configure MCP Servers_, which will open `cline_mcp_settings.json`. 41 | 6. Add the `user-feedback-mcp` server: 42 | 43 | ```json 44 | { 45 | "mcpServers": { 46 | "github.com/mrexodia/user-feedback-mcp": { 47 | "command": "uv", 48 | "args": [ 49 | "--directory", 50 | "c:\\MCP\\user-feedback-mcp", 51 | "run", 52 | "server.py" 53 | ], 54 | "timeout": 600, 55 | "autoApprove": [ 56 | "user_feedback" 57 | ] 58 | } 59 | } 60 | } 61 | 62 | ``` 63 | 64 | ## Development 65 | 66 | ```sh 67 | uv run fastmcp dev server.py 68 | ``` 69 | 70 | This will open a web interface at http://localhost:5173 and allow you to interact with the MCP tools for testing. 71 | 72 | ## Available tools 73 | 74 | ``` 75 | 76 | github.com/mrexodia/user-feedback-mcp 77 | user_feedback 78 | 79 | { 80 | "project_directory": "C:/MCP/user-feedback-mcp", 81 | "summary": "I've implemented the changes you requested." 82 | } 83 | 84 | 85 | ``` -------------------------------------------------------------------------------- /feedback_ui.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | import psutil 5 | import argparse 6 | import subprocess 7 | import threading 8 | from typing import Optional, TypedDict 9 | 10 | from PySide6.QtWidgets import ( 11 | QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, 12 | QLabel, QLineEdit, QPushButton, QCheckBox, QTextEdit, QGroupBox 13 | ) 14 | from PySide6.QtCore import Qt, Signal, QObject, QTimer, QSettings 15 | from PySide6.QtGui import QTextCursor, QIcon, QKeyEvent, QFont, QFontDatabase, QPalette, QColor 16 | 17 | class FeedbackResult(TypedDict): 18 | command_logs: str 19 | user_feedback: str 20 | 21 | class FeedbackConfig(TypedDict): 22 | run_command: str 23 | execute_automatically: bool = False 24 | 25 | def set_dark_title_bar(widget: QWidget, dark_title_bar: bool) -> None: 26 | # Ensure we're on Windows 27 | if sys.platform != "win32": 28 | return 29 | 30 | from ctypes import windll, c_uint32, byref 31 | 32 | # Get Windows build number 33 | build_number = sys.getwindowsversion().build 34 | if build_number < 17763: # Windows 10 1809 minimum 35 | return 36 | 37 | # Check if the widget's property already matches the setting 38 | dark_prop = widget.property("DarkTitleBar") 39 | if dark_prop is not None and dark_prop == dark_title_bar: 40 | return 41 | 42 | # Set the property (True if dark_title_bar != 0, False otherwise) 43 | widget.setProperty("DarkTitleBar", dark_title_bar) 44 | 45 | # Load dwmapi.dll and call DwmSetWindowAttribute 46 | dwmapi = windll.dwmapi 47 | hwnd = widget.winId() # Get the window handle 48 | attribute = 20 if build_number >= 18985 else 19 # Use newer attribute for newer builds 49 | c_dark_title_bar = c_uint32(dark_title_bar) # Convert to C-compatible uint32 50 | dwmapi.DwmSetWindowAttribute(hwnd, attribute, byref(c_dark_title_bar), 4) 51 | 52 | # HACK: Create a 1x1 pixel frameless window to force redraw 53 | temp_widget = QWidget(None, Qt.FramelessWindowHint) 54 | temp_widget.resize(1, 1) 55 | temp_widget.move(widget.pos()) 56 | temp_widget.show() 57 | temp_widget.deleteLater() # Safe deletion in Qt event loop 58 | 59 | def get_dark_mode_palette(app: QApplication): 60 | darkPalette = app.palette() 61 | darkPalette.setColor(QPalette.Window, QColor(53, 53, 53)) 62 | darkPalette.setColor(QPalette.WindowText, Qt.white) 63 | darkPalette.setColor(QPalette.Disabled, QPalette.WindowText, QColor(127, 127, 127)) 64 | darkPalette.setColor(QPalette.Base, QColor(42, 42, 42)) 65 | darkPalette.setColor(QPalette.AlternateBase, QColor(66, 66, 66)) 66 | darkPalette.setColor(QPalette.ToolTipBase, QColor(53, 53, 53)) 67 | darkPalette.setColor(QPalette.ToolTipText, Qt.white) 68 | darkPalette.setColor(QPalette.Text, Qt.white) 69 | darkPalette.setColor(QPalette.Disabled, QPalette.Text, QColor(127, 127, 127)) 70 | darkPalette.setColor(QPalette.Dark, QColor(35, 35, 35)) 71 | darkPalette.setColor(QPalette.Shadow, QColor(20, 20, 20)) 72 | darkPalette.setColor(QPalette.Button, QColor(53, 53, 53)) 73 | darkPalette.setColor(QPalette.ButtonText, Qt.white) 74 | darkPalette.setColor(QPalette.Disabled, QPalette.ButtonText, QColor(127, 127, 127)) 75 | darkPalette.setColor(QPalette.BrightText, Qt.red) 76 | darkPalette.setColor(QPalette.Link, QColor(42, 130, 218)) 77 | darkPalette.setColor(QPalette.Highlight, QColor(42, 130, 218)) 78 | darkPalette.setColor(QPalette.Disabled, QPalette.Highlight, QColor(80, 80, 80)) 79 | darkPalette.setColor(QPalette.HighlightedText, Qt.white) 80 | darkPalette.setColor(QPalette.Disabled, QPalette.HighlightedText, QColor(127, 127, 127)) 81 | darkPalette.setColor(QPalette.PlaceholderText, QColor(127, 127, 127)) 82 | return darkPalette 83 | 84 | def kill_tree(process: subprocess.Popen): 85 | killed: list[psutil.Process] = [] 86 | parent = psutil.Process(process.pid) 87 | for proc in parent.children(recursive=True): 88 | try: 89 | proc.kill() 90 | killed.append(proc) 91 | except psutil.Error: 92 | pass 93 | try: 94 | parent.kill() 95 | except psutil.Error: 96 | pass 97 | killed.append(parent) 98 | 99 | # Terminate any remaining processes 100 | for proc in killed: 101 | try: 102 | if proc.is_running(): 103 | proc.terminate() 104 | except psutil.Error: 105 | pass 106 | 107 | def get_user_environment() -> dict[str, str]: 108 | if sys.platform != "win32": 109 | return os.environ.copy() 110 | 111 | import ctypes 112 | from ctypes import wintypes 113 | 114 | # Load required DLLs 115 | advapi32 = ctypes.WinDLL("advapi32") 116 | userenv = ctypes.WinDLL("userenv") 117 | kernel32 = ctypes.WinDLL("kernel32") 118 | 119 | # Constants 120 | TOKEN_QUERY = 0x0008 121 | 122 | # Function prototypes 123 | OpenProcessToken = advapi32.OpenProcessToken 124 | OpenProcessToken.argtypes = [wintypes.HANDLE, wintypes.DWORD, ctypes.POINTER(wintypes.HANDLE)] 125 | OpenProcessToken.restype = wintypes.BOOL 126 | 127 | CreateEnvironmentBlock = userenv.CreateEnvironmentBlock 128 | CreateEnvironmentBlock.argtypes = [ctypes.POINTER(ctypes.c_void_p), wintypes.HANDLE, wintypes.BOOL] 129 | CreateEnvironmentBlock.restype = wintypes.BOOL 130 | 131 | DestroyEnvironmentBlock = userenv.DestroyEnvironmentBlock 132 | DestroyEnvironmentBlock.argtypes = [wintypes.LPVOID] 133 | DestroyEnvironmentBlock.restype = wintypes.BOOL 134 | 135 | GetCurrentProcess = kernel32.GetCurrentProcess 136 | GetCurrentProcess.argtypes = [] 137 | GetCurrentProcess.restype = wintypes.HANDLE 138 | 139 | CloseHandle = kernel32.CloseHandle 140 | CloseHandle.argtypes = [wintypes.HANDLE] 141 | CloseHandle.restype = wintypes.BOOL 142 | 143 | # Get process token 144 | token = wintypes.HANDLE() 145 | if not OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, ctypes.byref(token)): 146 | raise RuntimeError("Failed to open process token") 147 | 148 | try: 149 | # Create environment block 150 | environment = ctypes.c_void_p() 151 | if not CreateEnvironmentBlock(ctypes.byref(environment), token, False): 152 | raise RuntimeError("Failed to create environment block") 153 | 154 | try: 155 | # Convert environment block to list of strings 156 | result = {} 157 | env_ptr = ctypes.cast(environment, ctypes.POINTER(ctypes.c_wchar)) 158 | offset = 0 159 | 160 | while True: 161 | # Get string at current offset 162 | current_string = "" 163 | while env_ptr[offset] != "\0": 164 | current_string += env_ptr[offset] 165 | offset += 1 166 | 167 | # Skip null terminator 168 | offset += 1 169 | 170 | # Break if we hit double null terminator 171 | if not current_string: 172 | break 173 | 174 | equal_index = current_string.index("=") 175 | if equal_index == -1: 176 | continue 177 | 178 | key = current_string[:equal_index] 179 | value = current_string[equal_index + 1:] 180 | result[key] = value 181 | 182 | return result 183 | 184 | finally: 185 | DestroyEnvironmentBlock(environment) 186 | 187 | finally: 188 | CloseHandle(token) 189 | 190 | class FeedbackTextEdit(QTextEdit): 191 | def __init__(self, parent=None): 192 | super().__init__(parent) 193 | 194 | def keyPressEvent(self, event: QKeyEvent): 195 | if event.key() == Qt.Key_Return and event.modifiers() == Qt.ControlModifier: 196 | # Find the parent FeedbackUI instance and call submit 197 | parent = self.parent() 198 | while parent and not isinstance(parent, FeedbackUI): 199 | parent = parent.parent() 200 | if parent: 201 | parent._submit_feedback() 202 | else: 203 | super().keyPressEvent(event) 204 | 205 | class LogSignals(QObject): 206 | append_log = Signal(str) 207 | 208 | class FeedbackUI(QMainWindow): 209 | def __init__(self, project_directory: str, prompt: str): 210 | super().__init__() 211 | self.project_directory = project_directory 212 | self.prompt = prompt 213 | self.config_path = os.path.join(project_directory, ".user-feedback.json") 214 | self.config = self._load_config() 215 | 216 | self.process: Optional[subprocess.Popen] = None 217 | self.log_buffer = [] 218 | self.feedback_result = None 219 | self.log_signals = LogSignals() 220 | self.log_signals.append_log.connect(self._append_log) 221 | 222 | self.setWindowTitle("User Feedback") 223 | self.setWindowIcon(QIcon("icons/feedback.png")) 224 | self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint) 225 | 226 | self._create_ui() 227 | 228 | # Restore window geometry 229 | settings = QSettings("UserFeedback", "MainWindow") 230 | geometry = settings.value("geometry") 231 | if geometry: 232 | self.restoreGeometry(geometry) 233 | else: 234 | # Default size and center on screen 235 | self.resize(800, 600) 236 | screen = QApplication.primaryScreen().geometry() 237 | x = (screen.width() - 800) // 2 238 | y = (screen.height() - 600) // 2 239 | self.move(x, y) 240 | 241 | # Restore window state 242 | state = settings.value("windowState") 243 | if state: 244 | self.restoreState(state) 245 | 246 | set_dark_title_bar(self, True) 247 | 248 | if self.config.get("execute_automatically", False): 249 | self._run_command() 250 | 251 | def _load_config(self) -> FeedbackConfig: 252 | try: 253 | if os.path.exists(self.config_path): 254 | with open(self.config_path, "r") as f: 255 | return FeedbackConfig(**json.load(f)) 256 | except Exception: 257 | pass 258 | return FeedbackConfig(run_command="", execute_automatically=False) 259 | 260 | def _save_config(self): 261 | with open(self.config_path, "w") as f: 262 | json.dump(self.config, f, indent=2) 263 | 264 | def _format_windows_path(self, path: str) -> str: 265 | if sys.platform == "win32": 266 | # Convert forward slashes to backslashes 267 | path = path.replace("/", "\\") 268 | # Capitalize drive letter if path starts with x:\ 269 | if len(path) >= 2 and path[1] == ":" and path[0].isalpha(): 270 | path = path[0].upper() + path[1:] 271 | return path 272 | 273 | def _create_ui(self): 274 | central_widget = QWidget() 275 | self.setCentralWidget(central_widget) 276 | layout = QVBoxLayout(central_widget) 277 | 278 | # Command section 279 | command_group = QGroupBox("Command") 280 | command_layout = QVBoxLayout(command_group) 281 | 282 | # Working directory label 283 | formatted_path = self._format_windows_path(self.project_directory) 284 | working_dir_label = QLabel(f"Working directory: {formatted_path}") 285 | command_layout.addWidget(working_dir_label) 286 | 287 | # Command input row 288 | command_input_layout = QHBoxLayout() 289 | self.command_entry = QLineEdit() 290 | self.command_entry.setText(self.config["run_command"]) 291 | self.command_entry.returnPressed.connect(self._run_command) 292 | self.command_entry.textChanged.connect(self._update_config) 293 | self.run_button = QPushButton("&Run") 294 | self.run_button.clicked.connect(self._run_command) 295 | 296 | command_input_layout.addWidget(self.command_entry) 297 | command_input_layout.addWidget(self.run_button) 298 | command_layout.addLayout(command_input_layout) 299 | 300 | # Auto-execute and save config row 301 | auto_layout = QHBoxLayout() 302 | self.auto_check = QCheckBox("Execute automatically") 303 | self.auto_check.setChecked(self.config.get("execute_automatically", False)) 304 | self.auto_check.stateChanged.connect(self._update_config) 305 | 306 | save_button = QPushButton("&Save Configuration") 307 | save_button.clicked.connect(self._save_config) 308 | 309 | auto_layout.addWidget(self.auto_check) 310 | auto_layout.addStretch() 311 | auto_layout.addWidget(save_button) 312 | command_layout.addLayout(auto_layout) 313 | 314 | layout.addWidget(command_group) 315 | 316 | # Feedback section with fixed size 317 | feedback_group = QGroupBox("Feedback") 318 | feedback_layout = QVBoxLayout(feedback_group) 319 | feedback_group.setFixedHeight(150) 320 | 321 | self.feedback_text = FeedbackTextEdit() 322 | self.feedback_text.setMinimumHeight(60) 323 | self.feedback_text.setPlaceholderText(self.prompt) 324 | submit_button = QPushButton("Submit &Feedback (Ctrl+Enter)") 325 | submit_button.clicked.connect(self._submit_feedback) 326 | 327 | feedback_layout.addWidget(self.feedback_text) 328 | feedback_layout.addWidget(submit_button) 329 | 330 | # Console section (takes remaining space) 331 | console_group = QGroupBox("Console") 332 | console_layout = QVBoxLayout(console_group) 333 | console_group.setMinimumHeight(200) 334 | 335 | # Log text area 336 | self.log_text = QTextEdit() 337 | self.log_text.setReadOnly(True) 338 | font = QFont(QFontDatabase.systemFont(QFontDatabase.FixedFont)) 339 | font.setPointSize(9) 340 | self.log_text.setFont(font) 341 | console_layout.addWidget(self.log_text) 342 | 343 | # Clear button 344 | button_layout = QHBoxLayout() 345 | self.clear_button = QPushButton("&Clear") 346 | self.clear_button.clicked.connect(self.clear_logs) 347 | button_layout.addStretch() 348 | button_layout.addWidget(self.clear_button) 349 | console_layout.addLayout(button_layout) 350 | 351 | # Add widgets in reverse order (feedback at bottom) 352 | layout.addWidget(console_group, stretch=1) # Takes all remaining space 353 | layout.addWidget(feedback_group) # Fixed size, no stretch 354 | 355 | def _update_config(self): 356 | self.config = { 357 | "run_command": self.command_entry.text(), 358 | "execute_automatically": self.auto_check.isChecked() 359 | } 360 | 361 | def _append_log(self, text: str): 362 | self.log_buffer.append(text) 363 | self.log_text.append(text.rstrip()) 364 | cursor = self.log_text.textCursor() 365 | cursor.movePosition(QTextCursor.End) 366 | self.log_text.setTextCursor(cursor) 367 | 368 | def _check_process_status(self): 369 | if self.process and self.process.poll() is not None: 370 | # Process has terminated 371 | exit_code = self.process.poll() 372 | self._append_log(f"\nProcess exited with code {exit_code}\n") 373 | self.run_button.setText("&Run") 374 | self.process = None 375 | self.activateWindow() 376 | self.feedback_text.setFocus() 377 | 378 | def _run_command(self): 379 | if self.process: 380 | kill_tree(self.process) 381 | self.process = None 382 | self.run_button.setText("&Run") 383 | return 384 | 385 | # Clear the log buffer but keep UI logs visible 386 | self.log_buffer = [] 387 | 388 | command = self.command_entry.text() 389 | if not command: 390 | self._append_log("Please enter a command to run\n") 391 | return 392 | 393 | self._append_log(f"$ {command}\n") 394 | self.run_button.setText("Sto&p") 395 | 396 | try: 397 | self.process = subprocess.Popen( 398 | command, 399 | shell=True, 400 | cwd=self.project_directory, 401 | stdout=subprocess.PIPE, 402 | stderr=subprocess.PIPE, 403 | env=get_user_environment(), 404 | text=True, 405 | bufsize=1, 406 | encoding="utf-8", 407 | errors="ignore", 408 | close_fds=True, 409 | ) 410 | 411 | def read_output(pipe): 412 | for line in iter(pipe.readline, ""): 413 | self.log_signals.append_log.emit(line) 414 | 415 | threading.Thread( 416 | target=read_output, 417 | args=(self.process.stdout,), 418 | daemon=True 419 | ).start() 420 | 421 | threading.Thread( 422 | target=read_output, 423 | args=(self.process.stderr,), 424 | daemon=True 425 | ).start() 426 | 427 | # Start process status checking 428 | self.status_timer = QTimer() 429 | self.status_timer.timeout.connect(self._check_process_status) 430 | self.status_timer.start(100) # Check every 100ms 431 | 432 | except Exception as e: 433 | self._append_log(f"Error running command: {str(e)}\n") 434 | self.run_button.setText("&Run") 435 | 436 | def _submit_feedback(self): 437 | self.feedback_result = FeedbackResult( 438 | logs="".join(self.log_buffer), 439 | user_feedback=self.feedback_text.toPlainText().strip(), 440 | ) 441 | self.close() 442 | 443 | def clear_logs(self): 444 | self.log_buffer = [] 445 | self.log_text.clear() 446 | 447 | def closeEvent(self, event): 448 | # Save window geometry and state 449 | settings = QSettings("UserFeedback", "MainWindow") 450 | settings.setValue("geometry", self.saveGeometry()) 451 | settings.setValue("windowState", self.saveState()) 452 | 453 | if self.process: 454 | kill_tree(self.process) 455 | super().closeEvent(event) 456 | 457 | def run(self) -> FeedbackResult: 458 | self.show() 459 | QApplication.instance().exec() 460 | 461 | if self.process: 462 | kill_tree(self.process) 463 | 464 | if not self.feedback_result: 465 | return FeedbackResult(logs="".join(self.log_buffer), user_feedback="") 466 | 467 | return self.feedback_result 468 | 469 | def feedback_ui(project_directory: str, prompt: str, output_file: Optional[str] = None) -> Optional[FeedbackResult]: 470 | app = QApplication.instance() or QApplication() 471 | app.setPalette(get_dark_mode_palette(app)) 472 | app.setStyle("Fusion") 473 | ui = FeedbackUI(project_directory, prompt) 474 | result = ui.run() 475 | 476 | if output_file and result: 477 | # Ensure the directory exists 478 | os.makedirs(os.path.dirname(output_file) if os.path.dirname(output_file) else ".", exist_ok=True) 479 | # Save the result to the output file 480 | with open(output_file, "w") as f: 481 | json.dump(result, f) 482 | return None 483 | 484 | return result 485 | 486 | if __name__ == "__main__": 487 | parser = argparse.ArgumentParser(description="Run the feedback UI") 488 | parser.add_argument("--project-directory", default=os.getcwd(), help="The project directory to run the command in") 489 | parser.add_argument("--prompt", default="I implemented the changes you requested.", help="The prompt to show to the user") 490 | parser.add_argument("--output-file", help="Path to save the feedback result as JSON") 491 | args = parser.parse_args() 492 | 493 | result = feedback_ui(args.project_directory, args.prompt, args.output_file) 494 | if result: 495 | print(f"\nLogs collected: \n{result['logs']}") 496 | print(f"\nFeedback received:\n{result['user_feedback']}") 497 | sys.exit(0) 498 | -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | revision = 1 3 | requires-python = ">=3.11" 4 | 5 | [[package]] 6 | name = "annotated-types" 7 | version = "0.7.0" 8 | source = { registry = "https://pypi.org/simple" } 9 | sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } 10 | wheels = [ 11 | { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, 12 | ] 13 | 14 | [[package]] 15 | name = "anyio" 16 | version = "4.9.0" 17 | source = { registry = "https://pypi.org/simple" } 18 | dependencies = [ 19 | { name = "idna" }, 20 | { name = "sniffio" }, 21 | { name = "typing-extensions", marker = "python_full_version < '3.13'" }, 22 | ] 23 | sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 } 24 | wheels = [ 25 | { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 }, 26 | ] 27 | 28 | [[package]] 29 | name = "certifi" 30 | version = "2025.1.31" 31 | source = { registry = "https://pypi.org/simple" } 32 | sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } 33 | wheels = [ 34 | { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, 35 | ] 36 | 37 | [[package]] 38 | name = "click" 39 | version = "8.1.8" 40 | source = { registry = "https://pypi.org/simple" } 41 | dependencies = [ 42 | { name = "colorama", marker = "sys_platform == 'win32'" }, 43 | ] 44 | sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } 45 | wheels = [ 46 | { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, 47 | ] 48 | 49 | [[package]] 50 | name = "colorama" 51 | version = "0.4.6" 52 | source = { registry = "https://pypi.org/simple" } 53 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } 54 | wheels = [ 55 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, 56 | ] 57 | 58 | [[package]] 59 | name = "fastmcp" 60 | version = "0.4.1" 61 | source = { registry = "https://pypi.org/simple" } 62 | dependencies = [ 63 | { name = "httpx" }, 64 | { name = "mcp" }, 65 | { name = "pydantic" }, 66 | { name = "pydantic-settings" }, 67 | { name = "python-dotenv" }, 68 | { name = "typer" }, 69 | ] 70 | sdist = { url = "https://files.pythonhosted.org/packages/6f/84/17b549133263d7ee77141970769bbc401525526bf1af043ea6842bce1a55/fastmcp-0.4.1.tar.gz", hash = "sha256:713ad3b8e4e04841c9e2f3ca022b053adb89a286ceffad0d69ae7b56f31cbe64", size = 785575 } 71 | wheels = [ 72 | { url = "https://files.pythonhosted.org/packages/79/0b/008a340435fe8f0879e9d608f48af2737ad48440e09bd33b83b3fd03798b/fastmcp-0.4.1-py3-none-any.whl", hash = "sha256:664b42c376fb89ec90a50c9433f5a1f4d24f36696d6c41b024b427ae545f9619", size = 35282 }, 73 | ] 74 | 75 | [[package]] 76 | name = "h11" 77 | version = "0.14.0" 78 | source = { registry = "https://pypi.org/simple" } 79 | sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } 80 | wheels = [ 81 | { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, 82 | ] 83 | 84 | [[package]] 85 | name = "httpcore" 86 | version = "1.0.7" 87 | source = { registry = "https://pypi.org/simple" } 88 | dependencies = [ 89 | { name = "certifi" }, 90 | { name = "h11" }, 91 | ] 92 | sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 } 93 | wheels = [ 94 | { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 }, 95 | ] 96 | 97 | [[package]] 98 | name = "httpx" 99 | version = "0.28.1" 100 | source = { registry = "https://pypi.org/simple" } 101 | dependencies = [ 102 | { name = "anyio" }, 103 | { name = "certifi" }, 104 | { name = "httpcore" }, 105 | { name = "idna" }, 106 | ] 107 | sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } 108 | wheels = [ 109 | { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, 110 | ] 111 | 112 | [[package]] 113 | name = "httpx-sse" 114 | version = "0.4.0" 115 | source = { registry = "https://pypi.org/simple" } 116 | sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 } 117 | wheels = [ 118 | { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 }, 119 | ] 120 | 121 | [[package]] 122 | name = "idna" 123 | version = "3.10" 124 | source = { registry = "https://pypi.org/simple" } 125 | sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } 126 | wheels = [ 127 | { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, 128 | ] 129 | 130 | [[package]] 131 | name = "markdown-it-py" 132 | version = "3.0.0" 133 | source = { registry = "https://pypi.org/simple" } 134 | dependencies = [ 135 | { name = "mdurl" }, 136 | ] 137 | sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } 138 | wheels = [ 139 | { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, 140 | ] 141 | 142 | [[package]] 143 | name = "mcp" 144 | version = "1.5.0" 145 | source = { registry = "https://pypi.org/simple" } 146 | dependencies = [ 147 | { name = "anyio" }, 148 | { name = "httpx" }, 149 | { name = "httpx-sse" }, 150 | { name = "pydantic" }, 151 | { name = "pydantic-settings" }, 152 | { name = "sse-starlette" }, 153 | { name = "starlette" }, 154 | { name = "uvicorn" }, 155 | ] 156 | sdist = { url = "https://files.pythonhosted.org/packages/6d/c9/c55764824e893fdebe777ac7223200986a275c3191dba9169f8eb6d7c978/mcp-1.5.0.tar.gz", hash = "sha256:5b2766c05e68e01a2034875e250139839498c61792163a7b221fc170c12f5aa9", size = 159128 } 157 | wheels = [ 158 | { url = "https://files.pythonhosted.org/packages/c1/d1/3ff566ecf322077d861f1a68a1ff025cad337417bd66ad22a7c6f7dfcfaf/mcp-1.5.0-py3-none-any.whl", hash = "sha256:51c3f35ce93cb702f7513c12406bbea9665ef75a08db909200b07da9db641527", size = 73734 }, 159 | ] 160 | 161 | [[package]] 162 | name = "mdurl" 163 | version = "0.1.2" 164 | source = { registry = "https://pypi.org/simple" } 165 | sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } 166 | wheels = [ 167 | { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, 168 | ] 169 | 170 | [[package]] 171 | name = "psutil" 172 | version = "7.0.0" 173 | source = { registry = "https://pypi.org/simple" } 174 | sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003 } 175 | wheels = [ 176 | { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051 }, 177 | { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535 }, 178 | { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004 }, 179 | { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986 }, 180 | { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544 }, 181 | { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053 }, 182 | { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885 }, 183 | ] 184 | 185 | [[package]] 186 | name = "pydantic" 187 | version = "2.10.6" 188 | source = { registry = "https://pypi.org/simple" } 189 | dependencies = [ 190 | { name = "annotated-types" }, 191 | { name = "pydantic-core" }, 192 | { name = "typing-extensions" }, 193 | ] 194 | sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681 } 195 | wheels = [ 196 | { url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696 }, 197 | ] 198 | 199 | [[package]] 200 | name = "pydantic-core" 201 | version = "2.27.2" 202 | source = { registry = "https://pypi.org/simple" } 203 | dependencies = [ 204 | { name = "typing-extensions" }, 205 | ] 206 | sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 } 207 | wheels = [ 208 | { url = "https://files.pythonhosted.org/packages/c2/89/f3450af9d09d44eea1f2c369f49e8f181d742f28220f88cc4dfaae91ea6e/pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", size = 1893421 }, 209 | { url = "https://files.pythonhosted.org/packages/9e/e3/71fe85af2021f3f386da42d291412e5baf6ce7716bd7101ea49c810eda90/pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", size = 1814998 }, 210 | { url = "https://files.pythonhosted.org/packages/a6/3c/724039e0d848fd69dbf5806894e26479577316c6f0f112bacaf67aa889ac/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", size = 1826167 }, 211 | { url = "https://files.pythonhosted.org/packages/2b/5b/1b29e8c1fb5f3199a9a57c1452004ff39f494bbe9bdbe9a81e18172e40d3/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", size = 1865071 }, 212 | { url = "https://files.pythonhosted.org/packages/89/6c/3985203863d76bb7d7266e36970d7e3b6385148c18a68cc8915fd8c84d57/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", size = 2036244 }, 213 | { url = "https://files.pythonhosted.org/packages/0e/41/f15316858a246b5d723f7d7f599f79e37493b2e84bfc789e58d88c209f8a/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", size = 2737470 }, 214 | { url = "https://files.pythonhosted.org/packages/a8/7c/b860618c25678bbd6d1d99dbdfdf0510ccb50790099b963ff78a124b754f/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", size = 1992291 }, 215 | { url = "https://files.pythonhosted.org/packages/bf/73/42c3742a391eccbeab39f15213ecda3104ae8682ba3c0c28069fbcb8c10d/pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", size = 1994613 }, 216 | { url = "https://files.pythonhosted.org/packages/94/7a/941e89096d1175d56f59340f3a8ebaf20762fef222c298ea96d36a6328c5/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", size = 2002355 }, 217 | { url = "https://files.pythonhosted.org/packages/6e/95/2359937a73d49e336a5a19848713555605d4d8d6940c3ec6c6c0ca4dcf25/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", size = 2126661 }, 218 | { url = "https://files.pythonhosted.org/packages/2b/4c/ca02b7bdb6012a1adef21a50625b14f43ed4d11f1fc237f9d7490aa5078c/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", size = 2153261 }, 219 | { url = "https://files.pythonhosted.org/packages/72/9d/a241db83f973049a1092a079272ffe2e3e82e98561ef6214ab53fe53b1c7/pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", size = 1812361 }, 220 | { url = "https://files.pythonhosted.org/packages/e8/ef/013f07248041b74abd48a385e2110aa3a9bbfef0fbd97d4e6d07d2f5b89a/pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", size = 1982484 }, 221 | { url = "https://files.pythonhosted.org/packages/10/1c/16b3a3e3398fd29dca77cea0a1d998d6bde3902fa2706985191e2313cc76/pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", size = 1867102 }, 222 | { url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127 }, 223 | { url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340 }, 224 | { url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900 }, 225 | { url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177 }, 226 | { url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046 }, 227 | { url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386 }, 228 | { url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060 }, 229 | { url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870 }, 230 | { url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822 }, 231 | { url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364 }, 232 | { url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303 }, 233 | { url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064 }, 234 | { url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046 }, 235 | { url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092 }, 236 | { url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709 }, 237 | { url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273 }, 238 | { url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027 }, 239 | { url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888 }, 240 | { url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738 }, 241 | { url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138 }, 242 | { url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025 }, 243 | { url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633 }, 244 | { url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404 }, 245 | { url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130 }, 246 | { url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946 }, 247 | { url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387 }, 248 | { url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453 }, 249 | { url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186 }, 250 | ] 251 | 252 | [[package]] 253 | name = "pydantic-settings" 254 | version = "2.8.1" 255 | source = { registry = "https://pypi.org/simple" } 256 | dependencies = [ 257 | { name = "pydantic" }, 258 | { name = "python-dotenv" }, 259 | ] 260 | sdist = { url = "https://files.pythonhosted.org/packages/88/82/c79424d7d8c29b994fb01d277da57b0a9b09cc03c3ff875f9bd8a86b2145/pydantic_settings-2.8.1.tar.gz", hash = "sha256:d5c663dfbe9db9d5e1c646b2e161da12f0d734d422ee56f567d0ea2cee4e8585", size = 83550 } 261 | wheels = [ 262 | { url = "https://files.pythonhosted.org/packages/0b/53/a64f03044927dc47aafe029c42a5b7aabc38dfb813475e0e1bf71c4a59d0/pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c", size = 30839 }, 263 | ] 264 | 265 | [[package]] 266 | name = "pygments" 267 | version = "2.19.1" 268 | source = { registry = "https://pypi.org/simple" } 269 | sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } 270 | wheels = [ 271 | { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, 272 | ] 273 | 274 | [[package]] 275 | name = "pyside6" 276 | version = "6.8.2.1" 277 | source = { registry = "https://pypi.org/simple" } 278 | dependencies = [ 279 | { name = "pyside6-addons" }, 280 | { name = "pyside6-essentials" }, 281 | { name = "shiboken6" }, 282 | ] 283 | wheels = [ 284 | { url = "https://files.pythonhosted.org/packages/14/0f/bdb12758448b52497dba7a3bbfb5855dfb29129c64ddbda4da56c4b11f6c/PySide6-6.8.2.1-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:3fcb551729f235475b2abe7d919027de54a65d850e744f60716f890202273720", size = 550254 }, 285 | { url = "https://files.pythonhosted.org/packages/fa/00/0b232a25eeb8671202d7a7ec92893bd25b965debfd1d5d7aad637b067efe/PySide6-6.8.2.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:23d2a1a77b25459a049c4276b4e0bbfb375b73d3921061b1a16bcfa64e1fe517", size = 550489 }, 286 | { url = "https://files.pythonhosted.org/packages/8b/8a/9eb78cf71233399236c257cf85770ca4673ed0b9b959895856285157f643/PySide6-6.8.2.1-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:bfefa80a93db06dc64c0e7beef0377c9b8ca51e007cfc34575defe065af893b6", size = 550491 }, 287 | { url = "https://files.pythonhosted.org/packages/fb/3d/3e626e1953408cb8977a050ce54b1f1adff9a4c06bb519f6d56ebaf9310c/PySide6-6.8.2.1-cp39-abi3-win_amd64.whl", hash = "sha256:92361e41727910e3560ea5ba494fabecc76cd20892c9fcb2ced07619081c4e65", size = 556167 }, 288 | ] 289 | 290 | [[package]] 291 | name = "pyside6-addons" 292 | version = "6.8.2.1" 293 | source = { registry = "https://pypi.org/simple" } 294 | dependencies = [ 295 | { name = "pyside6-essentials" }, 296 | { name = "shiboken6" }, 297 | ] 298 | wheels = [ 299 | { url = "https://files.pythonhosted.org/packages/6e/79/a868ffac6eb446afdd25312b61872d0d11173032d50320d48b5277b68ccf/PySide6_Addons-6.8.2.1-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:5558816018042fecd0d782111ced529585a23ea9a010b518f8495764f578a01f", size = 302704501 }, 300 | { url = "https://files.pythonhosted.org/packages/95/3a/93e0028805c50ceff8b8ae0f274d502805b8a864129b83d705ab12d48f78/PySide6_Addons-6.8.2.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f3d85e676851ada8238bc76ebfacbee738fc0b35b3bc15c9765dd107b8ee6ec4", size = 160641392 }, 301 | { url = "https://files.pythonhosted.org/packages/84/5c/e822e4ef6c2140b273cb0f8531d7e200c8771bd61832decc524fc318c335/PySide6_Addons-6.8.2.1-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:d904179f16deeca4ba440b4ef78e8d54df2b994b46784ad9d53b741082f3b2a7", size = 156398179 }, 302 | { url = "https://files.pythonhosted.org/packages/0a/f8/98f85194f85a1fcff44ad98cd80cf6e856f7edee9e744fba81dec48b0ae9/PySide6_Addons-6.8.2.1-cp39-abi3-win_amd64.whl", hash = "sha256:c761cc45022aa79d8419e671e7fb34a4a3e5b3826f1e68fcb819bd6e3a387fbb", size = 127973648 }, 303 | ] 304 | 305 | [[package]] 306 | name = "pyside6-essentials" 307 | version = "6.8.2.1" 308 | source = { registry = "https://pypi.org/simple" } 309 | dependencies = [ 310 | { name = "shiboken6" }, 311 | ] 312 | wheels = [ 313 | { url = "https://files.pythonhosted.org/packages/01/bb/0127a53530cec0f9e7268e2fe235322b7b6e592caeb36c558b64da6ec52c/PySide6_Essentials-6.8.2.1-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:ae5cc48f7e9a08e73e3ec2387ce245c8150e620b8d5a87548ebd4b8e3aeae49b", size = 134909713 }, 314 | { url = "https://files.pythonhosted.org/packages/d2/f9/aa4ff511ff1f3dd177f7e8f5a635e03fe578fa2045c8d6be4577e7db3b28/PySide6_Essentials-6.8.2.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5ab31e5395a4724102edd6e8ff980fa3f7cde2aa79050763a1dcc30bb914195a", size = 95331575 }, 315 | { url = "https://files.pythonhosted.org/packages/fd/69/595002d860ee58431fe7add081d6f54fff94ae9680f2eb8cd355c1649bb6/PySide6_Essentials-6.8.2.1-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:7aed46f91d44399b4c713cf7387f5fb6f0114413fbcdbde493a528fb8e19f6ed", size = 93200219 }, 316 | { url = "https://files.pythonhosted.org/packages/5b/54/28a8b03f327e2c1d27d4a1ccf1a44997afc73c00ad07125d889640367194/PySide6_Essentials-6.8.2.1-cp39-abi3-win_amd64.whl", hash = "sha256:18de224f09108998d194e60f2fb8a1e86367dd525dd8a6192598e80e6ada649e", size = 72502927 }, 317 | ] 318 | 319 | [[package]] 320 | name = "python-dotenv" 321 | version = "1.0.1" 322 | source = { registry = "https://pypi.org/simple" } 323 | sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 } 324 | wheels = [ 325 | { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 }, 326 | ] 327 | 328 | [[package]] 329 | name = "rich" 330 | version = "13.9.4" 331 | source = { registry = "https://pypi.org/simple" } 332 | dependencies = [ 333 | { name = "markdown-it-py" }, 334 | { name = "pygments" }, 335 | ] 336 | sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 } 337 | wheels = [ 338 | { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 }, 339 | ] 340 | 341 | [[package]] 342 | name = "shellingham" 343 | version = "1.5.4" 344 | source = { registry = "https://pypi.org/simple" } 345 | sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 } 346 | wheels = [ 347 | { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, 348 | ] 349 | 350 | [[package]] 351 | name = "shiboken6" 352 | version = "6.8.2.1" 353 | source = { registry = "https://pypi.org/simple" } 354 | wheels = [ 355 | { url = "https://files.pythonhosted.org/packages/d8/8f/71ccc3642edb59efaca35d4ba974248b1d7847f5e4d87d3ea323e73b2cab/shiboken6-6.8.2.1-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:d3dedeb3732ecfc920c9f97da769c0022a1c3bda99346a9eba56fbf093deaa75", size = 401266 }, 356 | { url = "https://files.pythonhosted.org/packages/7b/ff/ab4f287b9573e50b5a47c10e2af8feb5abecc3c7431bd5deec135efc969e/shiboken6-6.8.2.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c83e90056f13d0872cc4d2b7bf60b6d6e3b1b172f1f91910c0ba5b641af01758", size = 204273 }, 357 | { url = "https://files.pythonhosted.org/packages/a6/b0/4fb102eb5260ee06d379769f3c4f0b82ef397c15f1cbbbbb3f6dceb86d5d/shiboken6-6.8.2.1-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:8592401423acc693f51dbbfae5e7493cc3ed6738be79daaf90afa07f4da5bb25", size = 200909 }, 358 | { url = "https://files.pythonhosted.org/packages/ae/88/b56bdb38a11066e4eecd1da6be4205bb406398b733b392b11c5aaf9547f7/shiboken6-6.8.2.1-cp39-abi3-win_amd64.whl", hash = "sha256:1b751d47b759762b7ca31bad278d52eca4105d3028880d93979261ebbfba810c", size = 1150270 }, 359 | ] 360 | 361 | [[package]] 362 | name = "sniffio" 363 | version = "1.3.1" 364 | source = { registry = "https://pypi.org/simple" } 365 | sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } 366 | wheels = [ 367 | { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, 368 | ] 369 | 370 | [[package]] 371 | name = "sse-starlette" 372 | version = "2.2.1" 373 | source = { registry = "https://pypi.org/simple" } 374 | dependencies = [ 375 | { name = "anyio" }, 376 | { name = "starlette" }, 377 | ] 378 | sdist = { url = "https://files.pythonhosted.org/packages/71/a4/80d2a11af59fe75b48230846989e93979c892d3a20016b42bb44edb9e398/sse_starlette-2.2.1.tar.gz", hash = "sha256:54470d5f19274aeed6b2d473430b08b4b379ea851d953b11d7f1c4a2c118b419", size = 17376 } 379 | wheels = [ 380 | { url = "https://files.pythonhosted.org/packages/d9/e0/5b8bd393f27f4a62461c5cf2479c75a2cc2ffa330976f9f00f5f6e4f50eb/sse_starlette-2.2.1-py3-none-any.whl", hash = "sha256:6410a3d3ba0c89e7675d4c273a301d64649c03a5ef1ca101f10b47f895fd0e99", size = 10120 }, 381 | ] 382 | 383 | [[package]] 384 | name = "starlette" 385 | version = "0.46.1" 386 | source = { registry = "https://pypi.org/simple" } 387 | dependencies = [ 388 | { name = "anyio" }, 389 | ] 390 | sdist = { url = "https://files.pythonhosted.org/packages/04/1b/52b27f2e13ceedc79a908e29eac426a63465a1a01248e5f24aa36a62aeb3/starlette-0.46.1.tar.gz", hash = "sha256:3c88d58ee4bd1bb807c0d1acb381838afc7752f9ddaec81bbe4383611d833230", size = 2580102 } 391 | wheels = [ 392 | { url = "https://files.pythonhosted.org/packages/a0/4b/528ccf7a982216885a1ff4908e886b8fb5f19862d1962f56a3fce2435a70/starlette-0.46.1-py3-none-any.whl", hash = "sha256:77c74ed9d2720138b25875133f3a2dae6d854af2ec37dceb56aef370c1d8a227", size = 71995 }, 393 | ] 394 | 395 | [[package]] 396 | name = "typer" 397 | version = "0.15.2" 398 | source = { registry = "https://pypi.org/simple" } 399 | dependencies = [ 400 | { name = "click" }, 401 | { name = "rich" }, 402 | { name = "shellingham" }, 403 | { name = "typing-extensions" }, 404 | ] 405 | sdist = { url = "https://files.pythonhosted.org/packages/8b/6f/3991f0f1c7fcb2df31aef28e0594d8d54b05393a0e4e34c65e475c2a5d41/typer-0.15.2.tar.gz", hash = "sha256:ab2fab47533a813c49fe1f16b1a370fd5819099c00b119e0633df65f22144ba5", size = 100711 } 406 | wheels = [ 407 | { url = "https://files.pythonhosted.org/packages/7f/fc/5b29fea8cee020515ca82cc68e3b8e1e34bb19a3535ad854cac9257b414c/typer-0.15.2-py3-none-any.whl", hash = "sha256:46a499c6107d645a9c13f7ee46c5d5096cae6f5fc57dd11eccbbb9ae3e44ddfc", size = 45061 }, 408 | ] 409 | 410 | [[package]] 411 | name = "typing-extensions" 412 | version = "4.12.2" 413 | source = { registry = "https://pypi.org/simple" } 414 | sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } 415 | wheels = [ 416 | { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, 417 | ] 418 | 419 | [[package]] 420 | name = "user-feedback-mcp" 421 | version = "0.1.0" 422 | source = { virtual = "." } 423 | dependencies = [ 424 | { name = "fastmcp" }, 425 | { name = "psutil" }, 426 | { name = "pyside6" }, 427 | ] 428 | 429 | [package.metadata] 430 | requires-dist = [ 431 | { name = "fastmcp", specifier = ">=0.4.1" }, 432 | { name = "psutil", specifier = ">=7.0.0" }, 433 | { name = "pyside6", specifier = ">=6.8.2.1" }, 434 | ] 435 | 436 | [[package]] 437 | name = "uvicorn" 438 | version = "0.34.0" 439 | source = { registry = "https://pypi.org/simple" } 440 | dependencies = [ 441 | { name = "click" }, 442 | { name = "h11" }, 443 | ] 444 | sdist = { url = "https://files.pythonhosted.org/packages/4b/4d/938bd85e5bf2edeec766267a5015ad969730bb91e31b44021dfe8b22df6c/uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9", size = 76568 } 445 | wheels = [ 446 | { url = "https://files.pythonhosted.org/packages/61/14/33a3a1352cfa71812a3a21e8c9bfb83f60b0011f5e36f2b1399d51928209/uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", size = 62315 }, 447 | ] 448 | --------------------------------------------------------------------------------