package cmd import ( "encoding/json" "fmt" "os" "text/tabwriter" "time" "github.com/spf13/cobra" "github.com/nextlevelbuilder/goclaw/internal/store" "github.com/nextlevelbuilder/goclaw/pkg/protocol" ) func cronCmd() *cobra.Command { cmd := &cobra.Command{ Use: "cron", Short: "Manage scheduled cron jobs", } cmd.AddCommand(cronListCmd()) cmd.AddCommand(cronDeleteCmd()) cmd.AddCommand(cronToggleCmd()) return cmd } func cronListCmd() *cobra.Command { var jsonOutput bool var showDisabled bool cmd := &cobra.Command{ Use: "list", Short: "List all cron jobs", Run: func(cmd *cobra.Command, args []string) { cronListRPC(showDisabled, jsonOutput) }, } cmd.Flags().BoolVar(&jsonOutput, "json", false, "output as JSON") cmd.Flags().BoolVar(&showDisabled, "all", false, "include disabled jobs") return cmd } func cronDeleteCmd() *cobra.Command { return &cobra.Command{ Use: "delete [jobId]", Short: "Delete a cron job", Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { cronDeleteRPC(args[0]) }, } } func cronToggleCmd() *cobra.Command { return &cobra.Command{ Use: "toggle [jobId] [true|false]", Short: "Enable or disable a cron job", Args: cobra.ExactArgs(2), Run: func(cmd *cobra.Command, args []string) { enabled := args[1] == "true" || args[1] == "1" || args[1] == "on" cronToggleRPC(args[0], enabled) }, } } // --- RPC implementations --- func cronListRPC(showDisabled, jsonOutput bool) { requireGateway() params, _ := json.Marshal(map[string]any{"includeDisabled": showDisabled}) resp, err := gatewayRPC(protocol.MethodCronList, params) if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } if !resp.OK { fmt.Fprintf(os.Stderr, "Failed: %s\n", resp.Error.Message) os.Exit(1) } raw, _ := json.Marshal(resp.Payload) var result struct { Jobs []store.CronJob `json:"jobs"` } if err := json.Unmarshal(raw, &result); err != nil { fmt.Fprintf(os.Stderr, "Error parsing response: %v\n", err) os.Exit(1) } printCronJobs(result.Jobs, jsonOutput) } func cronDeleteRPC(jobID string) { requireGateway() params, _ := json.Marshal(map[string]string{"jobId": jobID}) resp, err := gatewayRPC(protocol.MethodCronDelete, params) if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } if !resp.OK { fmt.Fprintf(os.Stderr, "Failed: %s\n", resp.Error.Message) os.Exit(1) } fmt.Printf("Deleted job %s\n", jobID) } func cronToggleRPC(jobID string, enabled bool) { requireGateway() params, _ := json.Marshal(map[string]any{"jobId": jobID, "enabled": enabled}) resp, err := gatewayRPC(protocol.MethodCronToggle, params) if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } if !resp.OK { fmt.Fprintf(os.Stderr, "Failed: %s\n", resp.Error.Message) os.Exit(1) } fmt.Printf("Job %s enabled=%v\n", jobID, enabled) } // --- Shared display --- func printCronJobs(jobs []store.CronJob, jsonOutput bool) { if jsonOutput { data, _ := json.MarshalIndent(jobs, "", " ") fmt.Println(string(data)) return } if len(jobs) == 0 { fmt.Println("No cron jobs configured.") return } tw := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) fmt.Fprintf(tw, "ID\tNAME\tENABLED\tSCHEDULE\tLAST RUN\n") for _, j := range jobs { schedule := j.Schedule.Kind if j.Schedule.Expr != "" { schedule = j.Schedule.Expr } else if j.Schedule.EveryMS != nil { d := time.Duration(*j.Schedule.EveryMS) * time.Millisecond schedule = "every " + d.String() } lastRun := "never" if j.State.LastRunAtMS != nil { lastRun = time.UnixMilli(*j.State.LastRunAtMS).Format(time.DateTime) } idShort := j.ID if len(idShort) > 8 { idShort = idShort[:8] } fmt.Fprintf(tw, "%s\t%s\t%v\t%s\t%s\n", idShort, j.Name, j.Enabled, schedule, lastRun) } tw.Flush() }