umu-commander 1.4.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.

Potentially problematic release.


This version of umu-commander might be problematic. Click here for more details.

File without changes
@@ -0,0 +1,43 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ # analogous to Proton version
5
+ @dataclass
6
+ class Element:
7
+ group_id: str = ""
8
+ value: str = ""
9
+ info: str = ""
10
+
11
+ # proton terminology for convenience
12
+ @property
13
+ def dir(self):
14
+ return self.group_id
15
+
16
+ @property
17
+ def version_num(self):
18
+ return self.value
19
+
20
+ @property
21
+ def user_count(self):
22
+ return self.info
23
+
24
+ @user_count.setter
25
+ def user_count(self, value):
26
+ self.info = value
27
+
28
+
29
+ # analogous to Proton directory
30
+ @dataclass
31
+ class Group:
32
+ identity: str = ""
33
+ label: str = ""
34
+ elements: list[Element] = list
35
+
36
+ # proton terminology for convenience
37
+ @property
38
+ def path(self):
39
+ return self.identity
40
+
41
+ @property
42
+ def versions(self):
43
+ return self.elements
@@ -0,0 +1,22 @@
1
+ import os
2
+ from pathlib import Path
3
+
4
+ from umu_commander.classes import Element
5
+
6
+ PROTON_DIRS: list[str] = [
7
+ os.path.join(Path.home(), ".local/share/Steam/compatibilitytools.d/"),
8
+ os.path.join(Path.home(), ".local/share/umu/compatibilitytools"),
9
+ ]
10
+ # where UMU saves its UMU-Proton versions
11
+ UMU_PROTON_DIR: str = os.path.join(
12
+ Path.home(), ".local/share/Steam/compatibilitytools.d/"
13
+ )
14
+ DB_NAME: str = "tracking.json"
15
+ DB_DIR: str = os.path.join(Path.home(), ".local/share/umu/compatibilitytools")
16
+ CONFIG_NAME: str = "umu-config.toml"
17
+ # default WINE prefix directory
18
+ PREFIX_DIR: str = os.path.join(Path.home(), ".local/share/wineprefixes/")
19
+ # Label, override string, follow the example
20
+ DLL_OVERRIDES_OPTIONS: list[Element] = [
21
+ Element(info="winhttp for BepInEx", value="winhttp.dll=n;")
22
+ ]
umu_commander/db.py ADDED
@@ -0,0 +1,66 @@
1
+ import json
2
+ from collections import defaultdict
3
+ from json import JSONDecodeError
4
+
5
+ from umu_commander.configuration import *
6
+
7
+ _db: defaultdict[str, defaultdict[str, list[str]]]
8
+
9
+
10
+ def load():
11
+ global _db
12
+
13
+ if not os.path.exists(DB_DIR):
14
+ os.mkdir(DB_DIR)
15
+
16
+ try:
17
+ with open(os.path.join(DB_DIR, DB_NAME), "rt") as db_file:
18
+ _db = defaultdict(lambda: defaultdict(list))
19
+ _db.update(json.load(db_file))
20
+
21
+ except JSONDecodeError:
22
+ print(f"Could not decode DB file, is it valid JSON?")
23
+ raise JSONDecodeError("", "", 0)
24
+
25
+ except FileNotFoundError:
26
+ _db = defaultdict(lambda: defaultdict(list))
27
+
28
+
29
+ def dump():
30
+ with open(os.path.join(DB_DIR, DB_NAME), "wt") as db_file:
31
+ # noinspection PyTypeChecker
32
+ json.dump(_db, db_file, indent="\t")
33
+
34
+
35
+ def copy():
36
+ return _db.copy()
37
+
38
+
39
+ def get(proton_dir: str, proton_ver: str = None) -> dict[str, list[str]] | list[str]:
40
+ if proton_ver is None:
41
+ return _db[proton_dir]
42
+
43
+ return _db[proton_dir][proton_ver]
44
+
45
+
46
+ # add user_dir to proton_ver's of proton_dir list of users
47
+ def add_to(proton_dir: str, proton_ver: str, user_dir: str):
48
+ global _db
49
+
50
+ if proton_ver not in _db[proton_dir]:
51
+ _db[proton_dir][proton_ver] = []
52
+
53
+ _db[proton_dir][proton_ver].append(user_dir)
54
+
55
+
56
+ def remove_from(proton_dir: str, proton_ver: str, user_dir: str):
57
+ global _db
58
+
59
+ if user_dir in _db[proton_dir][proton_ver]:
60
+ _db[proton_dir][proton_ver].remove(user_dir)
61
+
62
+
63
+ def delete(proton_dir: str, proton_ver: str):
64
+ global _db
65
+
66
+ del _db[proton_dir][proton_ver]
umu_commander/main.py ADDED
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/python3
2
+ import sys
3
+ from json import JSONDecodeError
4
+
5
+ from umu_commander import db, tracking, umu_config
6
+ from umu_commander.configuration import *
7
+
8
+
9
+ def print_help():
10
+ print(
11
+ "umu-commander is an interactive CLI tool to help you manage Proton versions used by umu, as well as create enhanced launch configs.",
12
+ "",
13
+ "For details, explanations, and more, see the README.md file, or visit https://github.com/Mpaxlamitsounas/umu-commander.",
14
+ sep="\n",
15
+ )
16
+
17
+
18
+ def main() -> int:
19
+ try:
20
+ db.load()
21
+ except JSONDecodeError:
22
+ print(f"Tracking file at {os.path.join(DB_DIR, DB_NAME)} could not be read.")
23
+ return 2
24
+
25
+ if len(sys.argv) == 1:
26
+ print_help()
27
+ return 0
28
+
29
+ verb: str = sys.argv[1]
30
+ match verb:
31
+ case "track":
32
+ tracking.track()
33
+ case "untrack":
34
+ tracking.untrack()
35
+ case "users":
36
+ tracking.users()
37
+ case "delete":
38
+ tracking.delete()
39
+ case "create":
40
+ umu_config.create()
41
+ case "run":
42
+ umu_config.run()
43
+ case _:
44
+ print("Invalid verb.")
45
+ print_help()
46
+ return 3
47
+
48
+ tracking.untrack_unlinked()
49
+ db.dump()
50
+
51
+ return 0
52
+
53
+
54
+ if __name__ == "__main__":
55
+ exit(main())
@@ -0,0 +1,66 @@
1
+ import os
2
+ import re
3
+ import subprocess
4
+
5
+ from umu_commander.classes import Element, Group
6
+ from umu_commander.configuration import DB_NAME, PROTON_DIRS, UMU_PROTON_DIR
7
+
8
+
9
+ def _natural_sort_proton_ver_key(e: Element, _nsre=re.compile(r"(\d+)")):
10
+ s: str = e.value
11
+ return [int(text) if text.isdigit() else text for text in _nsre.split(s)]
12
+
13
+
14
+ def refresh_proton_versions():
15
+ print("Updating umu Proton.")
16
+ umu_update_process = subprocess.run(
17
+ ["umu-run", '""'],
18
+ env={"PROTONPATH": "UMU-Latest", "UMU_LOG": "debug"},
19
+ capture_output=True,
20
+ text=True,
21
+ )
22
+
23
+ for line in umu_update_process.stderr.split("\n"):
24
+ if "PROTONPATH" in line and "/" in line:
25
+ try:
26
+ left: int = line.rfind("/") + 1
27
+ print(f"Using {line[left:len(line) - 1]}.")
28
+ except ValueError:
29
+ print("Could not fetch latest UMU-Proton.")
30
+
31
+ break
32
+
33
+
34
+ def _sort_proton_versions(versions: list[Element]) -> list[Element]:
35
+ return sorted(versions, key=_natural_sort_proton_ver_key, reverse=True)
36
+
37
+
38
+ def collect_proton_versions(sort: bool = False) -> list[Group]:
39
+ version_groups: list[Group] = []
40
+ for proton_dir in PROTON_DIRS:
41
+ versions: list[Element] = [
42
+ Element(proton_dir, version, "")
43
+ for version in os.listdir(proton_dir)
44
+ if version != DB_NAME
45
+ ]
46
+ if sort:
47
+ versions = sorted(versions, key=_natural_sort_proton_ver_key, reverse=True)
48
+
49
+ version_groups.append(
50
+ Group(proton_dir, f"Proton versions in {proton_dir}:", versions)
51
+ )
52
+
53
+ return version_groups
54
+
55
+
56
+ def get_latest_umu_proton():
57
+ umu_proton_versions: list[Element] = [
58
+ Element(UMU_PROTON_DIR, version, "")
59
+ for version in os.listdir(UMU_PROTON_DIR)
60
+ if "UMU" in version and version != DB_NAME
61
+ ]
62
+ umu_proton_versions = sorted(
63
+ umu_proton_versions, key=_natural_sort_proton_ver_key, reverse=True
64
+ )
65
+
66
+ return umu_proton_versions[0].version_num
@@ -0,0 +1,97 @@
1
+ import shutil
2
+
3
+ from umu_commander import db
4
+ from umu_commander.classes import Group
5
+ from umu_commander.configuration import *
6
+ from umu_commander.proton import (
7
+ collect_proton_versions,
8
+ get_latest_umu_proton,
9
+ refresh_proton_versions,
10
+ )
11
+ from umu_commander.util import (
12
+ get_selection,
13
+ )
14
+
15
+
16
+ def untrack(quiet: bool = False):
17
+ current_dir: str = os.getcwd()
18
+ for proton_dir in db.copy().keys():
19
+ for proton_ver in db.get(proton_dir):
20
+ db.remove_from(proton_dir, proton_ver, current_dir)
21
+
22
+ if not quiet:
23
+ print("Directory removed from all user lists.")
24
+
25
+
26
+ def track(proton: Element = None, refresh_versions: bool = True, quiet: bool = False):
27
+ if refresh_versions:
28
+ refresh_proton_versions()
29
+
30
+ if proton is None:
31
+ proton: Element = get_selection(
32
+ "Select Proton version to add directory as user:",
33
+ None,
34
+ collect_proton_versions(sort=True),
35
+ )
36
+
37
+ untrack(quiet=True)
38
+ current_directory: str = os.getcwd()
39
+ db.add_to(proton.dir, proton.version_num, current_directory)
40
+
41
+ if not quiet:
42
+ print(
43
+ f"Directory {current_directory} added to Proton version's {proton.version_num} in {proton.dir} user list."
44
+ )
45
+
46
+
47
+ def users():
48
+ proton_version_dirs: list[Group] = collect_proton_versions(sort=True)
49
+
50
+ for proton_dir in proton_version_dirs:
51
+ for proton in proton_dir.versions:
52
+ if proton.version_num in db.get(proton.dir):
53
+ proton.user_count = (
54
+ "(" + str(len(db.get(proton.dir, proton.version_num))) + ")"
55
+ )
56
+ else:
57
+ proton.user_count = "(-)"
58
+
59
+ proton: Element = get_selection(
60
+ "Select Proton version to view user list:", None, proton_version_dirs
61
+ )
62
+
63
+ if proton.dir in db.copy() and proton.version_num in db.get(proton.dir):
64
+ version_users: list[str] = db.get(proton.dir, proton.version_num)
65
+ if len(version_users) > 0:
66
+ print(f"Directories using {proton.version_num} of {proton.dir}:")
67
+ print(*version_users, sep="\n")
68
+ else:
69
+ print("No directories currently use this version.")
70
+ else:
71
+ print("This version hasn't been used by umu before.")
72
+
73
+
74
+ def delete():
75
+ for proton_dir in db.copy().keys():
76
+ for proton_ver, version_users in db.get(proton_dir).copy().items():
77
+ if proton_ver == get_latest_umu_proton():
78
+ continue
79
+
80
+ if len(version_users) == 0:
81
+ selection: str = input(
82
+ f"{proton_ver} in {proton_dir} has no using directories, delete? (Y/N) ? "
83
+ )
84
+ if selection.lower() == "y":
85
+ try:
86
+ shutil.rmtree(os.path.join(proton_dir, proton_ver))
87
+ except FileNotFoundError:
88
+ pass
89
+ db.delete(proton_dir, proton_ver)
90
+
91
+
92
+ def untrack_unlinked():
93
+ for proton_dir in db.copy().keys():
94
+ for proton_ver, version_users in db.get(proton_dir).items():
95
+ for user in version_users:
96
+ if not os.path.exists(user):
97
+ db.remove_from(proton_dir, proton_ver, user)
@@ -0,0 +1,147 @@
1
+ import subprocess
2
+ import tomllib
3
+ from collections.abc import Mapping
4
+ from typing import Any
5
+
6
+ from umu_commander import tracking
7
+ from umu_commander.configuration import *
8
+ from umu_commander.proton import collect_proton_versions, refresh_proton_versions
9
+ from umu_commander.util import (
10
+ get_selection,
11
+ values_to_elements,
12
+ )
13
+
14
+
15
+ def _write(params: Mapping[str, Any]):
16
+ config: str = "[umu]\n"
17
+ for key in set(params.keys()) - {"env", "launch_args"}:
18
+ config += f'{key} = "{params[key]}"\n'
19
+
20
+ config += f"launch_args = {params["launch_args"]}\n"
21
+
22
+ config += "\n[env]\n"
23
+ for key, value in params["env"].items():
24
+ config += f'{key} = "{value}"\n'
25
+
26
+ config_file = open(CONFIG_NAME, "wt")
27
+ config_file.write(config)
28
+ config_file.close()
29
+
30
+
31
+ def create():
32
+ refresh_proton_versions()
33
+
34
+ params: dict[str, Any] = {"env": {}}
35
+
36
+ # Prefix selection
37
+ selection: str = get_selection(
38
+ "Select wine prefix:",
39
+ values_to_elements([*os.listdir(PREFIX_DIR), "Current directory"]),
40
+ None,
41
+ ).value
42
+
43
+ if selection == "Current directory":
44
+ params["prefix"] = os.path.join(os.getcwd(), "prefix")
45
+ else:
46
+ params["prefix"] = os.path.join(PREFIX_DIR, selection)
47
+
48
+ # Proton selection
49
+ selected_umu_latest: bool = False
50
+ proton: Element = get_selection(
51
+ "Select Proton version:",
52
+ values_to_elements(["Always latest UMU Proton"]),
53
+ [*collect_proton_versions(sort=True)],
54
+ )
55
+ if proton.value == "Always latest UMU Proton":
56
+ selected_umu_latest = True
57
+ else:
58
+ params["proton"] = os.path.join(proton.dir, proton.version_num)
59
+
60
+ # Select DLL overrides
61
+ possible_overrides: list[Element] = [
62
+ Element(info="Reset"),
63
+ Element(info="Done"),
64
+ *DLL_OVERRIDES_OPTIONS,
65
+ ]
66
+ selected: set[int] = set()
67
+ while True:
68
+ print("Select DLLs to override, you can select multiple:")
69
+ for idx, override in enumerate(possible_overrides):
70
+ if idx in selected:
71
+ idx = "Y"
72
+ print(f"{idx}) {override.info}")
73
+
74
+ try:
75
+ index: int = int(input("? "))
76
+ print("")
77
+ except ValueError:
78
+ continue
79
+
80
+ if index == 0:
81
+ selected = set()
82
+ continue
83
+
84
+ if index == 1:
85
+ break
86
+
87
+ index: int = int(index)
88
+ if index - 1 < len(possible_overrides):
89
+ selected.add(index)
90
+
91
+ if len(selected) > 0:
92
+ params["env"]["WINEDLLOVERRIDES"] = ""
93
+ for selection in selected:
94
+ # noinspection PyTypeChecker
95
+ params["env"]["WINEDLLOVERRIDES"] += possible_overrides[selection].value
96
+
97
+ # Set language locale
98
+ match get_selection(
99
+ "Select locale:",
100
+ values_to_elements(["Default", "Japanese"]),
101
+ None,
102
+ ).value:
103
+ case "Default":
104
+ pass
105
+ case "Japanese":
106
+ params["env"]["LANG"] = "ja_JP.UTF8"
107
+
108
+ # Input executable launch args
109
+ launch_args: list[str] = input(
110
+ "Enter executable options, separated by spaces:\n? "
111
+ ).split()
112
+ params["launch_args"] = launch_args
113
+
114
+ # Select executable name
115
+ files: list[str] = [
116
+ file
117
+ for file in os.listdir(os.getcwd())
118
+ if os.path.isfile(os.path.join(os.getcwd(), file))
119
+ ]
120
+ executable_name: str = get_selection(
121
+ "Select game executable:", values_to_elements(files), None
122
+ ).value
123
+ params["exe"] = executable_name
124
+
125
+ try:
126
+ _write(params)
127
+ print(f"Configuration file {CONFIG_NAME} created at {os.getcwd()}.")
128
+ print(f"Use by running umu-commander run.")
129
+ if not selected_umu_latest:
130
+ tracking.track(proton, False)
131
+ except:
132
+ print("Could not create configuration file.")
133
+
134
+
135
+ def run():
136
+ with open(CONFIG_NAME, "rb") as toml_file:
137
+ # noinspection PyTypeChecker
138
+ config = tomllib.load(toml_file)
139
+
140
+ if not os.path.exists(config["umu"]["prefix"]):
141
+ os.mkdir(config["umu"]["prefix"])
142
+
143
+ os.environ.update(config.get("env", {}))
144
+ subprocess.run(
145
+ args=["umu-run", "--config", CONFIG_NAME],
146
+ env=os.environ,
147
+ )
umu_commander/util.py ADDED
@@ -0,0 +1,94 @@
1
+ from umu_commander.classes import Group
2
+ from umu_commander.configuration import *
3
+
4
+
5
+ def values_to_elements(values: list[str]) -> list[Element]:
6
+ return [Element(value=value) for value in values]
7
+
8
+
9
+ def _selection_set_valid(
10
+ selection_elements: list[Element] | None, selection_groups: list[Group] | None
11
+ ):
12
+ if (selection_elements is None or len(selection_elements) == 0) and (
13
+ len(selection_groups) == 0
14
+ or all([len(group.elements) == 0 for group in selection_groups])
15
+ ):
16
+ return False
17
+ else:
18
+ return True
19
+
20
+
21
+ def _print_selection_group(
22
+ elements: list[Element], enum_start_idx: int, tab: bool = True
23
+ ):
24
+ prefix: str = "\t" if tab else ""
25
+ print(
26
+ *[
27
+ f"{prefix}{idx}) {element.value} {element.info}"
28
+ for idx, element in enumerate(elements, start=enum_start_idx)
29
+ ],
30
+ sep="\n",
31
+ )
32
+ print("")
33
+
34
+
35
+ def _translate_index_to_selection(
36
+ selection_index: int,
37
+ selection_elements: list[Element],
38
+ selection_groups: list[Group],
39
+ ) -> Element:
40
+ len_counter: int = 0
41
+
42
+ if selection_elements is not None:
43
+ selection_groups.insert(0, Group(elements=selection_elements))
44
+
45
+ for group in selection_groups:
46
+ len_counter += len(group.elements)
47
+ if len_counter > selection_index:
48
+ break
49
+
50
+ return Element(
51
+ group.identity, group.elements[selection_index - len_counter].value, ""
52
+ )
53
+
54
+
55
+ def get_selection(
56
+ prompt: str,
57
+ selection_elements: list[Element] | None,
58
+ selection_groups: list[Group] | None,
59
+ ) -> Element:
60
+ if not _selection_set_valid(selection_elements, selection_groups):
61
+ print("Nothing to select from.")
62
+ exit(4)
63
+
64
+ if selection_groups is None:
65
+ selection_groups = []
66
+
67
+ while True:
68
+ enum_start_idx: int = 1
69
+
70
+ print(prompt)
71
+
72
+ if selection_elements is not None:
73
+ _print_selection_group(selection_elements, enum_start_idx, tab=False)
74
+ enum_start_idx += len(selection_elements)
75
+
76
+ for group_idx, group in enumerate(selection_groups):
77
+ if len(group.elements) == 0:
78
+ continue
79
+
80
+ print(group.label)
81
+ _print_selection_group(group.elements, enum_start_idx)
82
+
83
+ enum_start_idx += len(group.elements)
84
+
85
+ selection_index: str = input("? ")
86
+ print("")
87
+ if selection_index.isdecimal():
88
+ selection_index: int = int(selection_index) - 1
89
+ if enum_start_idx - 1 > selection_index >= 0:
90
+ break
91
+
92
+ return _translate_index_to_selection(
93
+ selection_index, selection_elements, selection_groups
94
+ )
@@ -0,0 +1,43 @@
1
+ Metadata-Version: 2.4
2
+ Name: umu-commander
3
+ Version: 1.4.0
4
+ Summary: umu-commander is an interactive CLI tool to help you manage umu.
5
+ Project-URL: Homepage, https://github.com/Mpaxlamitsounas/umu-commander
6
+ Project-URL: Issues, https://github.com/Mpaxlamitsounas/umu-commander/issues
7
+ Author-email: Mpaxlamitsounas <worldstudy123@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE.txt
10
+ Keywords: umu
11
+ Classifier: Operating System :: POSIX :: Linux
12
+ Classifier: Programming Language :: Python :: 3
13
+ Requires-Python: >=3.12
14
+ Description-Content-Type: text/markdown
15
+
16
+ ## umu-commander
17
+ ### umu-commander is an interactive CLI tool to help you manage Proton versions used by umu, as well as create enhanced launch configs.
18
+
19
+ Proton versions can be tracked and untracked, with the intention of being safely removable once no game depends on a specific one.\
20
+ What directories each Proton version is being used by is tracked within `tracking.json` inside your umu Proton directory by default.
21
+
22
+ Vanilla umu configuration files currently do not support setting environmental variables, this tool adds such functionality by adding an extra TOML table in the umu config itself. An example config is available under the name `example_config.toml`.
23
+
24
+ umu-commander will look for your installed Proton versions using a predetermined list of directories, if you notice some versions missing, the listed can be edited within the `configuration.py` file in the src directory.
25
+
26
+ ### Verbs
27
+ umu-commander needs one of the following verbs specified after the executable name:
28
+ * track: Adds the current directory to a specified Proton version's list of users.
29
+ * If the directory is already in another list, it will be removed from it.
30
+ * The create verb will automatically track the current directory.
31
+ * This will not update any existing configs.
32
+ * untrack: Removes the current directory from all tracking lists.
33
+ * users: Lists each Proton version's users.
34
+ * delete: Interactively deletes any Proton version in the tracking database with no users.
35
+ * This will actually remove the Proton directories, use at your own risk.
36
+ * If a Proton version has not been tracked before, it will not be removed, neither will the latest umu Proton.
37
+ * umu-commander will not delete anything without invoking this verb and receiving confirmation.
38
+ * create: Creates a custom configuration file in the current directory.
39
+ * run: Uses the config in the current directory to run the program.
40
+ * This is NOT equivalent to `umu-run --config <config_name>`, as vanilla umu configs do not support setting environment variables as of 07/2025.
41
+
42
+ ### Installation/Usage
43
+ Add umu-run to your PATH and then install with pipx by running `pipx install umu-commander`. After that you can invoke umu-commander by running `umu-commander <verb>`.
@@ -0,0 +1,14 @@
1
+ umu_commander/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ umu_commander/classes.py,sha256=-_5vgcWIwQ3btE8yVc1v8sbBKH9iAR_qqO0XogtqhK8,784
3
+ umu_commander/configuration.py,sha256=EOlXyB5xs2befhNdmUzlgWzZqUVAuOhfAEp3P7DCcPc,798
4
+ umu_commander/db.py,sha256=T_Rw2WbPi3Ig4kS1xUhfeLeH0IXm7ZIFwx_KOsrvij8,1596
5
+ umu_commander/main.py,sha256=CW8GopWCOLzWMS8_43RihRmjm_4yu0IXVGiMC7yap2k,1306
6
+ umu_commander/proton.py,sha256=KrkrBcNlRNpfwtAloUQQbThw6ioWiDgtQCqqlHYEQRg,2031
7
+ umu_commander/tracking.py,sha256=NdRarb_r65pDZXZer9Ls4uWqYhEd48qODp9hF68xpEQ,3219
8
+ umu_commander/umu_config.py,sha256=jBpIvf55QmolgHX43XM5Lmg_xUbL13zCLZMjqdAGalE,4189
9
+ umu_commander/util.py,sha256=f5YaYvyug4Tv8ydkhFy-AwEgWtVycV9-576A7h1D-dg,2644
10
+ umu_commander-1.4.0.dist-info/METADATA,sha256=r7MWhMvSQwoXcoB_2ukpOtZ5uF3gHljvJ88dc7xNF-c,2791
11
+ umu_commander-1.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
12
+ umu_commander-1.4.0.dist-info/entry_points.txt,sha256=7RGP35zAHeEojZ-sv7JIITuMeH_VVNuG2g2_SUrUnbM,58
13
+ umu_commander-1.4.0.dist-info/licenses/LICENSE.txt,sha256=yipFXBRmVZ2Q44x1q18HccPUAECBQLXAOAr21aS57uY,1071
14
+ umu_commander-1.4.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ umu-commander = umu_commander.main:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 mpaxlamitsounas
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.