12 Commits

Author SHA1 Message Date
bsjohnson20 ba18af4a70 change to folder text
Package Application with PyInstaller / build (push) Successful in 44s
2025-07-23 17:42:23 +01:00
bsjohnson20 d977b2b621 add small exception handler for unable to open folder 2025-07-23 17:41:53 +01:00
LunaChocken 26840e7a40 added timing
Package Application with PyInstaller / build (push) Successful in 27s
2025-06-09 18:02:02 +01:00
LunaChocken 153bf640be add caching
Package Application with PyInstaller / build (push) Successful in 26s
2025-06-09 17:26:44 +01:00
LunaChocken 711579e2b1 rename file
Package Application with PyInstaller / build (push) Successful in 27s
2025-06-09 16:10:22 +01:00
LunaChocken a4f0e5eece add caching
Package Application with PyInstaller / build (push) Failing after 45s
2025-06-09 16:07:29 +01:00
LunaChocken d8a49d04a3 change name of executable
Package Application with PyInstaller / build (push) Has been cancelled
2025-06-09 14:53:01 +01:00
LunaChocken 9c2073f1ad fix acf_path having "dir" indicator and not file
Package Application with PyInstaller / build (push) Successful in 27s
2025-06-08 23:02:33 +01:00
LunaChocken 4c39ab90dc Refactor game info display with TableItem class
Package Application with PyInstaller / build (push) Successful in 28s
2025-06-08 20:34:36 +01:00
LunaChocken 10e4b73b00 add copy of gitea workflow to github folder workflows
Package Application with PyInstaller / build (push) Successful in 27s
2025-06-08 20:04:39 +01:00
LunaChocken e3c35285f2 - Add all command to display All games
Package Application with PyInstaller / build (push) Successful in 26s
- Remove printing all games immediately
2025-06-08 19:30:10 +01:00
LunaChocken 02731ad9ca Disable release builder. Doesnt work
Package Application with PyInstaller / build (push) Successful in 31s
Add ctrl+C better support using try except statements
Add workshop paths
2025-06-07 23:52:05 +01:00
11 changed files with 310 additions and 96 deletions
+4 -6
View File
@@ -7,14 +7,13 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11.0'
python-version: "3.11.0"
- name: Install dependencies
run: >
@@ -24,11 +23,10 @@ jobs:
- name: Package Application with PyInstaller
run: |
pyinstaller steamPathCLI.spec
pyinstaller spath.spec
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: steamPathCLI.sh
path: dist/steamPathCLI.sh
name: spath.sh
path: dist/spath.sh
+7 -5
View File
@@ -1,7 +1,9 @@
# DISABLED
name: release
on:
workflow_dispatch:
# on:
# workflow_dispatch:
jobs:
release:
@@ -16,7 +18,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11.0'
python-version: "3.11.0"
- name: Install dependencies
run: >
python -m pip install --upgrade pip
@@ -25,9 +27,9 @@ jobs:
- name: Package Application with PyInstaller
run: |
pyinstaller steamPathCLI.spec
pyinstaller spath.spec
uses: https://gitea.com/actions/release-action@main
with:
files: |-
dist/**
api_key: '${{secrets.RELEASE_TOKEN}}'
api_key: "${{secrets.RELEASE_TOKEN}}"
+32
View File
@@ -0,0 +1,32 @@
name: Package Application with PyInstaller
on:
push:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.11.0"
- name: Install dependencies
run: >
python -m pip install --upgrade pip
pip install . # Install dependencies; modify if using requirements.txt
- name: Package Application with PyInstaller
run: |
pyinstaller spath.spec
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: spath.sh
path: dist/spath.sh
+116 -22
View File
@@ -6,47 +6,121 @@ from rich.columns import Columns
from rich.panel import Panel
import src.vdfparser as vd
from src.prompt_helper import PromptHelper
# Commandline options support
import argparse
# basic attempt at making program faster
import datetime
from src.timing import timeit
from cachier import cachier
cachier = cachier(stale_after=datetime.timedelta(days=1), cache_dir='/tmp/.cache')
class TableItem:
def __init__(self, name, data='', colour='white', type='desc'):
self.name = name
self.data = data
self.type = type
self.colour = colour
def generate_link(self):
if self.type == 'file':
return f"[{self.colour}][link=file://{self.data}]file[/link][/{self.colour}]"
elif self.type == 'dir':
return f"[{self.colour}][link=file://{self.data}]dir[/link][/{self.colour}]"
else:
return f"[{self.colour}]{self.data}[/{self.colour}]"
def __str__(self):
return f"\n[white]{self.name}[/white]: [{self.colour}]"+self.generate_link()+""
class SteamGamePathTool:
def __init__(self):
self.steam_path = vd.fetch_steam_path()
# arg mode means user input will be skipped and take all args from command line
def __init__(self, steamPath=vd.fetch_steam_path(), arg_mode_enabled=None, game_name=None, game_id=None, all=False):
self.steam_path = steamPath
self.steam_vdf_path = vd.fetch_steam_vdf()
# arg support
self.arg_mode_enabled = arg_mode_enabled
self.game_name = game_name
self.game_id = game_id
self.all = all
if self.steam_vdf_path is None:
print("Steam VDF file not found")
# Add user input to attempt to find
self.steam_vdf_path = prompt("Enter the path to the Steam VDF file: ", default=self.steam_path)
# Fetch steam's vdf file and convert to json dict
self.steam_vdf = vd.parse_vdf(self.steam_vdf_path)
# Possible other locations where steamlibrary could be found
self.steam_library_locations = vd.find_extra_locations(self.steam_vdf)
games = vd.fetchall_vdfs(self.steam_vdf)
games = self.sort_games(games)
console = Console()
game_rend = [Panel(self.get_game_content(game), expand=True) for game in games]
console.print(Columns(game_rend))
self.games = self.sort_games(games)
self.prompter = PromptHelper(games)
for library in self.steam_library_locations:
print(f"[+] Found Steam library at: {library}")
self.prompter = PromptHelper(games)
if arg_mode_enabled:
self.arg_mode()
return
while True:
self.prompt_user()
input("Press Enter to continue...")
try:
input("Press Enter to continue...")
except KeyboardInterrupt:
print("\nExiting...")
break
def prompt_user(self):
game = self.prompter.prompt_game(text="Input (game name | appid | q/quit): ")
if game is None:
def arg_mode(self):
if self.all:
console = Console()
game_rend = [Panel(self.get_game_content(game), expand=True) for game in self.games]
console.print(Columns(game_rend))
return
if self.game_id:
game = self.prompter.find_game_num(self.game_id)
elif self.game_name:
game = self.prompter.find_game_str(self.game_name)
else:
return
console = Console()
console.print(Panel(self.get_game_content(game), expand=True))
data = self.get_game_content(game)
if data:
console.print(Panel(data, expand=True))
else:
console.print("No game found")
@timeit
@cachier
def prepare_all_games(self):
game_rend = [Panel(self.get_game_content(game), expand=True) for game in self.games]
return game_rend
def print_all_games(self):
game_rend = self.prepare_all_games()
console = Console()
console.print(Columns(game_rend))
def prompt_user(self):
game = self.prompter.prompt_game(text="Input (game name | appid | all | q/quit): ")
if game is None:
return
elif game == 'fetch_all_games':
self.print_all_games()
else:
console = Console()
console.print(Panel(self.get_game_content(game), expand=True))
def sort_games(self, games):
return sorted(games, key=lambda x: x['name'])
@cachier
def get_game_content(self, game):
"""
Takes a game dictionary and returns a string that formats game information into a string renderable by the console in the rich library.
@@ -55,14 +129,34 @@ class SteamGamePathTool:
:return: string formatted for table using rich library
"""
# Spaces must be replaced with %20 otherwise they won't link properly
string = f"""[b]{game['name']}[/b]
[white]Game ID: [yellow]{game['appid']}
[white]Game Size: [red]{int(game['SizeOnDisk'])/(1024*1024)/1024:.2f} GB[/red]
[white]Game acf: [green][link=file://{game['acf_path'].replace(' ', '%20')}]file[/link][/green]
[white]Game Path: [green][link=file://{game['true_path'].replace(' ', '%20')}]dir[/link][/green]"""
if game['compatdata_path']:
string += f"\n\t[white]Compatdata dir: [blue][link=file://{game['compatdata_path'].replace(' ', '%20')}]dir[/link][/blue]"
try:
string = f"""[b]{game['name']}[/b]"""
string+=str(TableItem(name="Game ID", data=f"{game['appid']}", colour="blue",type="desc"))
string+=str(TableItem(name="Game acf", data=f"{game['acf_path'].replace(' ', '%20')}", colour="green",type="file"))
string+=str(TableItem(name="Game Path", data=f"{game['true_path'].replace(' ', '%20')}", colour="blue",type="dir"))
if game['workshop_path']:
string+=str(TableItem(name="Game Workshop Path", data=f"{game['workshop_path'].replace(' ', '%20')}", colour="blue", type="dir"))
if game["compatdata_path"]:
string+=str(TableItem(name="Game Compatdata Path",data=f"{game['compatdata_path'].replace(' ', '%20')}",colour="blue",type="dir"))
except TypeError:
return None
return string
if __name__ == "__main__":
steam_path_tool = SteamGamePathTool()
print("""Welcome to S-Path
A tool to print the locations of steam games, their respective gameID, workshop path, etc.""")
parser = argparse.ArgumentParser()
parser.add_argument('--name', help='Game name to search for')
parser.add_argument('--id', help='Game ID to search for')
# parser.add_argument('--help', help='Display help message')
parser.add_argument('--steam-path', help='Steam path to use')
parser.add_argument('--all', help='Displays all games installed', action='store_true')
args = parser.parse_args()
if args.name or args.id or args.all:
steam_path_tool = SteamGamePathTool(game_id=args.id, game_name=args.name, arg_mode_enabled=True, all=args.all)
else:
steam_path_tool = SteamGamePathTool()
+1
View File
@@ -5,6 +5,7 @@ description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"cachier>=3.1.2",
"prompt-toolkit>=3.0.51",
"pyinstaller>=6.14.0",
"rich>=14.0.0",
+5 -2
View File
@@ -1,11 +1,14 @@
# -*- mode: python ; coding: utf-8 -*-
from PyInstaller.utils.hooks import collect_data_files
datas = []
datas += collect_data_files('cachier')
a = Analysis(
['main.py'],
pathex=[],
binaries=[],
datas=[],
datas=datas,
hiddenimports=[],
hookspath=[],
hooksconfig={},
@@ -22,7 +25,7 @@ exe = EXE(
a.binaries,
a.datas,
[],
name='main',
name='spath.sh',
debug=False,
bootloader_ignore_signals=False,
strip=False,
+34 -11
View File
@@ -1,11 +1,19 @@
import prompt_toolkit as pt
from prompt_toolkit.completion import WordCompleter, FuzzyCompleter
import sys
import datetime
from src.timing import timeit
from cachier import cachier
cachier = cachier(stale_after=datetime.timedelta(days=1), cache_dir='/tmp/.cache')
@timeit
@cachier
def generate_completer(game_list):
g_list = [x['name'] for x in game_list] + [x['appid'] for x in game_list]
g_list.append("q")
g_list.append("quit")
g_list.append("all")
return FuzzyCompleter(WordCompleter(g_list))
class PromptHelper:
@@ -13,11 +21,16 @@ class PromptHelper:
self.game_list = game_list
self.completer = generate_completer(game_list)
def find_game_str(self, game_name):
@timeit
@cachier
def find_game_str(self, game_name) -> dict|None:
for game in self.game_list:
if game['name'] == game_name:
return game
def find_game_num(self, appid: str):
@timeit
@cachier
def find_game_num(self, appid: str) -> dict|None:
for game in self.game_list:
if game['appid'] == appid:
return game
@@ -25,13 +38,23 @@ class PromptHelper:
return None
def prompt_game(self, text, default="None"):
response = pt.prompt(text, completer=self.completer, complete_while_typing=True)
if response == "":
return None
elif response == "q" or response == "quit":
print("Goodbye!")
try:
response = pt.prompt(text, completer=self.completer, complete_while_typing=True)
except KeyboardInterrupt:
print("\nCtrl+C received. Exiting.")
sys.exit(0)
elif response[0].isalpha():
return self.find_game_str(response)
else:
return self.find_game_num(response)
try:
if response == "":
return None
elif response == "all":
return "fetch_all_games"
elif response == "q" or response == "quit":
print("Goodbye!")
sys.exit(0)
elif response[0].isalpha():
return self.find_game_str(response)
else:
return self.find_game_num(response)
except ValueError:
print("Invalid input")
return None
+16
View File
@@ -0,0 +1,16 @@
from functools import wraps
import time
debug = True
def timeit(func):
@wraps(func)
def timeit_wrapper(*args, **kwargs):
start_time = time.perf_counter()
result = func(*args, **kwargs)
end_time = time.perf_counter()
total_time = end_time - start_time
if debug:
print(f'Function {func.__name__} Took {total_time:.4f} seconds.')
return result
return timeit_wrapper
+25 -12
View File
@@ -1,6 +1,12 @@
import os
import vdf
from cachier import cachier
import datetime
from src.timing import timeit
cachier = cachier(stale_after=datetime.timedelta(days=1), cache_dir='/tmp/.cache')
@cachier
@timeit
def parse_vdf(steam_vdf_path) -> dict:
"""
Reads the Steam vdf file at the given path and returns the corresponding dict.
@@ -11,6 +17,7 @@ def parse_vdf(steam_vdf_path) -> dict:
vdf_data = vdf.loads(open(steam_vdf_path, 'r').read())
return vdf_data
@cachier
def fetch_ids(vdf_json: dict):
"""
Prints all Steam app IDs from the given vdf data.
@@ -27,8 +34,8 @@ def fetch_steam_path() -> str:
"""
Determines the Steam installation path by checking common locations.
Returns the path to the Steam 'steamapps' directory.
Prefers the path used by the apt installation if it exists.
Returns the path to the Steam 'steamapps' directory.
Prefers the path used by the apt installation if it exists.
Otherwise, returns the path used by the Flatpak installation.
:return: Path to the Steam 'steamapps' directory
@@ -76,11 +83,13 @@ def find_extra_locations(vdf_json):
return steam_library_locations
# Reads manifest of individual game vdfs
@cachier
def read_game_vdf(gameID: int, steam_vdf_json: dict, path, game) -> dict:
with open(os.path.join(path, game), 'r') as f:
game_vdf = vdf.loads(f.read())
return game_vdf['AppState']
@cachier
def fetchall_vdfs(steam_vdf_json: dict):
"""
Reads all Steam game manifests in the given Steam VDF data and returns a list of dictionaries containing information about each game.
@@ -92,14 +101,18 @@ def fetchall_vdfs(steam_vdf_json: dict):
for library in steam_vdf_json["libraryfolders"]:
path = steam_vdf_json['libraryfolders'][library]['path']
steamapps = os.path.join(path, "steamapps")
for game in os.listdir(steamapps):
if game.endswith(".acf"):
gameID = int(game.split('.')[0].split('_')[1])
parsed_game = read_game_vdf(gameID, steam_vdf_json, steamapps, game)
parsed_game['acf_path'] = os.path.join(steamapps, game)
parsed_game['root_steam_folder'] = path
parsed_game['true_path'] = os.path.join(steamapps, "common", parsed_game['installdir'])
parsed_game['compatdata_path'] = os.path.join(steamapps, "compatdata", str(gameID)) if os.path.exists(os.path.join(steamapps, "compatdata", str(gameID))) else None
games.append(parsed_game)
# print("Game name:", parsed_game['name'], "ID:", gameID, "Path:", parsed_game['true_path'])
try:
for game in os.listdir(steamapps):
if game.endswith(".acf"):
gameID = int(game.split('.')[0].split('_')[1])
parsed_game = read_game_vdf(gameID, steam_vdf_json, steamapps, game)
parsed_game['acf_path'] = os.path.join(steamapps, game)
parsed_game['root_steam_folder'] = path
parsed_game['true_path'] = os.path.join(steamapps, "common", parsed_game['installdir'])
parsed_game['compatdata_path'] = os.path.join(steamapps, "compatdata", str(gameID)) if os.path.exists(os.path.join(steamapps, "compatdata", str(gameID))) else None
parsed_game['workshop_path'] = os.path.join(steamapps, "workshop", "content", str(gameID)) if os.path.exists(os.path.join(steamapps, "workshop", "content", str(gameID))) else ""
games.append(parsed_game)
# print("Game name:", parsed_game['name'], "ID:", gameID, "Path:", parsed_game['true_path'])
except Exception as e:
print(f"Error processing folder {steamapps}: {e}")
return games
-38
View File
@@ -1,38 +0,0 @@
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['main.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='steamPathCLI.sh',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
Generated
+70
View File
@@ -11,6 +11,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/4d/3f/3bc3f1d83f6e4a7fcb834d3720544ca597590425be5ba9db032b2bf322a2/altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff", size = 21212 },
]
[[package]]
name = "cachier"
version = "3.1.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "portalocker" },
{ name = "watchdog" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d8/95/f17aaba6b786f968a8078c8e86f8004652ba9157d8aeb329837c85d10a43/cachier-3.1.2.tar.gz", hash = "sha256:8ef53b6ae83ba04d8864d2e1f45eb8b46eecb611d0f6a24c006e8fda074c6557", size = 27319 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d7/9f/11e07e8ffbc921d7c963568937b0290ac9204455a3f3cd866164e82f36ba/cachier-3.1.2-py3-none-any.whl", hash = "sha256:24c0fefd6aef1d38a4337590aba82f3c4bd844df18393ba93c7fd7dcc98da304", size = 24098 },
]
[[package]]
name = "macholib"
version = "1.16.3"
@@ -62,6 +75,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/55/26/d0ad8b448476d0a1e8d3ea5622dc77b916db84c6aa3cb1e1c0965af948fc/pefile-2023.2.7-py3-none-any.whl", hash = "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6", size = 71791 },
]
[[package]]
name = "portalocker"
version = "3.1.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pywin32", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ac/91/8bfe23e1f7f630f2061ef38b5225d9fda9068d6a30fcbc187951e678e630/portalocker-3.1.1.tar.gz", hash = "sha256:ec20f6dda2ad9ce89fa399a5f31f4f1495f515958f0cb7ca6543cef7bb5a749e", size = 43708 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f7/60/1974cfdd5bb770568ddc6f89f3e0df4cfdd1acffd5a609dff5e95f48c6e2/portalocker-3.1.1-py3-none-any.whl", hash = "sha256:80e984e24de292ff258a5bea0e4f3f778fff84c0ae1275dbaebc4658de4aacb3", size = 19661 },
]
[[package]]
name = "prompt-toolkit"
version = "3.0.51"
@@ -124,6 +149,22 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d3/e1/ed48c7074145898e5c5b0072e87be975c5bd6a1d0f08c27a1daa7064fca0/pyinstaller_hooks_contrib-2025.4-py3-none-any.whl", hash = "sha256:6c2d73269b4c484eb40051fc1acee0beb113c2cfb3b37437b8394faae6f0d072", size = 434451 },
]
[[package]]
name = "pywin32"
version = "310"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f7/b1/68aa2986129fb1011dabbe95f0136f44509afaf072b12b8f815905a39f33/pywin32-310-cp311-cp311-win32.whl", hash = "sha256:1e765f9564e83011a63321bb9d27ec456a0ed90d3732c4b2e312b855365ed8bd", size = 8784284 },
{ url = "https://files.pythonhosted.org/packages/b3/bd/d1592635992dd8db5bb8ace0551bc3a769de1ac8850200cfa517e72739fb/pywin32-310-cp311-cp311-win_amd64.whl", hash = "sha256:126298077a9d7c95c53823934f000599f66ec9296b09167810eb24875f32689c", size = 9520748 },
{ url = "https://files.pythonhosted.org/packages/90/b1/ac8b1ffce6603849eb45a91cf126c0fa5431f186c2e768bf56889c46f51c/pywin32-310-cp311-cp311-win_arm64.whl", hash = "sha256:19ec5fc9b1d51c4350be7bb00760ffce46e6c95eaf2f0b2f1150657b1a43c582", size = 8455941 },
{ url = "https://files.pythonhosted.org/packages/6b/ec/4fdbe47932f671d6e348474ea35ed94227fb5df56a7c30cbbb42cd396ed0/pywin32-310-cp312-cp312-win32.whl", hash = "sha256:8a75a5cc3893e83a108c05d82198880704c44bbaee4d06e442e471d3c9ea4f3d", size = 8796239 },
{ url = "https://files.pythonhosted.org/packages/e3/e5/b0627f8bb84e06991bea89ad8153a9e50ace40b2e1195d68e9dff6b03d0f/pywin32-310-cp312-cp312-win_amd64.whl", hash = "sha256:bf5c397c9a9a19a6f62f3fb821fbf36cac08f03770056711f765ec1503972060", size = 9503839 },
{ url = "https://files.pythonhosted.org/packages/1f/32/9ccf53748df72301a89713936645a664ec001abd35ecc8578beda593d37d/pywin32-310-cp312-cp312-win_arm64.whl", hash = "sha256:2349cc906eae872d0663d4d6290d13b90621eaf78964bb1578632ff20e152966", size = 8459470 },
{ url = "https://files.pythonhosted.org/packages/1c/09/9c1b978ffc4ae53999e89c19c77ba882d9fce476729f23ef55211ea1c034/pywin32-310-cp313-cp313-win32.whl", hash = "sha256:5d241a659c496ada3253cd01cfaa779b048e90ce4b2b38cd44168ad555ce74ab", size = 8794384 },
{ url = "https://files.pythonhosted.org/packages/45/3c/b4640f740ffebadd5d34df35fecba0e1cfef8fde9f3e594df91c28ad9b50/pywin32-310-cp313-cp313-win_amd64.whl", hash = "sha256:667827eb3a90208ddbdcc9e860c81bde63a135710e21e4cb3348968e4bd5249e", size = 9503039 },
{ url = "https://files.pythonhosted.org/packages/b4/f4/f785020090fb050e7fb6d34b780f2231f302609dc964672f72bfaeb59a28/pywin32-310-cp313-cp313-win_arm64.whl", hash = "sha256:e308f831de771482b7cf692a1f308f8fca701b2d8f9dde6cc440c7da17e47b33", size = 8458152 },
]
[[package]]
name = "pywin32-ctypes"
version = "0.2.3"
@@ -160,6 +201,7 @@ name = "steamgamepath"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "cachier" },
{ name = "prompt-toolkit" },
{ name = "pyinstaller" },
{ name = "rich" },
@@ -168,6 +210,7 @@ dependencies = [
[package.metadata]
requires-dist = [
{ name = "cachier", specifier = ">=3.1.2" },
{ name = "prompt-toolkit", specifier = ">=3.0.51" },
{ name = "pyinstaller", specifier = ">=6.14.0" },
{ name = "rich", specifier = ">=14.0.0" },
@@ -183,6 +226,33 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/96/60/6456b687cf55cf60020dcd01f9bc51561c3cc84f05fd8e0feb71ce60f894/vdf-3.4-py2.py3-none-any.whl", hash = "sha256:68c1a125cc49e343d535af2dd25074e9cb0908c6607f073947c4a04bbe234534", size = 10357 },
]
[[package]]
name = "watchdog"
version = "6.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393 },
{ url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392 },
{ url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019 },
{ url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471 },
{ url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449 },
{ url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054 },
{ url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480 },
{ url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451 },
{ url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057 },
{ url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079 },
{ url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078 },
{ url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076 },
{ url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077 },
{ url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078 },
{ url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077 },
{ url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078 },
{ url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065 },
{ url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070 },
{ url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067 },
]
[[package]]
name = "wcwidth"
version = "0.2.13"