Files
fegger 4b7ddcb1c1 Add daily coding summary tool for Obsidian
Integrates with Zed editor history and Git commits to track daily
coding activity. Writes idempotent `## Coding Work` sections into
Obsidian daily notes with project breakdowns and commit stats.
2026-05-26 14:52:09 +02:00

142 lines
4.0 KiB
Python

#!/usr/bin/env python3
"""Daily Coding Summary for Obsidian
Track coding activity from Zed editors and Git commits, then write
a summary into your Obsidian daily note.
Usage:
python main.py
python main.py --vault ~/Obsidian/MyVault
python main.py --code-dir ~/Code --code-dir ~/Work
python main.py --date 2026-05-20
"""
import argparse
import sys
from datetime import datetime, timezone
from pathlib import Path
from utils.config import date_range, get_vault_path
from utils.git_tracker import fetch_git_activity
from utils.obsidian_writer import write_daily_summary
from utils.zed_tracker import fetch_zed_activity
def parse_date(date_str: str) -> datetime:
"""Parse YYYY-MM-DD string into a datetime object."""
try:
dt = datetime.strptime(date_str, "%Y-%m-%d")
return dt.replace(tzinfo=timezone.utc).astimezone()
except ValueError:
raise argparse.ArgumentTypeError(
f"Invalid date format: '{date_str}'. Expected YYYY-MM-DD."
)
def main():
parser = argparse.ArgumentParser(
description="Track daily coding activity and write it to Obsidian."
)
parser.add_argument(
"--vault",
dest="vault",
default=None,
help="Path to the Obsidian vault. Overrides OBSIDIAN_VAULT env var.",
)
parser.add_argument(
"--code-dir",
dest="code_dirs",
action="append",
default=None,
help="Directory to scan for git repos (default: ~/Code). Can be given multiple times.",
)
parser.add_argument(
"--daily-folder",
dest="daily_folder",
default=None,
help="Folder inside the vault for daily notes (default: Daily Notes). Overrides OBSIDIAN_DAILY_FOLDER env var.",
)
parser.add_argument(
"--date",
dest="date",
type=parse_date,
default=None,
help="Date to summarize (YYYY-MM-DD). Defaults to today.",
)
parser.add_argument(
"--no-zed",
dest="no_zed",
action="store_true",
help="Skip Zed editor tracking.",
)
parser.add_argument(
"--no-git",
dest="no_git",
action="store_true",
help="Skip Git commit tracking.",
)
parser.add_argument(
"--dry-run",
dest="dry_run",
action="store_true",
help="Print the summary instead of writing to Obsidian.",
)
args = parser.parse_args()
try:
vault_path = get_vault_path(args.vault)
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
if args.daily_folder:
daily_folder = args.daily_folder
else:
import os
daily_folder = os.environ.get("OBSIDIAN_DAILY_FOLDER", "Daily Notes")
code_dirs = args.code_dirs
if code_dirs:
code_dirs = [Path(d).expanduser().resolve() for d in code_dirs]
else:
code_dirs = [Path.home() / "Code"]
target_date = args.date or datetime.now(timezone.utc).astimezone()
start_ts, end_ts = date_range(target_date)
zed_projects = []
git_repos = []
if not args.no_zed:
print("🔍 Reading Zed editor history…")
zed_projects = fetch_zed_activity(start_ts, end_ts, code_dirs=code_dirs)
print(f" Found {len(zed_projects)} project(s) with editor activity.")
if not args.no_git:
print("🔍 Scanning git repositories…")
git_repos = fetch_git_activity(start_ts, end_ts, code_dirs=code_dirs)
print(f" Found {len(git_repos)} repo(s) with commits today.")
if args.dry_run:
from utils.obsidian_writer import generate_markdown
print("\n--- Daily Summary ---\n")
print(generate_markdown(zed_projects, git_repos, target_date))
print("\n---------------------")
return
note_path = write_daily_summary(
vault_path=vault_path,
zed_projects=zed_projects,
git_repos=git_repos,
daily_folder=daily_folder,
date=target_date,
)
print(f"✅ Daily note updated: {note_path}")
if __name__ == "__main__":
main()