4b7ddcb1c1
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.
142 lines
4.0 KiB
Python
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()
|