diff --git a/cmd/serve/tools.go b/cmd/serve/tools.go index 1cd9292..572149d 100644 --- a/cmd/serve/tools.go +++ b/cmd/serve/tools.go @@ -359,7 +359,7 @@ func registerStaticTools(server *mcp.Server, f *cmdutil.Factory) { // workflow_delete -- DELETE /api/workflows/{id} server.AddTool(&mcp.Tool{ Name: "workflow_delete", - Description: "Delete a workflow by ID", + Description: "Delete a workflow by ID. Use force=true to delete workflows that have execution history.", InputSchema: map[string]any{ "type": "object", "properties": map[string]any{ @@ -367,11 +367,19 @@ func registerStaticTools(server *mcp.Server, f *cmdutil.Factory) { "type": "string", "description": "The workflow ID to delete", }, + "force": map[string]any{ + "type": "boolean", + "description": "Force delete even if the workflow has execution history. This will permanently delete all runs and logs.", + }, }, "required": []string{"workflow_id"}, }, }, makeStaticHandler(f, http.MethodDelete, func(args map[string]any, baseURL string) string { - return baseURL + "/api/workflows/" + getStringArg(args, "workflow_id") + u := baseURL + "/api/workflows/" + getStringArg(args, "workflow_id") + if force, ok := args["force"]; ok && force == true { + u += "?force=true" + } + return u }, nil)) // workflow_execute -- POST /api/workflow/{id}/execute diff --git a/cmd/workflow/delete.go b/cmd/workflow/delete.go index 47698c4..dc10889 100644 --- a/cmd/workflow/delete.go +++ b/cmd/workflow/delete.go @@ -24,17 +24,22 @@ func NewDeleteCmd(f *cmdutil.Factory) *cobra.Command { kh wf delete abc123 # Delete without prompting - kh wf delete abc123 --yes`, + kh wf delete abc123 --yes + + # Force delete a workflow that has execution history + kh wf delete abc123 --force`, RunE: func(cmd *cobra.Command, args []string) error { workflowID := args[0] - yes, err := cmd.Flags().GetBool("yes") - if err != nil { - yes = false - } + force, _ := cmd.Flags().GetBool("force") + yes, _ := cmd.Flags().GetBool("yes") if !yes && f.IOStreams.IsTerminal() { - fmt.Fprintf(f.IOStreams.Out, "Delete workflow %s? This cannot be undone. (y/N) ", workflowID) + prompt := fmt.Sprintf("Delete workflow %s? This cannot be undone. (y/N) ", workflowID) + if force { + prompt = fmt.Sprintf("Force delete workflow %s and all its execution history? This cannot be undone. (y/N) ", workflowID) + } + fmt.Fprint(f.IOStreams.Out, prompt) scanner := bufio.NewScanner(f.IOStreams.In) if scanner.Scan() { answer := strings.TrimSpace(strings.ToLower(scanner.Text())) @@ -55,6 +60,9 @@ func NewDeleteCmd(f *cmdutil.Factory) *cobra.Command { host := cmdutil.ResolveHost(cmd, cfg) url := khhttp.BuildBaseURL(host) + "/api/workflows/" + workflowID + if force { + url += "?force=true" + } req, err := client.NewRequest(http.MethodDelete, url, nil) if err != nil { return err @@ -62,15 +70,16 @@ func NewDeleteCmd(f *cmdutil.Factory) *cobra.Command { resp, err := client.Do(req) if err != nil { - return fmt.Errorf("cannot delete workflow %s: it may have existing runs that prevent deletion", workflowID) + return fmt.Errorf("cannot delete workflow %s: %w", workflowID, err) } defer resp.Body.Close() if resp.StatusCode == http.StatusNotFound { return cmdutil.NotFoundError{Err: fmt.Errorf("workflow %q not found", workflowID)} } - if resp.StatusCode == http.StatusInternalServerError { - return fmt.Errorf("cannot delete workflow %s: workflow has existing runs that prevent deletion", workflowID) + if resp.StatusCode == http.StatusConflict && !force { + fmt.Fprintf(f.IOStreams.ErrOut, "Workflow has execution history. Use --force to delete the workflow and all its runs.\n") + return fmt.Errorf("workflow %s has existing runs that prevent deletion", workflowID) } if resp.StatusCode != http.StatusOK { return khhttp.NewAPIError(resp) @@ -90,6 +99,7 @@ func NewDeleteCmd(f *cmdutil.Factory) *cobra.Command { } cmd.Flags().BoolP("yes", "y", false, "Skip confirmation prompt") + cmd.Flags().Bool("force", false, "Force delete even if workflow has execution history") return cmd } diff --git a/docs/kh_workflow.md b/docs/kh_workflow.md index 471d3fe..d6acd5d 100644 --- a/docs/kh_workflow.md +++ b/docs/kh_workflow.md @@ -31,6 +31,7 @@ Manage workflows ### SEE ALSO * [kh](kh.md) - KeeperHub CLI +* [kh workflow delete](kh_workflow_delete.md) - Delete a workflow * [kh workflow get](kh_workflow_get.md) - Get a workflow * [kh workflow go-live](kh_workflow_go-live.md) - Publish a workflow * [kh workflow list](kh_workflow_list.md) - List workflows diff --git a/docs/kh_workflow_delete.md b/docs/kh_workflow_delete.md new file mode 100644 index 0000000..df18c83 --- /dev/null +++ b/docs/kh_workflow_delete.md @@ -0,0 +1,41 @@ +## kh workflow delete + +Delete a workflow + +``` +kh workflow delete [flags] +``` + +### Examples + +``` + # Delete a workflow (will prompt for confirmation) + kh wf delete abc123 + + # Delete without prompting + kh wf delete abc123 --yes + + # Force delete a workflow that has execution history + kh wf delete abc123 --force +``` + +### Options + +``` + --force Force delete even if workflow has execution history + -h, --help help for delete + -y, --yes Skip confirmation prompt +``` + +### Options inherited from parent commands + +``` + -H, --host string KeeperHub host (default: app.keeperhub.com) + --jq string Filter JSON output with a jq expression + --json Output as JSON + --no-color Disable color output +``` + +### SEE ALSO + +* [kh workflow](kh_workflow.md) - Manage workflows