import json import os import subprocess import sys from pathlib import Path from urllib.request import Request, urlopen from urllib.error import HTTPError, URLError REPO_PATH = Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else Path.cwd() GITEA_BASE = os.environ.get("GITEA_BASE", "https://gitea.schaefinik.xyz") GITEA_TOKEN = os.environ.get("GITEA_TOKEN", "").strip() FILES = { "README.md": """# bitcoin-programm Lokales Projekt zur strukturierten Erfassung und Verwaltung von: - To-dos - Kalendereinträgen - Dokumentation - später: Bitcoin-Transaktionen und Auswertungen ## Ziel Das Projekt dient als zentrales Arbeitsverzeichnis für persönliche Verwaltung und spätere Automatisierung. Geplant ist: - eine einfache Eingabemaske für To-dos und Kalendertermine - CSV als einfache Zwischen- und Exportform - später ein SQLite-Backend für Auswertung und Dashboard - optional Sync mit Google Calendar oder iCloud Kalender ## Aktueller Fokus Der aktuelle Fokus liegt auf: 1. Dokumentation 2. To-do-Erfassung 3. Kalender-Erfassung 4. saubere Datenstruktur für späteren Import nach SQLite Bitcoin-Tracking kann später ergänzt werden. """, "docs/00_overview.md": """# Überblick Dieses Projekt ist ein lokales Arbeits- und Dokumentationsprojekt für persönliche Organisation. ## Zweck Das Projekt soll folgende Bereiche abdecken: - To-dos - Kalendertermine - Projektdokumentation - später Bitcoin-Tracking - später SQLite-basierte Auswertung ## Leitprinzipien 1. Daten sauber strukturieren 2. Eingaben einfach halten 3. CSV als Zwischenformat nutzbar machen 4. später SQL-Backend ergänzen 5. lokale Kontrolle behalten ## Arbeitsweise ### 1. Erfassung Neue Einträge werden über eine Eingabemaske erfasst. ### 2. Speicherung Die Daten werden in CSV abgelegt und später in SQLite überführt. ## Zielbild Am Ende soll das Projekt enthalten: - eine einfache Eingabemaske - eine saubere Dokumentation - exportierbare Daten - ein lokales Dashboard - Kalenderanbindung - To-do-Verwaltung - später Bitcoin-Verwaltung ## Status Der Fokus liegt aktuell auf Dokumentation, Kalender und To-do. Bitcoin-Tracking wird später ergänzt. """, "docs/01_requirements.md": """# Anforderungen ## Hauptziel Das Projekt soll eine einfache und verlässliche Verwaltung von To-dos und Kalenderterminen ermöglichen. ## Funktionale Anforderungen ### To-dos - To-dos sollen über eine Eingabemaske erfasst werden - Pflichtfelder müssen vorhanden sein - feste Werte sollen per Dropdown gewählt werden - die Daten sollen als CSV exportierbar sein ### Kalender - Kalendertermine sollen über eine Eingabemaske erfasst werden - Datum und Uhrzeit müssen wählbar sein - Terminarten sollen per Dropdown auswählbar sein - Wiederholungen sollen unterstützt werden - Erinnerungen sollen vorbereitet sein ### Datenhaltung - CSV als Zwischenspeicher - später SQLite als Datenbasis - klare Trennung von Rohdaten und verarbeiteten Daten ## Nicht-funktionale Anforderungen - lokale Nutzung ohne Cloud-Zwang - später synchronisierbar mit Google Calendar oder iCloud - leicht verständlich und wartbar - importfreundlich für SQLite - dokumentierbar und versionierbar ## UI-Anforderungen Die Eingabemaske soll enthalten: - Pflichtfelder - Dropdowns - Datumsauswahl - Uhrzeitauswahl - Textfelder für Notizen - klare Trennung zwischen To-do und Kalender ## Datenqualität - einheitliche Schreibweisen - feste Statuswerte - saubere Datumsformate - keine freien Sonderformate in Pflichtfeldern - keine Mischfelder für unterschiedliche Datentypen """, "docs/02_todo_calendar.md": """# To-do- und Kalenderkonzept ## Ziel Dieses Projekt soll eine einfache Möglichkeit bieten, Aufgaben und Termine zentral zu erfassen. ## To-do-Felder - id - titel - beschreibung - kategorie - prioritaet - status ## Kalender-Felder • später synchronisierbar mit Google Calendar oder iCloud • leicht verständlich und wartbar • importfreundlich für SQLite • dokumentierbar und versionierbar UI-Anforderungen Die Eingabemaske soll enthalten: • Pflichtfelder • Dropdowns • Datumsauswahl • Uhrzeitauswahl • Textfelder für Notizen • klare Trennung zwischen To-do und Kalender Datenqualität • einheitliche Schreibweisen • feste Statuswerte • saubere Datumsformate • keine freien Sonderformate in Pflichtfeldern • keine Mischfelder für unterschiedliche Datentypen """, "docs/02_todo_calendar.md": """# To-do- und Kalenderkonzept Ziel Dieses Projekt soll eine einfache Möglichkeit bieten, Aufgaben und Termine zentral zu erfassen. To-do-Felder • id • titel • beschreibung • kategorie • prioritaet • status • faellig_am • wiederholen • notiz Kalender-Felder • id • titel • datum • uhrzeit • dauer_min • kalender • kategorie • erinnerung_min • wiederholen • notiz Dropdown-Werte Priorität • Niedrig • Mittel • Hoch Status • Offen • In Arbeit Kategorie • Pflichtfelder müssen ausgefüllt sein 2. Daten als CSV speichern beschreibung TEXT, faellig_am TEXT, id INTEGER PRIMARY KEY AUTOINCREMENT, ( "requirements.txt": """streamlit return 1 ig(page_title="Bitcoin-Programm", layout="wide") st.set_page_config(page_title="Bitcoin-Programm", layout="wide") nt(kalender); faellig_am = st.date_input("Fällig am", value=date.today()) "id": new_todo_id, "faellig_am": faellig_am.isoformat(), st.markdown("### To-dos") st.download_button("Kalender CSV herunterladen", data=cal_df.to_csv(index=False).encode("utf-8"), tail = tail.split("/", 1)[1] tail = remote_url.split(":", 1)[1] prioritaetPTEXTeNOTtNULL DEFAULT 'Mittel', ("utf-8"), status TEXT NOT NULL DEFAULTt'Offen',und: {REPO_PATH}") faellig_am TEXT, wiederholen TEXT NOT NULL DEFAULT 'Nein', {REPO_PATH}") notiz TEXT, prcreated_atpTEXT"NOTENULLADEFAULT (datetime('now')), updated_at TEXT NOT NULL DEFAULTs(datetime('now'))out.strip()) de("utf-8"), ); "(clean)")) CREATE TABLE IF NOT EXISTS calendar_event ( id INTEGER PRIMARY KEY AUTOINCREMENT, retitelnTEXT NOToNULL,l = get_fetch_remote() datum TEXT NOT NULL, te_url}") utput=capture) uhrzeit TEXT NOT NULL, dauer_min INTEGER NOT NULL DEFAULT}60, kalender TEXT NOT NULL DEFAULT 'Privat', fokategorieoTEXTtNOT NULLSDEFAULT)'Sonstiges', erinnerung_min INTEGER NOT NULL DEFAULT 30, wiederholen TEXT NOT NULL DEFAULT 'Nein', notiz TEXT, pycreated_ateTEXTuNOTsNULLxDEFAULTe(datetime('now')),e", "app.py"], cwd=REPO_PATH, text=True, updated_at TEXT NOT NULL DEFAULT (datetime('now')) ); != 0: CREATE TABLE IF NOT EXISTS settings ( key TEXT PRIMARY KEY, value TEXT NOT NULL"app.py syntax check failed") ); CREATE"INDEX IFENOTEEXISTS"idx_todo_faellig_ameONitodo(faellig_am);p.py") CREATE INDEX IF NOT EXISTS idx_todo_status ON todo(status); CREATE INDEX IF NOT EXISTS idx_calendar_event_datum ON calendar_event(datum); CREATE INDEX IF NOT EXISTS idx_calendar_event_kalender ON calendar_event(kalender); """, it("commit", "-m", "docs: add todo-calendar workflow and streamlit form") "requirements.txt": """streamlit pandas """ranch = git("branch", "--show-current").stdout.strip() } APP_PY = """import streamlit as st, remote_name) import pandas as pd iffromapathlib importnPath from datetime import date, time ─ ◉ DATA_DIR = Path("data") e TODO_FILE = DATA_DIR / "todo.csv" CALENDAR_FILEz=zDATA_DIRe/a"calendar.csv" DATA_DIR.modir(parents=True, exist_ok=True)rn dein benannter Remote `bitcoin-programm` TODO_COLUMNS = [ t a d t e ─ ( "id","titel","beschreibung","kategorie","prioritaet","status", e ─ "faellig_am","wiederholen","notiz" e ]- CALENDAR_COLUMNSe=u[g / Kontext-Zusammenfassung P "id","titel","datum","uhrzeit","dauer_min","kalender","kategorie", ─ ٩ "erinnerung_min","wiederholen","notiz"o-dos, Kalender und später Bitcoin-Tracking {"title ─ ( ] e : T o e b s L ext=True, {"title bit def load_csv(pathe Path, columns: list[str]) -> pd.DataFrame: ` chaefinik.xyz/admin/bitcoin` ─ ( if path.exists(): e **Sicherdfi=spd.read_csv(path)cht im Chat, nur via `GITEA_TOKEN` in der Host-Umgebung ─ ( for col in columns: e r u s i r d r pt statt direkter e if col not in df.columns: ridf[col]c=i"" lokal ausführen, dann `git status`/Commit/Push prüfen return df[columns] n o n k r Z nfassung für ─ ( • return pd.DatmFrame(columns=columns) e E e {"title def save_csv(df: p .DataFram , path: Path) -> None: df.to_csv(path, index=False) ─ (´・def)next_id(df: pd.DataFrame)p->5int:ini none │ 94k/272k │ [████░░░░░░] 35% │ 2h 20m ─ {"title if df.empty or "id" not in df.columns: return 1 try: return int(df["id"].max()) + 1 except Exception: return 1 st.set_page_config(page_title="Bitcoin-Programm", layout="wide") st.title("Bitcoin-Programm – To-do & Kalender") tab1, tab2, tab3 = st.tabs(["To-do anlegen", "Kalendertermin anlegen", "Daten ansehen"]) with tab1: st.subheader("Neues To-do") todo_df = load_csv(TODO_FILE, TODO_COLUMNS) new_todo_id = next_id(todo_df) with st.form("todo_form", clear_on_submit=True): titel = st.text_input("Titel") beschreibung = st.text_area("Beschreibung") kategorie = st.selectbox("Kategorie", ["Allgemein", "Bitcoin", "Dokumentation", "Kalender", "Admin", "Sonstiges"]) prioritaet = st.selectbox("Priorität", ["Niedrig", "Mittel", "Hoch"]) status = st.selectbox("Status", ["Offen", "In Arbeit", "Wartet", "Erledigt"]) faellig_am = st.date_input("Fällig am", value=date.today()) wiederholen = st.selectbox("Wiederholen", ["Nein", "Täglich", "Wöchentlich", "Monatlich"]) notiz = st.text_area("Notiz") submitted = st.form_submit_button("To-do speichern") if submitted: if not titel.strip(): st.error("Bitte mindestens einen Titel eingeben.") else: new_row = pd.DataFrame([{ "id": new_todo_id, "titel": titel, "beschreibung": beschreibung, "kategorie": kategorie, "prioritaet": prioritaet, "status": status, "faellig_am": faellig_am.isoformat(), "wiederholen": wiederholen, "notiz": notiz, }]) todo_df = pd.concat([todo_df, new_row], ignore_index=True) save_csv(todo_df, TODO_FILE) st.success("To-do gespeichert.") with tab2: st.subheader("Neuen Kalendereintrag") cal_df = load_csv(CALENDAR_FILE, CALENDAR_COLUMNS) new_cal_id = next_id(cal_df) with st.form("calendar_form", clear_on_submit=True): titel = st.text_input("Titel") datum = st.date_input("Datum", value=date.today()) uhrzeit = st.time_input("Uhrzeit", value=time(9, 0)) dauer_min = st.number_input("Dauer in Minuten", min_value=5, max_value=1440, value=60, step=5) kalender = st.selectbox("Kalender", ["Arbeit", "Privat", "Bitcoin", "Todo"]) kategorie = st.selectbox("Kategorie", ["Meeting", "Aufgabe", "Erinnerung", "Termin", "Sonstiges"]) erinnerung_min = st.number_input("Erinnerung vorab in Minuten", min_value=0, max_value=10080, value=30, step=5) wiederholen = st.selectbox("Wiederholen", ["Nein", "Täglich", "Wöchentlich", "Monatlich"]) notiz = st.text_area("Notiz") submitted = st.form_submit_button("Kalendertermin speichern") if submitted: if not titel.strip(): st.error("Bitte mindestens einen Titel eingeben.") else: new_row = pd.DataFrame([{ "id": new_cal_id, "titel": titel, "datum": datum.isoformat(), "uhrzeit": uhrzeit.strftime("%H:%M"), "dauer_min": int(dauer_min), "kalender": kalender, "kategorie": kategorie, "erinnerung_min": int(erinnerung_min), "wiederholen": wiederholen, "notiz": notiz, }]) cal_df = pd.concat([cal_df, new_row], ignore_index=True) save_csv(cal_df, CALENDAR_FILE) st.success("Kalendertermin gespeichert.") with tab3: st.subheader("Gespeicherte Daten") col1, col2 = st.columns(2) with col1: st.markdown("### To-dos") todo_df = load_csv(TODO_FILE, TODO_COLUMNS) st.dataframe(todo_df, use_container_width=True) st.download_button("To-do CSV herunterladen", data=todo_df.to_csv(index=False).encode("utf-8"), file_name="todo.csv", mime="text/csv") with col2: st.markdown("### Kalender") cal_df = load_csv(CALENDAR_FILE, CALENDAR_COLUMNS) st.dataframe(cal_df, use_container_width=True) st.download_button("Kalender CSV herunterladen", data=cal_df.to_csv(index=False).encode("utf-8"), file_name="calendar.csv", mime="text/csv") """ def run(cmd, check=True, capture=True): return subprocess.run(cmd, cwd=REPO_PATH, check=check, text=True, capture_output=capture) def git(*args, check=True): return run(["git", *args], check=check) def write_file(path, content): full = REPO_PATH / path full.parent.mkdir(parents=True, exist_ok=True) full.write_text(content, encoding="utf-8") print(f"OK wrote {path}") def get_fetch_remote(): remotes = git("remote", "-v").stdout.strip().splitlines() if not remotes: raise SystemExit("No git remotes configured") for line in remotes: parts = line.split() if len(parts) >= 3 and parts[2] == "(fetch)": return parts[0], parts[1] parts = remotes[0].split() return parts[0], parts[1] def get_repo_owner_name(): _, remote_url = get_fetch_remote() if remote_url.endswith(".git"): remote_url = remote_url[:-4] if "://" in remote_url: tail = remote_url.split("://", 1)[1] tail = tail.split("/", 1)[1] else: tail = remote_url.split(":", 1)[1] owner, repo = tail.split("/", 1) return owner, repo def main(): if not REPO_PATH.exists(): raise SystemExit(f"Repo path not found: {REPO_PATH}") if not (REPO_PATH / ".git").exists(): raise SystemExit(f"No .git directory in {REPO_PATH}") print("Repo path:", REPO_PATH) print("Branch:", git("branch", "--show-current").stdout.strip()) print("Status:\n" + (git("status", "--short").stdout.strip() or "(clean)")) print("Remotes:\n" + git("remote", "-v").stdout.strip()) remote_name, remote_url = get_fetch_remote() print(f"Using remote: {remote_name} -> {remote_url}") owner, repo = get_repo_owner_name() print(f"Detected repo: {owner}/{repo}") for path, content in FILES.items(): write_file(path, content) write_file("app.py", APP_PY) py = subprocess.run([sys.executable, "-m", "py_compile", "app.py"], cwd=REPO_PATH, text=True, capture_output=True) if py.returncode != 0: print(py.stdout) print(py.stderr) raise SystemExit("app.py syntax check failed") git("add", "README.md", "docs", "sqlite", "requirements.txt", "app.py") if git("diff", "--cached", "--quiet", check=False).returncode == 0: print("No changes to commit.") else: git("commit", "-m", "docs: add todo-calendar workflow and streamlit form") print("Committed.") branch = git("branch", "--show-current").stdout.strip() git("push", remote_name, branch) print("Pushed to origin remote:", remote_name) if __name__ == "__main__": main()