Today, this simple manager for python scripts with autodetect python modules and add to description for each script with Claude AI.

See the source code:
# -*- coding: utf-8 -*-
import os
import re
import ast
import sys
import json
import hashlib
import subprocess
import tkinter as tk
from tkinter import ttk, messagebox
import tkinter.font as tkfont
DEFAULT_ROOT = "/storage/emulated/0/Download"
CATALOG_NAME = ".scripts_catalog.json"
FONT_SIZE = 10
try:
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
except NameError:
BASE_DIR = os.getcwd()
STD = getattr(sys, "stdlib_module_names", None)
if STD is None:
STD = set((
"__future__ abc argparse array ast asyncio base64 bisect bz2 calendar cmd code codecs collections "
"configparser contextlib copy csv ctypes curses dataclasses datetime decimal difflib email enum "
"fnmatch fractions ftplib functools gc getopt getpass gettext glob gzip hashlib heapq hmac html http "
"imaplib importlib inspect io itertools json locale logging lzma math mimetypes msvcrt multiprocessing "
"operator optparse os pathlib pdb pickle platform pprint profile queue random re readline sched secrets "
"select shelve shutil signal smtplib socket sqlite3 ssl stat statistics string struct subprocess sys "
"tarfile telnetlib tempfile textwrap threading time timeit tkinter traceback turtle types typing "
"unittest urllib uuid warnings weakref webbrowser winreg xml zipfile zlib"
).split())
ALIASES = {
"PIL": "Pillow", "cv2": "opencv-python", "yt_dlp": "yt-dlp", "bs4": "beautifulsoup4",
"sklearn": "scikit-learn", "yaml": "PyYAML", "dotenv": "python-dotenv", "serial": "pyserial",
"win32api": "pywin32", "win32com": "pywin32", "Crypto": "pycryptodome", "skimage": "scikit-image",
"dateutil": "python-dateutil", "OpenGL": "PyOpenGL", "bpy": "bpy (Blender)",
}
def safe(text):
return str(text).encode("utf-8", "replace").decode("utf-8")
def read_json(path):
try:
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
except Exception:
return None
def is_local(mod, full, root_folder):
root_n = os.path.normpath(root_folder)
cur = os.path.normpath(os.path.dirname(full))
while True:
is_root = (cur == root_n)
if os.path.isfile(os.path.join(cur, mod + ".py")):
return True
if not is_root and os.path.isdir(os.path.join(cur, mod)):
return True
if is_root or len(cur) <= len(root_n):
return False
cur = os.path.dirname(cur)
def analyze(full, root_folder):
try:
with open(full, "r", encoding="utf-8", errors="replace") as f:
text = f.read(300000)
except OSError:
return "Libs: nu pot citi fișierul"
mods = []
try:
for node in ast.walk(ast.parse(text)):
if isinstance(node, ast.Import):
for a in node.names:
mods.append(a.name.split(".")[0])
elif isinstance(node, ast.ImportFrom):
if node.module and node.level == 0:
mods.append(node.module.split(".")[0])
except (SyntaxError, ValueError):
pattern = r"^\s*(?:from\s+([A-Za-z_]\w*)[\w.]*\s+import\s|import\s+([A-Za-z_]\w*))"
for m in re.finditer(pattern, text, re.M):
mods.append(m.group(1) or m.group(2))
libs = []
for m in mods:
if m in STD or is_local(m, full, root_folder):
continue
name = ALIASES.get(m, m)
if name not in libs:
libs.append(name)
libs.sort(key=str.lower)
return "Libs: " + (", ".join(libs) if libs else "doar biblioteca standard")
class App:
def __init__(self, root):
self.root = root
root.title("Python Scripts Manager")
root.geometry("900x600")
self.scripts = []
self.files = {}
self.root_folder = ""
self.font = tkfont.Font(family="Arial", size=FONT_SIZE)
style = ttk.Style()
style.configure("Treeview", font=self.font, rowheight=self.font.metrics("linespace") + 14)
style.configure("Treeview.Heading", font=(self.font.actual("family"), FONT_SIZE, "bold"))
top = tk.Frame(root)
top.pack(fill="x", padx=6, pady=4)
tk.Label(top, text="Folder:").pack(side="left")
self.path_var = tk.StringVar(value=DEFAULT_ROOT)
tk.Entry(top, textvariable=self.path_var, font=self.font).pack(side="left", fill="x", expand=True, padx=4)
tk.Button(top, text="Scan", command=self.load_scripts).pack(side="left")
search = tk.Frame(root)
search.pack(fill="x", padx=6)
tk.Label(search, text="Caută:").pack(side="left")
self.search_var = tk.StringVar()
self.search_var.trace_add("write", lambda *a: self.refresh_table())
tk.Entry(search, textvariable=self.search_var, font=self.font).pack(side="left", fill="x", expand=True, padx=4)
frame = tk.Frame(root)
frame.pack(fill="both", expand=True, padx=6, pady=4)
self.tree = ttk.Treeview(frame, columns=("name", "rel", "desc"), show="headings", selectmode="browse")
self.tree.heading("name", text="Script")
self.tree.heading("rel", text="Path relativ")
self.tree.heading("desc", text="Descriere")
self.tree.column("name", width=180)
self.tree.column("rel", width=300)
self.tree.column("desc", width=300)
self.tree.tag_configure("auto", foreground="gray")
vsb = ttk.Scrollbar(frame, orient="vertical", command=self.tree.yview)
hsb = ttk.Scrollbar(frame, orient="horizontal", command=self.tree.xview)
self.tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)
self.tree.grid(row=0, column=0, sticky="nsew")
vsb.grid(row=0, column=1, sticky="ns")
hsb.grid(row=1, column=0, sticky="ew")
frame.rowconfigure(0, weight=1)
frame.columnconfigure(0, weight=1)
self.tree.bind("", lambda e: self.open_detail())
btns = tk.Frame(root)
btns.pack(fill="x", padx=6, pady=4)
tk.Button(btns, text="Run", command=self.run_script, bg="#2196F3", fg="white", width=8).pack(side="left", padx=2)
tk.Button(btns, text="Copy path", command=self.copy_path, width=8).pack(side="left", padx=2)
tk.Button(btns, text="Detalii", command=self.open_detail, width=8).pack(side="left", padx=2)
tk.Button(btns, text="Auto toate", command=self.auto_all, width=8).pack(side="left", padx=2)
self.status = tk.Label(root, text="Ready", anchor="w")
self.status.pack(fill="x", padx=6, pady=2)
self.load_scripts()
def primary_file(self):
return os.path.join(self.root_folder, CATALOG_NAME)
def fallback_file(self):
h = hashlib.md5(self.root_folder.encode("utf-8", "replace")).hexdigest()[:8]
return os.path.join(BASE_DIR, "catalog_" + h + ".json")
def save_catalog(self):
payload = {"version": 1, "root": self.root_folder, "files": self.files}
for path in (self.primary_file(), self.fallback_file()):
try:
with open(path, "w", encoding="utf-8") as f:
json.dump(payload, f, indent=2, ensure_ascii=False)
return path
except OSError:
continue
messagebox.showerror("Eroare", "Nu pot salva catalogul.")
return None
def load_scripts(self):
folder = self.path_var.get().strip()
if not os.path.isdir(folder):
messagebox.showerror("Eroare", "Folder inexistent:\n" + safe(folder))
return
self.root_folder = folder
data = read_json(self.primary_file()) or read_json(self.fallback_file()) or {}
old = data.get("files", {})
found = []
for dirpath, dirs, files in os.walk(folder):
dirs[:] = [d for d in dirs if d != "__pycache__"]
for file in files:
if file.endswith(".py"):
full = os.path.join(dirpath, file)
rel = os.path.relpath(full, folder).replace("\\", "/")
found.append((file, rel, full))
found.sort(key=lambda s: s[1].lower())
found_rels = set(s[1] for s in found)
orphans = {r: e for r, e in old.items() if r not in found_rels and e.get("desc")}
new_files = {}
for name, rel, full in found:
try:
st = os.stat(full)
size, mtime = st.st_size, int(st.st_mtime)
except OSError:
size, mtime = 0, 0
entry = old.get(rel)
if entry is None:
entry = {"desc": ""}
for orel, oe in list(orphans.items()):
if os.path.basename(orel) == name and oe.get("size") == size:
entry = {"desc": oe.get("desc", ""), "auto": oe.get("auto", "")}
del orphans[orel]
break
entry = dict(entry)
entry["size"] = size
entry["mtime"] = mtime
new_files[rel] = entry
new_files.update(orphans)
self.files = new_files
self.scripts = found
saved = self.save_catalog()
self.refresh_table()
if saved:
self.status.config(text=str(len(found)) + " scripturi | catalog: " + safe(saved))
def refresh_table(self):
q = self.search_var.get().strip().lower()
self.tree.delete(*self.tree.get_children())
shown = 0
for i, (name, rel, full) in enumerate(self.scripts):
entry = self.files.get(rel, {})
desc = entry.get("desc", "")
auto = entry.get("auto", "")
text = desc if desc else ("~ " + auto if auto else "")
if q and q not in name.lower() and q not in rel.lower() and q not in text.lower():
continue
tags = ("auto",) if (not desc and auto) else ()
self.tree.insert("", "end", iid=str(i), values=(safe(name), safe(rel), safe(text)), tags=tags)
shown += 1
if q:
self.status.config(text=str(shown) + " din " + str(len(self.scripts)) + " scripturi")
def selected(self):
sel = self.tree.selection()
if not sel:
messagebox.showinfo("Info", "Selectează un script din listă.")
return None
return int(sel[0])
def open_detail(self):
idx = self.selected()
if idx is None:
return
name, rel, full = self.scripts[idx]
entry = self.files.setdefault(rel, {})
win = tk.Toplevel(self.root)
win.title("Detalii")
win.transient(self.root)
w = max(self.root.winfo_width(), 320)
win.geometry(str(w) + "x" + str(max(self.root.winfo_height() // 2, 280)))
tk.Label(win, text=safe(rel), wraplength=w - 20, justify="left", anchor="w").pack(fill="x", padx=8, pady=4)
bar = tk.Frame(win)
bar.pack(fill="x", padx=8)
box = tk.Text(win, wrap="word", height=8, font=self.font)
box.pack(fill="both", expand=True, padx=8, pady=6)
box.insert("1.0", entry.get("desc") or entry.get("auto", ""))
def do_auto():
if box.get("1.0", "end").strip():
if not messagebox.askyesno("Auto detect", "Înlocuiesc textul din câmp?", parent=win):
return
info = analyze(full, self.root_folder)
entry["auto"] = info
box.delete("1.0", "end")
box.insert("1.0", info)
def do_update():
entry["desc"] = box.get("1.0", "end").strip()
self.save_catalog()
self.refresh_table()
win.destroy()
tk.Button(bar, text="Auto detect", command=do_auto, width=10).pack(side="left", padx=2)
tk.Button(bar, text="Update", command=do_update, bg="#4CAF50", fg="white", width=8).pack(side="left", padx=2)
tk.Button(bar, text="Închide", command=win.destroy, width=8).pack(side="left", padx=2)
win.grab_set()
def auto_all(self):
total = len(self.scripts)
if not total:
return
if not messagebox.askyesno("Auto toate", "Regenerez Libs pentru " + str(total) + " scripturi?\nDescrierile tale nu se modifică."):
return
for n, (name, rel, full) in enumerate(self.scripts, 1):
self.files.setdefault(rel, {})["auto"] = analyze(full, self.root_folder)
if n % 5 == 0:
self.status.config(text="Analiză " + str(n) + "/" + str(total))
self.root.update_idletasks()
self.save_catalog()
self.refresh_table()
self.status.config(text="Libs generat pentru " + str(total) + " scripturi")
def run_script(self):
idx = self.selected()
if idx is None:
return
full = self.scripts[idx][2]
try:
subprocess.Popen([sys.executable, full], cwd=os.path.dirname(full))
self.status.config(text="Rulat: " + safe(os.path.basename(full)))
except Exception as e:
messagebox.showerror("Eroare", "Eroare la rulare:\n" + safe(e))
def copy_path(self):
idx = self.selected()
if idx is None:
return
full = self.scripts[idx][2]
self.root.clipboard_clear()
self.root.clipboard_append(full)
self.status.config(text="Copiat: " + safe(full))
if __name__ == "__main__":
root = tk.Tk()
App(root)
root.mainloop()