diff --git a/SKILL_TASK_MANAGER.md b/SKILL_TASK_MANAGER.md new file mode 100644 index 00000000..1c2add6e --- /dev/null +++ b/SKILL_TASK_MANAGER.md @@ -0,0 +1,18 @@ +# SKILL: Task Manager + +## Commands +- `#task`: Start a new task session. + 1. Run `python3 scripts/list_projects.py` + 2. Ask user to choose project number. + 3. Run `python3 scripts/list_tasks.py ` + 4. Ask user to choose task number (or 'new' for new task - not impl yet, ask for manual ID if needed). + 5. Run `python3 scripts/select_task.py ` + +- `#fertig`: Finish current task. + 1. Ask user for short summary of work. + 2. Run `python3 scripts/finish_task.py ""` + 3. Ask user if they want to push (`git push`). + +## Notes +- Requires `.env.notion` with `NOTION_API_KEY`. +- Stores state in `.dev_session/SESSION_INFO`. diff --git a/scripts/clawd_notion.py b/scripts/clawd_notion.py new file mode 100644 index 00000000..2d792c5f --- /dev/null +++ b/scripts/clawd_notion.py @@ -0,0 +1,89 @@ +import os +import json +import urllib.request +import urllib.error +from datetime import datetime + +# Load env +def load_env(): + paths = [".env.notion", "../.env.notion", "/app/.env.notion"] + for path in paths: + if os.path.exists(path): + with open(path) as f: + for line in f: + line = line.strip() + if line and not line.startswith('#') and '=' in line: + key, val = line.split('=', 1) + os.environ[key] = val.strip('"\'') + break + +load_env() +TOKEN = os.environ.get("NOTION_API_KEY") + +def request(method, url, data=None): + if not TOKEN: + print("ERROR: NOTION_API_KEY not found.") + return None + + headers = { + "Authorization": f"Bearer {TOKEN}", + "Content-Type": "application/json", + "Notion-Version": "2022-06-28" + } + + req = urllib.request.Request(url, headers=headers, method=method) + if data: + req.data = json.dumps(data).encode('utf-8') + + try: + with urllib.request.urlopen(req) as resp: + return json.loads(resp.read().decode('utf-8')) + except urllib.error.HTTPError as e: + print(f"HTTP Error {e.code}: {e.read().decode('utf-8')}") + return None + except Exception as e: + print(f"Request Error: {e}") + return None + +def find_db(title): + res = request("POST", "https://api.notion.com/v1/search", { + "query": title, + "filter": {"value": "database", "property": "object"} + }) + if not res: return None + for item in res.get("results", []): + if item["title"][0]["plain_text"].lower() == title.lower(): + return item["id"] + return None + +def query_db(db_id, filter_payload=None): + payload = {} + if filter_payload: payload["filter"] = filter_payload + res = request("POST", f"https://api.notion.com/v1/databases/{db_id}/query", payload) + return res.get("results", []) if res else [] + +def get_title(page): + props = page.get("properties", {}) + for key, val in props.items(): + if val["type"] == "title" and val["title"]: + return val["title"][0]["plain_text"] + return "Untitled" + +def get_status_options(db_id): + res = request("GET", f"https://api.notion.com/v1/databases/{db_id}") + if not res: return [] + props = res.get("properties", {}) + status = props.get("Status", {}).get("status", {}) + return [opt["name"] for opt in status.get("options", [])] + +def update_page(page_id, props): + return request("PATCH", f"https://api.notion.com/v1/pages/{page_id}", {"properties": props}) + +def append_blocks(page_id, blocks): + return request("PATCH", f"https://api.notion.com/v1/blocks/{page_id}/children", {"children": blocks}) + +def create_page(parent_db, props): + return request("POST", "https://api.notion.com/v1/pages", { + "parent": {"database_id": parent_db}, + "properties": props + }) diff --git a/scripts/finish_task.py b/scripts/finish_task.py new file mode 100644 index 00000000..454b33f8 --- /dev/null +++ b/scripts/finish_task.py @@ -0,0 +1,55 @@ +import clawd_notion as notion +import sys +import json +import os +import subprocess +from datetime import datetime + +SESSION_FILE = ".dev_session/SESSION_INFO" + +def main(): + if not os.path.exists(SESSION_FILE): + print("Keine aktive Session.") + return + + # Parse args manually strictly for summary + # Usage: python finish_task.py "My Summary" + summary = sys.argv[1] if len(sys.argv) > 1 else "Update" + + with open(SESSION_FILE) as f: + session = json.load(f) + + task_id = session["task_id"] + + # 1. Update Notion + # Calculate time + start = datetime.fromisoformat(session["start_time"]) + hours = (datetime.now() - start).total_seconds() / 3600 + + # Get current duration + # (Skipping read for now, just appending blocks) + + blocks = [ + { + "object": "block", + "type": "heading_2", + "heading_2": {"rich_text": [{"text": {"content": f"Update {datetime.now().strftime('%Y-%m-%d %H:%M')}"}}]} + }, + { + "object": "block", + "type": "paragraph", + "paragraph": {"rich_text": [{"text": {"content": f"Time invested: {hours:.2f}h\n\n{summary}"}}]} + } + ] + notion.append_blocks(task_id, blocks) + + # 2. Git Commit + subprocess.run(["git", "add", "."]) + subprocess.run(["git", "commit", "-m", f"[{task_id[:4]}] {summary}"]) + + # Cleanup + os.remove(SESSION_FILE) + print("Session beendet, Notion geupdated, Commited.") + +if __name__ == "__main__": + main() diff --git a/scripts/list_projects.py b/scripts/list_projects.py new file mode 100644 index 00000000..c6338bb9 --- /dev/null +++ b/scripts/list_projects.py @@ -0,0 +1,16 @@ +import clawd_notion as notion +import json + +def main(): + db_id = notion.find_db("Projects [UT]") + if not db_id: + print("Projects DB not found.") + return + + projects = notion.query_db(db_id) + print("Verfügbare Projekte:") + for i, p in enumerate(projects): + print(f"{i+1}. {notion.get_title(p)} (ID: {p['id']})") + +if __name__ == "__main__": + main() diff --git a/scripts/list_tasks.py b/scripts/list_tasks.py new file mode 100644 index 00000000..40a56d0a --- /dev/null +++ b/scripts/list_tasks.py @@ -0,0 +1,28 @@ +import clawd_notion as notion +import sys +import json + +def main(): + if len(sys.argv) < 2: + print("Usage: python list_tasks.py ") + return + + project_id = sys.argv[1] + db_id = notion.find_db("Tasks [UT]") + + tasks = notion.query_db(db_id, { + "property": "Project", + "relation": {"contains": project_id} + }) + + # Filter out done tasks if needed, or sort + # For now, just list all linked + + print(f"Tasks für Projekt {project_id}:") + for i, t in enumerate(tasks): + status = t["properties"].get("Status", {}).get("status", {}).get("name", "No Status") + if status == "Done": continue + print(f"{i+1}. [{status}] {notion.get_title(t)} (ID: {t['id']})") + +if __name__ == "__main__": + main() diff --git a/scripts/select_task.py b/scripts/select_task.py new file mode 100644 index 00000000..eb5234a6 --- /dev/null +++ b/scripts/select_task.py @@ -0,0 +1,30 @@ +import clawd_notion as notion +import sys +import json +import os +from datetime import datetime + +SESSION_FILE = ".dev_session/SESSION_INFO" + +def main(): + if len(sys.argv) < 2: + print("Usage: python select_task.py ") + return + + task_id = sys.argv[1] + + # Set status to Doing + notion.update_page(task_id, {"Status": {"status": {"name": "Doing"}}}) + + # Save Session + os.makedirs(os.path.dirname(SESSION_FILE), exist_ok=True) + with open(SESSION_FILE, "w") as f: + json.dump({ + "task_id": task_id, + "start_time": datetime.now().isoformat() + }, f) + + print(f"Session gestartet für Task {task_id}. Status auf 'Doing' gesetzt.") + +if __name__ == "__main__": + main()