package commands import ( "bytes" "encoding/json" "fmt" "strings" "git.hangman-lab.top/zhi/HarborForge.Cli/internal/client" "git.hangman-lab.top/zhi/HarborForge.Cli/internal/config" "git.hangman-lab.top/zhi/HarborForge.Cli/internal/output" ) // --- Slot Commands --- // RunCalendarSchedule implements `hf calendar schedule [--job ] [--date ]`. func RunCalendarSchedule(args []string, tokenFlag string) { token := ResolveToken(tokenFlag) if len(args) < 3 { output.Error("usage: hf calendar schedule [--job ] [--date ] [--priority <0-99>]") } slotType := args[0] scheduledAt := args[1] estimatedDuration := args[2] date, jobCode, priority := "", "", "" remaining := args[3:] for i := 0; i < len(remaining); i++ { switch remaining[i] { case "--date": if i+1 >= len(remaining) { output.Error("--date requires a value") } i++ date = remaining[i] case "--job": if i+1 >= len(remaining) { output.Error("--job requires a value") } i++ jobCode = remaining[i] case "--priority": if i+1 >= len(remaining) { output.Error("--priority requires a value") } i++ priority = remaining[i] default: output.Errorf("unknown flag: %s", remaining[i]) } } payload := map[string]interface{}{ "slot_type": slotType, "scheduled_at": scheduledAt, "estimated_duration": estimatedDuration, } if date != "" { payload["date"] = date } if priority != "" { payload["priority"] = priority } if jobCode != "" { payload["event_type"] = "job" payload["event_data"] = map[string]interface{}{"type": "Task", "code": jobCode} } body, err := json.Marshal(payload) if err != nil { output.Errorf("cannot marshal payload: %v", err) } cfg, err := config.Load() if err != nil { output.Errorf("config error: %v", err) } c := client.New(cfg.BaseURL, token) data, err := c.Post("/calendar/slots", bytes.NewReader(body)) if err != nil { output.Errorf("failed to schedule slot: %v", err) } if output.JSONMode { var raw json.RawMessage if err := json.Unmarshal(data, &raw); err != nil { output.Errorf("invalid JSON response: %v", err) } output.PrintJSON(raw) return } // Check for warnings var resp map[string]interface{} if err := json.Unmarshal(data, &resp); err == nil { if ws, ok := resp["warnings"]; ok { if warnings, ok := ws.([]interface{}); ok && len(warnings) > 0 { fmt.Println("⚠️ Workload warnings:") for _, w := range warnings { if wm, ok := w.(map[string]interface{}); ok { if msg, ok := wm["message"].(string); ok { fmt.Printf(" - %s\n", msg) } } } } } } fmt.Printf("slot scheduled: %s at %s (%s min)\n", slotType, scheduledAt, estimatedDuration) } // RunCalendarShow implements `hf calendar show [--date ]`. func RunCalendarShow(args []string, tokenFlag string) { token := ResolveToken(tokenFlag) date := "" for i := 0; i < len(args); i++ { switch args[i] { case "--date": if i+1 >= len(args) { output.Error("--date requires a value") } i++ date = args[i] default: output.Errorf("unknown flag: %s", args[i]) } } cfg, err := config.Load() if err != nil { output.Errorf("config error: %v", err) } c := client.New(cfg.BaseURL, token) path := "/calendar/day" if date != "" { path += "?date=" + date } data, err := c.Get(path) if err != nil { output.Errorf("failed to show calendar: %v", err) } if output.JSONMode { var raw json.RawMessage if err := json.Unmarshal(data, &raw); err != nil { output.Errorf("invalid JSON response: %v", err) } output.PrintJSON(raw) return } var resp struct { Slots []map[string]interface{} `json:"slots"` } if err := json.Unmarshal(data, &resp); err != nil { output.Errorf("cannot parse calendar: %v", err) } slots := resp.Slots if len(slots) == 0 { if date != "" { fmt.Printf("No slots for %s\n", date) } else { fmt.Println("No slots for today") } return } headers := []string{"ID", "TIME", "TYPE", "DURATION", "PRIORITY", "STATUS", "EVENT"} var rows [][]string for _, s := range slots { slotID := fmt.Sprintf("%v", s["slot_id"]) scheduled := fmt.Sprintf("%v", s["scheduled_at"]) slotType := fmt.Sprintf("%v", s["slot_type"]) dur := fmt.Sprintf("%v min", s["estimated_duration"]) pri := fmt.Sprintf("%v", s["priority"]) status := fmt.Sprintf("%v", s["status"]) event := "" if et, ok := s["event_type"]; ok && et != nil { event = fmt.Sprintf("%v", et) } if ed, ok := s["event_data"]; ok && ed != nil { if edm, ok := ed.(map[string]interface{}); ok { if code, ok := edm["code"]; ok { event += " " + fmt.Sprintf("%v", code) } if ev, ok := edm["event"]; ok { event += " " + fmt.Sprintf("%v", ev) } } } if isVirt, ok := s["is_virtual"]; ok && isVirt == true { slotID += " (plan)" } rows = append(rows, []string{slotID, scheduled, slotType, dur, pri, status, strings.TrimSpace(event)}) } output.PrintTable(headers, rows) } // RunCalendarEdit implements `hf calendar edit [--date ] [flags]`. func RunCalendarEdit(args []string, tokenFlag string) { token := ResolveToken(tokenFlag) if len(args) < 1 { output.Error("usage: hf calendar edit [--date ] [--slot-type ] [--estimated-duration ] [--scheduled-at ] [--job ]") } slotID := args[0] date, slotType, duration, scheduledAt, jobCode := "", "", "", "", "" for i := 1; i < len(args); i++ { switch args[i] { case "--date": if i+1 >= len(args) { output.Error("--date requires a value") } i++ date = args[i] case "--slot-type": if i+1 >= len(args) { output.Error("--slot-type requires a value") } i++ slotType = args[i] case "--estimated-duration": if i+1 >= len(args) { output.Error("--estimated-duration requires a value") } i++ duration = args[i] case "--scheduled-at": if i+1 >= len(args) { output.Error("--scheduled-at requires a value") } i++ scheduledAt = args[i] case "--job": if i+1 >= len(args) { output.Error("--job requires a value") } i++ jobCode = args[i] default: output.Errorf("unknown flag: %s", args[i]) } } payload := make(map[string]interface{}) if slotType != "" { payload["slot_type"] = slotType } if duration != "" { payload["estimated_duration"] = duration } if scheduledAt != "" { payload["scheduled_at"] = scheduledAt } if jobCode != "" { payload["event_type"] = "job" payload["event_data"] = map[string]interface{}{"type": "Task", "code": jobCode} } body, err := json.Marshal(payload) if err != nil { output.Errorf("cannot marshal payload: %v", err) } cfg, err := config.Load() if err != nil { output.Errorf("config error: %v", err) } c := client.New(cfg.BaseURL, token) path := "/calendar/slots/" + slotID if strings.HasPrefix(slotID, "plan-") { path = "/calendar/slots/virtual/" + slotID } _ = date // kept for CLI compatibility; backend identifies virtual slots via slot-id data, err := c.Patch(path, bytes.NewReader(body)) if err != nil { output.Errorf("failed to edit slot: %v", err) } if output.JSONMode { var raw json.RawMessage if err := json.Unmarshal(data, &raw); err != nil { output.Errorf("invalid JSON response: %v", err) } output.PrintJSON(raw) return } var resp map[string]interface{} if err := json.Unmarshal(data, &resp); err == nil { if ws, ok := resp["warnings"]; ok { if warnings, ok := ws.([]interface{}); ok && len(warnings) > 0 { fmt.Println("⚠️ Workload warnings:") for _, w := range warnings { if wm, ok := w.(map[string]interface{}); ok { if msg, ok := wm["message"].(string); ok { fmt.Printf(" - %s\n", msg) } } } } } } fmt.Printf("slot edited: %s\n", slotID) } // RunCalendarCancel implements `hf calendar cancel [--date ] `. func RunCalendarCancel(args []string, tokenFlag string) { token := ResolveToken(tokenFlag) if len(args) < 1 { output.Error("usage: hf calendar cancel [--date ]") } slotID := args[0] date := "" for i := 1; i < len(args); i++ { switch args[i] { case "--date": if i+1 >= len(args) { output.Error("--date requires a value") } i++ date = args[i] default: output.Errorf("unknown flag: %s", args[i]) } } cfg, err := config.Load() if err != nil { output.Errorf("config error: %v", err) } c := client.New(cfg.BaseURL, token) path := "/calendar/slots/" + slotID + "/cancel" if strings.HasPrefix(slotID, "plan-") { path = "/calendar/slots/virtual/" + slotID + "/cancel" } _ = date // kept for CLI compatibility; backend identifies virtual slots via slot-id _, err = c.Post(path, nil) if err != nil { output.Errorf("failed to cancel slot: %v", err) } fmt.Printf("slot cancelled: %s\n", slotID) } // RunCalendarDateList implements `hf calendar date-list`. func RunCalendarDateList(args []string, tokenFlag string) { token := ResolveToken(tokenFlag) cfg, err := config.Load() if err != nil { output.Errorf("config error: %v", err) } c := client.New(cfg.BaseURL, token) data, err := c.Get("/calendar/dates") if err != nil { output.Errorf("failed to list dates: %v", err) } if output.JSONMode { var raw json.RawMessage if err := json.Unmarshal(data, &raw); err != nil { output.Errorf("invalid JSON response: %v", err) } output.PrintJSON(raw) return } var resp struct { Dates []string `json:"dates"` } if err := json.Unmarshal(data, &resp); err != nil { output.Errorf("cannot parse dates: %v", err) } dates := resp.Dates if len(dates) == 0 { fmt.Println("No future dates with materialized slots") return } for _, d := range dates { fmt.Println(d) } } // --- Plan Commands --- // RunCalendarPlanSchedule implements `hf calendar plan-schedule --at [--on-day ] [--on-week <1-4>] [--on-month ]`. func RunCalendarPlanSchedule(args []string, tokenFlag string) { token := ResolveToken(tokenFlag) if len(args) < 2 { output.Error("usage: hf calendar plan-schedule --at [--on-day ] [--on-week <1-4>] [--on-month ]") } slotType := args[0] duration := args[1] atTime, onDay, onWeek, onMonth := "", "", "", "" for i := 2; i < len(args); i++ { switch args[i] { case "--at": if i+1 >= len(args) { output.Error("--at requires a value") } i++ atTime = args[i] case "--on-day": if i+1 >= len(args) { output.Error("--on-day requires a value") } i++ onDay = args[i] case "--on-week": if i+1 >= len(args) { output.Error("--on-week requires a value") } i++ onWeek = args[i] case "--on-month": if i+1 >= len(args) { output.Error("--on-month requires a value") } i++ onMonth = args[i] default: output.Errorf("unknown flag: %s", args[i]) } } if atTime == "" { output.Error("--at is required") } payload := map[string]interface{}{ "slot_type": slotType, "estimated_duration": duration, "at_time": atTime, } if onDay != "" { payload["on_day"] = onDay } if onWeek != "" { payload["on_week"] = onWeek } if onMonth != "" { payload["on_month"] = onMonth } body, err := json.Marshal(payload) if err != nil { output.Errorf("cannot marshal payload: %v", err) } cfg, err := config.Load() if err != nil { output.Errorf("config error: %v", err) } c := client.New(cfg.BaseURL, token) data, err := c.Post("/calendar/plans", bytes.NewReader(body)) if err != nil { output.Errorf("failed to create plan: %v", err) } if output.JSONMode { var raw json.RawMessage if err := json.Unmarshal(data, &raw); err != nil { output.Errorf("invalid JSON response: %v", err) } output.PrintJSON(raw) return } fmt.Printf("plan created: %s at %s (%s min)\n", slotType, atTime, duration) } // RunCalendarPlanList implements `hf calendar plan-list`. func RunCalendarPlanList(args []string, tokenFlag string) { token := ResolveToken(tokenFlag) cfg, err := config.Load() if err != nil { output.Errorf("config error: %v", err) } c := client.New(cfg.BaseURL, token) data, err := c.Get("/calendar/plans") if err != nil { output.Errorf("failed to list plans: %v", err) } if output.JSONMode { var raw json.RawMessage if err := json.Unmarshal(data, &raw); err != nil { output.Errorf("invalid JSON response: %v", err) } output.PrintJSON(raw) return } var resp struct { Plans []map[string]interface{} `json:"plans"` } if err := json.Unmarshal(data, &resp); err != nil { output.Errorf("cannot parse plans: %v", err) } plans := resp.Plans if len(plans) == 0 { fmt.Println("No schedule plans") return } headers := []string{"ID", "TYPE", "AT", "ON DAY", "ON WEEK", "ON MONTH", "DURATION", "ACTIVE"} var rows [][]string for _, p := range plans { id := fmt.Sprintf("%v", p["id"]) slotType := fmt.Sprintf("%v", p["slot_type"]) at := fmt.Sprintf("%v", p["at_time"]) onDay := "" if d, ok := p["on_day"]; ok && d != nil { onDay = fmt.Sprintf("%v", d) } onWeek := "" if w, ok := p["on_week"]; ok && w != nil { onWeek = fmt.Sprintf("%v", w) } onMonth := "" if m, ok := p["on_month"]; ok && m != nil { onMonth = fmt.Sprintf("%v", m) } dur := fmt.Sprintf("%v min", p["estimated_duration"]) active := "yes" if a, ok := p["is_active"]; ok && a == false { active = "no" } rows = append(rows, []string{id, slotType, at, onDay, onWeek, onMonth, dur, active}) } output.PrintTable(headers, rows) } // RunCalendarPlanEdit implements `hf calendar plan-edit [flags]`. func RunCalendarPlanEdit(args []string, tokenFlag string) { token := ResolveToken(tokenFlag) if len(args) < 1 { output.Error("usage: hf calendar plan-edit [--at ] [--on-day ] [--on-week <1-4>] [--on-month ] [--slot-type ] [--estimated-duration ]") } planID := args[0] payload := make(map[string]interface{}) for i := 1; i < len(args); i++ { switch args[i] { case "--at": if i+1 >= len(args) { output.Error("--at requires a value") } i++ payload["at_time"] = args[i] case "--on-day": if i+1 >= len(args) { output.Error("--on-day requires a value") } i++ payload["on_day"] = args[i] case "--on-week": if i+1 >= len(args) { output.Error("--on-week requires a value") } i++ payload["on_week"] = args[i] case "--on-month": if i+1 >= len(args) { output.Error("--on-month requires a value") } i++ payload["on_month"] = args[i] case "--slot-type": if i+1 >= len(args) { output.Error("--slot-type requires a value") } i++ payload["slot_type"] = args[i] case "--estimated-duration": if i+1 >= len(args) { output.Error("--estimated-duration requires a value") } i++ payload["estimated_duration"] = args[i] default: output.Errorf("unknown flag: %s", args[i]) } } if len(payload) == 0 { output.Error("nothing to edit — provide at least one flag") } body, err := json.Marshal(payload) if err != nil { output.Errorf("cannot marshal payload: %v", err) } cfg, err := config.Load() if err != nil { output.Errorf("config error: %v", err) } c := client.New(cfg.BaseURL, token) data, err := c.Patch("/calendar/plans/"+planID, bytes.NewReader(body)) if err != nil { output.Errorf("failed to edit plan: %v", err) } if output.JSONMode { var raw json.RawMessage if err := json.Unmarshal(data, &raw); err != nil { output.Errorf("invalid JSON response: %v", err) } output.PrintJSON(raw) return } fmt.Printf("plan edited: %s\n", planID) } // RunCalendarPlanCancel implements `hf calendar plan-cancel `. func RunCalendarPlanCancel(args []string, tokenFlag string) { token := ResolveToken(tokenFlag) if len(args) < 1 { output.Error("usage: hf calendar plan-cancel ") } planID := args[0] cfg, err := config.Load() if err != nil { output.Errorf("config error: %v", err) } c := client.New(cfg.BaseURL, token) _, err = c.Post("/calendar/plans/"+planID+"/cancel", nil) if err != nil { output.Errorf("failed to cancel plan: %v", err) } fmt.Printf("plan cancelled: %s\n", planID) }