cometusb 1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
cometusb/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "1.0"
cometusb/cometusb.py ADDED
@@ -0,0 +1,442 @@
1
+ import subprocess
2
+ import json
3
+ import sys
4
+ import argparse
5
+ import glob
6
+ from tabulate import tabulate
7
+ import requests
8
+ from tqdm import tqdm
9
+ import zipfile as zf
10
+ import os
11
+
12
+
13
+ def main() -> None:
14
+ parser = argparse.ArgumentParser(
15
+ prog= "CometUSB.",
16
+ description="Create linux bootable USB."
17
+ )
18
+ parser.add_argument("-l", "--list-os", action="store_true", help="Shows list of the available Operating Systems.")
19
+ parser.add_argument("-o", "--operating-system", help="Name of the Operating System.")
20
+ parser.add_argument("-b","--bios-type", help="BIOS type (e.g., UEFI or Legacy), check what your TARGET SYSTEM supports.")
21
+ args = parser.parse_args()
22
+
23
+ # List of available Opereating System
24
+ OS = ["linuxmint"]
25
+
26
+ # Shows list of available Operating Systems.
27
+ if args.list_os:
28
+ for number in range(len(OS)):
29
+ print(number + 1, OS[number], sep=". ")
30
+ sys.exit() # Exits after showing the OS list
31
+
32
+ if not (args.operating_system and args.bios_type):
33
+ sys.exit("Argument missing\nType cometusb -h to see the usage.")
34
+ operating_system = Operating_System(args.operating_system.lower(), args.bios_type.lower())
35
+ print(operating_system)
36
+ operating_system.create()
37
+
38
+ class Operating_System():
39
+ """
40
+ This is the main object that contains all the information of the Operating System, target device and it's partition, bios-type etc.
41
+ Once the create method is invoked it will start the process which includes wiping and formatting the target disk, downloading and extracting of files and
42
+ applying bootloader.
43
+ """
44
+ def __init__(self, name: str, bios_type: str):
45
+ self.name = name
46
+ self._path_url = f"https://github.com/CometUSB/CometUSB/releases/download/{self.name}/"
47
+ self.disk_size_reqd = self.name
48
+ self.bios_type = bios_type
49
+ self.partition_style = self.bios_type
50
+ self.target_disk = get_disk_details()
51
+ self.disk_partitions = format_disk(self.target_disk, self.bios_type, self.disk_size_reqd) # Dictionary of newly created partitions with labels
52
+ self.files = self.name
53
+ self._architecture= "64 Bit"
54
+
55
+
56
+ def __str__(self) -> str:
57
+ return f"\nOS = {self.name.upper()}\nArchitecture = {self._architecture}\nTarget System BIOS Type = {self.bios_type}\nTarget Device = {self.target_disk}\nPartition Style = {self.partition_style.upper()}\nFiles to be Downloaded = {[name for name in self.files.keys()]}\n"
58
+
59
+ @property
60
+ def name(self) -> str:
61
+ return self._name
62
+
63
+ @name.setter
64
+ def name(self, name: str) -> None:
65
+ """
66
+ Sets name of the Operating System.
67
+
68
+ :param name: Name of the Operating System.
69
+ :type name: str
70
+ """
71
+ OS: list = ["linuxmint"]
72
+ if name not in OS:
73
+ sys.exit("[!] Invalid or Unsupported Operating System.\nEnter 'cometusb.py --OS-list' without quotes to see the supported list of Operating systems")
74
+
75
+ self._name: str = name
76
+
77
+ @property
78
+ def partition_style(self) -> str:
79
+ return self._partition_style
80
+
81
+ @partition_style.setter
82
+ def partition_style(self, bios_type: str) -> None:
83
+ """
84
+ Sets the partition style i.e, MBR or GPT.
85
+
86
+ :param bios_type: BIOS firmare type i.e, UEFI or Legacy.
87
+ :type bios_type: str
88
+ """
89
+ if bios_type == "uefi":
90
+ self._partition_style: str = "GPT"
91
+
92
+ else:
93
+ self._partition_style: str = "MBR"
94
+
95
+
96
+ @property
97
+ def bios_type(self) -> str:
98
+ return self._bios_type
99
+ @bios_type.setter
100
+ def bios_type(self, bios_type: str) -> None:
101
+ """
102
+ Sets the BIOS type.
103
+
104
+ :param bios_type: BIOS firmare type i.e, UEFI or Legacy.
105
+ :type bios_type: str
106
+ """
107
+ if bios_type in ["uefi", "legacy"]:
108
+ self._bios_type: str = bios_type
109
+
110
+ else:
111
+ sys.exit("[!] Invalid BIOS type.")
112
+
113
+ @property
114
+ def files(self) -> dict:
115
+ return self._files
116
+
117
+ @files.setter
118
+ def files(self, name: str) -> None:
119
+ """
120
+ Selects files of given Operating System for Installation.
121
+
122
+ :param name: Name of the Operating System.
123
+ :type name: str
124
+ """
125
+ if len(self.disk_partitions) == 2:
126
+ self._boot_partition, self._file_partition = self.disk_partitions.keys()
127
+ elif len(self.disk_partitions) == 1:
128
+ self._file_partition: str = [partition for partition in self.disk_partitions.keys()][0] # files_partition and boot_partition are the labels of boot partition and files partitions
129
+ self._boot_partition: str = self._file_partition
130
+
131
+ # Contains OS name and it's corresponding files and it's download path.
132
+ OS_FILES: dict = {
133
+ "linuxmint": {
134
+ "boot.zip": f"/mnt/{self._boot_partition}/",
135
+ "directories.zip":f"/mnt/{self._file_partition}/",
136
+ "filesystem.squashfs.aa": f"/mnt/{self._file_partition}/",
137
+ "filesystem.squashfs.ab": f"/mnt/{self._file_partition}/"
138
+ }
139
+ }
140
+
141
+ self._files: dict = OS_FILES[name]
142
+
143
+ @property
144
+ def disk_size_reqd(self):
145
+ return self._disk_size_reqd
146
+ @disk_size_reqd.setter
147
+ def disk_size_reqd(self, name):
148
+ """
149
+ Calculates the size of disk required for process.
150
+
151
+ :param name: Name of the Operating System.
152
+ """
153
+ OS_FILES = {
154
+ "linuxmint": [
155
+ "boot.zip",
156
+ "directories.zip",
157
+ "filesystem.squashfs.aa",
158
+ "filesystem.squashfs.ab"
159
+ ]
160
+ }
161
+
162
+ total_size: int = 0
163
+ for filename in OS_FILES[name]:
164
+ with requests.get(self._path_url + filename, stream = True) as response:
165
+ response.raise_for_status()
166
+ total_size += int(response.headers.get("content-length", 0))
167
+
168
+ total_size = total_size / (1024 * 1024 * 1024) # Total files size.
169
+
170
+ USB_SIZES = [4, 8, 16, 32, 64]
171
+ for usb_size in USB_SIZES:
172
+ if usb_size / (total_size * 2) > 1: # Divided by twice of the total_size because of merging of OS images will require space for merged file. Although splitted image files will be removed.
173
+ self._disk_size_reqd = usb_size
174
+ break
175
+
176
+ @property
177
+ def target_disk(self) -> str:
178
+ return self._target_disk
179
+
180
+ @target_disk.setter
181
+ def target_disk(self, target_disk: str) -> None:
182
+ """
183
+ Sets the target disk.
184
+
185
+ :param target_disk: Name of the target disk.
186
+ :type target_disk: str
187
+ """
188
+ self._target_disk: str = target_disk
189
+
190
+ @property
191
+ def disk_partitions(self) -> dict:
192
+ return self._disk_partitions
193
+
194
+ @disk_partitions.setter
195
+ def disk_partitions(self, partitions: dict) -> None:
196
+ """
197
+ Sets partitions with of the target disk.
198
+
199
+ :param partitions: Dictionary of all the newly created partition of target disk with corresponding labels.
200
+ :type partitions: dict
201
+ """
202
+ self._disk_partitions: dict = partitions
203
+
204
+ def bootloader(self) -> None:
205
+ """
206
+ Applies bootloader to the partition of the target disk containing boot files.
207
+ """
208
+ print(f"Applying bootloader on {self.target_disk} for {self.bios_type} systems...")
209
+ if self.bios_type == "uefi":
210
+ subprocess.run(["sudo", "grub-install" ,"--target=x86_64-efi", f"--efi-directory=/mnt/{self._boot_partition}", f"--boot-directory=/mnt/{self._boot_partition}/boot", "--removable"])
211
+
212
+ else:
213
+ subprocess.run(["sudo", "grub-install" ,"--target=i386-pc", f"--boot-directory=/mnt/{self._boot_partition}/boot", f"{self.target_disk}"])
214
+
215
+ def create(self) -> None:
216
+ """
217
+ This method calls all the required functions in sequence to perform the necessary operations to make the bootable media.
218
+ """
219
+ # Mounting newly created partitions.
220
+ mount_usb(self.disk_partitions)
221
+
222
+ # Disk configuration info.
223
+ print(f"\n[*] Disk {self.target_disk} configuration.")
224
+ subprocess.run(["lsblk", self.target_disk, "-o", "NAME,SIZE,FSTYPE,FSVER,LABEL,MOUNTPOINTS"])
225
+
226
+ for filename, download_dir in self.files.items():
227
+ print() #To create gap between progress bars.
228
+ print(f"Downloading {filename} into {download_dir}")
229
+ downloader(self._path_url + filename, download_dir)
230
+ print() #To create gap between progress bars.
231
+ if filename.endswith(".zip"):
232
+ # Extracting to create the directory tree structure
233
+ extractor(download_dir + filename, download_dir)
234
+ print() #To create gap between progress bars.
235
+ os.remove(download_dir + filename) # Removing the zip file after extracting to free space in the USB.
236
+ if filename.endswith(".aa"):
237
+ image_name, download_dir = filename.rstrip(".aa"), download_dir
238
+ print(f"[*] Making OS Image {image_name} ready for installation.\n[*] This may take a while depending upon your removable disk {self.target_disk}.")
239
+ subprocess.run(f"sudo cat {download_dir}{image_name}.* > {download_dir}casper/{image_name}", shell=True, stdout=subprocess.DEVNULL)
240
+
241
+ print("[*] Cleaning unnecessary file...")
242
+ for splitted_image_file in glob.glob(f"{download_dir}{image_name}.*"):
243
+ os.remove(splitted_image_file)
244
+
245
+ # Applying bootloader
246
+ self.bootloader()
247
+
248
+ print("Media created succesfully\nNOTE: Linux disk sometimes is not detected in BIOS, try disabling secure boot of your BIOS if facing any issues while booting.")
249
+
250
+ def get_disk_details() -> str:
251
+ """
252
+ Executes the 'lsblk' command to retrieve detailed information for physical
253
+ block disks using JSON output for robust, programmatic parsing.
254
+
255
+ :return: A string of target device.
256
+ :rtype: str
257
+ """
258
+
259
+ LSBLK_CMD = ['lsblk', '-d','-J', '-o', 'NAME,SIZE,VENDOR,MODEL,RM']
260
+
261
+
262
+ # Execute the command, capture output, and ensure success
263
+ result = subprocess.run(
264
+ LSBLK_CMD,
265
+ capture_output=True,
266
+ text=True,
267
+ check=True
268
+ )
269
+
270
+ # Parse the JSON output into a Python dictionary
271
+ data = json.loads(result.stdout)
272
+
273
+ disks = [
274
+ [
275
+ disk.get('name', 'N/A'),
276
+ disk.get('size', 'N/A'),
277
+ disk.get('vendor', 'N/A'),
278
+ disk.get('model', 'N/A')
279
+ ]
280
+ for disk in data.get('blockdevices', []) if disk.get('rm', 'N/A') == True
281
+ ]
282
+
283
+ if not disks:
284
+ sys.exit("No USB/removable media found.")
285
+ headers = [header.capitalize() for header in data.get("blockdevices")[0].keys()]
286
+ headers[2] = "Interface" # Renaming Vendor column to Interface
287
+ print(tabulate(disks, headers=headers, tablefmt="grid"))
288
+
289
+
290
+ return f"/dev/{input("Enter disk: ")}"
291
+
292
+ def format_disk(disk: str, bios_type: str, size: int) -> str:
293
+ """
294
+ Formats the target disk, converts it into GPT or MBR then create partitions and filesystems compatible for the given BIOS Type i.e, UEFI or Legacy.
295
+
296
+ :param disk: Name of target disk.
297
+ :type disk: str
298
+ :param bios_type: BIOS firmware type.
299
+ :type bios_type: str
300
+ :param size: Size of the target disk required.
301
+ :return: Dictionary of all the partition of target disk with corresponding labels.
302
+ :rtype: str
303
+ """
304
+ # Confirming to Format the USB
305
+ print(f"\n[!] MINIMUM {size} GB Disk is required.\n[*] This will ERASE all data on {disk}")
306
+ if input("Type 'yes' to continue: ").strip().lower() != "yes":
307
+ sys.exit("Aborted by user.")
308
+
309
+ partitions = glob.glob(disk + "?")
310
+ unmount_usb(partitions)
311
+ try:
312
+
313
+ # Wipe partition table
314
+ print(f"\n[*] Wiping disk {disk}")
315
+ subprocess.run(["sudo", "wipefs", "-a", disk], check=True)
316
+
317
+ print(f"\n[*] Creating partitions for {bios_type} systems...")
318
+ if bios_type == "uefi":
319
+ # Create new partition table and partition
320
+ subprocess.run(["sudo", "parted", "-s", disk, "mklabel", "gpt"], check=True)
321
+ subprocess.run(["sudo", "parted", "-s", disk, "mkpart", "primary", "1MiB", "501MiB"], check=True)
322
+ partition = glob.glob(disk + "?")
323
+ boot_partition = partition[0]
324
+ subprocess.run(["sudo", "parted", "-s", disk, "mkpart", "primary", "501MiB", "100%"], check=True)
325
+ partition = glob.glob(disk + "?")
326
+ partition.remove(boot_partition)
327
+ files_partition = partition[0]
328
+
329
+ # Refreshing the partitions
330
+ subprocess.run(["sudo", "partprobe", disk], check=True)
331
+ subprocess.run(["sudo", "udevadm", "settle"], check=True)
332
+
333
+ # Creating the filesystems
334
+ print(f"\n[*] Creating filesystems ...")
335
+ subprocess.run(["sudo", "mkfs.fat", "-F", "32", "-n", "COMET_BOOT", boot_partition], check=True)
336
+ subprocess.run(["sudo", "parted", "-s", disk, "set", "1", "esp", "on"], check=True)
337
+ subprocess.run(["sudo", "mkfs.ntfs", "-f", files_partition, "-L", "COMET_FILES"], check=True)
338
+
339
+ elif bios_type == "legacy":
340
+ # Create new partition table and partition
341
+ subprocess.run(["sudo", "parted", "-s", disk, "mklabel", "msdos"], check=True)
342
+ subprocess.run(["sudo", "parted", "-s", disk, "mkpart", "primary", "0%", "100%"], check=True)
343
+ partition = glob.glob(disk + "?")
344
+
345
+ files_partition = partition[0] # Only one partition is here same for installation and boot files.
346
+
347
+ # Refreshing the partitions
348
+ subprocess.run(["sudo", "partprobe", disk], check=True)
349
+ subprocess.run(["sudo", "udevadm", "settle"], check=True)
350
+
351
+ # Creating the filesystems
352
+ print(f"\n[*] Creating filesystems ...")
353
+
354
+ subprocess.run(["sudo", "mkfs.ntfs", "-f", files_partition, "-L", "COMET"], check=True)
355
+ subprocess.run(["sudo", "parted", "-s", disk, "set", "1", "boot", "on"], check=True)
356
+
357
+ print(f"[*] USB {disk} formatted successfully!\n")
358
+
359
+ except subprocess.CalledProcessError:
360
+ sys.exit("[*] Something went wrong, please retry.")
361
+
362
+ if len(glob.glob(disk + "?")) == 2:
363
+ return {"COMET_BOOT": boot_partition, "COMET_FILES": files_partition}
364
+ else:
365
+ return {"COMET": files_partition}
366
+
367
+ def unmount_usb(partitions: list) -> None:
368
+ """
369
+ Unmounts all the existing partitions of the target disk to intitate the formatting process by format_disk function.
370
+
371
+ :param partitions: List of all the partitions in the target disk.
372
+ :type partitions: list
373
+ """
374
+
375
+ for part in partitions:
376
+ print(f"Unmounting: {part}")
377
+ subprocess.run(["sudo", "umount", "-f", part])
378
+
379
+ def mount_usb(partitions: dict) -> None:
380
+ """
381
+ Mounts all the newly created partitions of the target disk by format_disk function.
382
+
383
+ :param partitions: Dictionary of all the newly created partition of target disk with corresponding labels.
384
+ :type partitions: dict
385
+ """
386
+ for part_label in partitions.keys():
387
+ print(f"Mounting: {partitions[part_label]} on /mnt/{part_label}")
388
+ subprocess.run(["sudo", "mkdir", "-p", f"/mnt/{part_label}"])
389
+ result = subprocess.run(["sudo", "mount", partitions[part_label], f"/mnt/{part_label}"])
390
+ if result.returncode > 8:
391
+ sys.exit(f"\n[*] Failed to mount {partitions[part_label]} on /mnt/{part_label}.\n[*] Please retry...")
392
+
393
+ def downloader(url: str, download_dir: str) -> None:
394
+ """
395
+ Downloads all the files from github release page of the corresponding operating system of the CometUSB organization.
396
+
397
+ :param url: Download URL of individual files.
398
+ :type url: str
399
+ :param download_dir: Download location of the given files.
400
+ :type download_dir: str
401
+ """
402
+ with requests.get(url, stream = True) as response:
403
+ response.raise_for_status()
404
+ total_size: int = int(response.headers.get("content-length", 0))
405
+ chunk_size: int = 1024 * 200 # 200KB chunk for smoother progress update.
406
+ with open(f"{download_dir}{os.path.basename(url)}", "wb") as file, tqdm(
407
+ total = total_size,
408
+ unit = "B",
409
+ unit_scale = True,
410
+ unit_divisor = 1024,
411
+ desc = f"{os.path.basename(url)}"
412
+ ) as progress:
413
+ for chunk in response.iter_content(chunk_size = chunk_size):
414
+ download = file.write(chunk)
415
+ progress.update(download)
416
+
417
+ def extractor(archive_path: str, extract_dir: str) -> None:
418
+ """
419
+ Extracts the compressed archive to given location.
420
+
421
+ :param archive_path: Complete path of the compressed archives.
422
+ :type archive_path: str
423
+ :param extract_dir: Extract folder for the corresponding archive.
424
+ :type extract_dir: str
425
+ """
426
+ with zf.ZipFile(archive_path, "r") as archive, tqdm(
427
+ total = sum(file_info.file_size for file_info in archive.infolist()),
428
+ unit = "B",
429
+ unit_scale = True,
430
+ unit_divisor = 1024,
431
+ desc = f"Extracting {os.path.basename(archive_path)}"
432
+ ) as progress:
433
+
434
+ for file_info in archive.infolist():
435
+
436
+ archive.extract(file_info, path = extract_dir)
437
+ progress.update(file_info.file_size)
438
+
439
+
440
+ if __name__ == "__main__":
441
+ main()
442
+
@@ -0,0 +1,95 @@
1
+ Metadata-Version: 2.4
2
+ Name: cometusb
3
+ Version: 1.0
4
+ Summary: Create Linux bootable USB from CLI
5
+ Author-email: Shahid Rahaman <shahidrahman333@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/CometUSB/CometUSB
8
+ Project-URL: Documentation, https://CometUSB.github.io/CometUSB/
9
+ Project-URL: Changelog, https://github.com/CometUSB/CometUSB/releases
10
+ Requires-Python: >=3.8
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: requests
14
+ Requires-Dist: tqdm
15
+ Requires-Dist: tabulate
16
+ Dynamic: license-file
17
+
18
+ # CometUSB
19
+ ##### _Python package for Linux distributions to create bootable USB._
20
+ _**See full documentation at: https://CometUSB.github.io/CometUSB/**_
21
+ ## Description
22
+ This is a python package exclusively for linux distributions. It has list of linux distributions you can choose to create the bootable media. In the corresponding release section you will find the installation files of several linux distributions.
23
+
24
+ ## Features
25
+ ---
26
+ - Create UEFI with GPT and Legacy BIOS with MBR bootable USBs.
27
+ - Dual-partition layout: In case of UEFI system two partitions first small FAT32 for boot then NTFS in rest of the space for other files are created.
28
+ - Single-partition layout: In case of legacy systems only one NTFS partition is created in the entire disk for both boot and installation files.
29
+ - Automatically install GRUB for UEFI or legacy systems.
30
+ - Only shows removable disk to format which prevents wiping your main HDD/SSD. This avoids significant data loss.
31
+
32
+ ---
33
+
34
+ # Installation
35
+
36
+ CometUSB is a Python package available on PyPI. It requires Python 3.8 or higher.
37
+
38
+ ### Prerequisites
39
+
40
+ Before installation, ensure you have:
41
+
42
+ * **Linux Distribution:** CometUSB is exclusively for Linux.
43
+ * **Python 3.8+:** Installed on your system.
44
+ * **Administrative Rights (`sudo`):** The tool requires `sudo` privileges to manage disks and partitions.
45
+
46
+ ### Install via pip
47
+
48
+ The most straightforward way to install CometUSB is using `pip`:
49
+
50
+ ```bash
51
+ pip install cometusb
52
+ or
53
+ # Using python -m pip (recommended for virtual environments)
54
+ python -m pip install cometusb
55
+ or
56
+ python3 -m pip install cometusb
57
+ ```
58
+ _If none of the above commands work, find out how to install python package in your system._
59
+
60
+ # Usage Guide
61
+
62
+ CometUSB is run directly from the command line, requiring administrative privileges (`sudo`) because it must access and format your physical disk devices.
63
+
64
+ ### Basic Command Structure
65
+
66
+ You must always specify the Operating System name (`-o`) and the target system's BIOS type (`-b`).
67
+
68
+
69
+ `sudo cometusb -o <OS_NAME> -b <BIOS_TYPE>`
70
+
71
+ or
72
+
73
+ `sudo cometusb --operating-system <OS_Name> --bios-type <BIOS_TYPE>`
74
+
75
+ _e.g, `sudo cometusb -o linuxmint -b uefi`_
76
+
77
+ _Type `cometusb -h` or `cometusb --help` to see the usage_
78
+
79
+ _Type `cometusb --list-os` or `cometusb -l` to see the list of available Operating System_
80
+
81
+ # CLI Reference
82
+
83
+ ### Required Arguments
84
+
85
+ | Option | Long Option | Description | Example |
86
+ | :--- | :--- | :--- | :--- |
87
+ | `-o` | `--operating-system` | **Name of the Operating System** to download and install.| `-o linuxmint` |
88
+ | `-b` | `--bios-type` | **BIOS type** (boot mode) for the target system. Specifies the partitioning scheme (GPT/MBR) and GRUB installation method. | `-b uefi` |
89
+
90
+ ### Optional Arguments
91
+
92
+ | Option | Long Option | Description |
93
+ | :--- | :--- | :--- |
94
+ | `-l` | `--list-os` | Shows a list of the currently available and supported Operating Systems. |
95
+ | `-h` | `--help` | Show the help message and exit. |
@@ -0,0 +1,8 @@
1
+ cometusb/__init__.py,sha256=2HXaaHC9Xl9BqTkMCzF1KrnViBZbueeO8znZSwwWG4c,19
2
+ cometusb/cometusb.py,sha256=oz0YgeQYMggnQ8uKdLhk4iTx0t4yeD3lL2y6T4hUfRc,17444
3
+ cometusb-1.0.dist-info/licenses/LICENSE,sha256=8qFj_2PByO82AFLB_JtLyieuvxC3mx59F1GZrqyM_Do,1083
4
+ cometusb-1.0.dist-info/METADATA,sha256=ME3uj1gS9ZaVZtbI3PmKFhSln0cM19FdGyVGbwdJ8V8,3474
5
+ cometusb-1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
+ cometusb-1.0.dist-info/entry_points.txt,sha256=iQZz0EMQuvYR1q8t_c_EbDCMd6jDJxmtJMWQ1lrvrbc,52
7
+ cometusb-1.0.dist-info/top_level.txt,sha256=qFT-0B2ImDxACh5UPwEUzPGLRgYoIX_OLKPeMsdTkog,9
8
+ cometusb-1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ cometusb = cometusb.cometusb:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 CometUSB by Shahid Rahaman
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.
@@ -0,0 +1 @@
1
+ cometusb