diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/PIRC.iml b/.idea/PIRC.iml
new file mode 100644
index 0000000..bec9418
--- /dev/null
+++ b/.idea/PIRC.iml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..462ce8b
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..013ad0b
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/__pycache__/pirc_client.cpython-313.pyc b/__pycache__/pirc_client.cpython-313.pyc
new file mode 100644
index 0000000..af26360
Binary files /dev/null and b/__pycache__/pirc_client.cpython-313.pyc differ
diff --git a/__pycache__/pirc_setup_wizard.cpython-313.pyc b/__pycache__/pirc_setup_wizard.cpython-313.pyc
new file mode 100644
index 0000000..fc83786
Binary files /dev/null and b/__pycache__/pirc_setup_wizard.cpython-313.pyc differ
diff --git a/main.py b/main.py
new file mode 100644
index 0000000..3072cd4
--- /dev/null
+++ b/main.py
@@ -0,0 +1,28 @@
+# main.py
+import sys
+import json
+import os
+from PySide6.QtWidgets import QApplication
+from pirc_setup_wizard import PIRCSetupWizard
+from pirc_client import PIRCClient
+
+CONFIG_FILE = "pirc_config.json"
+
+def main():
+ app = QApplication(sys.argv)
+
+ # Check if config exists
+ if not os.path.exists(CONFIG_FILE):
+ wizard = PIRCSetupWizard()
+ if wizard.exec() != 0:
+ pass # Wizard handled saving config
+ else:
+ print("Setup cancelled.")
+ sys.exit(0)
+
+ client = PIRCClient()
+ client.show()
+ sys.exit(app.exec())
+
+if __name__ == "__main__":
+ main()
diff --git a/pirc_client.py b/pirc_client.py
new file mode 100644
index 0000000..746ffd1
--- /dev/null
+++ b/pirc_client.py
@@ -0,0 +1,350 @@
+import sys
+import socket
+import threading
+import json
+import os
+from datetime import datetime
+from PySide6.QtWidgets import (
+ QApplication, QWidget, QVBoxLayout, QHBoxLayout, QTextEdit,
+ QLineEdit, QPushButton, QLabel, QListWidget, QListWidgetItem,
+ QMessageBox, QDialog, QDialogButtonBox, QFormLayout, QLineEdit as QLE
+)
+from PySide6.QtCore import Qt, Signal, QObject
+from PySide6.QtGui import QIcon
+
+CONFIG_FILE = "pirc_config.json"
+
+
+class IRCWorker(QObject):
+ message_received = Signal(str)
+ system_message = Signal(str)
+ user_list_update = Signal(list)
+ disconnected = Signal()
+
+ def __init__(self, server, port, nick, channels):
+ super().__init__()
+ self.server = server
+ self.port = port
+ self.nick = nick
+ self.channels = channels
+ self.running = True
+ self.sock = None
+ self.current_users = []
+
+ def start(self):
+ try:
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.sock.connect((self.server, self.port))
+ self.sock.send(f"NICK {self.nick}\r\n".encode("utf-8"))
+ self.sock.send(f"USER {self.nick} 0 * :PIRC User\r\n".encode("utf-8"))
+
+ for channel in self.channels:
+ self.sock.send(f"JOIN {channel}\r\n".encode("utf-8"))
+ self.system_message.emit(f"Joined {channel}")
+
+ threading.Thread(target=self.listen, daemon=True).start()
+ except Exception as e:
+ self.system_message.emit(f"Connection failed: {e}")
+ self.disconnected.emit()
+
+ def listen(self):
+ try:
+ while self.running:
+ data = self.sock.recv(4096).decode("utf-8", errors="ignore")
+ if not data:
+ break
+
+ for line in data.split("\r\n"):
+ if not line:
+ continue
+
+ # PING/PONG
+ if line.startswith("PING"):
+ self.sock.send(f"PONG {line.split()[1]}\r\n".encode("utf-8"))
+ continue
+
+ # Ignore raw handshake notices
+ if line.startswith(":") and "NOTICE * :***" in line:
+ continue
+
+ parts = line.split()
+ if len(parts) > 1 and parts[1].isdigit():
+ code = int(parts[1])
+ if code == 353: # NAMES list
+ name_parts = line.split(":", 2)
+ if len(name_parts) < 3:
+ continue
+ users_chunk = name_parts[2].split()
+ self.current_users.extend(users_chunk)
+ elif code == 366: # End of NAMES list
+ self.user_list_update.emit(self.current_users)
+ self.current_users = []
+ elif code in [0o01, 332, 333]:
+ self.system_message.emit(line)
+ continue
+ # PRIVMSG
+ if "PRIVMSG" in line:
+ msg_parts = line.split(":", 2)
+ if len(msg_parts) >= 3:
+ prefix = msg_parts[1].split("!")[0]
+ message = msg_parts[2]
+ self.message_received.emit(f"<{prefix}> {message}")
+ continue
+
+ # JOIN
+ if "JOIN" in line:
+ user = line.split("!")[0].replace(":", "")
+ chan = line.split("JOIN")[1].strip()
+ if user not in self.current_users:
+ self.current_users.append(user)
+ self.user_list_update.emit(self.current_users)
+ self.system_message.emit(f"{user} joined {chan}")
+ continue
+
+ # PART/QUIT
+ if "PART" in line or "QUIT" in line:
+ user = line.split("!")[0].replace(":", "")
+ if user in self.current_users:
+ self.current_users.remove(user)
+ self.user_list_update.emit(self.current_users)
+ self.system_message.emit(f"{user} left")
+ continue
+
+ # Other system messages
+ self.system_message.emit(line)
+ except Exception as e:
+ self.system_message.emit(f"Error: {e}")
+ finally:
+ self.disconnected.emit()
+
+ def send_message(self, channel, message):
+ try:
+ self.sock.send(f"PRIVMSG {channel} :{message}\r\n".encode("utf-8"))
+ except Exception as e:
+ self.system_message.emit(f"Failed to send message: {e}")
+
+ def stop(self):
+ self.running = False
+ try:
+ self.sock.send(b"QUIT :Goodbye!\r\n")
+ self.sock.close()
+ except:
+ pass
+
+
+class AddServerDialog(QDialog):
+ def __init__(self):
+ super().__init__()
+ self.setWindowTitle("Add Server")
+ self.setFixedSize(300, 200)
+ layout = QFormLayout()
+ self.server_input = QLE()
+ self.port_input = QLE()
+ self.port_input.setText("6667")
+ self.channels_input = QLE()
+ layout.addRow("Server:", self.server_input)
+ layout.addRow("Port:", self.port_input)
+ layout.addRow("Channels (comma-separated):", self.channels_input)
+ buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
+ buttons.accepted.connect(self.accept)
+ buttons.rejected.connect(self.reject)
+ layout.addWidget(buttons)
+ self.setLayout(layout)
+
+ def get_data(self):
+ return (
+ self.server_input.text().strip(),
+ int(self.port_input.text().strip()),
+ [c.strip() for c in self.channels_input.text().split(",") if c.strip()]
+ )
+
+
+class PIRCClient(QWidget):
+ def __init__(self):
+ super().__init__()
+ self.setWindowTitle("PIRC — Python IRC Client")
+ self.setGeometry(100, 100, 1000, 600)
+ self.worker = None
+
+ self.load_config()
+ self.init_ui()
+
+ if self.favorites:
+ first = self.favorites[0]
+ self.connect_to_server(first["server"], first.get("port", 6667), first["channels"])
+ else:
+ self.system_message("No favorite servers found — click the + button to connect.")
+
+ def load_config(self):
+ if os.path.exists(CONFIG_FILE):
+ with open(CONFIG_FILE, "r") as f:
+ cfg = json.load(f)
+ self.nick = cfg.get("username", "PIRCUser")
+ self.favorites = cfg.get("favorites", [])
+ else:
+ self.nick = "PIRCUser"
+ self.favorites = []
+
+ def save_config(self):
+ cfg = {
+ "username": self.nick,
+ "favorites": self.favorites
+ }
+ with open(CONFIG_FILE, "w") as f:
+ json.dump(cfg, f, indent=4)
+
+ def init_ui(self):
+ layout = QHBoxLayout()
+
+ # Server sidebar
+ sidebar_layout = QVBoxLayout()
+ self.server_list = QListWidget()
+ for fav in self.favorites:
+ item = QListWidgetItem(fav["server"])
+ self.server_list.addItem(item)
+ self.server_list.itemClicked.connect(self.server_selected)
+ sidebar_layout.addWidget(self.server_list)
+
+ # Add server button with + icon
+ self.add_server_button = QPushButton()
+ self.add_server_button.setIcon(QIcon("add.png"))
+ self.add_server_button.setText("") # icon only
+ self.add_server_button.clicked.connect(self.add_server)
+ self.disconnect_button = QPushButton("Disconnect")
+ self.disconnect_button.clicked.connect(self.disconnect_server)
+ sidebar_layout.addWidget(self.add_server_button)
+ sidebar_layout.addWidget(self.disconnect_button)
+ layout.addLayout(sidebar_layout, 2)
+
+ # Chat + user list
+ chat_layout = QVBoxLayout()
+ self.info_label = QLabel("Disconnected")
+ self.info_label.setStyleSheet("font-weight: bold; padding: 4px;")
+ chat_layout.addWidget(self.info_label)
+
+ chat_area_layout = QHBoxLayout()
+ self.chat_display = QTextEdit()
+ self.chat_display.setReadOnly(True)
+ chat_area_layout.addWidget(self.chat_display, 7)
+
+ # User list sidebar
+ self.user_list = QListWidget()
+ chat_area_layout.addWidget(self.user_list, 3)
+
+ chat_layout.addLayout(chat_area_layout, 8)
+
+ # Message input
+ input_layout = QHBoxLayout()
+ self.message_input = QLineEdit()
+ self.message_input.returnPressed.connect(self.send_message)
+ self.send_button = QPushButton("Send")
+ self.send_button.clicked.connect(self.send_message)
+ input_layout.addWidget(self.message_input)
+ input_layout.addWidget(self.send_button)
+ chat_layout.addLayout(input_layout)
+
+ layout.addLayout(chat_layout, 8)
+ self.setLayout(layout)
+
+ def connect_to_server(self, server, port, channels):
+ if self.worker:
+ self.disconnect_server()
+ self.worker = IRCWorker(server, port, self.nick, channels)
+ self.worker.message_received.connect(self.display_message)
+ self.worker.system_message.connect(self.system_message)
+ self.worker.user_list_update.connect(self.update_user_list)
+ self.worker.disconnected.connect(self.handle_disconnect)
+ self.worker.start()
+ self.info_label.setText(f"Connected to {server} as {self.nick}")
+ self.system_message(f"Connected to {server}")
+
+ def disconnect_server(self):
+ if self.worker:
+ self.worker.stop()
+ self.worker = None
+ self.system_message("Disconnected from server.")
+ self.info_label.setText("Disconnected")
+ self.user_list.clear()
+
+ def handle_disconnect(self):
+ self.worker = None
+ self.system_message("Connection closed by server.")
+ self.info_label.setText("Disconnected")
+ self.user_list.clear()
+
+ def server_selected(self, item):
+ selected_server = item.text()
+ fav = next((f for f in self.favorites if f["server"] == selected_server), None)
+ if fav:
+ self.connect_to_server(fav["server"], fav.get("port", 6667), fav["channels"])
+
+ def add_server(self):
+ dialog = AddServerDialog()
+ if dialog.exec() == QDialog.Accepted:
+ server, port, channels = dialog.get_data()
+ if not server:
+ QMessageBox.warning(self, "Error", "Server cannot be empty.")
+ return
+ new_server = {"server": server, "port": port, "channels": channels}
+ self.favorites.append(new_server)
+ self.save_config()
+ self.server_list.addItem(server)
+ self.connect_to_server(server, port, channels)
+
+ def display_message(self, msg):
+ timestamp = datetime.now().strftime("[%H:%M:%S]")
+ self.chat_display.append(f"{timestamp} {msg}")
+
+ def system_message(self, msg):
+ timestamp = datetime.now().strftime("[%H:%M:%S]")
+ self.chat_display.append(
+ f'
{timestamp} [System] {msg}
'
+ )
+
+ def update_user_list(self, users):
+ self.user_list.clear()
+ admins = []
+ normal_users = []
+
+ for user in users:
+ if user.startswith("@"):
+ admins.append(user[1:]) # remove @ for display
+ elif user.startswith("+"):
+ normal_users.append(user[1:]) # optional remove +
+ else:
+ normal_users.append(user)
+
+ admins.sort()
+ normal_users.sort()
+ ordered_users = admins + normal_users
+
+ for user in ordered_users:
+ item = QListWidgetItem(user)
+ if user in admins:
+ item.setIcon(QIcon("admin.png"))
+ else:
+ item.setIcon(QIcon("user.png"))
+ self.user_list.addItem(item)
+
+ def send_message(self):
+ text = self.message_input.text().strip()
+ if not text:
+ return
+ if self.worker and self.worker.channels:
+ self.worker.send_message(self.worker.channels[0], text)
+ self.display_message(f"<{self.nick}> {text}")
+ else:
+ self.system_message("Not connected to a server.")
+ self.message_input.clear()
+
+ def closeEvent(self, event):
+ if self.worker:
+ self.worker.stop()
+ super().closeEvent(event)
+
+
+if __name__ == "__main__":
+ app = QApplication(sys.argv)
+ client = PIRCClient()
+ client.show()
+ sys.exit(app.exec())
diff --git a/pirc_config.json b/pirc_config.json
new file mode 100644
index 0000000..9b9c7f6
--- /dev/null
+++ b/pirc_config.json
@@ -0,0 +1,10 @@
+{
+ "username": "OptimiDev",
+ "favorites": [
+ {
+ "server": "irc.libera.chat",
+ "port": 6667,
+ "channels": ["#python"]
+ }
+ ]
+}
diff --git a/pirc_setup_wizard.py b/pirc_setup_wizard.py
new file mode 100644
index 0000000..db7433a
--- /dev/null
+++ b/pirc_setup_wizard.py
@@ -0,0 +1,138 @@
+# pirC_setup_wizard.py
+import sys
+import json
+from PySide6.QtWidgets import (
+ QApplication, QDialog, QVBoxLayout, QLabel, QLineEdit, QPushButton,
+ QListWidget, QHBoxLayout, QStackedLayout, QWidget, QDialogButtonBox
+)
+
+CONFIG_FILE = "pirc_config.json"
+
+class PIRCSetupWizard(QDialog):
+ def __init__(self):
+ super().__init__()
+ self.setWindowTitle("PIRC Setup Wizard")
+ self.setGeometry(200, 200, 500, 400)
+
+ self.nicknames = []
+ self.favorites = []
+
+ # Multi-step layout
+ self.layout = QVBoxLayout()
+ self.stack = QStackedLayout()
+ self.layout.addLayout(self.stack)
+ self.setLayout(self.layout)
+
+ # Step 0: Welcome
+ self.step_welcome = QWidget()
+ w_layout = QVBoxLayout()
+ w_layout.addWidget(QLabel(
+ "Welcome to PIRC!\n\nThis wizard will guide you through the setup:\n"
+ "1. Add your usernames (multiple allowed)\n"
+ "2. Add favorite servers/channels (optional)\n"
+ "3. Open the chat window\n"
+ ))
+ self.next0 = QPushButton("Start")
+ self.next0.clicked.connect(lambda: self.stack.setCurrentIndex(1))
+ w_layout.addWidget(self.next0)
+ self.step_welcome.setLayout(w_layout)
+ self.stack.addWidget(self.step_welcome)
+
+ # Step 1: Usernames
+ self.step_users = QWidget()
+ u_layout = QVBoxLayout()
+ u_layout.addWidget(QLabel("Step 1: Add your usernames"))
+ self.nick_input = QLineEdit()
+ self.nick_input.setPlaceholderText("Enter a username")
+ u_layout.addWidget(self.nick_input)
+ self.nick_list = QListWidget()
+ u_layout.addWidget(self.nick_list)
+ h_btn_layout = QHBoxLayout()
+ self.add_nick_btn = QPushButton("Add")
+ self.add_nick_btn.clicked.connect(self.add_nickname)
+ self.remove_nick_btn = QPushButton("Remove Selected")
+ self.remove_nick_btn.clicked.connect(self.remove_nickname)
+ h_btn_layout.addWidget(self.add_nick_btn)
+ h_btn_layout.addWidget(self.remove_nick_btn)
+ u_layout.addLayout(h_btn_layout)
+ self.next1 = QPushButton("Next")
+ self.next1.clicked.connect(lambda: self.stack.setCurrentIndex(2))
+ u_layout.addWidget(self.next1)
+ self.step_users.setLayout(u_layout)
+ self.stack.addWidget(self.step_users)
+
+ # Step 2: Favorites
+ self.step_fav = QWidget()
+ f_layout = QVBoxLayout()
+ f_layout.addWidget(QLabel("Step 2: Add favorite servers/channels (optional)"))
+ self.server_input = QLineEdit()
+ self.server_input.setPlaceholderText("Server address (e.g., irc.libera.chat)")
+ self.channel_input = QLineEdit()
+ self.channel_input.setPlaceholderText("Channel (e.g., #python)")
+ f_layout.addWidget(self.server_input)
+ f_layout.addWidget(self.channel_input)
+ self.fav_list = QListWidget()
+ f_layout.addWidget(self.fav_list)
+ h_fav_btn_layout = QHBoxLayout()
+ self.add_fav_btn = QPushButton("Add")
+ self.add_fav_btn.clicked.connect(self.add_favorite)
+ self.remove_fav_btn = QPushButton("Remove Selected")
+ self.remove_fav_btn.clicked.connect(self.remove_favorite)
+ h_fav_btn_layout.addWidget(self.add_fav_btn)
+ h_fav_btn_layout.addWidget(self.remove_fav_btn)
+ f_layout.addLayout(h_fav_btn_layout)
+ self.next2 = QPushButton("Finish")
+ self.next2.clicked.connect(self.finish_setup)
+ f_layout.addWidget(self.next2)
+ self.step_fav.setLayout(f_layout)
+ self.stack.addWidget(self.step_fav)
+
+ # --- Nicknames ---
+ def add_nickname(self):
+ nick = self.nick_input.text().strip()
+ if nick:
+ self.nicknames.append(nick)
+ self.nick_list.addItem(nick)
+ self.nick_input.clear()
+
+ def remove_nickname(self):
+ selected = self.nick_list.currentRow()
+ if selected >= 0:
+ self.nick_list.takeItem(selected)
+ self.nicknames.pop(selected)
+
+ # --- Favorites ---
+ def add_favorite(self):
+ server = self.server_input.text().strip()
+ channel = self.channel_input.text().strip()
+ if server and channel:
+ self.favorites.append({"server": server, "channel": channel})
+ self.fav_list.addItem(f"{server} {channel}")
+ self.server_input.clear()
+ self.channel_input.clear()
+
+ def remove_favorite(self):
+ selected = self.fav_list.currentRow()
+ if selected >= 0:
+ self.fav_list.takeItem(selected)
+ self.favorites.pop(selected)
+
+ # --- Finish ---
+ def finish_setup(self):
+ config = {
+ "nicknames": self.nicknames,
+ "favorites": self.favorites
+ }
+ with open(CONFIG_FILE, "w") as f:
+ json.dump(config, f, indent=4)
+ self.accept() # Close wizard
+
+# --- Test ---
+if __name__ == "__main__":
+ app = QApplication(sys.argv)
+ wizard = PIRCSetupWizard()
+ if wizard.exec() == QDialog.Accepted:
+ print("Setup completed!")
+ with open(CONFIG_FILE, "r") as f:
+ print(json.load(f))
+ sys.exit(0)
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..2fc14bb
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,9 @@
+[project]
+name = "pirc"
+version = "0.1.0"
+description = "Add your description here"
+requires-python = ">=3.13"
+dependencies = [
+ "pyqt6>=6.9.1",
+ "pyside6>=6.10.0",
+]
diff --git a/uv.lock b/uv.lock
new file mode 100644
index 0000000..76db6b4
--- /dev/null
+++ b/uv.lock
@@ -0,0 +1,126 @@
+version = 1
+revision = 3
+requires-python = ">=3.13"
+
+[[package]]
+name = "pirc"
+version = "0.1.0"
+source = { virtual = "." }
+dependencies = [
+ { name = "pyqt6" },
+ { name = "pyside6" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "pyqt6", specifier = ">=6.9.1" },
+ { name = "pyside6", specifier = ">=6.10.0" },
+]
+
+[[package]]
+name = "pyqt6"
+version = "6.9.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pyqt6-qt6" },
+ { name = "pyqt6-sip" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/32/1b/567f46eb43ca961efd38d7a0b73efb70d7342854f075fd919179fdb2a571/pyqt6-6.9.1.tar.gz", hash = "sha256:50642be03fb40f1c2111a09a1f5a0f79813e039c15e78267e6faaf8a96c1c3a6", size = 1067230, upload-time = "2025-06-06T08:49:30.307Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/18/c4/fc2a69cf3df09b213185ef5a677c3940cd20e7855d29e40061a685b9c6ee/pyqt6-6.9.1-cp39-abi3-macosx_10_14_universal2.whl", hash = "sha256:33c23d28f6608747ecc8bfd04c8795f61631af9db4fb1e6c2a7523ec4cc916d9", size = 59770566, upload-time = "2025-06-06T08:48:20.331Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/78/92f3c46440a83ebe22ae614bd6792e7b052bcb58ff128f677f5662015184/pyqt6-6.9.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:37884df27f774e2e1c0c96fa41e817a222329b80ffc6241725b0dc8c110acb35", size = 37804959, upload-time = "2025-06-06T08:48:39.587Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/5e/e77fa2761d809cd08d724f44af01a4b6ceb0ff9648e43173187b0e4fac4e/pyqt6-6.9.1-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:055870b703c1a49ca621f8a89e2ec4d848e6c739d39367eb9687af3b056d9aa3", size = 40414608, upload-time = "2025-06-06T08:49:00.26Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/09/69cf80456b6a985e06dd24ed0c2d3451e43567bf2807a5f3a86ef7a74a2e/pyqt6-6.9.1-cp39-abi3-win_amd64.whl", hash = "sha256:15b95bd273bb6288b070ed7a9503d5ff377aa4882dd6d175f07cad28cdb21da0", size = 25717996, upload-time = "2025-06-06T08:49:13.208Z" },
+ { url = "https://files.pythonhosted.org/packages/52/b3/0839d8fd18b86362a4de384740f2f6b6885b5d06fda7720f8a335425e316/pyqt6-6.9.1-cp39-abi3-win_arm64.whl", hash = "sha256:08792c72d130a02e3248a120f0b9bbb4bf4319095f92865bc5b365b00518f53d", size = 25212132, upload-time = "2025-06-06T08:49:27.41Z" },
+]
+
+[[package]]
+name = "pyqt6-qt6"
+version = "6.9.2"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/14/6f/fe2cd9cb2201c685be2f50c8c915df97848cac3dca4bad44bc3aed56fc63/pyqt6_qt6-6.9.2-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:183b62be49216da80c7df1931d74885610a88f74812489d29610d13b7c215a1c", size = 66568266, upload-time = "2025-09-01T11:43:31.339Z" },
+ { url = "https://files.pythonhosted.org/packages/db/1d/47dc51b4383b350f4ff6b1db461b01eba580030683ffa65475b4fdd9b80d/pyqt6_qt6-6.9.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7897fb74ee21bdc87b5ccf84e94f4a551377e792fd180a9211c17eb41c3338a3", size = 60859706, upload-time = "2025-09-01T11:43:36.624Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/07/21f7dc188e35b46631707f3b40ace5643a0e03a8e1e446854826d08a04ae/pyqt6_qt6-6.9.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:9abfc0ee4a8293a6442128ae3f87f68e82e2a949d7b9caabd98c86ba5679ab48", size = 82322871, upload-time = "2025-09-01T11:43:41.685Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/c0/da658e735817feaa35ddfddb4c5d699291e8b8e3138e69ad7ae1a38a7db8/pyqt6_qt6-6.9.2-py3-none-manylinux_2_39_aarch64.whl", hash = "sha256:940aac6462532578e8ddefe0494cd17e33a85e0f3cfb21c612f56ab9ad7bc871", size = 80826693, upload-time = "2025-09-01T11:43:46.823Z" },
+ { url = "https://files.pythonhosted.org/packages/63/3a/d811ed1aa579b93ab56188d1371b05eacb4188599d83e72b761263a10f92/pyqt6_qt6-6.9.2-py3-none-win_amd64.whl", hash = "sha256:f9289768039bef4a63e5949b7f8cfbbddc3b6d24bd58c21ba0f2921bed8d1c08", size = 74147171, upload-time = "2025-09-01T11:43:53.468Z" },
+ { url = "https://files.pythonhosted.org/packages/57/59/7db6c5ddcb60ef3ecca2040274a30e8bc35b569c49e25e1cf2ef9f159426/pyqt6_qt6-6.9.2-py3-none-win_arm64.whl", hash = "sha256:8f82944ef68c8f8c78aa8eca4832c7bc05116c6de00a3bad8af5a0d63d1caafb", size = 54534019, upload-time = "2025-09-01T11:43:58.763Z" },
+]
+
+[[package]]
+name = "pyqt6-sip"
+version = "13.10.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/2f/4a/96daf6c2e4f689faae9bd8cebb52754e76522c58a6af9b5ec86a2e8ec8b4/pyqt6_sip-13.10.2.tar.gz", hash = "sha256:464ad156bf526500ce6bd05cac7a82280af6309974d816739b4a9a627156fafe", size = 92548, upload-time = "2025-05-23T12:26:49.901Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a1/1e/979ea64c98ca26979d8ce11e9a36579e17d22a71f51d7366d6eec3c82c13/pyqt6_sip-13.10.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8b5d06a0eac36038fa8734657d99b5fe92263ae7a0cd0a67be6acfe220a063e1", size = 112227, upload-time = "2025-05-23T12:26:38.758Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/21/84c230048e3bfef4a9209d16e56dcd2ae10590d03a31556ae8b5f1dcc724/pyqt6_sip-13.10.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad376a6078da37b049fdf9d6637d71b52727e65c4496a80b753ddc8d27526aca", size = 322920, upload-time = "2025-05-23T12:26:39.856Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/1e/c6a28a142f14e735088534cc92951c3f48cccd77cdd4f3b10d7996be420f/pyqt6_sip-13.10.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3dde8024d055f496eba7d44061c5a1ba4eb72fc95e5a9d7a0dbc908317e0888b", size = 303833, upload-time = "2025-05-23T12:26:41.075Z" },
+ { url = "https://files.pythonhosted.org/packages/89/63/e5adf350c1c3123d4865c013f164c5265512fa79f09ad464fb2fdf9f9e61/pyqt6_sip-13.10.2-cp313-cp313-win_amd64.whl", hash = "sha256:0b097eb58b4df936c4a2a88a2f367c8bb5c20ff049a45a7917ad75d698e3b277", size = 53527, upload-time = "2025-05-23T12:26:42.625Z" },
+ { url = "https://files.pythonhosted.org/packages/58/74/2df4195306d050fbf4963fb5636108a66e5afa6dc05fd9e81e51ec96c384/pyqt6_sip-13.10.2-cp313-cp313-win_arm64.whl", hash = "sha256:cc6a1dfdf324efaac6e7b890a608385205e652845c62130de919fd73a6326244", size = 45373, upload-time = "2025-05-23T12:26:43.536Z" },
+ { url = "https://files.pythonhosted.org/packages/23/57/74b4eb7a51b9133958daa8409b55de95e44feb694d4e2e3eba81a070ca20/pyqt6_sip-13.10.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8a76a06a8e5c5b1f17a3f6f3c834ca324877e07b960b18b8b9bbfd9c536ec658", size = 112354, upload-time = "2025-10-08T08:44:00.22Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/cb/fdef02e0d6ee8443a9683a43650d61c6474b634b6ae6e1c6f097da6310bf/pyqt6_sip-13.10.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9128d770a611200529468397d710bc972f1dcfe12bfcbb09a3ccddcd4d54fa5b", size = 323488, upload-time = "2025-10-08T08:44:01.965Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/5b/8ede8d6234c3ea884cbd097d7d47ff9910fb114efe041af62b4453acd23b/pyqt6_sip-13.10.2-cp314-cp314-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d820a0fae7315932c08f27dc0a7e33e0f50fe351001601a8eb9cf6f22b04562e", size = 303881, upload-time = "2025-10-08T08:44:04.086Z" },
+ { url = "https://files.pythonhosted.org/packages/be/44/b5e78b072d1594643b0f1ff348f2bf54d4adb5a3f9b9f0989c54e33238d6/pyqt6_sip-13.10.2-cp314-cp314-win_amd64.whl", hash = "sha256:3213bb6e102d3842a3bb7e59d5f6e55f176c80880ff0b39d0dac0cfe58313fb3", size = 55098, upload-time = "2025-10-08T08:44:08.943Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/91/357e9fcef5d830c3d50503d35e0357818aca3540f78748cc214dfa015d00/pyqt6_sip-13.10.2-cp314-cp314-win_arm64.whl", hash = "sha256:ce33ff1f94960ad4b08035e39fa0c3c9a67070bec39ffe3e435c792721504726", size = 46088, upload-time = "2025-10-08T08:44:10.014Z" },
+]
+
+[[package]]
+name = "pyside6"
+version = "6.10.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pyside6-addons" },
+ { name = "pyside6-essentials" },
+ { name = "shiboken6" },
+]
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e6/98/84b16f78b5d92dd234fb1eb9890a350a5b0c83d985bb8c44a92f813a2d02/pyside6-6.10.0-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:c2cbc5dc2a164e3c7c51b3435e24203e90e5edd518c865466afccbd2e5872bb0", size = 558115, upload-time = "2025-10-08T09:47:09.246Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/76/0961c8c5653ecb60a6881b649dcb6b71a6be5bd1c8d441ecc48ac7f50b1a/pyside6-6.10.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ae8c3c8339cd7c3c9faa7cc5c52670dcc8662ccf4b63a6fed61c6345b90c4c01", size = 557762, upload-time = "2025-10-08T09:47:11.819Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/73/6187502fff8b6599443d15c46dd900b2ded24be5aacb2becce33f6faf566/pyside6-6.10.0-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:9f402f883e640048fab246d36e298a5e16df9b18ba2e8c519877e472d3602820", size = 558299, upload-time = "2025-10-08T09:47:14.255Z" },
+ { url = "https://files.pythonhosted.org/packages/43/67/94794ebaf198bbdb35cb77f19f38370f9b323b036ab149874bc33c38faab/pyside6-6.10.0-cp39-abi3-win_amd64.whl", hash = "sha256:70a8bcc73ea8d6baab70bba311eac77b9a1d31f658d0b418e15eb6ea36c97e6f", size = 564367, upload-time = "2025-10-08T09:47:16.287Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/cc/552331d413c1b933d54ed45e33cc7ff29d0b239677975fe2528e7ac8bfbc/pyside6-6.10.0-cp39-abi3-win_arm64.whl", hash = "sha256:4b709bdeeb89d386059343a5a706fc185cee37b517bda44c7d6b64d5fdaf3339", size = 548826, upload-time = "2025-10-08T09:47:18.399Z" },
+]
+
+[[package]]
+name = "pyside6-addons"
+version = "6.10.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pyside6-essentials" },
+ { name = "shiboken6" },
+]
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/47/23/9fbdec2ce16244ac3fe28e6d44c39c70465c93a03325939a792fd00fde7f/pyside6_addons-6.10.0-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:88e61e21ee4643cdd9efb39ec52f4dc1ac74c0b45c5b7fa453d03c094f0a8a5c", size = 322248256, upload-time = "2025-10-08T09:47:37.844Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/b8/d129210f2c7366b4e1bf5bb6230be42052b29e8ba1b1d7db6ef333cf5a39/pyside6_addons-6.10.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:08d4ed46c4c9a353a9eb84134678f8fdd4ce17fb8cce2b3686172a7575025464", size = 170238987, upload-time = "2025-10-08T09:47:51.446Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/ae/ede1edd009395092219f3437d2ee59f9ba93739c28c040542ed47c6cc831/pyside6_addons-6.10.0-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:15d32229d681be0bba1b936c4a300da43d01e1917ada5b57f9e03a387c245ab0", size = 165939425, upload-time = "2025-10-08T09:48:02.073Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/5d/a3c32f85ac7f905c95679967c0ddda0ba043c273b75623cc90d8185064e4/pyside6_addons-6.10.0-cp39-abi3-win_amd64.whl", hash = "sha256:99d93a32c17c5f6d797c3b90dd58f2a8bae13abde81e85802c34ceafaee11859", size = 164814172, upload-time = "2025-10-08T09:48:12.891Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/2a/4ff71b09571202c8e1320c45276fc1d0cd81ee53107dfc17bb22d4243f88/pyside6_addons-6.10.0-cp39-abi3-win_arm64.whl", hash = "sha256:92536427413f3b6557cf53f1a515cd766725ee46a170aff57ad2ff1dfce0ffb1", size = 34104251, upload-time = "2025-10-08T09:48:18.287Z" },
+]
+
+[[package]]
+name = "pyside6-essentials"
+version = "6.10.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "shiboken6" },
+]
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e5/55/bad02ab890c8b8101abef0db4a2e5304be78a69e23a438e4d8555b664467/pyside6_essentials-6.10.0-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:003e871effe1f3e5b876bde715c15a780d876682005a6e989d89f48b8b93e93a", size = 105034090, upload-time = "2025-10-08T09:48:24.944Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/75/e17efc7eb900993e0e3925885635c6cf373c817196f09bcbcc102b00ac94/pyside6_essentials-6.10.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:1d5e013a8698e37ab8ef360e6960794eb5ef20832a8d562e649b8c5a0574b2d8", size = 76362150, upload-time = "2025-10-08T09:48:31.849Z" },
+ { url = "https://files.pythonhosted.org/packages/06/62/fbd1e81caafcda97b147c03f5b06cfaadd8da5fa8298f527d2ec648fa5b7/pyside6_essentials-6.10.0-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:b1dd0864f0577a448fb44426b91cafff7ee7cccd1782ba66491e1c668033f998", size = 75454169, upload-time = "2025-10-08T09:48:38.21Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/3a/d8211d17e6ca70f641c6ebd309f08ef18930acda60e74082c75875a274da/pyside6_essentials-6.10.0-cp39-abi3-win_amd64.whl", hash = "sha256:fc167eb211dd1580e20ba90d299e74898e7a5a1306d832421e879641fc03b6fe", size = 74361794, upload-time = "2025-10-08T09:48:44.335Z" },
+ { url = "https://files.pythonhosted.org/packages/61/e9/0e22e3c10325c4ff09447fadb43f7962afb82cef0b65358f5704251c6b32/pyside6_essentials-6.10.0-cp39-abi3-win_arm64.whl", hash = "sha256:6dd0936394cb14da2fd8e869899f5e0925a738b1c8d74c2f22503720ea363fb1", size = 55099467, upload-time = "2025-10-08T09:48:50.902Z" },
+]
+
+[[package]]
+name = "shiboken6"
+version = "6.10.0"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fd/78/3e730aea82089dd82b1e092bc265778bda329459e6ad9b7134eec5fff3f2/shiboken6-6.10.0-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:7a5f5f400ebfb3a13616030815708289c2154e701a60b9db7833b843e0bee543", size = 476535, upload-time = "2025-10-08T09:49:08Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/09/4ffa3284a17b6b765d45b41c9a7f1b2cde6c617c853ac6f170fb62bbbece/shiboken6-6.10.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:e612734da515d683696980107cdc0396a3ae0f07b059f0f422ec8a2333810234", size = 271098, upload-time = "2025-10-08T09:49:09.47Z" },
+ { url = "https://files.pythonhosted.org/packages/31/29/00e26f33a0fb259c2edce9c761a7a438d7531ca514bdb1a4c072673bd437/shiboken6-6.10.0-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:b01377e68d14132360efb0f4b7233006d26aa8ae9bb50edf00960c2a5f52d148", size = 267698, upload-time = "2025-10-08T09:49:10.694Z" },
+ { url = "https://files.pythonhosted.org/packages/11/30/e4624a7e3f0dc9796b701079b77defcce0d32d1afc86bb1d0df04bc3d9e2/shiboken6-6.10.0-cp39-abi3-win_amd64.whl", hash = "sha256:0bc5631c1bf150cbef768a17f5f289aae1cb4db6c6b0c19b2421394e27783717", size = 1234227, upload-time = "2025-10-08T09:49:12.774Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/e5/0ab862005ea87dc8647ba958a3099b3b0115fd6491c65da5c5a0f6364db1/shiboken6-6.10.0-cp39-abi3-win_arm64.whl", hash = "sha256:dfc4beab5fec7dbbebbb418f3bf99af865d6953aa0795435563d4cbb82093b61", size = 1794775, upload-time = "2025-10-08T09:49:14.641Z" },
+]