feat: notifications system, webhook retry, issue assign endpoint, CLI milestones/notifications/overdue commands

This commit is contained in:
Zhi
2026-02-23 00:11:26 +00:00
parent 8e6aec8062
commit 0a8b18729b
3 changed files with 204 additions and 0 deletions

70
cli.py
View File

@@ -145,6 +145,54 @@ def cmd_stats(args):
print(f" {t}: {c}")
def cmd_milestones(args):
params = f"?project_id={args.project}" if args.project else ""
milestones = _request("GET", f"/milestones{params}")
if not milestones:
print(" No milestones found.")
return
for m in milestones:
status_icon = "🟢" if m["status"] == "open" else ""
due = f" (due: {m['due_date'][:10]})" if m.get("due_date") else ""
print(f" {status_icon} #{m['id']} {m['title']}{due}")
def cmd_milestone_progress(args):
result = _request("GET", f"/milestones/{args.milestone_id}/progress")
bar_len = 20
filled = int(bar_len * result["progress_pct"] / 100)
bar = "" * filled + "" * (bar_len - filled)
print(f" {result['title']}")
print(f" [{bar}] {result['progress_pct']}% ({result['completed']}/{result['total_issues']})")
def cmd_notifications(args):
params = [f"user_id={args.user}"]
if args.unread:
params.append("unread_only=true")
qs = "&".join(params)
notifs = _request("GET", f"/notifications?{qs}")
if not notifs:
print(" No notifications.")
return
for n in notifs:
icon = "🔴" if not n["is_read"] else ""
print(f" {icon} [{n['type']}] {n['title']}")
def cmd_overdue(args):
params = f"?project_id={args.project}" if args.project else ""
issues = _request("GET", f"/issues/overdue{params}")
if not issues:
print(" No overdue issues! 🎉")
return
for i in issues:
due = i.get("due_date", "?")[:10] if i.get("due_date") else "?"
print(f" ⏰ #{i['id']} [{i['priority']}] {i['title']} (due: {due})")
def main():
parser = argparse.ArgumentParser(description="HarborForge CLI")
sub = parser.add_subparsers(dest="command")
@@ -201,6 +249,24 @@ def main():
p_stats = sub.add_parser("stats", help="Dashboard stats")
p_stats.add_argument("--project", "-p", type=int)
# milestones
p_ms = sub.add_parser("milestones", help="List milestones")
p_ms.add_argument("--project", "-p", type=int)
# milestone progress
p_msp = sub.add_parser("milestone-progress", help="Show milestone progress")
p_msp.add_argument("milestone_id", type=int)
# notifications
p_notif = sub.add_parser("notifications", help="List notifications")
p_notif.add_argument("--user", "-u", type=int, required=True)
p_notif.add_argument("--unread", action="store_true")
# overdue
p_overdue = sub.add_parser("overdue", help="List overdue issues")
p_overdue.add_argument("--project", "-p", type=int)
args = parser.parse_args()
if not args.command:
parser.print_help()
@@ -217,6 +283,10 @@ def main():
"search": cmd_search,
"transition": cmd_transition,
"stats": cmd_stats,
"milestones": cmd_milestones,
"milestone-progress": cmd_milestone_progress,
"notifications": cmd_notifications,
"overdue": cmd_overdue,
}
cmds[args.command](args)