#!/bin/bash # Sprint CLI for Gantt Board (API passthrough) # Usage: ./sprint.sh [create|list|get|update|delete|close] [options] set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck source=./lib/api_client.sh source "$SCRIPT_DIR/lib/api_client.sh" RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' log_info() { echo -e "${BLUE}i${NC} $1"; } log_success() { echo -e "${GREEN}ok${NC} $1"; } log_warning() { echo -e "${YELLOW}warn${NC} $1"; } log_error() { echo -e "${RED}error${NC} $1"; } UUID_PATTERN='^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' check_dependencies() { if ! command -v jq >/dev/null 2>&1; then log_error "jq is required. Install with: brew install jq" exit 1 fi } show_usage() { cat << 'USAGE' Sprint CLI for Gantt Board USAGE: ./sprint.sh [COMMAND] [OPTIONS] COMMANDS: create Create a new sprint list List all sprints get Get a specific sprint update Update a sprint delete Delete a sprint close Mark sprint completed CREATE OPTIONS: --name "Name" Sprint name (required) --goal "Goal" Sprint goal --start-date "YYYY-MM-DD" Start date --end-date "YYYY-MM-DD" End date LIST OPTIONS: --status Filter by status --active Show sprints in progress for today (start <= today <= end) --json Output as JSON USAGE } resolve_sprint_id() { local identifier="$1" if [[ "$identifier" =~ $UUID_PATTERN ]]; then echo "$identifier" return 0 fi local response response=$(api_call GET "/sprints") local sprint_id sprint_id=$(echo "$response" | jq -r --arg q "$identifier" ' .sprints | map(select((.name // "") | ascii_downcase | contains($q | ascii_downcase))) | .[0].id // empty ') if [[ -n "$sprint_id" ]]; then echo "$sprint_id" return 0 fi log_error "Sprint '$identifier' not found" return 1 } cmd_create() { local name="" local goal="" local start_date="" local end_date="" while [[ $# -gt 0 ]]; do case "${1:-}" in --name) name="${2:-}"; shift 2 ;; --goal) goal="${2:-}"; shift 2 ;; --start-date) start_date="${2:-}"; shift 2 ;; --end-date) end_date="${2:-}"; shift 2 ;; *) shift ;; esac done if [[ -z "$name" ]]; then log_error "Name is required (use --name)" exit 1 fi local payload payload=$(jq -n \ --arg name "$name" \ --arg goal "$goal" \ --arg startDate "$start_date" \ --arg endDate "$end_date" \ '{ name: $name, goal: (if $goal == "" then null else $goal end), startDate: (if $startDate == "" then null else $startDate end), endDate: (if $endDate == "" then null else $endDate end) }') log_info "Creating sprint..." api_call POST "/sprints" "$payload" | jq . } cmd_list() { local filter_status="" local active_only=false local output_json=false while [[ $# -gt 0 ]]; do case "${1:-}" in --status) filter_status="${2:-}"; shift 2 ;; --active) active_only=true; shift ;; --json) output_json=true; shift ;; *) shift ;; esac done local endpoint="/sprints" local query_params=() if [[ -n "$filter_status" ]]; then query_params+=("status=$(urlencode "$filter_status")") fi if [[ "$active_only" == true ]]; then query_params+=("inProgress=true") fi if [[ ${#query_params[@]} -gt 0 ]]; then endpoint+="?$(IFS='&'; echo "${query_params[*]}")" fi local response response=$(api_call GET "$endpoint") local sprints_json sprints_json=$(echo "$response" | jq '.sprints') if [[ "$output_json" == true ]]; then echo "$sprints_json" | jq . return fi local count count=$(echo "$sprints_json" | jq 'length') log_success "Found $count sprint(s)" printf "%-36s %-25s %-10s %-12s %-12s\n" "ID" "NAME" "STATUS" "START" "END" printf "%-36s %-25s %-10s %-12s %-12s\n" "------------------------------------" "-------------------------" "----------" "------------" "------------" echo "$sprints_json" | jq -r '.[] | [.id, (.name // "" | tostring | .[0:23]), (.status // "N/A"), (.start_date // .startDate // "N/A"), (.end_date // .endDate // "N/A")] | @tsv' \ | while IFS=$'\t' read -r id name status start end; do printf "%-36s %-25s %-10s %-12s %-12s\n" "$id" "$name" "$status" "$start" "$end" done } cmd_get() { local identifier="${1:-}" if [[ -z "$identifier" ]]; then log_error "Sprint ID or name required" exit 1 fi local sprint_id sprint_id=$(resolve_sprint_id "$identifier") log_info "Fetching sprint $sprint_id..." api_call GET "/sprints/$sprint_id" | jq '.sprint' } cmd_update() { local identifier="${1:-}" shift || true if [[ -z "$identifier" ]]; then log_error "Sprint ID or name required" exit 1 fi local sprint_id sprint_id=$(resolve_sprint_id "$identifier") local updates='{}' while [[ $# -gt 0 ]]; do case "${1:-}" in --name) updates=$(echo "$updates" | jq --arg v "${2:-}" '. + {name: $v}'); shift 2 ;; --goal) updates=$(echo "$updates" | jq --arg v "${2:-}" '. + {goal: $v}'); shift 2 ;; --start-date) updates=$(echo "$updates" | jq --arg v "${2:-}" '. + {startDate: $v}'); shift 2 ;; --end-date) updates=$(echo "$updates" | jq --arg v "${2:-}" '. + {endDate: $v}'); shift 2 ;; *) shift ;; esac done if [[ "$updates" == "{}" ]]; then log_warning "No update fields specified" exit 0 fi local payload payload=$(echo "$updates" | jq --arg id "$sprint_id" '. + {id: $id}') log_info "Updating sprint $sprint_id..." api_call PATCH "/sprints" "$payload" | jq . } cmd_close() { local identifier="${1:-}" if [[ -z "$identifier" ]]; then log_error "Sprint ID or name required" exit 1 fi local sprint_id sprint_id=$(resolve_sprint_id "$identifier") log_info "Closing sprint $sprint_id..." api_call POST "/sprints/close" "{\"id\": \"$sprint_id\"}" | jq . } cmd_delete() { local identifier="${1:-}" if [[ -z "$identifier" ]]; then log_error "Sprint ID or name required" exit 1 fi local sprint_id sprint_id=$(resolve_sprint_id "$identifier") log_info "Deleting sprint $sprint_id..." api_call DELETE "/sprints" "{\"id\": \"$sprint_id\"}" | jq . } check_dependencies COMMAND="${1:-}" shift || true case "$COMMAND" in create) cmd_create "$@" ;; list) cmd_list "$@" ;; get) cmd_get "$@" ;; update) cmd_update "$@" ;; close) cmd_close "$@" ;; delete) cmd_delete "$@" ;; help|--help|-h|"") show_usage ;; *) log_error "Unknown command: $COMMAND" show_usage exit 1 ;; esac