feat: Skript zur Filterung inaktiver Skills
- filter_inactive_skills.py: Filtert Skills mit use_count=0 oder last_used_at=null - Ausschluss von Media-Skills (gif-search, heartmula, songsee, youtube-content) - Ausgabe sortiert nach last_used_at (älteste zuerst) Hinweis: Die Dateien liegen im scripts/ Verzeichnis, da es dort keine semantisch passendere Location gibt. Das Skript ist für den Hermes Skill-Index gedacht.
This commit is contained in:
@@ -0,0 +1,55 @@
|
|||||||
|
# Skript zur Filterung inaktiver Skills
|
||||||
|
|
||||||
|
## Zweck
|
||||||
|
|
||||||
|
Das Skript `filter_inactive_skills.py` identifiziert Skills aus dem Hermes Skill-Index, die **nicht** genutzt wurden und **keine** Media-Skills sind.
|
||||||
|
|
||||||
|
## Kriterien
|
||||||
|
|
||||||
|
Ein Skill gilt als inaktiv, wenn **mindestens eines** der folgenden Kriterien zutrifft:
|
||||||
|
- `use_count == 0`
|
||||||
|
- `last_used_at == null` (nie genutzt)
|
||||||
|
|
||||||
|
Media-Skills (die im `~/.hermes/skills/media/`-Verzeichnis liegen) werden **ausgeschlossen**:
|
||||||
|
- gif-search
|
||||||
|
- heartmula
|
||||||
|
- songsee
|
||||||
|
- youtube-content
|
||||||
|
|
||||||
|
## Ausgabe
|
||||||
|
|
||||||
|
Die Ergebnisse werden als Liste sortiert nach `last_used_at` (älteste Nutzung zuerst) ausgegeben. Skills, die **nie** genutzt wurden (`last_used_at == null`), erscheinen am Ende.
|
||||||
|
|
||||||
|
Beispiel-Ausgabe:
|
||||||
|
```
|
||||||
|
Inaktive Skills (use_count=0 oder last_used_at=null, ohne Media-Skills): 45
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
airtable use_count= 0 last_used_at=nie
|
||||||
|
apple-notes use_count= 0 last_used_at=nie
|
||||||
|
...
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verwendung
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 filter_inactive_skills.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dateistruktur
|
||||||
|
|
||||||
|
```
|
||||||
|
t_47fb5aaa/
|
||||||
|
├── filter_inactive_skills.py # Hauptskript
|
||||||
|
└── README.md # Diese Dokumentation
|
||||||
|
```
|
||||||
|
|
||||||
|
## Datenquelle
|
||||||
|
|
||||||
|
- Skill-Metadaten: `~/.hermes/skills/.usage.json`
|
||||||
|
- Media-Skills: `~/.hermes/skills/media/`
|
||||||
|
|
||||||
|
## Historie
|
||||||
|
|
||||||
|
- 2026-07-17: Erstellt im Rahmen von Kanban-Aufgabe t_47fb5aaa
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Skript zur Filterung inaktiver Skills.
|
||||||
|
|
||||||
|
Filtert Skills aus ~/.hermes/skills/.usage.json, die:
|
||||||
|
- use_count == 0 ODER last_used_at == null
|
||||||
|
- NICHT im media/ Verzeichnis liegen (Media-Skills werden ausgeschlossen)
|
||||||
|
|
||||||
|
Ausgabe: Liste sortiert nach last_used_at (älteste zuerst), nur Skills mit use_count 0.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
def load_usage_data(path: Path) -> dict:
|
||||||
|
"""Lädt die .usage.json-Datei."""
|
||||||
|
with open(path, 'r', encoding='utf-8') as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
def get_media_skills(skills_dir: Path) -> set:
|
||||||
|
"""Ermittelt alle Media-Skills aus dem media/ Verzeichnis."""
|
||||||
|
media_path = skills_dir / 'media'
|
||||||
|
if not media_path.is_dir():
|
||||||
|
return set()
|
||||||
|
|
||||||
|
media_skills = set()
|
||||||
|
for item in media_path.iterdir():
|
||||||
|
if item.is_dir():
|
||||||
|
media_skills.add(item.name)
|
||||||
|
return media_skills
|
||||||
|
|
||||||
|
|
||||||
|
def is_media_skill(skill_name: str, media_skills: set) -> bool:
|
||||||
|
"""Prüft, ob ein Skill ein Media-Skill ist."""
|
||||||
|
return skill_name in media_skills
|
||||||
|
|
||||||
|
|
||||||
|
def parse_datetime(dt_str: str | None) -> datetime | None:
|
||||||
|
"""Parsed einen datetime-string im ISO-Format (mit timezone)."""
|
||||||
|
if dt_str is None:
|
||||||
|
return None
|
||||||
|
# Entferne den timezone-Offset für datetime.fromisoformat (Python 3.11+ supportet +00:00)
|
||||||
|
# Aber da wir nur sortieren, reicht es, den String direkt zu nutzen
|
||||||
|
try:
|
||||||
|
return datetime.fromisoformat(dt_str)
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def filter_inactive_skills(usage: dict, media_skills: set) -> list:
|
||||||
|
"""
|
||||||
|
Filtert inaktive Skills.
|
||||||
|
|
||||||
|
Kriterien:
|
||||||
|
- use_count == 0 ODER last_used_at == null
|
||||||
|
- NICHT in media_skills
|
||||||
|
|
||||||
|
Gibt Liste zurück mit (skill_name, use_count, last_used_at)
|
||||||
|
"""
|
||||||
|
inactive = []
|
||||||
|
|
||||||
|
for skill_name, data in usage.items():
|
||||||
|
if is_media_skill(skill_name, media_skills):
|
||||||
|
continue
|
||||||
|
|
||||||
|
use_count = data.get('use_count', 0)
|
||||||
|
last_used_at = data.get('last_used_at')
|
||||||
|
|
||||||
|
# Filter: use_count == 0 ODER last_used_at == null
|
||||||
|
if use_count == 0 or last_used_at is None:
|
||||||
|
inactive.append({
|
||||||
|
'name': skill_name,
|
||||||
|
'use_count': use_count,
|
||||||
|
'last_used_at': last_used_at
|
||||||
|
})
|
||||||
|
|
||||||
|
# Sortieren nach last_used_at (älteste zuerst, None am Ende)
|
||||||
|
def sort_key(item):
|
||||||
|
dt = parse_datetime(item['last_used_at'])
|
||||||
|
if dt is None:
|
||||||
|
return (1, '') # None am Ende
|
||||||
|
return (0, dt.isoformat())
|
||||||
|
|
||||||
|
inactive.sort(key=sort_key)
|
||||||
|
return inactive
|
||||||
|
|
||||||
|
|
||||||
|
def print_results(skills: list) -> None:
|
||||||
|
"""Gibt die Ergebnisse lesbar aus."""
|
||||||
|
if not skills:
|
||||||
|
print("Keine inaktiven Skills gefunden.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"Inaktive Skills (use_count=0 oder last_used_at=null, ohne Media-Skills): {len(skills)}\n")
|
||||||
|
print("-" * 80)
|
||||||
|
|
||||||
|
for skill in skills:
|
||||||
|
name = skill['name']
|
||||||
|
use_count = skill['use_count']
|
||||||
|
last_used = skill['last_used_at'] if skill['last_used_at'] else 'nie'
|
||||||
|
print(f" {name:30} use_count={use_count:3} last_used_at={last_used}")
|
||||||
|
|
||||||
|
print("-" * 80)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
skills_dir = Path.home() / '.hermes' / 'skills'
|
||||||
|
usage_path = skills_dir / '.usage.json'
|
||||||
|
|
||||||
|
if not usage_path.exists():
|
||||||
|
print(f"Fehler: {usage_path} nicht gefunden.")
|
||||||
|
return
|
||||||
|
|
||||||
|
usage = load_usage_data(usage_path)
|
||||||
|
media_skills = get_media_skills(skills_dir)
|
||||||
|
inactive_skills = filter_inactive_skills(usage, media_skills)
|
||||||
|
print_results(inactive_skills)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user