#!/usr/bin/env bash # # gantt-api.sh - Helper script for Mosaic Stack Gantt/Project API queries # set -euo pipefail # Configuration from environment API_URL="${MOSAIC_API_URL:-http://localhost:3000}" WORKSPACE_ID="${MOSAIC_WORKSPACE_ID:-}" API_TOKEN="${MOSAIC_API_TOKEN:-}" # Check required environment variables if [[ -z "$WORKSPACE_ID" ]]; then echo "Error: MOSAIC_WORKSPACE_ID environment variable not set" >&2 exit 1 fi if [[ -z "$API_TOKEN" ]]; then echo "Error: MOSAIC_API_TOKEN environment variable not set" >&2 exit 1 fi # Helper function to make API requests api_request() { local method="$1" local endpoint="$2" local data="${3:-}" local url="${API_URL}${endpoint}" local curl_args=( -X "$method" -H "Content-Type: application/json" -H "X-Workspace-Id: $WORKSPACE_ID" -H "Authorization: Bearer $API_TOKEN" -s ) if [[ -n "$data" ]]; then curl_args+=(-d "$data") fi curl "${curl_args[@]}" "$url" } # List all projects list_projects() { local page="${1:-1}" local limit="${2:-50}" api_request GET "/projects?page=$page&limit=$limit" | jq . } # Get a single project with tasks get_project() { local project_id="$1" api_request GET "/projects/$project_id" | jq . } # Get tasks with optional filters get_tasks() { local project_id="${1:-}" local filters="" if [[ -n "$project_id" ]]; then filters="projectId=$project_id" fi api_request GET "/tasks?$filters" | jq . } # Get a single task get_task() { local task_id="$1" api_request GET "/tasks/$task_id" | jq . } # Get dependency chain for a task get_dependencies() { local task_id="$1" # Get the task local task_json task_json=$(get_task "$task_id") # Extract dependency IDs from metadata local dep_ids dep_ids=$(echo "$task_json" | jq -r '.metadata.dependencies // [] | .[]') if [[ -z "$dep_ids" ]]; then echo "Task has no dependencies" return fi echo "Dependencies for task: $(echo "$task_json" | jq -r '.title')" echo "" # Fetch each dependency while IFS= read -r dep_id; do if [[ -n "$dep_id" ]]; then local dep_task dep_task=$(get_task "$dep_id") echo "- $(echo "$dep_task" | jq -r '.title') [$(echo "$dep_task" | jq -r '.status')]" echo " Due: $(echo "$dep_task" | jq -r '.dueDate // "No due date"')" fi done <<< "$dep_ids" } # Calculate critical path for a project critical_path() { local project_id="$1" # Get all tasks for the project local tasks_json tasks_json=$(get_tasks "$project_id") # Use jq to build dependency graph and find longest path echo "$tasks_json" | jq -r ' .data as $tasks | # Build adjacency list ($tasks | map({ id: .id, title: .title, status: .status, dueDate: .dueDate, dependencies: (.metadata.dependencies // []) })) as $nodes | # Find tasks with no dependencies (starting points) ($nodes | map(select(.dependencies | length == 0))) as $starts | # Display structure "Critical Path Analysis\n" + "======================\n\n" + "Starting tasks (no dependencies):\n" + ($starts | map("- \(.title) [\(.status)]") | join("\n")) + "\n\nAll tasks with dependencies:\n" + ($nodes | map( select(.dependencies | length > 0) | "- \(.title) [\(.status)]\n Depends on: \(.dependencies | join(", "))" ) | join("\n")) ' } # Main command dispatcher main() { if [[ $# -lt 1 ]]; then echo "Usage: $0 [args...]" >&2 echo "" >&2 echo "Commands:" >&2 echo " projects - List all projects" >&2 echo " project - Get project details" >&2 echo " tasks [project-id] - Get tasks (optionally filtered by project)" >&2 echo " task - Get task details" >&2 echo " dependencies - Get dependency chain for task" >&2 echo " critical-path - Calculate critical path for project" >&2 exit 1 fi local command="$1" shift case "$command" in projects) list_projects "$@" ;; project) if [[ $# -lt 1 ]]; then echo "Error: project command requires project ID" >&2 exit 1 fi get_project "$1" ;; tasks) get_tasks "$@" ;; task) if [[ $# -lt 1 ]]; then echo "Error: task command requires task ID" >&2 exit 1 fi get_task "$1" ;; dependencies) if [[ $# -lt 1 ]]; then echo "Error: dependencies command requires task ID" >&2 exit 1 fi get_dependencies "$1" ;; critical-path) if [[ $# -lt 1 ]]; then echo "Error: critical-path command requires project ID" >&2 exit 1 fi critical_path "$1" ;; *) echo "Error: unknown command '$command'" >&2 exit 1 ;; esac } main "$@"