From c1ac7bb6f3f3245e5bf50884b0316fc402e58121 Mon Sep 17 00:00:00 2001 From: shiv Date: Sun, 9 Aug 2026 16:28:40 +0530 Subject: [PATCH 01/12] feat(workflow): add replay, approve, templates, and task-definitions subcommands --- cmd/workflow_approve.go | 63 ++++++++++++++ cmd/workflow_replay.go | 49 +++++++++++ cmd/workflow_task_definitions.go | 90 +++++++++++++++++++ cmd/workflow_templates.go | 145 +++++++++++++++++++++++++++++++ 4 files changed, 347 insertions(+) create mode 100644 cmd/workflow_approve.go create mode 100644 cmd/workflow_replay.go create mode 100644 cmd/workflow_task_definitions.go create mode 100644 cmd/workflow_templates.go diff --git a/cmd/workflow_approve.go b/cmd/workflow_approve.go new file mode 100644 index 0000000..c16fb53 --- /dev/null +++ b/cmd/workflow_approve.go @@ -0,0 +1,63 @@ +package cmd + +import ( + "github.com/nudgebee/nbctl/pkg/client" + "github.com/nudgebee/nbctl/pkg/format" + "github.com/spf13/cobra" +) + +var workflowApproveCmd = &cobra.Command{ + Use: "approve ", + Short: "Complete a human approval gate for a pending workflow execution", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + executionID := args[0] + taskID, _ := cmd.Flags().GetString("task") + reject, _ := cmd.Flags().GetBool("reject") + comments, _ := cmd.Flags().GetString("comments") + + status := "approved" + if reject { + status = "rejected" + } + + graphqlClient := client.NewClient() + + req := client.NewRequest(` + mutation CompleteWorkflowApproval($request: WorkflowCompleteApprovalRequest!) { + workflow_complete_approval(request: $request) { + status + message + } + } + `) + req.Var("request", map[string]any{ + "execution_id": executionID, + "task_id": taskID, + "status": status, + "comments": comments, + }) + + var respData struct { + WorkflowCompleteApproval struct { + Status string `json:"status"` + Message string `json:"message"` + } `json:"workflow_complete_approval"` + } + + if err := graphqlClient.Run(cmd.Context(), req, &respData); err != nil { + return err + } + + format.GetFormat().Print(respData.WorkflowCompleteApproval) + return nil + }, +} + +func init() { + workflowCmd.AddCommand(workflowApproveCmd) + workflowApproveCmd.Flags().String("task", "", "Task ID waiting for approval (required)") + workflowApproveCmd.Flags().Bool("reject", false, "Reject the approval step instead of approving") + workflowApproveCmd.Flags().String("comments", "", "Optional comments for the approval decision") + _ = workflowApproveCmd.MarkFlagRequired("task") +} diff --git a/cmd/workflow_replay.go b/cmd/workflow_replay.go new file mode 100644 index 0000000..5883a81 --- /dev/null +++ b/cmd/workflow_replay.go @@ -0,0 +1,49 @@ +package cmd + +import ( + "github.com/nudgebee/nbctl/pkg/client" + "github.com/nudgebee/nbctl/pkg/format" + "github.com/spf13/cobra" +) + +var workflowReplayCmd = &cobra.Command{ + Use: "replay ", + Short: "Replay a previous or failed workflow execution", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + executionID := args[0] + graphqlClient := client.NewClient() + + req := client.NewRequest(` + mutation ReplayWorkflowExecution($request: WorkflowRetriggerRequest!) { + workflow_replay_execution(request: $request) { + execution_id + status + message + } + } + `) + req.Var("request", map[string]any{ + "execution_id": executionID, + }) + + var respData struct { + WorkflowReplayExecution struct { + ExecutionID string `json:"execution_id"` + Status string `json:"status"` + Message string `json:"message"` + } `json:"workflow_replay_execution"` + } + + if err := graphqlClient.Run(cmd.Context(), req, &respData); err != nil { + return err + } + + format.GetFormat().Print(respData.WorkflowReplayExecution) + return nil + }, +} + +func init() { + workflowCmd.AddCommand(workflowReplayCmd) +} diff --git a/cmd/workflow_task_definitions.go b/cmd/workflow_task_definitions.go new file mode 100644 index 0000000..f87ddcc --- /dev/null +++ b/cmd/workflow_task_definitions.go @@ -0,0 +1,90 @@ +package cmd + +import ( + "strings" + + "github.com/nudgebee/nbctl/pkg/client" + "github.com/nudgebee/nbctl/pkg/format" + "github.com/spf13/cobra" +) + +var workflowTaskDefinitionsCmd = &cobra.Command{ + Use: "task-definitions", + Short: "List supported workflow task definitions and action schemas", + RunE: func(cmd *cobra.Command, args []string) error { + nameFilter, _ := cmd.Flags().GetString("name") + limit, _ := cmd.Flags().GetInt("limit") + + graphqlClient := client.NewClient() + + req := client.NewRequest(` + query ListWorkflowTaskDefinitions($params: WorkflowTaskDefinitionListRequest!) { + workflow_list_taskdefinitions(params: $params) { + tasks { + name + description + aliases + } + } + } + `) + + params := map[string]any{ + "limit": limit, + } + if nameFilter != "" { + params["name"] = nameFilter + } + req.Var("params", params) + + var respData struct { + WorkflowListTaskdefinitions struct { + Tasks []struct { + Name string `json:"name"` + Description string `json:"description"` + Aliases []string `json:"aliases"` + } `json:"tasks"` + } `json:"workflow_list_taskdefinitions"` + } + + if err := graphqlClient.Run(cmd.Context(), req, &respData); err != nil { + return err + } + + type taskRow struct { + Name string `json:"name"` + Description string `json:"description"` + Aliases string `json:"aliases"` + } + var rows []taskRow + for _, t := range respData.WorkflowListTaskdefinitions.Tasks { + aliasesStr := "-" + if len(t.Aliases) > 0 { + aliasesStr = strings.Join(t.Aliases, ", ") + } + rows = append(rows, taskRow{ + Name: t.Name, + Description: t.Description, + Aliases: aliasesStr, + }) + } + + table := format.TabularData{ + Data: rows, + Fields: []format.TableField{ + {Header: "Task Name", Field: "Name"}, + {Header: "Description", Field: "Description"}, + {Header: "Aliases", Field: "Aliases"}, + }, + } + format.GetFormat().Print(table) + + return nil + }, +} + +func init() { + workflowCmd.AddCommand(workflowTaskDefinitionsCmd) + workflowTaskDefinitionsCmd.Flags().String("name", "", "Filter task definitions by name") + workflowTaskDefinitionsCmd.Flags().Int("limit", 100, "Maximum number of task definitions to return") +} diff --git a/cmd/workflow_templates.go b/cmd/workflow_templates.go new file mode 100644 index 0000000..f9bf264 --- /dev/null +++ b/cmd/workflow_templates.go @@ -0,0 +1,145 @@ +package cmd + +import ( + "fmt" + + "github.com/nudgebee/nbctl/pkg/client" + "github.com/nudgebee/nbctl/pkg/format" + "github.com/spf13/cobra" +) + +var workflowTemplatesCmd = &cobra.Command{ + Use: "templates", + Short: "Browse and inspect pre-built workflow templates", +} + +var workflowTemplatesListCmd = &cobra.Command{ + Use: "list", + Short: "List pre-built workflow templates", + RunE: func(cmd *cobra.Command, args []string) error { + typeFlag, _ := cmd.Flags().GetString("type") + category, _ := cmd.Flags().GetString("category") + limit, _ := cmd.Flags().GetInt("limit") + + graphqlClient := client.NewClient() + + req := client.NewRequest(` + query ListWorkflowTemplates($request: WorkflowListTemplateRequest!) { + workflow_list_template(request: $request) { + total_count + templates { + id + name + description + category + is_system + status + } + } + } + `) + + input := map[string]any{ + "type": typeFlag, + "limit": limit, + } + if category != "" { + input["category"] = category + } + req.Var("request", input) + + var respData struct { + WorkflowListTemplate struct { + TotalCount int `json:"total_count"` + Templates []struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Category string `json:"category"` + IsSystem bool `json:"is_system"` + Status string `json:"status"` + } `json:"templates"` + } `json:"workflow_list_template"` + } + + if err := graphqlClient.Run(cmd.Context(), req, &respData); err != nil { + return err + } + + table := format.TabularData{ + Data: respData.WorkflowListTemplate.Templates, + Fields: []format.TableField{ + {Header: "Template ID", Field: "ID"}, + {Header: "Template Name", Field: "Name"}, + {Header: "Category", Field: "Category"}, + {Header: "System", Field: "IsSystem"}, + {Header: "Description", Field: "Description"}, + }, + } + format.GetFormat().Print(table) + + return nil + }, +} + +var workflowTemplatesGetCmd = &cobra.Command{ + Use: "get ", + Short: "Get details for a specific workflow template", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + templateID := args[0] + typeFlag, _ := cmd.Flags().GetString("type") + graphqlClient := client.NewClient() + + req := client.NewRequest(` + query GetWorkflowTemplate($request: WorkflowGetTemplateRequest!) { + workflow_get_template(request: $request) { + id + name + description + category + is_system + status + } + } + `) + req.Var("request", map[string]any{ + "type": typeFlag, + "id": templateID, + }) + + var respData struct { + WorkflowGetTemplate struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Category string `json:"category"` + IsSystem bool `json:"is_system"` + Status string `json:"status"` + } `json:"workflow_get_template"` + } + + if err := graphqlClient.Run(cmd.Context(), req, &respData); err != nil { + return err + } + + if respData.WorkflowGetTemplate.ID == "" { + return fmt.Errorf("workflow template '%s' not found", templateID) + } + + format.GetFormat().Print(respData.WorkflowGetTemplate) + return nil + }, +} + +func init() { + workflowCmd.AddCommand(workflowTemplatesCmd) + workflowTemplatesCmd.AddCommand(workflowTemplatesListCmd) + workflowTemplatesCmd.AddCommand(workflowTemplatesGetCmd) + + workflowTemplatesListCmd.Flags().String("type", "system", "Template type (system or custom)") + workflowTemplatesListCmd.Flags().String("category", "", "Filter templates by category") + workflowTemplatesListCmd.Flags().Int("limit", 50, "Maximum number of templates to return") + + workflowTemplatesGetCmd.Flags().String("type", "system", "Template type (system or custom)") +} From 8ba1c2dfdd39479c7afad544b86430d1c2b362a9 Mon Sep 17 00:00:00 2001 From: shiv Date: Sun, 9 Aug 2026 16:32:28 +0530 Subject: [PATCH 02/12] fix(workflow): validate template type flag and pre-allocate task-definitions slice --- cmd/workflow_task_definitions.go | 2 +- cmd/workflow_templates.go | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/cmd/workflow_task_definitions.go b/cmd/workflow_task_definitions.go index f87ddcc..8115ac8 100644 --- a/cmd/workflow_task_definitions.go +++ b/cmd/workflow_task_definitions.go @@ -56,7 +56,7 @@ var workflowTaskDefinitionsCmd = &cobra.Command{ Description string `json:"description"` Aliases string `json:"aliases"` } - var rows []taskRow + rows := make([]taskRow, 0, len(respData.WorkflowListTaskdefinitions.Tasks)) for _, t := range respData.WorkflowListTaskdefinitions.Tasks { aliasesStr := "-" if len(t.Aliases) > 0 { diff --git a/cmd/workflow_templates.go b/cmd/workflow_templates.go index f9bf264..7547e8b 100644 --- a/cmd/workflow_templates.go +++ b/cmd/workflow_templates.go @@ -21,6 +21,10 @@ var workflowTemplatesListCmd = &cobra.Command{ category, _ := cmd.Flags().GetString("category") limit, _ := cmd.Flags().GetInt("limit") + if typeFlag != "system" && typeFlag != "custom" && typeFlag != "all" { + return fmt.Errorf("invalid template type '%s': must be 'system', 'custom', or 'all'", typeFlag) + } + graphqlClient := client.NewClient() req := client.NewRequest(` @@ -89,6 +93,11 @@ var workflowTemplatesGetCmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { templateID := args[0] typeFlag, _ := cmd.Flags().GetString("type") + + if typeFlag != "system" && typeFlag != "custom" && typeFlag != "all" { + return fmt.Errorf("invalid template type '%s': must be 'system', 'custom', or 'all'", typeFlag) + } + graphqlClient := client.NewClient() req := client.NewRequest(` From 2e691d4f9bd23615cbb710024f2e54407c2a531e Mon Sep 17 00:00:00 2001 From: shiv Date: Sun, 9 Aug 2026 16:42:35 +0530 Subject: [PATCH 03/12] fix(workflow): make type flag optional in templates get --- cmd/workflow_templates.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/cmd/workflow_templates.go b/cmd/workflow_templates.go index 7547e8b..c3aaeb2 100644 --- a/cmd/workflow_templates.go +++ b/cmd/workflow_templates.go @@ -94,10 +94,15 @@ var workflowTemplatesGetCmd = &cobra.Command{ templateID := args[0] typeFlag, _ := cmd.Flags().GetString("type") - if typeFlag != "system" && typeFlag != "custom" && typeFlag != "all" { + if typeFlag != "" && typeFlag != "system" && typeFlag != "custom" && typeFlag != "all" { return fmt.Errorf("invalid template type '%s': must be 'system', 'custom', or 'all'", typeFlag) } + typeVal := typeFlag + if typeVal == "" { + typeVal = "all" + } + graphqlClient := client.NewClient() req := client.NewRequest(` @@ -113,7 +118,7 @@ var workflowTemplatesGetCmd = &cobra.Command{ } `) req.Var("request", map[string]any{ - "type": typeFlag, + "type": typeVal, "id": templateID, }) @@ -150,5 +155,5 @@ func init() { workflowTemplatesListCmd.Flags().String("category", "", "Filter templates by category") workflowTemplatesListCmd.Flags().Int("limit", 50, "Maximum number of templates to return") - workflowTemplatesGetCmd.Flags().String("type", "system", "Template type (system or custom)") + workflowTemplatesGetCmd.Flags().String("type", "", "Optional template type filter (system or custom)") } From ac973e27739bb0721f74c96fa83e377a4c99aa3f Mon Sep 17 00:00:00 2001 From: shiv Date: Sun, 9 Aug 2026 16:44:26 +0530 Subject: [PATCH 04/12] fix(workflow): use pointer for nullable template response and clarify flag descriptions --- cmd/workflow_templates.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/workflow_templates.go b/cmd/workflow_templates.go index c3aaeb2..aa2bcb8 100644 --- a/cmd/workflow_templates.go +++ b/cmd/workflow_templates.go @@ -123,7 +123,7 @@ var workflowTemplatesGetCmd = &cobra.Command{ }) var respData struct { - WorkflowGetTemplate struct { + WorkflowGetTemplate *struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description"` @@ -137,7 +137,7 @@ var workflowTemplatesGetCmd = &cobra.Command{ return err } - if respData.WorkflowGetTemplate.ID == "" { + if respData.WorkflowGetTemplate == nil || respData.WorkflowGetTemplate.ID == "" { return fmt.Errorf("workflow template '%s' not found", templateID) } @@ -151,9 +151,9 @@ func init() { workflowTemplatesCmd.AddCommand(workflowTemplatesListCmd) workflowTemplatesCmd.AddCommand(workflowTemplatesGetCmd) - workflowTemplatesListCmd.Flags().String("type", "system", "Template type (system or custom)") + workflowTemplatesListCmd.Flags().String("type", "system", "Template type (system, custom, or all)") workflowTemplatesListCmd.Flags().String("category", "", "Filter templates by category") workflowTemplatesListCmd.Flags().Int("limit", 50, "Maximum number of templates to return") - workflowTemplatesGetCmd.Flags().String("type", "", "Optional template type filter (system or custom)") + workflowTemplatesGetCmd.Flags().String("type", "", "Optional template type filter (system, custom, or all)") } From d2cc2ef35f01bf1df0928706d892c290a6b2a199 Mon Sep 17 00:00:00 2001 From: shiv Date: Sun, 9 Aug 2026 17:11:01 +0530 Subject: [PATCH 05/12] fix(workflow): use non-pointer struct for WorkflowGetTemplate in templates get --- cmd/workflow_templates.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/workflow_templates.go b/cmd/workflow_templates.go index aa2bcb8..30e2ecd 100644 --- a/cmd/workflow_templates.go +++ b/cmd/workflow_templates.go @@ -123,7 +123,7 @@ var workflowTemplatesGetCmd = &cobra.Command{ }) var respData struct { - WorkflowGetTemplate *struct { + WorkflowGetTemplate struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description"` @@ -137,7 +137,7 @@ var workflowTemplatesGetCmd = &cobra.Command{ return err } - if respData.WorkflowGetTemplate == nil || respData.WorkflowGetTemplate.ID == "" { + if respData.WorkflowGetTemplate.ID == "" { return fmt.Errorf("workflow template '%s' not found", templateID) } From 1b34f6bff2b7e0c397cee7fc4f031799e1204c58 Mon Sep 17 00:00:00 2001 From: shiv Date: Sun, 9 Aug 2026 17:13:14 +0530 Subject: [PATCH 06/12] fix(workflow): normalize template type flag using strings.ToLower --- cmd/workflow_templates.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/workflow_templates.go b/cmd/workflow_templates.go index 30e2ecd..f71f19a 100644 --- a/cmd/workflow_templates.go +++ b/cmd/workflow_templates.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "strings" "github.com/nudgebee/nbctl/pkg/client" "github.com/nudgebee/nbctl/pkg/format" @@ -18,6 +19,7 @@ var workflowTemplatesListCmd = &cobra.Command{ Short: "List pre-built workflow templates", RunE: func(cmd *cobra.Command, args []string) error { typeFlag, _ := cmd.Flags().GetString("type") + typeFlag = strings.ToLower(typeFlag) category, _ := cmd.Flags().GetString("category") limit, _ := cmd.Flags().GetInt("limit") @@ -93,6 +95,7 @@ var workflowTemplatesGetCmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { templateID := args[0] typeFlag, _ := cmd.Flags().GetString("type") + typeFlag = strings.ToLower(typeFlag) if typeFlag != "" && typeFlag != "system" && typeFlag != "custom" && typeFlag != "all" { return fmt.Errorf("invalid template type '%s': must be 'system', 'custom', or 'all'", typeFlag) From ce8d5b9d295eb76e420f798a0a19a882f22630fb Mon Sep 17 00:00:00 2001 From: shiv Date: Sun, 9 Aug 2026 17:15:45 +0530 Subject: [PATCH 07/12] refactor(workflow): use package-level client.Run and validate non-empty task flag --- cmd/workflow_approve.go | 11 ++++++++--- cmd/workflow_replay.go | 4 +--- cmd/workflow_task_definitions.go | 4 +--- cmd/workflow_templates.go | 8 ++------ 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/cmd/workflow_approve.go b/cmd/workflow_approve.go index c16fb53..9194215 100644 --- a/cmd/workflow_approve.go +++ b/cmd/workflow_approve.go @@ -1,6 +1,9 @@ package cmd import ( + "fmt" + "strings" + "github.com/nudgebee/nbctl/pkg/client" "github.com/nudgebee/nbctl/pkg/format" "github.com/spf13/cobra" @@ -13,6 +16,10 @@ var workflowApproveCmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { executionID := args[0] taskID, _ := cmd.Flags().GetString("task") + if strings.TrimSpace(taskID) == "" { + return fmt.Errorf("task flag is required and cannot be empty") + } + reject, _ := cmd.Flags().GetBool("reject") comments, _ := cmd.Flags().GetString("comments") @@ -21,8 +28,6 @@ var workflowApproveCmd = &cobra.Command{ status = "rejected" } - graphqlClient := client.NewClient() - req := client.NewRequest(` mutation CompleteWorkflowApproval($request: WorkflowCompleteApprovalRequest!) { workflow_complete_approval(request: $request) { @@ -45,7 +50,7 @@ var workflowApproveCmd = &cobra.Command{ } `json:"workflow_complete_approval"` } - if err := graphqlClient.Run(cmd.Context(), req, &respData); err != nil { + if err := client.Run(cmd.Context(), req, &respData); err != nil { return err } diff --git a/cmd/workflow_replay.go b/cmd/workflow_replay.go index 5883a81..13ba617 100644 --- a/cmd/workflow_replay.go +++ b/cmd/workflow_replay.go @@ -12,8 +12,6 @@ var workflowReplayCmd = &cobra.Command{ Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { executionID := args[0] - graphqlClient := client.NewClient() - req := client.NewRequest(` mutation ReplayWorkflowExecution($request: WorkflowRetriggerRequest!) { workflow_replay_execution(request: $request) { @@ -35,7 +33,7 @@ var workflowReplayCmd = &cobra.Command{ } `json:"workflow_replay_execution"` } - if err := graphqlClient.Run(cmd.Context(), req, &respData); err != nil { + if err := client.Run(cmd.Context(), req, &respData); err != nil { return err } diff --git a/cmd/workflow_task_definitions.go b/cmd/workflow_task_definitions.go index 8115ac8..d8c20b2 100644 --- a/cmd/workflow_task_definitions.go +++ b/cmd/workflow_task_definitions.go @@ -15,8 +15,6 @@ var workflowTaskDefinitionsCmd = &cobra.Command{ nameFilter, _ := cmd.Flags().GetString("name") limit, _ := cmd.Flags().GetInt("limit") - graphqlClient := client.NewClient() - req := client.NewRequest(` query ListWorkflowTaskDefinitions($params: WorkflowTaskDefinitionListRequest!) { workflow_list_taskdefinitions(params: $params) { @@ -47,7 +45,7 @@ var workflowTaskDefinitionsCmd = &cobra.Command{ } `json:"workflow_list_taskdefinitions"` } - if err := graphqlClient.Run(cmd.Context(), req, &respData); err != nil { + if err := client.Run(cmd.Context(), req, &respData); err != nil { return err } diff --git a/cmd/workflow_templates.go b/cmd/workflow_templates.go index f71f19a..533a4a1 100644 --- a/cmd/workflow_templates.go +++ b/cmd/workflow_templates.go @@ -27,8 +27,6 @@ var workflowTemplatesListCmd = &cobra.Command{ return fmt.Errorf("invalid template type '%s': must be 'system', 'custom', or 'all'", typeFlag) } - graphqlClient := client.NewClient() - req := client.NewRequest(` query ListWorkflowTemplates($request: WorkflowListTemplateRequest!) { workflow_list_template(request: $request) { @@ -68,7 +66,7 @@ var workflowTemplatesListCmd = &cobra.Command{ } `json:"workflow_list_template"` } - if err := graphqlClient.Run(cmd.Context(), req, &respData); err != nil { + if err := client.Run(cmd.Context(), req, &respData); err != nil { return err } @@ -106,8 +104,6 @@ var workflowTemplatesGetCmd = &cobra.Command{ typeVal = "all" } - graphqlClient := client.NewClient() - req := client.NewRequest(` query GetWorkflowTemplate($request: WorkflowGetTemplateRequest!) { workflow_get_template(request: $request) { @@ -136,7 +132,7 @@ var workflowTemplatesGetCmd = &cobra.Command{ } `json:"workflow_get_template"` } - if err := graphqlClient.Run(cmd.Context(), req, &respData); err != nil { + if err := client.Run(cmd.Context(), req, &respData); err != nil { return err } From fb1d905a5d74230e5a2b94a03436ecaa788eda67 Mon Sep 17 00:00:00 2001 From: shiv Date: Sun, 9 Aug 2026 17:17:43 +0530 Subject: [PATCH 08/12] refactor(workflow): extract workflowTemplate struct and omit empty comments in approve input --- cmd/workflow_approve.go | 9 ++++++--- cmd/workflow_templates.go | 29 ++++++++++++----------------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/cmd/workflow_approve.go b/cmd/workflow_approve.go index 9194215..8558556 100644 --- a/cmd/workflow_approve.go +++ b/cmd/workflow_approve.go @@ -36,12 +36,15 @@ var workflowApproveCmd = &cobra.Command{ } } `) - req.Var("request", map[string]any{ + input := map[string]any{ "execution_id": executionID, "task_id": taskID, "status": status, - "comments": comments, - }) + } + if comments != "" { + input["comments"] = comments + } + req.Var("request", input) var respData struct { WorkflowCompleteApproval struct { diff --git a/cmd/workflow_templates.go b/cmd/workflow_templates.go index 533a4a1..d177516 100644 --- a/cmd/workflow_templates.go +++ b/cmd/workflow_templates.go @@ -9,6 +9,15 @@ import ( "github.com/spf13/cobra" ) +type workflowTemplate struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Category string `json:"category"` + IsSystem bool `json:"is_system"` + Status string `json:"status"` +} + var workflowTemplatesCmd = &cobra.Command{ Use: "templates", Short: "Browse and inspect pre-built workflow templates", @@ -54,15 +63,8 @@ var workflowTemplatesListCmd = &cobra.Command{ var respData struct { WorkflowListTemplate struct { - TotalCount int `json:"total_count"` - Templates []struct { - ID string `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Category string `json:"category"` - IsSystem bool `json:"is_system"` - Status string `json:"status"` - } `json:"templates"` + TotalCount int `json:"total_count"` + Templates []workflowTemplate `json:"templates"` } `json:"workflow_list_template"` } @@ -122,14 +124,7 @@ var workflowTemplatesGetCmd = &cobra.Command{ }) var respData struct { - WorkflowGetTemplate struct { - ID string `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Category string `json:"category"` - IsSystem bool `json:"is_system"` - Status string `json:"status"` - } `json:"workflow_get_template"` + WorkflowGetTemplate workflowTemplate `json:"workflow_get_template"` } if err := client.Run(cmd.Context(), req, &respData); err != nil { From a2765978e5938bd8b42b98fdf0033cf18511e055 Mon Sep 17 00:00:00 2001 From: shiv Date: Sun, 9 Aug 2026 17:19:19 +0530 Subject: [PATCH 09/12] fix(workflow): validate non-empty positional arguments across commands --- cmd/workflow_approve.go | 5 ++++- cmd/workflow_replay.go | 8 +++++++- cmd/workflow_templates.go | 5 ++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/cmd/workflow_approve.go b/cmd/workflow_approve.go index 8558556..a645be2 100644 --- a/cmd/workflow_approve.go +++ b/cmd/workflow_approve.go @@ -14,7 +14,10 @@ var workflowApproveCmd = &cobra.Command{ Short: "Complete a human approval gate for a pending workflow execution", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - executionID := args[0] + executionID := strings.TrimSpace(args[0]) + if executionID == "" { + return fmt.Errorf("execution-id cannot be empty") + } taskID, _ := cmd.Flags().GetString("task") if strings.TrimSpace(taskID) == "" { return fmt.Errorf("task flag is required and cannot be empty") diff --git a/cmd/workflow_replay.go b/cmd/workflow_replay.go index 13ba617..0a66a3e 100644 --- a/cmd/workflow_replay.go +++ b/cmd/workflow_replay.go @@ -1,6 +1,9 @@ package cmd import ( + "fmt" + "strings" + "github.com/nudgebee/nbctl/pkg/client" "github.com/nudgebee/nbctl/pkg/format" "github.com/spf13/cobra" @@ -11,7 +14,10 @@ var workflowReplayCmd = &cobra.Command{ Short: "Replay a previous or failed workflow execution", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - executionID := args[0] + executionID := strings.TrimSpace(args[0]) + if executionID == "" { + return fmt.Errorf("execution-id cannot be empty") + } req := client.NewRequest(` mutation ReplayWorkflowExecution($request: WorkflowRetriggerRequest!) { workflow_replay_execution(request: $request) { diff --git a/cmd/workflow_templates.go b/cmd/workflow_templates.go index d177516..3904e0d 100644 --- a/cmd/workflow_templates.go +++ b/cmd/workflow_templates.go @@ -93,7 +93,10 @@ var workflowTemplatesGetCmd = &cobra.Command{ Short: "Get details for a specific workflow template", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - templateID := args[0] + templateID := strings.TrimSpace(args[0]) + if templateID == "" { + return fmt.Errorf("template-id cannot be empty") + } typeFlag, _ := cmd.Flags().GetString("type") typeFlag = strings.ToLower(typeFlag) From 434337197883450791d4f372004b3ea6f14a143d Mon Sep 17 00:00:00 2001 From: shiv Date: Sun, 9 Aug 2026 17:20:44 +0530 Subject: [PATCH 10/12] fix(workflow): use pointer struct for WorkflowGetTemplate response --- cmd/workflow_templates.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/workflow_templates.go b/cmd/workflow_templates.go index 3904e0d..3ffa009 100644 --- a/cmd/workflow_templates.go +++ b/cmd/workflow_templates.go @@ -127,14 +127,14 @@ var workflowTemplatesGetCmd = &cobra.Command{ }) var respData struct { - WorkflowGetTemplate workflowTemplate `json:"workflow_get_template"` + WorkflowGetTemplate *workflowTemplate `json:"workflow_get_template"` } if err := client.Run(cmd.Context(), req, &respData); err != nil { return err } - if respData.WorkflowGetTemplate.ID == "" { + if respData.WorkflowGetTemplate == nil || respData.WorkflowGetTemplate.ID == "" { return fmt.Errorf("workflow template '%s' not found", templateID) } From a3c87db8e6f62c305fd081eca5676ad52bc80374 Mon Sep 17 00:00:00 2001 From: shiv Date: Sun, 9 Aug 2026 17:22:28 +0530 Subject: [PATCH 11/12] fix(workflow): enforce Args: cobra.NoArgs and trim flag values --- cmd/workflow_approve.go | 4 +++- cmd/workflow_task_definitions.go | 2 ++ cmd/workflow_templates.go | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/cmd/workflow_approve.go b/cmd/workflow_approve.go index a645be2..77ab0bb 100644 --- a/cmd/workflow_approve.go +++ b/cmd/workflow_approve.go @@ -19,12 +19,14 @@ var workflowApproveCmd = &cobra.Command{ return fmt.Errorf("execution-id cannot be empty") } taskID, _ := cmd.Flags().GetString("task") - if strings.TrimSpace(taskID) == "" { + taskID = strings.TrimSpace(taskID) + if taskID == "" { return fmt.Errorf("task flag is required and cannot be empty") } reject, _ := cmd.Flags().GetBool("reject") comments, _ := cmd.Flags().GetString("comments") + comments = strings.TrimSpace(comments) status := "approved" if reject { diff --git a/cmd/workflow_task_definitions.go b/cmd/workflow_task_definitions.go index d8c20b2..e55bd79 100644 --- a/cmd/workflow_task_definitions.go +++ b/cmd/workflow_task_definitions.go @@ -11,8 +11,10 @@ import ( var workflowTaskDefinitionsCmd = &cobra.Command{ Use: "task-definitions", Short: "List supported workflow task definitions and action schemas", + Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { nameFilter, _ := cmd.Flags().GetString("name") + nameFilter = strings.TrimSpace(nameFilter) limit, _ := cmd.Flags().GetInt("limit") req := client.NewRequest(` diff --git a/cmd/workflow_templates.go b/cmd/workflow_templates.go index 3ffa009..5dc681d 100644 --- a/cmd/workflow_templates.go +++ b/cmd/workflow_templates.go @@ -26,10 +26,12 @@ var workflowTemplatesCmd = &cobra.Command{ var workflowTemplatesListCmd = &cobra.Command{ Use: "list", Short: "List pre-built workflow templates", + Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { typeFlag, _ := cmd.Flags().GetString("type") typeFlag = strings.ToLower(typeFlag) category, _ := cmd.Flags().GetString("category") + category = strings.TrimSpace(category) limit, _ := cmd.Flags().GetInt("limit") if typeFlag != "system" && typeFlag != "custom" && typeFlag != "all" { From b122ca6d71a4f1d658de0153874011a243b44635 Mon Sep 17 00:00:00 2001 From: shiv Date: Sun, 9 Aug 2026 17:23:13 +0530 Subject: [PATCH 12/12] fix(workflow): dereference template pointer for structured formatting --- cmd/workflow_templates.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/workflow_templates.go b/cmd/workflow_templates.go index 5dc681d..d604669 100644 --- a/cmd/workflow_templates.go +++ b/cmd/workflow_templates.go @@ -140,7 +140,7 @@ var workflowTemplatesGetCmd = &cobra.Command{ return fmt.Errorf("workflow template '%s' not found", templateID) } - format.GetFormat().Print(respData.WorkflowGetTemplate) + format.GetFormat().Print(*respData.WorkflowGetTemplate) return nil }, }