#!/usr/bin/env bash set -euo pipefail REPO="${PROJREPO:-$(cd "$(dirname "$0")/.." && pwd)}" LOCK="$REPO/.projects.lock" TS="$(date +%F_%H%M%S)" cd "$REPO" need() { command -v "$1" >/dev/null 2>&1 || { echo "ERROR: missing $1"; exit 1; }; } need jq need git need python3 need flock exec 9>"$LOCK" flock -n 9 || { echo "ERROR: projectsctl is already running"; exit 2; } backup() { mkdir -p "$REPO/backups" cp -a "$REPO/projects.json" "$REPO/backups/projects.json.$TS" } render() { ./tools/render_projects_index.py >/dev/null } git_sync() { git rev-parse --is-inside-work-tree >/dev/null git checkout main >/dev/null 2>&1 || true git pull --rebase --autostash >/dev/null 2>&1 || true } git_commit_push() { if git diff --quiet && git diff --cached --quiet; then echo "No changes to commit." exit 0 fi git add projects.json PROJECTS_INDEX.md git commit -m "projects: update ($TS)" >/dev/null git push origin main >/dev/null echo "OK: committed and pushed to main." } cmd="${1:-}"; shift || true case "$cmd" in add) name="${1:-}"; status="${2:-active}"; shift 2 || true repo="${1:-}"; local_path="${2:-}"; shift 2 || true notes="${*:-}" [[ -n "$name" ]] || { echo "usage: projectsctl add [status] [repo] [local_path] [notes...]"; exit 1; } git_sync backup tmp="$(mktemp)" jq --arg name "$name" --arg status "$status" --arg repo "$repo" --arg lp "$local_path" --arg notes "$notes" ' .projects |= ( . + [ {name:$name,status:$status,type:"",repo:$repo,local_path:$lp,notes:$notes} ] ) ' projects.json > "$tmp" mv "$tmp" projects.json render git_commit_push ;; set-status) name="${1:-}"; status="${2:-}" [[ -n "$name" && -n "$status" ]] || { echo "usage: projectsctl set-status "; exit 1; } git_sync backup tmp="$(mktemp)" jq --arg name "$name" --arg status "$status" ' .projects |= (map(if .name==$name then .status=$status else . end)) ' projects.json > "$tmp" mv "$tmp" projects.json render git_commit_push ;; update-notes) name="${1:-}"; shift || true notes="${*:-}" [[ -n "$name" && -n "$notes" ]] || { echo "usage: projectsctl update-notes "; exit 1; } git_sync backup tmp="$(mktemp)" jq --arg name "$name" --arg notes "$notes" ' .projects |= (map(if .name==$name then .notes=$notes else . end)) ' projects.json > "$tmp" mv "$tmp" projects.json render git_commit_push ;; list) python3 - <<'PY' import json p=json.load(open("projects.json")) for pr in p.get("projects",[]): print(f"{pr.get('status',''):8} {pr.get('name','')}") PY ;; render) render echo "OK: rendered PROJECTS_INDEX.md" ;; *) echo "usage:" echo " projectsctl add [status] [repo] [local_path] [notes...]" echo " projectsctl set-status " echo " projectsctl update-notes " echo " projectsctl list" echo " projectsctl render" exit 1 ;; esac