diff --git a/cli.py b/cli.py index 5247888..8346916 100755 --- a/cli.py +++ b/cli.py @@ -23,7 +23,6 @@ STATUS_ICON = { } TYPE_ICON = { "resolution": "βš–οΈ", - "task": "πŸ“‹", "story": "πŸ“–", "test": "πŸ§ͺ", "issue": "πŸ“Œ", @@ -151,10 +150,56 @@ def cmd_search(args): def cmd_transition(args): - result = _request("POST", f"/tasks/{args.task_id}/transition?new_status={args.status}") + body = {} + if args.comment: + body["comment"] = args.comment + result = _request("POST", f"/tasks/{args.task_id}/transition?new_status={args.status}", body or None) print(f"Task #{result['id']} transitioned to: {result['status']}") +# ── Propose commands ────────────────────────────────────────────── + +def cmd_proposes(args): + if not args.project: + print("Error: --project is required for proposes", file=sys.stderr) + sys.exit(1) + result = _request("GET", f"/projects/{args.project}/proposes") + items = result if isinstance(result, list) else result.get("items", []) + if not items: + print(" No proposes found.") + return + for p in items: + status_icon = STATUS_ICON.get(p["status"], "❓") + feat = f" β†’ task {p['feat_task_id']}" if p.get("feat_task_id") else "" + print(f" {status_icon} πŸ’‘ {p['propose_code']} {p['title']}{feat}") + + +def cmd_propose_create(args): + data = {"title": args.title} + if args.description: + data["description"] = args.description + result = _request("POST", f"/projects/{args.project}/proposes", data) + print(f"Created propose {result['propose_code']}: {result['title']}") + + +def cmd_propose_accept(args): + result = _request("POST", f"/projects/{args.project}/proposes/{args.propose_id}/accept?milestone_id={args.milestone}") + print(f"Propose #{args.propose_id} accepted β†’ task {result.get('feat_task_id', '?')}") + + +def cmd_propose_reject(args): + data = {} + if args.reason: + data["reason"] = args.reason + result = _request("POST", f"/projects/{args.project}/proposes/{args.propose_id}/reject", data or None) + print(f"Propose #{args.propose_id} rejected") + + +def cmd_propose_reopen(args): + result = _request("POST", f"/projects/{args.project}/proposes/{args.propose_id}/reopen") + print(f"Propose #{args.propose_id} reopened") + + def cmd_stats(args): params = f"?project_id={args.project}" if args.project else "" stats = _request("GET", f"/dashboard/stats{params}") @@ -170,8 +215,13 @@ def cmd_stats(args): def cmd_milestones(args): - params = f"?project_id={args.project}" if args.project else "" - milestones = _request("GET", f"/milestones{params}") + params = [] + if args.project: + params.append(f"project_id={args.project}") + if args.status: + params.append(f"status={args.status}") + qs = f"?{'&'.join(params)}" if params else "" + milestones = _request("GET", f"/milestones{qs}") if not milestones: print(" No milestones found.") return @@ -242,7 +292,7 @@ def main(): p_tasks = sub.add_parser("tasks", aliases=["issues"], help="List tasks") p_tasks.add_argument("--project", "-p", type=int) - p_tasks.add_argument("--type", "-t", choices=["task", "story", "test", "resolution", "issue", "maintenance", "research", "review"]) + p_tasks.add_argument("--type", "-t", choices=["story", "test", "resolution", "issue", "maintenance", "research", "review"]) p_tasks.add_argument("--status", "-s", choices=["open", "pending", "undergoing", "completed", "closed"]) p_create = sub.add_parser("create-task", aliases=["create-issue"], help="Create a task") @@ -250,7 +300,7 @@ def main(): p_create.add_argument("--project", "-p", type=int, required=True) p_create.add_argument("--milestone", "-m", type=int, required=True) p_create.add_argument("--reporter", "-r", type=int, required=True) - p_create.add_argument("--type", "-t", default="task", choices=["task", "story", "test", "resolution", "issue", "maintenance", "research", "review"]) + p_create.add_argument("--type", "-t", default="issue", choices=["story", "test", "resolution", "issue", "maintenance", "research", "review"]) p_create.add_argument("--subtype") p_create.add_argument("--priority", choices=["low", "medium", "high", "critical"]) p_create.add_argument("--description", "-d") @@ -271,12 +321,14 @@ def main(): p_trans = sub.add_parser("transition", help="Transition task status") p_trans.add_argument("task_id", type=int) p_trans.add_argument("status", choices=["open", "pending", "undergoing", "completed", "closed"]) + p_trans.add_argument("--comment", "-c", help="Comment (required for undergoingβ†’completed)") p_stats = sub.add_parser("stats", help="Dashboard stats") p_stats.add_argument("--project", "-p", type=int) p_ms = sub.add_parser("milestones", help="List milestones") p_ms.add_argument("--project", "-p", type=int) + p_ms.add_argument("--status", "-s", choices=["open", "freeze", "undergoing", "completed", "closed"]) p_msp = sub.add_parser("milestone-progress", help="Show milestone progress") p_msp.add_argument("milestone_id", type=int) @@ -296,6 +348,29 @@ def main(): p_worklogs = sub.add_parser("worklogs", help="List work logs for a task") p_worklogs.add_argument("task_id", type=int) + # ── Propose subcommands ── + p_proposes = sub.add_parser("proposes", help="List proposes for a project") + p_proposes.add_argument("--project", "-p", type=int, required=True) + + p_pc = sub.add_parser("propose-create", help="Create a propose") + p_pc.add_argument("title") + p_pc.add_argument("--project", "-p", type=int, required=True) + p_pc.add_argument("--description", "-d") + + p_pa = sub.add_parser("propose-accept", help="Accept a propose into a milestone") + p_pa.add_argument("propose_id", type=int) + p_pa.add_argument("--project", "-p", type=int, required=True) + p_pa.add_argument("--milestone", "-m", type=int, required=True) + + p_pr = sub.add_parser("propose-reject", help="Reject a propose") + p_pr.add_argument("propose_id", type=int) + p_pr.add_argument("--project", "-p", type=int, required=True) + p_pr.add_argument("--reason", "-r") + + p_pro = sub.add_parser("propose-reopen", help="Reopen a rejected propose") + p_pro.add_argument("propose_id", type=int) + p_pro.add_argument("--project", "-p", type=int, required=True) + args = parser.parse_args() if not args.command: parser.print_help() @@ -320,6 +395,11 @@ def main(): "overdue": cmd_overdue, "log-time": cmd_log_time, "worklogs": cmd_worklogs, + "proposes": cmd_proposes, + "propose-create": cmd_propose_create, + "propose-accept": cmd_propose_accept, + "propose-reject": cmd_propose_reject, + "propose-reopen": cmd_propose_reopen, } cmds[args.command](args)