From 8bf6457508ae096410fd95d379f315a303fc80ec Mon Sep 17 00:00:00 2001 From: OpenWaygate <27287694+OpenWaygate@users.noreply.github.com> Date: Thu, 20 Aug 2026 21:52:15 +0800 Subject: [PATCH] :lock: Add confirmation gate to MCP destructive tools * :recycle: Refactor IsInteractive to accept any * :recycle: Simplify Confirm to return bool * :recycle: Use simplified Confirm in mutation commands * :wrench: Update MODULE.bazel for go tool * :sparkles: Add ConfirmPreRun utility * :recycle: Migrate mutation commands to ConfirmPreRun * :art: Fix alignment and trailing newlines --- MODULE.bazel | 1 + cmd/BUILD.bazel | 6 +- cmd/abuseReport/insert.go | 21 ++- cmd/activity/activity.go | 12 +- cmd/agent/agents.go | 2 - cmd/caption/delete.go | 19 +- cmd/caption/download.go | 1 - cmd/caption/insert.go | 19 +- cmd/caption/update.go | 19 +- cmd/channel/channel.go | 1 - cmd/channel/update.go | 19 +- cmd/channelBanner/insert.go | 19 +- cmd/channelSection/delete.go | 21 ++- cmd/comment/delete.go | 19 +- cmd/comment/insert.go | 19 +- cmd/comment/markAsSpam.go | 21 ++- cmd/comment/setModerationStatus.go | 22 ++- cmd/comment/update.go | 19 +- cmd/commentThread/insert.go | 19 +- cmd/flags.go | 88 --------- cmd/flags_test.go | 171 ------------------ cmd/liveBroadcast/bind.go | 23 ++- cmd/liveBroadcast/delete.go | 20 +- cmd/liveBroadcast/insert.go | 19 +- cmd/liveBroadcast/insertCuepoint.go | 23 ++- cmd/liveBroadcast/transition.go | 23 ++- cmd/liveBroadcast/update.go | 23 ++- cmd/liveChatBan/delete.go | 21 ++- cmd/liveChatBan/insert.go | 23 ++- cmd/liveChatMessage/delete.go | 21 ++- cmd/liveChatMessage/insert.go | 21 ++- cmd/liveChatMessage/list.go | 1 - cmd/liveChatMessage/transition.go | 23 ++- cmd/liveChatModerator/delete.go | 21 ++- cmd/liveChatModerator/insert.go | 24 +-- cmd/liveChatModerator/list.go | 1 - cmd/liveStream/delete.go | 20 +- cmd/liveStream/insert.go | 19 +- cmd/liveStream/update.go | 21 ++- cmd/membershipsLevel/membershipsLevel.go | 2 +- cmd/playlist/delete.go | 19 +- cmd/playlist/insert.go | 19 +- cmd/playlist/update.go | 22 ++- cmd/playlistImage/delete.go | 21 ++- cmd/playlistImage/insert.go | 19 +- cmd/playlistImage/update.go | 23 ++- cmd/playlistItem/delete.go | 21 ++- cmd/playlistItem/insert.go | 19 +- cmd/playlistItem/update.go | 21 ++- cmd/subscription/delete.go | 21 ++- cmd/subscription/insert.go | 19 +- cmd/subscription/subscription.go | 2 +- cmd/thirdPartyLink/delete.go | 36 ++-- cmd/thirdPartyLink/insert.go | 28 +-- cmd/thirdPartyLink/list.go | 13 +- cmd/thirdPartyLink/thirdPartyLink.go | 14 +- cmd/thirdPartyLink/update.go | 28 +-- cmd/thumbnail/set.go | 19 +- cmd/video/delete.go | 19 +- cmd/video/getRating.go | 1 - cmd/video/insert.go | 19 +- cmd/video/rate.go | 22 ++- cmd/video/reportAbuse.go | 21 ++- cmd/video/update.go | 19 +- .../videoAbuseReportReason.go | 4 +- cmd/watermark/set.go | 19 +- cmd/watermark/unset.go | 19 +- pkg/common/common.go | 1 + pkg/constants.go | 20 +- pkg/utils/utils.go | 35 +++- pkg/utils/utils_test.go | 96 ++++++++-- 71 files changed, 783 insertions(+), 763 deletions(-) delete mode 100644 cmd/flags.go delete mode 100644 cmd/flags_test.go diff --git a/MODULE.bazel b/MODULE.bazel index 36e7a30..0648199 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -24,6 +24,7 @@ use_repo( "com_github_savioxavier_termlink", "com_github_spf13_cobra", "com_github_spf13_pflag", + "com_github_tc_hib_go_winres", "in_gopkg_yaml_v3", "org_golang_google_adk", "org_golang_google_api", diff --git a/cmd/BUILD.bazel b/cmd/BUILD.bazel index 1328693..215a2f4 100644 --- a/cmd/BUILD.bazel +++ b/cmd/BUILD.bazel @@ -27,7 +27,6 @@ go_library( "@com_github_eat_pray_ai_cobra_mcp//:cobra-mcp", "@com_github_savioxavier_termlink//:termlink", "@com_github_spf13_cobra//:cobra", - "@org_golang_x_term//:term", ], ) @@ -35,5 +34,8 @@ go_test( name = "cmd_test", srcs = ["flags_test.go"], embed = [":cmd"], - deps = ["@com_github_spf13_cobra//:cobra"], + deps = [ + "//pkg/utils", + "@com_github_spf13_cobra//:cobra", + ], ) diff --git a/cmd/abuseReport/insert.go b/cmd/abuseReport/insert.go index 8971a85..a61c707 100644 --- a/cmd/abuseReport/insert.go +++ b/cmd/abuseReport/insert.go @@ -5,6 +5,7 @@ package abuseReport import ( "encoding/json" + "fmt" "io" cobramcp "github.com/eat-pray-ai/cobra-mcp" @@ -48,6 +49,7 @@ var insertInSchema = &jsonschema.Schema{ Type: "string", Enum: []any{"json", "yaml", "silent"}, Description: pkg.SilentUsage, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -63,6 +65,9 @@ func init() { }, }, cobramcp.GenToolHandler( insertTool, func(input abuseReport.AbuseReport, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Insert(writer) }, ), @@ -89,11 +94,10 @@ func init() { &parts, "parts", "p", []string{"snippet"}, "Parts to include", ) insertCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + insertCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = insertCmd.MarkFlagRequired("abuseTypes") _ = insertCmd.MarkFlagRequired("subjectId") _ = insertCmd.MarkFlagRequired("subjectTypeId") - cmd.AddMutationFlags(insertCmd) } var insertCmd = &cobra.Command{ @@ -101,15 +105,12 @@ var insertCmd = &cobra.Command{ Short: insertShort, Long: insertLong, Example: insertExample, - Run: func(c *cobra.Command, args []string) { + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf("Would report abuse for %s %s", subjectTypeId, subjectId) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { output, _ := c.Flags().GetString("output") - err := cmd.Confirm( - c, "Would report abuse for %s %s", subjectTypeId, subjectId, - ) - if err != nil { - utils.HandleCmdError(err, c) - return - } input := abuseReport.NewAbuseReport( abuseReport.WithAbuseTypes(abuseTypes), abuseReport.WithDescription(description), diff --git a/cmd/activity/activity.go b/cmd/activity/activity.go index 8177de9..1d8496a 100644 --- a/cmd/activity/activity.go +++ b/cmd/activity/activity.go @@ -9,13 +9,13 @@ import ( ) const ( - short = "Manage activities on YouTube" - long = "Manage activities on YouTube. Use this tool to list channel activities." - ciUsage = "ID of the channel" + short = "Manage activities on YouTube" + long = "Manage activities on YouTube. Use this tool to list channel activities." + ciUsage = "ID of the channel" forUsage = "home|mine" - paUsage = "Filter on activities published after this date" - pbUsage = "Filter on activities published before this date" - rcUsage = "Display the content as seen by viewers in this country" + paUsage = "Filter on activities published after this date" + pbUsage = "Filter on activities published before this date" + rcUsage = "Display the content as seen by viewers in this country" ) var ( diff --git a/cmd/agent/agents.go b/cmd/agent/agents.go index 9c89f23..6455b17 100644 --- a/cmd/agent/agents.go +++ b/cmd/agent/agents.go @@ -162,7 +162,6 @@ var agentDefs = map[string]agentDef{ }, } - func init() { for _, def := range agentDefs { if v, ok := os.LookupEnv(def.envKey); ok && v != "" { @@ -171,7 +170,6 @@ func init() { } } - func buildOrchestrator( advancedModel, liteModel model.LLM, mcpToolSet tool.Toolset, ) ( diff --git a/cmd/caption/delete.go b/cmd/caption/delete.go index b53b85b..35408cd 100644 --- a/cmd/caption/delete.go +++ b/cmd/caption/delete.go @@ -4,6 +4,7 @@ package caption import ( + "fmt" "io" "strings" @@ -38,6 +39,7 @@ var deleteInSchema = &jsonschema.Schema{ }, "on_behalf_of": {Type: "string", Description: pkg.OBOUsage}, "on_behalf_of_content_owner": {Type: "string", Description: pkg.OBOCOUsage}, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -53,6 +55,9 @@ func init() { }, }, cobramcp.GenToolHandler( deleteTool, func(input caption.Caption, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Delete(writer) }, ), @@ -64,9 +69,8 @@ func init() { deleteCmd.Flags().StringVarP( &onBehalfOfContentOwner, "onBehalfOfContentOwner", "B", "", pkg.OBOCOUsage, ) - + deleteCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = deleteCmd.MarkFlagRequired("ids") - cmd.AddMutationFlags(deleteCmd) } var deleteCmd = &cobra.Command{ @@ -74,12 +78,11 @@ var deleteCmd = &cobra.Command{ Short: deleteShort, Long: deleteLong, Example: deleteExample, - Run: func(c *cobra.Command, args []string) { - err := cmd.Confirm(c, "Would delete caption(s): %s", strings.Join(ids, ", ")) - if err != nil { - utils.HandleCmdError(err, c) - return - } + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf("Would delete caption(s): %s", strings.Join(ids, ", ")) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { input := caption.NewCaption( caption.WithIds(ids), caption.WithOnBehalfOf(onBehalfOf), diff --git a/cmd/caption/download.go b/cmd/caption/download.go index a91bc18..629c896 100644 --- a/cmd/caption/download.go +++ b/cmd/caption/download.go @@ -78,7 +78,6 @@ func init() { downloadCmd.Flags().StringVarP( &onBehalfOfContentOwner, "onBehalfOfContentOwner", "B", "", pkg.OBOCOUsage, ) - _ = downloadCmd.MarkFlagRequired("id") _ = downloadCmd.MarkFlagRequired("file") } diff --git a/cmd/caption/insert.go b/cmd/caption/insert.go index 9ac4aaf..cba0556 100644 --- a/cmd/caption/insert.go +++ b/cmd/caption/insert.go @@ -5,6 +5,7 @@ package caption import ( "encoding/json" + "fmt" "io" cobramcp "github.com/eat-pray-ai/cobra-mcp" @@ -59,6 +60,7 @@ var insertInSchema = &jsonschema.Schema{ Enum: []any{"json", "yaml", "silent"}, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -74,6 +76,9 @@ func init() { }, }, cobramcp.GenToolHandler( insertTool, func(input caption.Caption, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Insert(writer) }, ), @@ -104,10 +109,9 @@ func init() { &onBehalfOfContentOwner, "onBehalfOfContentOwner", "B", "", pkg.OBOCOUsage, ) insertCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + insertCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = insertCmd.MarkFlagRequired("file") _ = insertCmd.MarkFlagRequired("videoId") - cmd.AddMutationFlags(insertCmd) } var insertCmd = &cobra.Command{ @@ -115,13 +119,12 @@ var insertCmd = &cobra.Command{ Short: insertShort, Long: insertLong, Example: insertExample, - Run: func(c *cobra.Command, args []string) { + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf("Would insert caption: %s", file) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { output, _ := c.Flags().GetString("output") - err := cmd.Confirm(c, "Would insert caption") - if err != nil { - utils.HandleCmdError(err, c) - return - } input := caption.NewCaption( caption.WithFile(file), caption.WithAudioTrackType(audioTrackType), diff --git a/cmd/caption/update.go b/cmd/caption/update.go index 74d9d04..0cbc9fb 100644 --- a/cmd/caption/update.go +++ b/cmd/caption/update.go @@ -5,6 +5,7 @@ package caption import ( "encoding/json" + "fmt" "io" cobramcp "github.com/eat-pray-ai/cobra-mcp" @@ -58,6 +59,7 @@ var updateInSchema = &jsonschema.Schema{ Enum: []any{"json", "yaml", "silent"}, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -73,6 +75,9 @@ func init() { }, }, cobramcp.GenToolHandler( updateTool, func(input caption.Caption, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Update(writer) }, ), @@ -103,9 +108,8 @@ func init() { &onBehalfOfContentOwner, "onBehalfOfContentOwner", "B", "", pkg.OBOCOUsage, ) updateCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + updateCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = updateCmd.MarkFlagRequired("videoId") - cmd.AddMutationFlags(updateCmd) } var updateCmd = &cobra.Command{ @@ -113,13 +117,12 @@ var updateCmd = &cobra.Command{ Short: updateShort, Long: updateLong, Example: updateExample, - Run: func(c *cobra.Command, args []string) { + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf("Would update caption for video: %s", videoId) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { output, _ := c.Flags().GetString("output") - err := cmd.Confirm(c, "Would update caption for video: %s", videoId) - if err != nil { - utils.HandleCmdError(err, c) - return - } input := caption.NewCaption( caption.WithFile(file), caption.WithAudioTrackType(audioTrackType), diff --git a/cmd/channel/channel.go b/cmd/channel/channel.go index e9438ff..b536ad0 100644 --- a/cmd/channel/channel.go +++ b/cmd/channel/channel.go @@ -53,4 +53,3 @@ var channelCmd = &cobra.Command{ func init() { cmd.RootCmd.AddCommand(channelCmd) } - diff --git a/cmd/channel/update.go b/cmd/channel/update.go index cf0bbcb..5afb64f 100644 --- a/cmd/channel/update.go +++ b/cmd/channel/update.go @@ -5,6 +5,7 @@ package channel import ( "encoding/json" + "fmt" "io" "strings" @@ -48,6 +49,7 @@ var updateInSchema = &jsonschema.Schema{ Type: "string", Enum: []any{"json", "yaml", "silent"}, Description: pkg.SilentUsage, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -63,6 +65,9 @@ func init() { }, }, cobramcp.GenToolHandler( updateTool, func(input channel.Channel, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Update(writer) }, ), @@ -80,9 +85,8 @@ func init() { ) updateCmd.Flags().StringVarP(&title, "title", "t", "", titleUsage) updateCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + updateCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = updateCmd.MarkFlagRequired("id") - cmd.AddMutationFlags(updateCmd) } var updateCmd = &cobra.Command{ @@ -90,13 +94,12 @@ var updateCmd = &cobra.Command{ Short: updateShort, Long: updateLong, Example: updateExample, - Run: func(c *cobra.Command, args []string) { + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf("Would update channel: %s", strings.Join(ids, ", ")) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { output, _ := c.Flags().GetString("output") - err := cmd.Confirm(c, "Would update channel: %s", strings.Join(ids, ", ")) - if err != nil { - utils.HandleCmdError(err, c) - return - } input := channel.NewChannel( channel.WithIds(ids), channel.WithCountry(country), diff --git a/cmd/channelBanner/insert.go b/cmd/channelBanner/insert.go index 63ec907..d73f9ac 100644 --- a/cmd/channelBanner/insert.go +++ b/cmd/channelBanner/insert.go @@ -5,6 +5,7 @@ package channelBanner import ( "encoding/json" + "fmt" "io" cobramcp "github.com/eat-pray-ai/cobra-mcp" @@ -46,6 +47,7 @@ var insertInSchema = &jsonschema.Schema{ Type: "string", Enum: []any{"json", "yaml", "silent"}, Description: pkg.SilentUsage, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -62,6 +64,9 @@ func init() { }, cobramcp.GenToolHandler( insertTool, func(input channelBanner.ChannelBanner, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Insert(writer) }, ), @@ -78,10 +83,9 @@ func init() { pkg.OBOCOCUsage, ) insertCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + insertCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = insertCmd.MarkFlagRequired("channelId") _ = insertCmd.MarkFlagRequired("file") - cmd.AddMutationFlags(insertCmd) } var insertCmd = &cobra.Command{ @@ -89,13 +93,12 @@ var insertCmd = &cobra.Command{ Short: insertShort, Long: insertLong, Example: insertExample, - Run: func(c *cobra.Command, args []string) { + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf("Would insert channel banner: %s", file) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { output, _ := c.Flags().GetString("output") - err := cmd.Confirm(c, "Would insert channel banner") - if err != nil { - utils.HandleCmdError(err, c) - return - } input := channelBanner.NewChannelBanner( channelBanner.WithChannelId(channelId), channelBanner.WithFile(file), diff --git a/cmd/channelSection/delete.go b/cmd/channelSection/delete.go index 82fffd7..36c5d8a 100644 --- a/cmd/channelSection/delete.go +++ b/cmd/channelSection/delete.go @@ -4,6 +4,7 @@ package channelSection import ( + "fmt" "io" "strings" @@ -37,6 +38,7 @@ var deleteInSchema = &jsonschema.Schema{ Items: &jsonschema.Schema{Type: "string"}, }, "on_behalf_of_content_owner": {Type: "string", Description: pkg.OBOCOUsage}, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -53,6 +55,9 @@ func init() { }, cobramcp.GenToolHandler( deleteTool, func(input channelSection.ChannelSection, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Delete(writer) }, ), @@ -63,9 +68,8 @@ func init() { deleteCmd.Flags().StringVarP( &onBehalfOfContentOwner, "onBehalfOfContentOwner", "b", "", pkg.OBOCOUsage, ) - + deleteCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = deleteCmd.MarkFlagRequired("ids") - cmd.AddMutationFlags(deleteCmd) } var deleteCmd = &cobra.Command{ @@ -73,14 +77,13 @@ var deleteCmd = &cobra.Command{ Short: deleteShort, Long: deleteLong, Example: deleteExample, - Run: func(c *cobra.Command, args []string) { - err := cmd.Confirm( - c, "Would delete channel section(s): %s", strings.Join(ids, ", "), + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf( + "Would delete channel section(s): %s", strings.Join(ids, ", "), ) - if err != nil { - utils.HandleCmdError(err, c) - return - } + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { input := channelSection.NewChannelSection( channelSection.WithIds(ids), channelSection.WithOnBehalfOfContentOwner(onBehalfOfContentOwner), diff --git a/cmd/comment/delete.go b/cmd/comment/delete.go index ba7fd1d..aa71fc6 100644 --- a/cmd/comment/delete.go +++ b/cmd/comment/delete.go @@ -4,11 +4,13 @@ package comment import ( + "fmt" "io" "strings" cobramcp "github.com/eat-pray-ai/cobra-mcp" "github.com/eat-pray-ai/yutu/cmd" + "github.com/eat-pray-ai/yutu/pkg" "github.com/eat-pray-ai/yutu/pkg/comment" "github.com/eat-pray-ai/yutu/pkg/utils" "github.com/google/jsonschema-go/jsonschema" @@ -34,6 +36,7 @@ var deleteInSchema = &jsonschema.Schema{ Type: "array", Description: idsUsage, Items: &jsonschema.Schema{Type: "string"}, }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -49,6 +52,9 @@ func init() { }, }, cobramcp.GenToolHandler( deleteTool, func(input comment.Comment, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Delete(writer) }, ), @@ -56,8 +62,8 @@ func init() { commentCmd.AddCommand(deleteCmd) deleteCmd.Flags().StringSliceVarP(&ids, "ids", "i", []string{}, idsUsage) + deleteCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = deleteCmd.MarkFlagRequired("ids") - cmd.AddMutationFlags(deleteCmd) } var deleteCmd = &cobra.Command{ @@ -65,12 +71,11 @@ var deleteCmd = &cobra.Command{ Short: deleteShort, Long: deleteLong, Example: deleteExample, - Run: func(c *cobra.Command, args []string) { - err := cmd.Confirm(c, "Would delete comment(s): %s", strings.Join(ids, ", ")) - if err != nil { - utils.HandleCmdError(err, c) - return - } + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf("Would delete comment(s): %s", strings.Join(ids, ", ")) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { input := comment.NewComment(comment.WithIds(ids)) utils.HandleCmdError(input.Delete(c.OutOrStdout()), c) }, diff --git a/cmd/comment/insert.go b/cmd/comment/insert.go index f27c6ba..d9775f0 100644 --- a/cmd/comment/insert.go +++ b/cmd/comment/insert.go @@ -5,6 +5,7 @@ package comment import ( "encoding/json" + "fmt" "io" cobramcp "github.com/eat-pray-ai/cobra-mcp" @@ -44,6 +45,7 @@ var insertInSchema = &jsonschema.Schema{ Type: "string", Enum: []any{"json", "yaml", "silent"}, Description: pkg.SilentUsage, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -59,6 +61,9 @@ func init() { }, }, cobramcp.GenToolHandler( insertTool, func(input comment.Comment, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Insert(writer) }, ), @@ -80,10 +85,9 @@ func init() { ) insertCmd.Flags().StringVarP(&videoId, "videoId", "v", "", vidUsage) insertCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + insertCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = insertCmd.MarkFlagRequired("parentId") _ = insertCmd.MarkFlagRequired("textOriginal") - cmd.AddMutationFlags(insertCmd) } var insertCmd = &cobra.Command{ @@ -91,13 +95,12 @@ var insertCmd = &cobra.Command{ Short: insertShort, Long: insertLong, Example: insertExample, - Run: func(c *cobra.Command, args []string) { + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf("Would insert comment on parent: %s", parentId) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { output, _ := c.Flags().GetString("output") - err := cmd.Confirm(c, "Would insert comment") - if err != nil { - utils.HandleCmdError(err, c) - return - } input := comment.NewComment( comment.WithAuthorChannelId(authorChannelId), comment.WithChannelId(channelId), diff --git a/cmd/comment/markAsSpam.go b/cmd/comment/markAsSpam.go index 36e4f41..059fd15 100644 --- a/cmd/comment/markAsSpam.go +++ b/cmd/comment/markAsSpam.go @@ -5,6 +5,7 @@ package comment import ( "encoding/json" + "fmt" "io" "strings" @@ -40,6 +41,7 @@ var markAsSpamInSchema = &jsonschema.Schema{ Type: "string", Enum: []any{"json", "yaml", "silent"}, Description: pkg.SilentUsage, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -55,6 +57,9 @@ func init() { }, }, cobramcp.GenToolHandler( masTool, func(input comment.Comment, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.MarkAsSpam(writer) }, ), @@ -63,9 +68,8 @@ func init() { markAsSpamCmd.Flags().StringSliceVarP(&ids, "ids", "i", []string{}, idsUsage) markAsSpamCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + markAsSpamCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = markAsSpamCmd.MarkFlagRequired("ids") - cmd.AddMutationFlags(markAsSpamCmd) } var markAsSpamCmd = &cobra.Command{ @@ -73,13 +77,14 @@ var markAsSpamCmd = &cobra.Command{ Short: masShort, Long: masLong, Example: masExample, - Run: func(c *cobra.Command, args []string) { + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf( + "Would mark comment(s) as spam: %s", strings.Join(ids, ", "), + ) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { output, _ := c.Flags().GetString("output") - err := cmd.Confirm(c, "Would mark comment(s) as spam: %s", strings.Join(ids, ", ")) - if err != nil { - utils.HandleCmdError(err, c) - return - } input := comment.NewComment( comment.WithIds(ids), comment.WithOutput(output), diff --git a/cmd/comment/setModerationStatus.go b/cmd/comment/setModerationStatus.go index 7b63d22..a7d7117 100644 --- a/cmd/comment/setModerationStatus.go +++ b/cmd/comment/setModerationStatus.go @@ -5,6 +5,7 @@ package comment import ( "encoding/json" + "fmt" "io" "strings" @@ -47,6 +48,7 @@ var setModerationStatusInSchema = &jsonschema.Schema{ Type: "string", Enum: []any{"json", "yaml", "silent"}, Description: pkg.SilentUsage, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -63,6 +65,9 @@ func init() { }, }, cobramcp.GenToolHandler( smsTool, func(input comment.Comment, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.SetModerationStatus(writer) }, ), @@ -79,10 +84,9 @@ func init() { banAuthor, "banAuthor", "A", false, baUsage, ) setModerationStatusCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + setModerationStatusCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = setModerationStatusCmd.MarkFlagRequired("ids") _ = setModerationStatusCmd.MarkFlagRequired("moderationStatus") - cmd.AddMutationFlags(setModerationStatusCmd) } var setModerationStatusCmd = &cobra.Command{ @@ -90,13 +94,15 @@ var setModerationStatusCmd = &cobra.Command{ Short: smsShort, Long: smsLong, Example: smsExample, - Run: func(c *cobra.Command, args []string) { + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf( + "Would set moderation status of comment(s): %s to %s", + strings.Join(ids, ", "), moderationStatus, + ) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { output, _ := c.Flags().GetString("output") - err := cmd.Confirm(c, "Would set moderation status of comment(s): %s to %s", strings.Join(ids, ", "), moderationStatus) - if err != nil { - utils.HandleCmdError(err, c) - return - } input := comment.NewComment( comment.WithIds(ids), comment.WithModerationStatus(moderationStatus), diff --git a/cmd/comment/update.go b/cmd/comment/update.go index a9a0ee6..acacf3a 100644 --- a/cmd/comment/update.go +++ b/cmd/comment/update.go @@ -5,6 +5,7 @@ package comment import ( "encoding/json" + "fmt" "io" "strings" @@ -49,6 +50,7 @@ var updateInSchema = &jsonschema.Schema{ Type: "string", Enum: []any{"json", "yaml", "silent"}, Description: pkg.SilentUsage, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -64,6 +66,9 @@ func init() { }, }, cobramcp.GenToolHandler( updateTool, func(input comment.Comment, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Update(writer) }, ), @@ -79,9 +84,8 @@ func init() { &viewerRating, "viewerRating", "r", "", vrUsage, ) updateCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + updateCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = updateCmd.MarkFlagRequired("id") - cmd.AddMutationFlags(updateCmd) } var updateCmd = &cobra.Command{ @@ -89,13 +93,12 @@ var updateCmd = &cobra.Command{ Short: updateShort, Long: updateLong, Example: updateExample, - Run: func(c *cobra.Command, args []string) { + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf("Would update comment: %s", strings.Join(ids, ", ")) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { output, _ := c.Flags().GetString("output") - err := cmd.Confirm(c, "Would update comment: %s", strings.Join(ids, ", ")) - if err != nil { - utils.HandleCmdError(err, c) - return - } input := comment.NewComment( comment.WithIds(ids), comment.WithCanRate(canRate), diff --git a/cmd/commentThread/insert.go b/cmd/commentThread/insert.go index ded734b..a7d9878 100644 --- a/cmd/commentThread/insert.go +++ b/cmd/commentThread/insert.go @@ -5,6 +5,7 @@ package commentThread import ( "encoding/json" + "fmt" "io" cobramcp "github.com/eat-pray-ai/cobra-mcp" @@ -42,6 +43,7 @@ var insertInSchema = &jsonschema.Schema{ Type: "string", Enum: []any{"json", "yaml", "silent"}, Description: pkg.SilentUsage, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -58,6 +60,9 @@ func init() { }, cobramcp.GenToolHandler( insertTool, func(input commentThread.CommentThread, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Insert(writer) }, ), @@ -70,11 +75,10 @@ func init() { insertCmd.Flags().StringVarP(&textOriginal, "textOriginal", "t", "", toUsage) insertCmd.Flags().StringVarP(&videoId, "videoId", "v", "", insertVidUsage) insertCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + insertCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = insertCmd.MarkFlagRequired("channelId") _ = insertCmd.MarkFlagRequired("textOriginal") _ = insertCmd.MarkFlagRequired("videoId") - cmd.AddMutationFlags(insertCmd) } var insertCmd = &cobra.Command{ @@ -82,13 +86,12 @@ var insertCmd = &cobra.Command{ Short: insertShort, Long: insertLong, Example: insertExample, - Run: func(c *cobra.Command, args []string) { + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf("Would insert comment thread on video: %s", videoId) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { output, _ := c.Flags().GetString("output") - err := cmd.Confirm(c, "Would insert comment thread") - if err != nil { - utils.HandleCmdError(err, c) - return - } input := commentThread.NewCommentThread( commentThread.WithAuthorChannelId(authorChannelId), commentThread.WithChannelId(channelId), diff --git a/cmd/flags.go b/cmd/flags.go deleted file mode 100644 index aeaa4f7..0000000 --- a/cmd/flags.go +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2026 eat-pray-ai & OpenWaygate -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "bufio" - "errors" - "fmt" - "os" - "strings" - - "github.com/eat-pray-ai/yutu/pkg/utils" - "github.com/spf13/cobra" - "golang.org/x/term" -) - -// ErrDryRun is returned by Confirm when --dry-run is set. -var ErrDryRun = utils.ErrDryRun - -// errAborted is returned by Confirm when the user declines the prompt. -var errAborted = errors.New("aborted") - -// AddMutationFlags registers --dry-run and --yes on the given command. -func AddMutationFlags(cmd *cobra.Command) { - cmd.Flags().Bool( - "dry-run", false, - "Print what would happen without calling the API", - ) - cmd.Flags().Bool( - "yes", false, - "Skip the confirmation prompt", - ) -} - -// DryRun returns whether --dry-run was passed. -func DryRun(cmd *cobra.Command) bool { - v, _ := cmd.Flags().GetBool("dry-run") - return v -} - -// Confirm checks dry-run and yes flags, then prompts the user for -// confirmation if running in an interactive terminal. -// -// Returns ErrDryRun if --dry-run is set, nil if --yes is set or the user -// confirms, and errAborted if the user declines. -func Confirm(cmd *cobra.Command, format string, args ...any) error { - isTTY := term.IsTerminal(int(os.Stdin.Fd())) - return confirmWithTTYOverride(cmd, isTTY, format, args...) -} - -// confirmWithTTYOverride is an internal helper that accepts a pre-computed -// isTTY value so tests can exercise the TTY and non-TTY paths without -// needing a real terminal. -func confirmWithTTYOverride(cmd *cobra.Command, isTTY bool, format string, args ...any) error { - msg := fmt.Sprintf(format, args...) - - // 1. --dry-run takes precedence over everything. - if DryRun(cmd) { - _, _ = fmt.Fprintln(cmd.OutOrStdout(), msg) - return ErrDryRun - } - - // 2. --yes skips the prompt entirely. - yes, _ := cmd.Flags().GetBool("yes") - if yes { - return nil - } - - // 3. Non-interactive stdin requires --yes. - if !isTTY { - return errors.New("terminal required for confirmation; use --yes to skip") - } - - // 4. Interactive prompt. - _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%s [y/N] ", msg) - - scanner := bufio.NewScanner(cmd.InOrStdin()) - if !scanner.Scan() { - return errAborted - } - answer := strings.TrimSpace(scanner.Text()) - - if answer == "y" || answer == "Y" { - return nil - } - return errAborted -} diff --git a/cmd/flags_test.go b/cmd/flags_test.go deleted file mode 100644 index 6580723..0000000 --- a/cmd/flags_test.go +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright 2026 eat-pray-ai & OpenWaygate -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "bytes" - "errors" - "strings" - "testing" - - "github.com/spf13/cobra" -) - -// newTestCmd creates a minimal cobra.Command with mutation flags registered. -func newTestCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "test", - Run: func(cmd *cobra.Command, args []string) {}, - } - AddMutationFlags(cmd) - return cmd -} - -func TestAddMutationFlags(t *testing.T) { - cmd := newTestCmd() - - dr := cmd.Flags().Lookup("dry-run") - if dr == nil { - t.Fatal("expected --dry-run flag to be registered") - } else if dr.DefValue != "false" { - t.Fatalf("expected --dry-run default to be false, got %s", dr.DefValue) - } - - yes := cmd.Flags().Lookup("yes") - if yes == nil { - t.Fatal("expected --yes flag to be registered") - } else if yes.DefValue != "false" { - t.Fatalf("expected --yes default to be false, got %s", yes.DefValue) - } -} - -func TestDryRun_DefaultFalse(t *testing.T) { - cmd := newTestCmd() - if DryRun(cmd) { - t.Fatal("expected DryRun to be false by default") - } -} - -func TestDryRun_SetTrue(t *testing.T) { - cmd := newTestCmd() - if err := cmd.Flags().Set("dry-run", "true"); err != nil { - t.Fatal(err) - } - if !DryRun(cmd) { - t.Fatal("expected DryRun to be true after setting flag") - } -} - -func TestConfirm_DryRun(t *testing.T) { - cmd := newTestCmd() - if err := cmd.Flags().Set("dry-run", "true"); err != nil { - t.Fatal(err) - } - - var stdout bytes.Buffer - cmd.SetOut(&stdout) - - err := Confirm(cmd, "would delete %s", "video123") - if !errors.Is(err, ErrDryRun) { - t.Fatalf("expected ErrDryRun, got %v", err) - } - - got := stdout.String() - if !strings.Contains(got, "would delete video123") { - t.Fatalf("expected message in stdout, got %q", got) - } -} - -func TestConfirm_Yes(t *testing.T) { - cmd := newTestCmd() - if err := cmd.Flags().Set("yes", "true"); err != nil { - t.Fatal(err) - } - - err := Confirm(cmd, "delete video?") - if err != nil { - t.Fatalf("expected nil, got %v", err) - } -} - -func TestConfirm_NonTTY(t *testing.T) { - cmd := newTestCmd() - cmd.SetIn(strings.NewReader("")) - - // Use the internal helper with isTTY=false to simulate non-TTY stdin. - err := confirmWithTTYOverride(cmd, false, "delete video?") - if err == nil { - t.Fatal("expected error for non-TTY stdin") - } - if !strings.Contains( - err.Error(), "terminal required for confirmation; use --yes to skip", - ) { - t.Fatalf("unexpected error message: %v", err) - } -} - -func TestConfirm_TTY_Yes(t *testing.T) { - cmd := newTestCmd() - cmd.SetIn(strings.NewReader("y\n")) - - var stderr bytes.Buffer - cmd.SetErr(&stderr) - - err := confirmWithTTYOverride(cmd, true, "delete %s?", "video123") - if err != nil { - t.Fatalf("expected nil, got %v", err) - } - - prompt := stderr.String() - if !strings.Contains(prompt, "delete video123?") { - t.Fatalf("expected prompt on stderr, got %q", prompt) - } - if !strings.Contains(prompt, "[y/N]") { - t.Fatalf("expected [y/N] in prompt, got %q", prompt) - } -} - -func TestConfirm_TTY_No(t *testing.T) { - cmd := newTestCmd() - cmd.SetIn(strings.NewReader("n\n")) - - var stderr bytes.Buffer - cmd.SetErr(&stderr) - - err := confirmWithTTYOverride(cmd, true, "delete video?") - if !errors.Is(err, errAborted) { - t.Fatalf("expected errAborted, got %v", err) - } -} - -func TestConfirm_TTY_Default(t *testing.T) { - cmd := newTestCmd() - cmd.SetIn(strings.NewReader("\n")) - - var stderr bytes.Buffer - cmd.SetErr(&stderr) - - err := confirmWithTTYOverride(cmd, true, "delete video?") - if !errors.Is(err, errAborted) { - t.Fatalf("expected errAborted for empty input (default N), got %v", err) - } -} - -func TestConfirm_DryRunTakesPrecedence(t *testing.T) { - cmd := newTestCmd() - if err := cmd.Flags().Set("dry-run", "true"); err != nil { - t.Fatal(err) - } - if err := cmd.Flags().Set("yes", "true"); err != nil { - t.Fatal(err) - } - - var stdout bytes.Buffer - cmd.SetOut(&stdout) - - err := Confirm(cmd, "would delete video") - if !errors.Is(err, ErrDryRun) { - t.Fatalf("expected ErrDryRun even with --yes, got %v", err) - } -} diff --git a/cmd/liveBroadcast/bind.go b/cmd/liveBroadcast/bind.go index b7fe7eb..aa65079 100644 --- a/cmd/liveBroadcast/bind.go +++ b/cmd/liveBroadcast/bind.go @@ -5,6 +5,7 @@ package liveBroadcast import ( "encoding/json" + "fmt" "io" "strings" @@ -50,6 +51,7 @@ var bindInSchema = &jsonschema.Schema{ Type: "string", Enum: []any{"json", "yaml", "silent"}, Description: pkg.SilentUsage, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -65,6 +67,9 @@ func init() { }, }, cobramcp.GenToolHandler( bindTool, func(input liveBroadcast.LiveBroadcast, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Bind(writer) }, ), @@ -87,9 +92,8 @@ func init() { pkg.PartsUsage, ) bindCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + bindCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = bindCmd.MarkFlagRequired("ids") - cmd.AddMutationFlags(bindCmd) } var bindCmd = &cobra.Command{ @@ -97,16 +101,15 @@ var bindCmd = &cobra.Command{ Short: bindShort, Long: bindLong, Example: bindExample, - Run: func(c *cobra.Command, args []string) { - output, _ := c.Flags().GetString("output") - err := cmd.Confirm( - c, "Would bind live broadcast(s) %s to stream %s", + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf( + "Would bind live broadcast(s) %s to stream %s", strings.Join(ids, ", "), streamId, ) - if err != nil { - utils.HandleCmdError(err, c) - return - } + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { + output, _ := c.Flags().GetString("output") input := liveBroadcast.NewLiveBroadcast( liveBroadcast.WithIds(ids), liveBroadcast.WithStreamId(streamId), diff --git a/cmd/liveBroadcast/delete.go b/cmd/liveBroadcast/delete.go index 5feda62..68d6cfa 100644 --- a/cmd/liveBroadcast/delete.go +++ b/cmd/liveBroadcast/delete.go @@ -4,6 +4,7 @@ package liveBroadcast import ( + "fmt" "io" "strings" @@ -40,6 +41,7 @@ var deleteInSchema = &jsonschema.Schema{ "on_behalf_of_content_owner_channel": { Type: "string", Description: obococUsage, }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -56,6 +58,9 @@ func init() { }, cobramcp.GenToolHandler( deleteTool, func(input liveBroadcast.LiveBroadcast, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Delete(writer) }, ), @@ -70,8 +75,8 @@ func init() { &onBehalfOfContentOwnerChannel, "onBehalfOfContentOwnerChannel", "B", "", obococUsage, ) + deleteCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = deleteCmd.MarkFlagRequired("ids") - cmd.AddMutationFlags(deleteCmd) } var deleteCmd = &cobra.Command{ @@ -79,14 +84,13 @@ var deleteCmd = &cobra.Command{ Short: deleteShort, Long: deleteLong, Example: deleteExample, - Run: func(c *cobra.Command, args []string) { - err := cmd.Confirm( - c, "Would delete live broadcast(s): %s", strings.Join(ids, ", "), + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf( + "Would delete live broadcast(s): %s", strings.Join(ids, ", "), ) - if err != nil { - utils.HandleCmdError(err, c) - return - } + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { input := liveBroadcast.NewLiveBroadcast( liveBroadcast.WithIds(ids), liveBroadcast.WithOnBehalfOfContentOwner(onBehalfOfContentOwner), diff --git a/cmd/liveBroadcast/insert.go b/cmd/liveBroadcast/insert.go index 8253434..f0465b9 100644 --- a/cmd/liveBroadcast/insert.go +++ b/cmd/liveBroadcast/insert.go @@ -5,6 +5,7 @@ package liveBroadcast import ( "encoding/json" + "fmt" "io" cobramcp "github.com/eat-pray-ai/cobra-mcp" @@ -52,6 +53,7 @@ var insertInSchema = &jsonschema.Schema{ Type: "string", Enum: []any{"json", "yaml", "silent"}, Description: pkg.SilentUsage, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -68,6 +70,9 @@ func init() { }, cobramcp.GenToolHandler( insertTool, func(input liveBroadcast.LiveBroadcast, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Insert(writer) }, ), @@ -95,10 +100,9 @@ func init() { pkg.PartsUsage, ) insertCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + insertCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = insertCmd.MarkFlagRequired("title") _ = insertCmd.MarkFlagRequired("scheduledStartTime") - cmd.AddMutationFlags(insertCmd) } var insertCmd = &cobra.Command{ @@ -106,13 +110,12 @@ var insertCmd = &cobra.Command{ Short: insertShort, Long: insertLong, Example: insertExample, - Run: func(c *cobra.Command, args []string) { + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf("Would create live broadcast: %s", title) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { output, _ := c.Flags().GetString("output") - err := cmd.Confirm(c, "Would create live broadcast: %s", title) - if err != nil { - utils.HandleCmdError(err, c) - return - } input := liveBroadcast.NewLiveBroadcast( liveBroadcast.WithTitle(title), liveBroadcast.WithDescription(description), diff --git a/cmd/liveBroadcast/insertCuepoint.go b/cmd/liveBroadcast/insertCuepoint.go index 5a75ed8..57034b1 100644 --- a/cmd/liveBroadcast/insertCuepoint.go +++ b/cmd/liveBroadcast/insertCuepoint.go @@ -5,6 +5,7 @@ package liveBroadcast import ( "encoding/json" + "fmt" "io" "strings" @@ -51,6 +52,7 @@ var insertCuepointInSchema = &jsonschema.Schema{ Type: "string", Enum: []any{"json", "yaml", "silent"}, Description: pkg.SilentUsage, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -67,6 +69,9 @@ func init() { }, cobramcp.GenToolHandler( insertCuepointTool, func(input liveBroadcast.LiveBroadcast, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.InsertCuepoint(writer) }, ), @@ -96,9 +101,8 @@ func init() { obococUsage, ) insertCuepointCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + insertCuepointCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = insertCuepointCmd.MarkFlagRequired("ids") - cmd.AddMutationFlags(insertCuepointCmd) } var insertCuepointCmd = &cobra.Command{ @@ -106,16 +110,15 @@ var insertCuepointCmd = &cobra.Command{ Short: insertCuepointShort, Long: insertCuepointLong, Example: insertCuepointExample, - Run: func(c *cobra.Command, args []string) { - output, _ := c.Flags().GetString("output") - err := cmd.Confirm( - c, "Would insert cuepoint into live broadcast(s): %s", + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf( + "Would insert cuepoint into live broadcast(s): %s", strings.Join(ids, ", "), ) - if err != nil { - utils.HandleCmdError(err, c) - return - } + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { + output, _ := c.Flags().GetString("output") input := liveBroadcast.NewLiveBroadcast( liveBroadcast.WithIds(ids), liveBroadcast.WithCueType(cueType), diff --git a/cmd/liveBroadcast/transition.go b/cmd/liveBroadcast/transition.go index d3354a8..2e99a60 100644 --- a/cmd/liveBroadcast/transition.go +++ b/cmd/liveBroadcast/transition.go @@ -5,6 +5,7 @@ package liveBroadcast import ( "encoding/json" + "fmt" "io" "strings" @@ -54,6 +55,7 @@ var transitionInSchema = &jsonschema.Schema{ Type: "string", Enum: []any{"json", "yaml", "silent"}, Description: pkg.SilentUsage, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -70,6 +72,9 @@ func init() { }, cobramcp.GenToolHandler( transitionTool, func(input liveBroadcast.LiveBroadcast, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Transition(writer) }, ), @@ -93,10 +98,9 @@ func init() { &parts, "parts", "p", []string{"id", "snippet", "status"}, pkg.PartsUsage, ) transitionCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + transitionCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = transitionCmd.MarkFlagRequired("ids") _ = transitionCmd.MarkFlagRequired("broadcastStatus") - cmd.AddMutationFlags(transitionCmd) } var transitionCmd = &cobra.Command{ @@ -104,16 +108,15 @@ var transitionCmd = &cobra.Command{ Short: transitionShort, Long: transitionLong, Example: transitionExample, - Run: func(c *cobra.Command, args []string) { - output, _ := c.Flags().GetString("output") - err := cmd.Confirm( - c, "Would transition live broadcast(s) %s to %s", + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf( + "Would transition live broadcast(s) %s to %s", strings.Join(ids, ", "), broadcastStatus, ) - if err != nil { - utils.HandleCmdError(err, c) - return - } + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { + output, _ := c.Flags().GetString("output") input := liveBroadcast.NewLiveBroadcast( liveBroadcast.WithIds(ids), liveBroadcast.WithBroadcastStatus(broadcastStatus), diff --git a/cmd/liveBroadcast/update.go b/cmd/liveBroadcast/update.go index 259f242..4699ba7 100644 --- a/cmd/liveBroadcast/update.go +++ b/cmd/liveBroadcast/update.go @@ -5,6 +5,7 @@ package liveBroadcast import ( "encoding/json" + "fmt" "io" "strings" @@ -53,6 +54,7 @@ var updateInSchema = &jsonschema.Schema{ Type: "string", Enum: []any{"json", "yaml", "silent"}, Description: pkg.SilentUsage, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -69,6 +71,9 @@ func init() { }, cobramcp.GenToolHandler( updateTool, func(input liveBroadcast.LiveBroadcast, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } input.MaxResults = 1 return input.Update(writer) }, @@ -94,9 +99,8 @@ func init() { obococUsage, ) updateCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + updateCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = updateCmd.MarkFlagRequired("id") - cmd.AddMutationFlags(updateCmd) } var updateCmd = &cobra.Command{ @@ -104,15 +108,14 @@ var updateCmd = &cobra.Command{ Short: updateShort, Long: updateLong, Example: updateExample, - Run: func(c *cobra.Command, args []string) { - output, _ := c.Flags().GetString("output") - err := cmd.Confirm( - c, "Would update live broadcast: %s", strings.Join(ids, ", "), + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf( + "Would update live broadcast: %s", strings.Join(ids, ", "), ) - if err != nil { - utils.HandleCmdError(err, c) - return - } + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { + output, _ := c.Flags().GetString("output") input := liveBroadcast.NewLiveBroadcast( liveBroadcast.WithIds(ids), liveBroadcast.WithTitle(title), diff --git a/cmd/liveChatBan/delete.go b/cmd/liveChatBan/delete.go index cec8171..fec9f2e 100644 --- a/cmd/liveChatBan/delete.go +++ b/cmd/liveChatBan/delete.go @@ -4,11 +4,13 @@ package liveChatBan import ( + "fmt" "io" "strings" cobramcp "github.com/eat-pray-ai/cobra-mcp" "github.com/eat-pray-ai/yutu/cmd" + "github.com/eat-pray-ai/yutu/pkg" "github.com/eat-pray-ai/yutu/pkg/liveChatBan" "github.com/eat-pray-ai/yutu/pkg/utils" "github.com/google/jsonschema-go/jsonschema" @@ -35,6 +37,7 @@ var deleteInSchema = &jsonschema.Schema{ Type: "array", Description: deleteIdsUsage, Items: &jsonschema.Schema{Type: "string"}, }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -50,6 +53,9 @@ func init() { }, }, cobramcp.GenToolHandler( deleteTool, func(input liveChatBan.LiveChatBan, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Delete(writer) }, ), @@ -57,8 +63,8 @@ func init() { liveChatBanCmd.AddCommand(deleteCmd) deleteCmd.Flags().StringSliceVarP(&ids, "ids", "i", []string{}, deleteIdsUsage) + deleteCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = deleteCmd.MarkFlagRequired("ids") - cmd.AddMutationFlags(deleteCmd) } var deleteCmd = &cobra.Command{ @@ -66,14 +72,13 @@ var deleteCmd = &cobra.Command{ Short: deleteShort, Long: deleteLong, Example: deleteExample, - Run: func(c *cobra.Command, args []string) { - err := cmd.Confirm( - c, "Would delete live chat ban(s): %s", strings.Join(ids, ", "), + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf( + "Would delete live chat ban(s): %s", strings.Join(ids, ", "), ) - if err != nil { - utils.HandleCmdError(err, c) - return - } + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { input := liveChatBan.NewLiveChatBan( liveChatBan.WithIds(ids), ) diff --git a/cmd/liveChatBan/insert.go b/cmd/liveChatBan/insert.go index a205091..445cb39 100644 --- a/cmd/liveChatBan/insert.go +++ b/cmd/liveChatBan/insert.go @@ -5,6 +5,7 @@ package liveChatBan import ( "encoding/json" + "fmt" "io" cobramcp "github.com/eat-pray-ai/cobra-mcp" @@ -43,6 +44,7 @@ var insertInSchema = &jsonschema.Schema{ Type: "string", Enum: []any{"json", "yaml", "silent"}, Description: pkg.SilentUsage, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -58,6 +60,9 @@ func init() { }, }, cobramcp.GenToolHandler( insertTool, func(input liveChatBan.LiveChatBan, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Insert(writer) }, ), @@ -76,11 +81,10 @@ func init() { &parts, "parts", "p", []string{"snippet"}, "Parts to include", ) insertCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + insertCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = insertCmd.MarkFlagRequired("liveChatId") _ = insertCmd.MarkFlagRequired("bannedUserChannelId") _ = insertCmd.MarkFlagRequired("banType") - cmd.AddMutationFlags(insertCmd) } var insertCmd = &cobra.Command{ @@ -88,15 +92,14 @@ var insertCmd = &cobra.Command{ Short: insertShort, Long: insertLong, Example: insertExample, - Run: func(c *cobra.Command, args []string) { - output, _ := c.Flags().GetString("output") - err := cmd.Confirm( - c, "Would ban user %s in live chat %s", bannedUserChannelId, liveChatId, + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf( + "Would ban user %s in live chat %s", bannedUserChannelId, liveChatId, ) - if err != nil { - utils.HandleCmdError(err, c) - return - } + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { + output, _ := c.Flags().GetString("output") input := liveChatBan.NewLiveChatBan( liveChatBan.WithLiveChatId(liveChatId), liveChatBan.WithBannedUserChannelId(bannedUserChannelId), diff --git a/cmd/liveChatMessage/delete.go b/cmd/liveChatMessage/delete.go index b781a35..2fe747a 100644 --- a/cmd/liveChatMessage/delete.go +++ b/cmd/liveChatMessage/delete.go @@ -4,11 +4,13 @@ package liveChatMessage import ( + "fmt" "io" "strings" cobramcp "github.com/eat-pray-ai/cobra-mcp" "github.com/eat-pray-ai/yutu/cmd" + "github.com/eat-pray-ai/yutu/pkg" "github.com/eat-pray-ai/yutu/pkg/liveChatMessage" "github.com/eat-pray-ai/yutu/pkg/utils" "github.com/google/jsonschema-go/jsonschema" @@ -35,6 +37,7 @@ var deleteInSchema = &jsonschema.Schema{ Type: "array", Description: deleteIdsUsage, Items: &jsonschema.Schema{Type: "string"}, }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -51,6 +54,9 @@ func init() { }, cobramcp.GenToolHandler( deleteTool, func(input liveChatMessage.LiveChatMessage, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Delete(writer) }, ), @@ -58,8 +64,8 @@ func init() { liveChatMessageCmd.AddCommand(deleteCmd) deleteCmd.Flags().StringSliceVarP(&ids, "ids", "i", []string{}, deleteIdsUsage) + deleteCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = deleteCmd.MarkFlagRequired("ids") - cmd.AddMutationFlags(deleteCmd) } var deleteCmd = &cobra.Command{ @@ -67,14 +73,13 @@ var deleteCmd = &cobra.Command{ Short: deleteShort, Long: deleteLong, Example: deleteExample, - Run: func(c *cobra.Command, args []string) { - err := cmd.Confirm( - c, "Would delete live chat message(s): %s", strings.Join(ids, ", "), + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf( + "Would delete live chat message(s): %s", strings.Join(ids, ", "), ) - if err != nil { - utils.HandleCmdError(err, c) - return - } + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { input := liveChatMessage.NewLiveChatMessage( liveChatMessage.WithIds(ids), ) diff --git a/cmd/liveChatMessage/insert.go b/cmd/liveChatMessage/insert.go index 005a3ce..35f20ee 100644 --- a/cmd/liveChatMessage/insert.go +++ b/cmd/liveChatMessage/insert.go @@ -5,6 +5,7 @@ package liveChatMessage import ( "encoding/json" + "fmt" "io" cobramcp "github.com/eat-pray-ai/cobra-mcp" @@ -39,6 +40,7 @@ var insertInSchema = &jsonschema.Schema{ Type: "string", Enum: []any{"json", "yaml", "silent"}, Description: pkg.SilentUsage, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -55,6 +57,9 @@ func init() { }, cobramcp.GenToolHandler( insertTool, func(input liveChatMessage.LiveChatMessage, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Insert(writer) }, ), @@ -67,10 +72,9 @@ func init() { &parts, "parts", "p", []string{"snippet"}, "Parts to include", ) insertCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + insertCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = insertCmd.MarkFlagRequired("liveChatId") _ = insertCmd.MarkFlagRequired("messageText") - cmd.AddMutationFlags(insertCmd) } var insertCmd = &cobra.Command{ @@ -78,15 +82,12 @@ var insertCmd = &cobra.Command{ Short: insertShort, Long: insertLong, Example: insertExample, - Run: func(c *cobra.Command, args []string) { + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf("Would send message to live chat %s", liveChatId) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { output, _ := c.Flags().GetString("output") - err := cmd.Confirm( - c, "Would send message to live chat %s", liveChatId, - ) - if err != nil { - utils.HandleCmdError(err, c) - return - } input := liveChatMessage.NewLiveChatMessage( liveChatMessage.WithLiveChatId(liveChatId), liveChatMessage.WithMessageText(messageText), diff --git a/cmd/liveChatMessage/list.go b/cmd/liveChatMessage/list.go index e6fd097..b2f8f2c 100644 --- a/cmd/liveChatMessage/list.go +++ b/cmd/liveChatMessage/list.go @@ -78,7 +78,6 @@ func init() { &parts, "parts", "p", []string{"snippet", "authorDetails"}, pkg.PartsUsage, ) listCmd.Flags().StringP("output", "o", "table", pkg.TableUsage) - _ = listCmd.MarkFlagRequired("liveChatId") } diff --git a/cmd/liveChatMessage/transition.go b/cmd/liveChatMessage/transition.go index ed42440..2c9daf7 100644 --- a/cmd/liveChatMessage/transition.go +++ b/cmd/liveChatMessage/transition.go @@ -5,6 +5,7 @@ package liveChatMessage import ( "encoding/json" + "fmt" "io" "strings" @@ -42,6 +43,7 @@ var transitionInSchema = &jsonschema.Schema{ Type: "string", Enum: []any{"json", "yaml", "silent"}, Description: pkg.SilentUsage, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -58,6 +60,9 @@ func init() { }, cobramcp.GenToolHandler( transitionTool, func(input liveChatMessage.LiveChatMessage, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Transition(writer) }, ), @@ -69,10 +74,9 @@ func init() { ) transitionCmd.Flags().StringVarP(&status, "status", "s", "", statusUsage) transitionCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + transitionCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = transitionCmd.MarkFlagRequired("ids") _ = transitionCmd.MarkFlagRequired("status") - cmd.AddMutationFlags(transitionCmd) } var transitionCmd = &cobra.Command{ @@ -80,16 +84,15 @@ var transitionCmd = &cobra.Command{ Short: transitionShort, Long: transitionLong, Example: transitionExample, - Run: func(c *cobra.Command, args []string) { - output, _ := c.Flags().GetString("output") - err := cmd.Confirm( - c, "Would transition live chat message(s) %s to status %s", + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf( + "Would transition live chat message(s) %s to status %s", strings.Join(ids, ", "), status, ) - if err != nil { - utils.HandleCmdError(err, c) - return - } + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { + output, _ := c.Flags().GetString("output") input := liveChatMessage.NewLiveChatMessage( liveChatMessage.WithIds(ids), liveChatMessage.WithStatus(status), diff --git a/cmd/liveChatModerator/delete.go b/cmd/liveChatModerator/delete.go index f16a6fd..9bd1fd0 100644 --- a/cmd/liveChatModerator/delete.go +++ b/cmd/liveChatModerator/delete.go @@ -4,11 +4,13 @@ package liveChatModerator import ( + "fmt" "io" "strings" cobramcp "github.com/eat-pray-ai/cobra-mcp" "github.com/eat-pray-ai/yutu/cmd" + "github.com/eat-pray-ai/yutu/pkg" "github.com/eat-pray-ai/yutu/pkg/liveChatModerator" "github.com/eat-pray-ai/yutu/pkg/utils" "github.com/google/jsonschema-go/jsonschema" @@ -35,6 +37,7 @@ var deleteInSchema = &jsonschema.Schema{ Type: "array", Description: deleteIdsUsage, Items: &jsonschema.Schema{Type: "string"}, }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -51,6 +54,9 @@ func init() { }, cobramcp.GenToolHandler( deleteTool, func(input liveChatModerator.LiveChatModerator, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Delete(writer) }, ), @@ -58,8 +64,8 @@ func init() { liveChatModeratorCmd.AddCommand(deleteCmd) deleteCmd.Flags().StringSliceVarP(&ids, "ids", "i", []string{}, deleteIdsUsage) + deleteCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = deleteCmd.MarkFlagRequired("ids") - cmd.AddMutationFlags(deleteCmd) } var deleteCmd = &cobra.Command{ @@ -67,14 +73,13 @@ var deleteCmd = &cobra.Command{ Short: deleteShort, Long: deleteLong, Example: deleteExample, - Run: func(c *cobra.Command, args []string) { - err := cmd.Confirm( - c, "Would delete live chat moderator(s): %s", strings.Join(ids, ", "), + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf( + "Would delete live chat moderator(s): %s", strings.Join(ids, ", "), ) - if err != nil { - utils.HandleCmdError(err, c) - return - } + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { input := liveChatModerator.NewLiveChatModerator( liveChatModerator.WithIds(ids), ) diff --git a/cmd/liveChatModerator/insert.go b/cmd/liveChatModerator/insert.go index b652783..ab9a9f0 100644 --- a/cmd/liveChatModerator/insert.go +++ b/cmd/liveChatModerator/insert.go @@ -5,6 +5,7 @@ package liveChatModerator import ( "encoding/json" + "fmt" "io" cobramcp "github.com/eat-pray-ai/cobra-mcp" @@ -39,6 +40,7 @@ var insertInSchema = &jsonschema.Schema{ Type: "string", Enum: []any{"json", "yaml", "silent"}, Description: pkg.SilentUsage, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -55,6 +57,9 @@ func init() { }, cobramcp.GenToolHandler( insertTool, func(input liveChatModerator.LiveChatModerator, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Insert(writer) }, ), @@ -69,10 +74,9 @@ func init() { &parts, "parts", "p", []string{"snippet"}, "Parts to include", ) insertCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + insertCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = insertCmd.MarkFlagRequired("liveChatId") _ = insertCmd.MarkFlagRequired("moderatorChannelId") - cmd.AddMutationFlags(insertCmd) } var insertCmd = &cobra.Command{ @@ -80,16 +84,14 @@ var insertCmd = &cobra.Command{ Short: insertShort, Long: insertLong, Example: insertExample, - Run: func(c *cobra.Command, args []string) { - output, _ := c.Flags().GetString("output") - err := cmd.Confirm( - c, "Would add moderator %s to live chat %s", moderatorChannelId, - liveChatId, + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf( + "Would add moderator %s to live chat %s", moderatorChannelId, liveChatId, ) - if err != nil { - utils.HandleCmdError(err, c) - return - } + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { + output, _ := c.Flags().GetString("output") input := liveChatModerator.NewLiveChatModerator( liveChatModerator.WithLiveChatId(liveChatId), liveChatModerator.WithModeratorChannelId(moderatorChannelId), diff --git a/cmd/liveChatModerator/list.go b/cmd/liveChatModerator/list.go index 4111a90..3bcf623 100644 --- a/cmd/liveChatModerator/list.go +++ b/cmd/liveChatModerator/list.go @@ -74,7 +74,6 @@ func init() { &parts, "parts", "p", []string{"snippet"}, pkg.PartsUsage, ) listCmd.Flags().StringP("output", "o", "table", pkg.TableUsage) - _ = listCmd.MarkFlagRequired("liveChatId") } diff --git a/cmd/liveStream/delete.go b/cmd/liveStream/delete.go index b2faedf..7129cbb 100644 --- a/cmd/liveStream/delete.go +++ b/cmd/liveStream/delete.go @@ -4,6 +4,7 @@ package liveStream import ( + "fmt" "io" "strings" @@ -40,6 +41,7 @@ var deleteInSchema = &jsonschema.Schema{ "on_behalf_of_content_owner_channel": { Type: "string", Description: obococUsage, }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -55,6 +57,9 @@ func init() { }, }, cobramcp.GenToolHandler( deleteTool, func(input liveStream.LiveStream, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Delete(writer) }, ), @@ -69,8 +74,8 @@ func init() { &onBehalfOfContentOwnerChannel, "onBehalfOfContentOwnerChannel", "B", "", obococUsage, ) + deleteCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = deleteCmd.MarkFlagRequired("ids") - cmd.AddMutationFlags(deleteCmd) } var deleteCmd = &cobra.Command{ @@ -78,14 +83,13 @@ var deleteCmd = &cobra.Command{ Short: deleteShort, Long: deleteLong, Example: deleteExample, - Run: func(c *cobra.Command, args []string) { - err := cmd.Confirm( - c, "Would delete live stream(s): %s", strings.Join(ids, ", "), + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf( + "Would delete live stream(s): %s", strings.Join(ids, ", "), ) - if err != nil { - utils.HandleCmdError(err, c) - return - } + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { input := liveStream.NewLiveStream( liveStream.WithIds(ids), liveStream.WithOnBehalfOfContentOwner(onBehalfOfContentOwner), diff --git a/cmd/liveStream/insert.go b/cmd/liveStream/insert.go index 01f40d3..724c9e3 100644 --- a/cmd/liveStream/insert.go +++ b/cmd/liveStream/insert.go @@ -5,6 +5,7 @@ package liveStream import ( "encoding/json" + "fmt" "io" cobramcp "github.com/eat-pray-ai/cobra-mcp" @@ -58,6 +59,7 @@ var insertInSchema = &jsonschema.Schema{ Type: "string", Enum: []any{"json", "yaml", "silent"}, Description: pkg.SilentUsage, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -73,6 +75,9 @@ func init() { }, }, cobramcp.GenToolHandler( insertTool, func(input liveStream.LiveStream, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Insert(writer) }, ), @@ -95,11 +100,10 @@ func init() { &parts, "parts", "p", []string{"snippet", "cdn", "status"}, pkg.PartsUsage, ) insertCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + insertCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = insertCmd.MarkFlagRequired("title") _ = insertCmd.MarkFlagRequired("ingestionType") _ = insertCmd.MarkFlagRequired("resolution") - cmd.AddMutationFlags(insertCmd) } var insertCmd = &cobra.Command{ @@ -107,13 +111,12 @@ var insertCmd = &cobra.Command{ Short: insertShort, Long: insertLong, Example: insertExample, - Run: func(c *cobra.Command, args []string) { + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf("Would create live stream: %s", title) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { output, _ := c.Flags().GetString("output") - err := cmd.Confirm(c, "Would create live stream: %s", title) - if err != nil { - utils.HandleCmdError(err, c) - return - } input := liveStream.NewLiveStream( liveStream.WithTitle(title), liveStream.WithDescription(description), diff --git a/cmd/liveStream/update.go b/cmd/liveStream/update.go index b1cad62..20ef051 100644 --- a/cmd/liveStream/update.go +++ b/cmd/liveStream/update.go @@ -5,6 +5,7 @@ package liveStream import ( "encoding/json" + "fmt" "io" "strings" @@ -59,6 +60,7 @@ var updateInSchema = &jsonschema.Schema{ Type: "string", Enum: []any{"json", "yaml", "silent"}, Description: pkg.SilentUsage, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -74,6 +76,9 @@ func init() { }, }, cobramcp.GenToolHandler( updateTool, func(input liveStream.LiveStream, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } input.MaxResults = 1 return input.Update(writer) }, @@ -95,9 +100,8 @@ func init() { obococUsage, ) updateCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + updateCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = updateCmd.MarkFlagRequired("id") - cmd.AddMutationFlags(updateCmd) } var updateCmd = &cobra.Command{ @@ -105,15 +109,12 @@ var updateCmd = &cobra.Command{ Short: updateShort, Long: updateLong, Example: updateExample, - Run: func(c *cobra.Command, args []string) { + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf("Would update live stream: %s", strings.Join(ids, ", ")) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { output, _ := c.Flags().GetString("output") - err := cmd.Confirm( - c, "Would update live stream: %s", strings.Join(ids, ", "), - ) - if err != nil { - utils.HandleCmdError(err, c) - return - } input := liveStream.NewLiveStream( liveStream.WithIds(ids), liveStream.WithTitle(title), diff --git a/cmd/membershipsLevel/membershipsLevel.go b/cmd/membershipsLevel/membershipsLevel.go index 9f27d7b..4abe487 100644 --- a/cmd/membershipsLevel/membershipsLevel.go +++ b/cmd/membershipsLevel/membershipsLevel.go @@ -14,7 +14,7 @@ const ( ) var ( - parts []string + parts []string ) var membershipsLevelCmd = &cobra.Command{ diff --git a/cmd/playlist/delete.go b/cmd/playlist/delete.go index bbd26de..f59d3c8 100644 --- a/cmd/playlist/delete.go +++ b/cmd/playlist/delete.go @@ -4,6 +4,7 @@ package playlist import ( + "fmt" "io" "strings" @@ -37,6 +38,7 @@ var deleteInSchema = &jsonschema.Schema{ Items: &jsonschema.Schema{Type: "string"}, }, "on_behalf_of_content_owner": {Type: "string", Description: pkg.OBOCOUsage}, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -52,6 +54,9 @@ func init() { }, }, cobramcp.GenToolHandler( deleteTool, func(input playlist.Playlist, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Delete(writer) }, ), @@ -62,9 +67,8 @@ func init() { deleteCmd.Flags().StringVarP( &onBehalfOfContentOwner, "onBehalfOfContentOwner", "b", "", pkg.OBOCOUsage, ) - + deleteCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = deleteCmd.MarkFlagRequired("ids") - cmd.AddMutationFlags(deleteCmd) } var deleteCmd = &cobra.Command{ @@ -72,12 +76,11 @@ var deleteCmd = &cobra.Command{ Short: deleteShort, Long: deleteLong, Example: deleteExample, - Run: func(c *cobra.Command, args []string) { - err := cmd.Confirm(c, "Would delete playlist(s): %s", strings.Join(ids, ", ")) - if err != nil { - utils.HandleCmdError(err, c) - return - } + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf("Would delete playlist(s): %s", strings.Join(ids, ", ")) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { input := playlist.NewPlaylist( playlist.WithIds(ids), playlist.WithOnBehalfOfContentOwner(onBehalfOfContentOwner), diff --git a/cmd/playlist/insert.go b/cmd/playlist/insert.go index 879b671..235511b 100644 --- a/cmd/playlist/insert.go +++ b/cmd/playlist/insert.go @@ -5,6 +5,7 @@ package playlist import ( "encoding/json" + "fmt" "io" cobramcp "github.com/eat-pray-ai/cobra-mcp" @@ -50,6 +51,7 @@ var insertInSchema = &jsonschema.Schema{ Type: "string", Enum: []any{"json", "yaml", "silent"}, Description: pkg.SilentUsage, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -65,6 +67,9 @@ func init() { }, }, cobramcp.GenToolHandler( insertTool, func(input playlist.Playlist, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Insert(writer) }, ), @@ -78,11 +83,10 @@ func init() { insertCmd.Flags().StringVarP(&channelId, "channelId", "c", "", insertCidUsage) insertCmd.Flags().StringVarP(&privacy, "privacy", "p", "", privacyUsage) insertCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + insertCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = insertCmd.MarkFlagRequired("title") _ = insertCmd.MarkFlagRequired("channelId") _ = insertCmd.MarkFlagRequired("privacy") - cmd.AddMutationFlags(insertCmd) } var insertCmd = &cobra.Command{ @@ -90,13 +94,12 @@ var insertCmd = &cobra.Command{ Short: insertShort, Long: insertLong, Example: insertExample, - Run: func(c *cobra.Command, args []string) { + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf("Would insert playlist: %s", title) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { output, _ := c.Flags().GetString("output") - err := cmd.Confirm(c, "Would insert playlist") - if err != nil { - utils.HandleCmdError(err, c) - return - } input := playlist.NewPlaylist( playlist.WithTitle(title), playlist.WithDescription(description), diff --git a/cmd/playlist/update.go b/cmd/playlist/update.go index 8b3abed..cef028a 100644 --- a/cmd/playlist/update.go +++ b/cmd/playlist/update.go @@ -5,6 +5,7 @@ package playlist import ( "encoding/json" + "fmt" "io" "strings" @@ -56,6 +57,7 @@ var updateInSchema = &jsonschema.Schema{ Type: "string", Enum: []any{"json", "yaml", "silent"}, Description: pkg.SilentUsage, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -71,6 +73,9 @@ func init() { }, }, cobramcp.GenToolHandler( updateTool, func(input playlist.Playlist, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Update(writer) }, ), @@ -84,9 +89,8 @@ func init() { updateCmd.Flags().StringVarP(&language, "language", "l", "", languageUsage) updateCmd.Flags().StringVarP(&privacy, "privacy", "p", "", privacyUsage) updateCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + updateCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = updateCmd.MarkFlagRequired("id") - cmd.AddMutationFlags(updateCmd) } var updateCmd = &cobra.Command{ @@ -94,13 +98,12 @@ var updateCmd = &cobra.Command{ Short: updateShort, Long: updateLong, Example: updateExample, - Run: func(c *cobra.Command, args []string) { + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf("Would update playlist: %s", strings.Join(ids, ", ")) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { output, _ := c.Flags().GetString("output") - confirmErr := cmd.Confirm(c, "Would update playlist: %s", strings.Join(ids, ", ")) - if confirmErr != nil { - utils.HandleCmdError(confirmErr, c) - return - } p := playlist.NewPlaylist( playlist.WithIds(ids), playlist.WithTitle(title), @@ -110,7 +113,6 @@ var updateCmd = &cobra.Command{ playlist.WithPrivacy(privacy), playlist.WithOutput(output), ) - err := p.Update(c.OutOrStdout()) - utils.HandleCmdError(err, c) + utils.HandleCmdError(p.Update(c.OutOrStdout()), c) }, } diff --git a/cmd/playlistImage/delete.go b/cmd/playlistImage/delete.go index 257c21f..b2bf0f0 100644 --- a/cmd/playlistImage/delete.go +++ b/cmd/playlistImage/delete.go @@ -4,6 +4,7 @@ package playlistImage import ( + "fmt" "io" "strings" @@ -36,6 +37,7 @@ var deleteInSchema = &jsonschema.Schema{ Items: &jsonschema.Schema{Type: "string"}, }, "on_behalf_of_content_owner": {Type: "string", Description: pkg.OBOCOUsage}, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -52,6 +54,9 @@ func init() { }, cobramcp.GenToolHandler( deleteTool, func(input playlistImage.PlaylistImage, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Delete(writer) }, ), @@ -62,9 +67,8 @@ func init() { deleteCmd.Flags().StringVarP( &onBehalfOfContentOwner, "onBehalfOfContentOwner", "b", "", pkg.OBOCOUsage, ) - + deleteCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = deleteCmd.MarkFlagRequired("ids") - cmd.AddMutationFlags(deleteCmd) } var deleteCmd = &cobra.Command{ @@ -72,14 +76,13 @@ var deleteCmd = &cobra.Command{ Short: deleteShort, Long: deleteLong, Example: deleteExample, - Run: func(c *cobra.Command, args []string) { - err := cmd.Confirm( - c, "Would delete playlist image(s): %s", strings.Join(ids, ", "), + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf( + "Would delete playlist image(s): %s", strings.Join(ids, ", "), ) - if err != nil { - utils.HandleCmdError(err, c) - return - } + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { input := playlistImage.NewPlaylistImage( playlistImage.WithIds(ids), playlistImage.WithOnBehalfOfContentOwner(onBehalfOfContentOwner), diff --git a/cmd/playlistImage/insert.go b/cmd/playlistImage/insert.go index c3035cf..d8cd161 100644 --- a/cmd/playlistImage/insert.go +++ b/cmd/playlistImage/insert.go @@ -5,6 +5,7 @@ package playlistImage import ( "encoding/json" + "fmt" "io" cobramcp "github.com/eat-pray-ai/cobra-mcp" @@ -56,6 +57,7 @@ var insertInSchema = &jsonschema.Schema{ Type: "string", Enum: []any{"json", "yaml", "silent"}, Description: pkg.SilentUsage, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -72,6 +74,9 @@ func init() { }, cobramcp.GenToolHandler( insertTool, func(input playlistImage.PlaylistImage, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Insert(writer) }, ), @@ -91,10 +96,9 @@ func init() { &onBehalfOfContentOwnerChannel, "onBehalfOfContentOwnerChannel", "B", "", pkg.OBOCOCUsage, ) - + insertCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = insertCmd.MarkFlagRequired("file") _ = insertCmd.MarkFlagRequired("playlistId") - cmd.AddMutationFlags(insertCmd) } var insertCmd = &cobra.Command{ @@ -102,13 +106,12 @@ var insertCmd = &cobra.Command{ Short: insertShort, Long: insertLong, Example: insertExample, - Run: func(c *cobra.Command, args []string) { + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf("Would insert playlist image: %s", file) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { output, _ := c.Flags().GetString("output") - confirmErr := cmd.Confirm(c, "Would insert playlist image") - if confirmErr != nil { - utils.HandleCmdError(confirmErr, c) - return - } pi := playlistImage.NewPlaylistImage( playlistImage.WithFile(file), playlistImage.WithPlaylistId(playlistId), diff --git a/cmd/playlistImage/update.go b/cmd/playlistImage/update.go index 5261ef8..514d19a 100644 --- a/cmd/playlistImage/update.go +++ b/cmd/playlistImage/update.go @@ -5,6 +5,7 @@ package playlistImage import ( "encoding/json" + "fmt" "io" cobramcp "github.com/eat-pray-ai/cobra-mcp" @@ -51,6 +52,7 @@ var updateInSchema = &jsonschema.Schema{ Type: "string", Enum: []any{"json", "yaml", "silent"}, Description: pkg.SilentUsage, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -67,6 +69,9 @@ func init() { }, cobramcp.GenToolHandler( updateTool, func(input playlistImage.PlaylistImage, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Update(writer) }, ), @@ -85,9 +90,8 @@ func init() { &onBehalfOfContentOwnerChannel, "onBehalfOfContentOwnerChannel", "B", "", pkg.OBOCOCUsage, ) - + updateCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = updateCmd.MarkFlagRequired("playlistId") - cmd.AddMutationFlags(updateCmd) } var updateCmd = &cobra.Command{ @@ -95,15 +99,14 @@ var updateCmd = &cobra.Command{ Short: updateShort, Long: updateLong, Example: updateExample, - Run: func(c *cobra.Command, args []string) { - output, _ := c.Flags().GetString("output") - err := cmd.Confirm( - c, "Would update playlist image for playlist: %s", playlistId, + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf( + "Would update playlist image for playlist: %s", playlistId, ) - if err != nil { - utils.HandleCmdError(err, c) - return - } + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { + output, _ := c.Flags().GetString("output") input := playlistImage.NewPlaylistImage( playlistImage.WithPlaylistId(playlistId), playlistImage.WithType(type_), diff --git a/cmd/playlistItem/delete.go b/cmd/playlistItem/delete.go index a58590d..74c3ebb 100644 --- a/cmd/playlistItem/delete.go +++ b/cmd/playlistItem/delete.go @@ -4,6 +4,7 @@ package playlistItem import ( + "fmt" "io" "strings" @@ -37,6 +38,7 @@ var deleteInSchema = &jsonschema.Schema{ Items: &jsonschema.Schema{Type: "string"}, }, "on_behalf_of_content_owner": {Type: "string", Description: pkg.OBOCOUsage}, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -52,6 +54,9 @@ func init() { }, }, cobramcp.GenToolHandler( deleteTool, func(input playlistItem.PlaylistItem, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Delete(writer) }, ), @@ -62,9 +67,8 @@ func init() { deleteCmd.Flags().StringVarP( &onBehalfOfContentOwner, "onBehalfOfContentOwner", "b", "", pkg.OBOCOUsage, ) - + deleteCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = deleteCmd.MarkFlagRequired("ids") - cmd.AddMutationFlags(deleteCmd) } var deleteCmd = &cobra.Command{ @@ -72,14 +76,13 @@ var deleteCmd = &cobra.Command{ Short: deleteShort, Long: deleteLong, Example: deleteExample, - Run: func(c *cobra.Command, args []string) { - err := cmd.Confirm( - c, "Would delete playlist item(s): %s", strings.Join(ids, ", "), + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf( + "Would delete playlist item(s): %s", strings.Join(ids, ", "), ) - if err != nil { - utils.HandleCmdError(err, c) - return - } + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { input := playlistItem.NewPlaylistItem( playlistItem.WithIds(ids), playlistItem.WithOnBehalfOfContentOwner(onBehalfOfContentOwner), diff --git a/cmd/playlistItem/insert.go b/cmd/playlistItem/insert.go index 92b776b..45cd112 100644 --- a/cmd/playlistItem/insert.go +++ b/cmd/playlistItem/insert.go @@ -5,6 +5,7 @@ package playlistItem import ( "encoding/json" + "fmt" "io" cobramcp "github.com/eat-pray-ai/cobra-mcp" @@ -52,6 +53,7 @@ var insertInSchema = &jsonschema.Schema{ Type: "string", Enum: []any{"json", "yaml", "silent"}, Description: pkg.SilentUsage, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -67,6 +69,9 @@ func init() { }, }, cobramcp.GenToolHandler( insertTool, func(input playlistItem.PlaylistItem, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Insert(writer) }, ), @@ -88,11 +93,10 @@ func init() { &onBehalfOfContentOwner, "onBehalfOfContentOwner", "b", "", pkg.OBOCOUsage, ) insertCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + insertCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = insertCmd.MarkFlagRequired("kind") _ = insertCmd.MarkFlagRequired("playlistId") _ = insertCmd.MarkFlagRequired("channelId") - cmd.AddMutationFlags(insertCmd) } var insertCmd = &cobra.Command{ @@ -100,13 +104,12 @@ var insertCmd = &cobra.Command{ Short: insertShort, Long: insertLong, Example: insertExample, - Run: func(c *cobra.Command, args []string) { + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf("Would insert playlist item to playlist: %s", playlistId) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { output, _ := c.Flags().GetString("output") - err := cmd.Confirm(c, "Would insert playlist item") - if err != nil { - utils.HandleCmdError(err, c) - return - } input := playlistItem.NewPlaylistItem( playlistItem.WithTitle(title), playlistItem.WithDescription(description), diff --git a/cmd/playlistItem/update.go b/cmd/playlistItem/update.go index 22a3543..e81ab8c 100644 --- a/cmd/playlistItem/update.go +++ b/cmd/playlistItem/update.go @@ -5,6 +5,7 @@ package playlistItem import ( "encoding/json" + "fmt" "io" "strings" @@ -50,6 +51,7 @@ var updateInSchema = &jsonschema.Schema{ Type: "string", Enum: []any{"json", "yaml", "silent"}, Description: pkg.SilentUsage, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -65,6 +67,9 @@ func init() { }, }, cobramcp.GenToolHandler( updateTool, func(input playlistItem.PlaylistItem, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Update(writer) }, ), @@ -79,9 +84,8 @@ func init() { &onBehalfOfContentOwner, "onBehalfOfContentOwner", "b", "", pkg.OBOCOUsage, ) updateCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + updateCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = updateCmd.MarkFlagRequired("id") - cmd.AddMutationFlags(updateCmd) } var updateCmd = &cobra.Command{ @@ -89,15 +93,12 @@ var updateCmd = &cobra.Command{ Short: updateShort, Long: updateLong, Example: updateExample, - Run: func(c *cobra.Command, args []string) { + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf("Would update playlist item: %s", strings.Join(ids, ", ")) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { output, _ := c.Flags().GetString("output") - err := cmd.Confirm( - c, "Would update playlist item: %s", strings.Join(ids, ", "), - ) - if err != nil { - utils.HandleCmdError(err, c) - return - } input := playlistItem.NewPlaylistItem( playlistItem.WithIds(ids), playlistItem.WithTitle(title), diff --git a/cmd/subscription/delete.go b/cmd/subscription/delete.go index aa6b789..9a90151 100644 --- a/cmd/subscription/delete.go +++ b/cmd/subscription/delete.go @@ -4,11 +4,13 @@ package subscription import ( + "fmt" "io" "strings" cobramcp "github.com/eat-pray-ai/cobra-mcp" "github.com/eat-pray-ai/yutu/cmd" + "github.com/eat-pray-ai/yutu/pkg" "github.com/eat-pray-ai/yutu/pkg/subscription" "github.com/eat-pray-ai/yutu/pkg/utils" "github.com/google/jsonschema-go/jsonschema" @@ -35,6 +37,7 @@ var deleteInSchema = &jsonschema.Schema{ Type: "array", Description: deleteIdsUsage, Items: &jsonschema.Schema{Type: "string"}, }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -50,6 +53,9 @@ func init() { }, }, cobramcp.GenToolHandler( deleteTool, func(input subscription.Subscription, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Delete(writer) }, ), @@ -57,8 +63,8 @@ func init() { subscriptionCmd.AddCommand(deleteCmd) deleteCmd.Flags().StringSliceVarP(&ids, "ids", "i", []string{}, deleteIdsUsage) + deleteCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = deleteCmd.MarkFlagRequired("ids") - cmd.AddMutationFlags(deleteCmd) } var deleteCmd = &cobra.Command{ @@ -66,12 +72,13 @@ var deleteCmd = &cobra.Command{ Short: deleteShort, Long: deleteLong, Example: deleteExample, - Run: func(c *cobra.Command, args []string) { - err := cmd.Confirm(c, "Would delete subscription(s): %s", strings.Join(ids, ", ")) - if err != nil { - utils.HandleCmdError(err, c) - return - } + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf( + "Would delete subscription(s): %s", strings.Join(ids, ", "), + ) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { input := subscription.NewSubscription( subscription.WithIds(ids), ) diff --git a/cmd/subscription/insert.go b/cmd/subscription/insert.go index 8b05370..3b02d8b 100644 --- a/cmd/subscription/insert.go +++ b/cmd/subscription/insert.go @@ -5,6 +5,7 @@ package subscription import ( "encoding/json" + "fmt" "io" cobramcp "github.com/eat-pray-ai/cobra-mcp" @@ -40,6 +41,7 @@ var insertInSchema = &jsonschema.Schema{ Type: "string", Enum: []any{"json", "yaml", "silent"}, Description: pkg.SilentUsage, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -55,6 +57,9 @@ func init() { }, }, cobramcp.GenToolHandler( insertTool, func(input subscription.Subscription, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Insert(writer) }, ), @@ -68,10 +73,9 @@ func init() { insertCmd.Flags().StringVarP(&channelId, "channelId", "c", "", insertCidUsage) insertCmd.Flags().StringVarP(&title, "title", "t", "", titleUsage) insertCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + insertCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = insertCmd.MarkFlagRequired("subscriberChannelId") _ = insertCmd.MarkFlagRequired("channelId") - cmd.AddMutationFlags(insertCmd) } var insertCmd = &cobra.Command{ @@ -79,13 +83,12 @@ var insertCmd = &cobra.Command{ Short: insertShort, Long: insertLong, Example: insertExample, - Run: func(c *cobra.Command, args []string) { + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf("Would insert subscription to channel: %s", channelId) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { output, _ := c.Flags().GetString("output") - err := cmd.Confirm(c, "Would insert subscription") - if err != nil { - utils.HandleCmdError(err, c) - return - } input := subscription.NewSubscription( subscription.WithSubscriberChannelId(subscriberChannelId), subscription.WithDescription(description), diff --git a/cmd/subscription/subscription.go b/cmd/subscription/subscription.go index 02f6220..073af6e 100644 --- a/cmd/subscription/subscription.go +++ b/cmd/subscription/subscription.go @@ -14,7 +14,7 @@ const ( scidUsage = "Subscriber's channel id" descUsage = "Description of the subscription" fcidUsage = "Return the subscriptions to the subset of these channels that the authenticated user is subscribed to" - forUsage = "mine|myRecentSubscribers|mySubscribers" + forUsage = "mine|myRecentSubscribers|mySubscribers" orderUsage = "subscriptionOrderUnspecified|relevance|unread|alphabetical" titleUsage = "Title of the subscription" ) diff --git a/cmd/thirdPartyLink/delete.go b/cmd/thirdPartyLink/delete.go index ce699c0..7ebc3d3 100644 --- a/cmd/thirdPartyLink/delete.go +++ b/cmd/thirdPartyLink/delete.go @@ -4,10 +4,12 @@ package thirdPartyLink import ( + "fmt" "io" cobramcp "github.com/eat-pray-ai/cobra-mcp" "github.com/eat-pray-ai/yutu/cmd" + "github.com/eat-pray-ai/yutu/pkg" "github.com/eat-pray-ai/yutu/pkg/thirdPartyLink" "github.com/eat-pray-ai/yutu/pkg/utils" "github.com/google/jsonschema-go/jsonschema" @@ -27,9 +29,13 @@ var deleteInSchema = &jsonschema.Schema{ Type: "object", Required: []string{"linking_token", "type"}, Properties: map[string]*jsonschema.Schema{ - "linking_token": {Type: "string", Description: ltUsage}, - "type": {Type: "string", Description: typeUsage, Enum: []any{"linkUnspecified", "channelToStoreLink"}}, + "linking_token": {Type: "string", Description: ltUsage}, + "type": { + Type: "string", Description: typeUsage, + Enum: []any{"linkUnspecified", "channelToStoreLink"}, + }, "external_channel_id": {Type: "string", Description: extCidUsage}, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -44,7 +50,11 @@ func init() { ReadOnlyHint: false, }, }, cobramcp.GenToolHandler( - deleteTool, func(input thirdPartyLink.ThirdPartyLink, writer io.Writer) error { + deleteTool, + func(input thirdPartyLink.ThirdPartyLink, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Delete(writer) }, ), @@ -53,11 +63,12 @@ func init() { deleteCmd.Flags().StringVarP(&linkingToken, "linkingToken", "l", "", ltUsage) deleteCmd.Flags().StringVarP(&linkType, "type", "t", "", typeUsage) - deleteCmd.Flags().StringVarP(&externalChannelId, "externalChannelId", "e", "", extCidUsage) - + deleteCmd.Flags().StringVarP( + &externalChannelId, "externalChannelId", "e", "", extCidUsage, + ) + deleteCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = deleteCmd.MarkFlagRequired("linkingToken") _ = deleteCmd.MarkFlagRequired("type") - cmd.AddMutationFlags(deleteCmd) } var deleteCmd = &cobra.Command{ @@ -65,12 +76,11 @@ var deleteCmd = &cobra.Command{ Short: deleteShort, Long: deleteLong, Example: deleteExample, - Run: func(c *cobra.Command, args []string) { - err := cmd.Confirm(c, "Would delete third-party link: %s", linkingToken) - if err != nil { - utils.HandleCmdError(err, c) - return - } + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf("Would delete third-party link: %s", linkingToken) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { input := thirdPartyLink.NewThirdPartyLink( thirdPartyLink.WithLinkingToken(linkingToken), thirdPartyLink.WithType(linkType), @@ -78,4 +88,4 @@ var deleteCmd = &cobra.Command{ ) utils.HandleCmdError(input.Delete(c.OutOrStdout()), c) }, -} \ No newline at end of file +} diff --git a/cmd/thirdPartyLink/insert.go b/cmd/thirdPartyLink/insert.go index 269ec30..a633bd1 100644 --- a/cmd/thirdPartyLink/insert.go +++ b/cmd/thirdPartyLink/insert.go @@ -5,6 +5,7 @@ package thirdPartyLink import ( "encoding/json" + "fmt" "io" cobramcp "github.com/eat-pray-ai/cobra-mcp" @@ -42,6 +43,7 @@ var insertInSchema = &jsonschema.Schema{ Type: "string", Enum: []any{"json", "yaml", "silent"}, Description: pkg.SilentUsage, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -56,7 +58,11 @@ func init() { ReadOnlyHint: false, }, }, cobramcp.GenToolHandler( - insertTool, func(input thirdPartyLink.ThirdPartyLink, writer io.Writer) error { + insertTool, + func(input thirdPartyLink.ThirdPartyLink, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Insert(writer) }, ), @@ -66,15 +72,16 @@ func init() { insertCmd.Flags().StringVarP(&linkingToken, "linkingToken", "l", "", ltUsage) insertCmd.Flags().StringVarP(&linkType, "type", "t", "", typeUsage) insertCmd.Flags().StringVarP(&linkStatus, "linkStatus", "s", "", statusUsage) - insertCmd.Flags().StringVarP(&externalChannelId, "externalChannelId", "e", "", extCidUsage) + insertCmd.Flags().StringVarP( + &externalChannelId, "externalChannelId", "e", "", extCidUsage, + ) insertCmd.Flags().StringSliceVarP( &parts, "parts", "p", []string{"snippet", "status"}, pkg.PartsUsage, ) insertCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + insertCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = insertCmd.MarkFlagRequired("linkingToken") _ = insertCmd.MarkFlagRequired("type") - cmd.AddMutationFlags(insertCmd) } var insertCmd = &cobra.Command{ @@ -82,13 +89,12 @@ var insertCmd = &cobra.Command{ Short: insertShort, Long: insertLong, Example: insertExample, - Run: func(c *cobra.Command, args []string) { + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf("Would insert third-party link: %s", linkingToken) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { output, _ := c.Flags().GetString("output") - err := cmd.Confirm(c, "Would insert third-party link") - if err != nil { - utils.HandleCmdError(err, c) - return - } input := thirdPartyLink.NewThirdPartyLink( thirdPartyLink.WithLinkingToken(linkingToken), thirdPartyLink.WithType(linkType), @@ -99,4 +105,4 @@ var insertCmd = &cobra.Command{ ) utils.HandleCmdError(input.Insert(c.OutOrStdout()), c) }, -} \ No newline at end of file +} diff --git a/cmd/thirdPartyLink/list.go b/cmd/thirdPartyLink/list.go index 776392d..7ccc403 100644 --- a/cmd/thirdPartyLink/list.go +++ b/cmd/thirdPartyLink/list.go @@ -33,8 +33,8 @@ var listInSchema = &jsonschema.Schema{ Type: "object", Required: []string{}, Properties: map[string]*jsonschema.Schema{ - "linking_token": {Type: "string", Description: ltUsage}, - "type": {Type: "string", Description: typeUsage, Enum: []any{"linkUnspecified", "channelToStoreLink"}}, + "linking_token": {Type: "string", Description: ltUsage}, + "type": {Type: "string", Description: typeUsage, Enum: []any{"linkUnspecified", "channelToStoreLink"}}, "external_channel_id": {Type: "string", Description: extCidUsage}, "parts": { Type: "array", Description: pkg.PartsUsage, @@ -59,7 +59,8 @@ func init() { ReadOnlyHint: true, }, }, cobramcp.GenToolHandler( - listTool, func(input thirdPartyLink.ThirdPartyLink, writer io.Writer) error { + listTool, + func(input thirdPartyLink.ThirdPartyLink, writer io.Writer) error { return input.List(writer) }, ), @@ -68,7 +69,9 @@ func init() { listCmd.Flags().StringVarP(&linkingToken, "linkingToken", "l", "", ltUsage) listCmd.Flags().StringVarP(&linkType, "type", "t", "", typeUsage) - listCmd.Flags().StringVarP(&externalChannelId, "externalChannelId", "e", "", extCidUsage) + listCmd.Flags().StringVarP( + &externalChannelId, "externalChannelId", "e", "", extCidUsage, + ) listCmd.Flags().StringSliceVarP( &parts, "parts", "p", []string{"snippet", "status"}, pkg.PartsUsage, ) @@ -91,4 +94,4 @@ var listCmd = &cobra.Command{ ) utils.HandleCmdError(input.List(cmd.OutOrStdout()), cmd) }, -} \ No newline at end of file +} diff --git a/cmd/thirdPartyLink/thirdPartyLink.go b/cmd/thirdPartyLink/thirdPartyLink.go index 07b38d8..207bc81 100644 --- a/cmd/thirdPartyLink/thirdPartyLink.go +++ b/cmd/thirdPartyLink/thirdPartyLink.go @@ -9,12 +9,12 @@ import ( ) const ( - short = "Manage YouTube third-party links" - long = "Manage YouTube third-party links. Use this tool to list, insert, update, or delete links between a YouTube channel and a third-party service." - ltUsage = "Linking token that identifies the YouTube account and channel" - typeUsage = "Type of the link: linkUnspecified|channelToStoreLink" - statusUsage = "Status of the link: unknown|failed|pending|linked" - extCidUsage = "Channel ID to which changes should be applied, for delegation" + short = "Manage YouTube third-party links" + long = "Manage YouTube third-party links. Use this tool to list, insert, update, or delete links between a YouTube channel and a third-party service." + ltUsage = "Linking token that identifies the YouTube account and channel" + typeUsage = "Type of the link: linkUnspecified|channelToStoreLink" + statusUsage = "Status of the link: unknown|failed|pending|linked" + extCidUsage = "Channel ID to which changes should be applied, for delegation" ) var ( @@ -36,4 +36,4 @@ var thirdPartyLinkCmd = &cobra.Command{ func init() { cmd.RootCmd.AddCommand(thirdPartyLinkCmd) -} \ No newline at end of file +} diff --git a/cmd/thirdPartyLink/update.go b/cmd/thirdPartyLink/update.go index 88f2a2f..ff0ff1f 100644 --- a/cmd/thirdPartyLink/update.go +++ b/cmd/thirdPartyLink/update.go @@ -5,6 +5,7 @@ package thirdPartyLink import ( "encoding/json" + "fmt" "io" cobramcp "github.com/eat-pray-ai/cobra-mcp" @@ -44,6 +45,7 @@ var updateInSchema = &jsonschema.Schema{ Type: "string", Enum: []any{"json", "yaml", "silent"}, Description: pkg.SilentUsage, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -58,7 +60,11 @@ func init() { ReadOnlyHint: false, }, }, cobramcp.GenToolHandler( - updateTool, func(input thirdPartyLink.ThirdPartyLink, writer io.Writer) error { + updateTool, + func(input thirdPartyLink.ThirdPartyLink, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Update(writer) }, ), @@ -68,14 +74,15 @@ func init() { updateCmd.Flags().StringVarP(&linkingToken, "linkingToken", "l", "", ltUsage) updateCmd.Flags().StringVarP(&linkType, "type", "t", "", typeUsage) updateCmd.Flags().StringVarP(&linkStatus, "linkStatus", "s", "", statusUsage) - updateCmd.Flags().StringVarP(&externalChannelId, "externalChannelId", "e", "", extCidUsage) + updateCmd.Flags().StringVarP( + &externalChannelId, "externalChannelId", "e", "", extCidUsage, + ) updateCmd.Flags().StringSliceVarP( &parts, "parts", "p", []string{"snippet", "status"}, pkg.PartsUsage, ) updateCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + updateCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = updateCmd.MarkFlagRequired("linkingToken") - cmd.AddMutationFlags(updateCmd) } var updateCmd = &cobra.Command{ @@ -83,13 +90,12 @@ var updateCmd = &cobra.Command{ Short: updateShort, Long: updateLong, Example: updateExample, - Run: func(c *cobra.Command, args []string) { + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf("Would update third-party link: %s", linkingToken) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { output, _ := c.Flags().GetString("output") - err := cmd.Confirm(c, "Would update third-party link: %s", linkingToken) - if err != nil { - utils.HandleCmdError(err, c) - return - } input := thirdPartyLink.NewThirdPartyLink( thirdPartyLink.WithLinkingToken(linkingToken), thirdPartyLink.WithType(linkType), @@ -100,4 +106,4 @@ var updateCmd = &cobra.Command{ ) utils.HandleCmdError(input.Update(c.OutOrStdout()), c) }, -} \ No newline at end of file +} diff --git a/cmd/thumbnail/set.go b/cmd/thumbnail/set.go index 5188042..c671ef4 100644 --- a/cmd/thumbnail/set.go +++ b/cmd/thumbnail/set.go @@ -5,6 +5,7 @@ package thumbnail import ( "encoding/json" + "fmt" "io" cobramcp "github.com/eat-pray-ai/cobra-mcp" @@ -37,6 +38,7 @@ var setInSchema = &jsonschema.Schema{ Type: "string", Enum: []any{"json", "yaml", "silent"}, Description: pkg.SilentUsage, Default: json.RawMessage(`"yaml"`), }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -52,6 +54,9 @@ func init() { }, }, cobramcp.GenToolHandler( setTool, func(input thumbnail.Thumbnail, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Set(writer) }, ), @@ -61,10 +66,9 @@ func init() { setCmd.Flags().StringVarP(&file, "file", "f", "", fileUsage) setCmd.Flags().StringVarP(&videoId, "videoId", "v", "", vidUsage) setCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + setCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = setCmd.MarkFlagRequired("file") _ = setCmd.MarkFlagRequired("videoId") - cmd.AddMutationFlags(setCmd) } var setCmd = &cobra.Command{ @@ -72,13 +76,12 @@ var setCmd = &cobra.Command{ Short: setShort, Long: setLong, Example: setExample, - Run: func(c *cobra.Command, args []string) { + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf("Would set thumbnail for video: %s", videoId) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { output, _ := c.Flags().GetString("output") - err := cmd.Confirm(c, "Would set thumbnail for video: %s", videoId) - if err != nil { - utils.HandleCmdError(err, c) - return - } input := thumbnail.NewThumbnail( thumbnail.WithFile(file), thumbnail.WithVideoId(videoId), diff --git a/cmd/video/delete.go b/cmd/video/delete.go index 340b56d..bb1fee6 100644 --- a/cmd/video/delete.go +++ b/cmd/video/delete.go @@ -4,11 +4,13 @@ package video import ( + "fmt" "io" "strings" cobramcp "github.com/eat-pray-ai/cobra-mcp" "github.com/eat-pray-ai/yutu/cmd" + "github.com/eat-pray-ai/yutu/pkg" "github.com/eat-pray-ai/yutu/pkg/utils" "github.com/eat-pray-ai/yutu/pkg/video" "github.com/google/jsonschema-go/jsonschema" @@ -35,6 +37,7 @@ var deleteInSchema = &jsonschema.Schema{ Type: "array", Description: deleteIdsUsage, Items: &jsonschema.Schema{Type: "string"}, }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -50,6 +53,9 @@ func init() { }, }, cobramcp.GenToolHandler( deleteTool, func(input video.Video, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Delete(writer) }, ), @@ -57,8 +63,8 @@ func init() { videoCmd.AddCommand(deleteCmd) deleteCmd.Flags().StringSliceVarP(&ids, "ids", "i", []string{}, deleteIdsUsage) + deleteCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = deleteCmd.MarkFlagRequired("ids") - cmd.AddMutationFlags(deleteCmd) } var deleteCmd = &cobra.Command{ @@ -66,12 +72,11 @@ var deleteCmd = &cobra.Command{ Short: deleteShort, Long: deleteLong, Example: deleteExample, - Run: func(c *cobra.Command, args []string) { - err := cmd.Confirm(c, "Would delete video(s): %s", strings.Join(ids, ", ")) - if err != nil { - utils.HandleCmdError(err, c) - return - } + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf("Would delete video(s): %s", strings.Join(ids, ", ")) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { input := video.NewVideo(video.WithIds(ids)) utils.HandleCmdError(input.Delete(c.OutOrStdout()), c) }, diff --git a/cmd/video/getRating.go b/cmd/video/getRating.go index f987e67..f3c2e48 100644 --- a/cmd/video/getRating.go +++ b/cmd/video/getRating.go @@ -67,7 +67,6 @@ func init() { &onBehalfOfContentOwner, "onBehalfOfContentOwner", "b", "", pkg.OBOCOUsage, ) getRatingCmd.Flags().StringP("output", "o", "", pkg.TableUsage) - _ = getRatingCmd.MarkFlagRequired("ids") } diff --git a/cmd/video/insert.go b/cmd/video/insert.go index a685073..a58a140 100644 --- a/cmd/video/insert.go +++ b/cmd/video/insert.go @@ -5,6 +5,7 @@ package video import ( "encoding/json" + "fmt" "io" cobramcp "github.com/eat-pray-ai/cobra-mcp" @@ -63,6 +64,7 @@ var insertInSchema = &jsonschema.Schema{ "stabilize": {Type: "boolean", Description: stabilizeUsage}, "notify_subscribers": {Type: "boolean", Description: nsUsage}, "public_stats_viewable": {Type: "boolean", Description: psvUsage}, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, "on_behalf_of_content_owner": { Type: "string", @@ -91,6 +93,9 @@ func init() { }, }, cobramcp.GenToolHandler( insertTool, func(input video.Video, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Insert(writer) }, ), @@ -135,11 +140,10 @@ func init() { pkg.OBOCOCUsage, ) insertCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + insertCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = insertCmd.MarkFlagRequired("file") _ = insertCmd.MarkFlagRequired("categoryId") _ = insertCmd.MarkFlagRequired("privacy") - cmd.AddMutationFlags(insertCmd) } var insertCmd = &cobra.Command{ @@ -147,13 +151,12 @@ var insertCmd = &cobra.Command{ Short: insertShort, Long: insertLong, Example: insertExample, - Run: func(c *cobra.Command, args []string) { + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf("Would insert video: %s", file) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { output, _ := c.Flags().GetString("output") - err := cmd.Confirm(c, "Would insert video") - if err != nil { - utils.HandleCmdError(err, c) - return - } input := video.NewVideo( video.WithAutoLevels(autoLevels), video.WithFile(file), diff --git a/cmd/video/rate.go b/cmd/video/rate.go index 13f90b5..76af76d 100644 --- a/cmd/video/rate.go +++ b/cmd/video/rate.go @@ -4,11 +4,13 @@ package video import ( + "fmt" "io" "strings" cobramcp "github.com/eat-pray-ai/cobra-mcp" "github.com/eat-pray-ai/yutu/cmd" + "github.com/eat-pray-ai/yutu/pkg" "github.com/eat-pray-ai/yutu/pkg/utils" "github.com/eat-pray-ai/yutu/pkg/video" "github.com/google/jsonschema-go/jsonschema" @@ -42,6 +44,7 @@ var rateInSchema = &jsonschema.Schema{ Type: "string", Description: rateRUsage, Enum: []any{"like", "dislike", "none"}, }, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -57,6 +60,9 @@ func init() { }, }, cobramcp.GenToolHandler( rateTool, func(input video.Video, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Rate(writer) }, ), @@ -65,10 +71,9 @@ func init() { rateCmd.Flags().StringSliceVarP(&ids, "ids", "i", []string{}, rateIdsUsage) rateCmd.Flags().StringVarP(&rating, "rating", "r", "", rateRUsage) - + rateCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = rateCmd.MarkFlagRequired("ids") _ = rateCmd.MarkFlagRequired("rating") - cmd.AddMutationFlags(rateCmd) } var rateCmd = &cobra.Command{ @@ -76,12 +81,13 @@ var rateCmd = &cobra.Command{ Short: rateShort, Long: rateLong, Example: rateExample, - Run: func(c *cobra.Command, args []string) { - err := cmd.Confirm(c, "Would rate video(s): %s as %s", strings.Join(ids, ", "), rating) - if err != nil { - utils.HandleCmdError(err, c) - return - } + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf( + "Would rate video(s): %s as %s", strings.Join(ids, ", "), rating, + ) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { input := video.NewVideo( video.WithIds(ids), video.WithRating(rating), diff --git a/cmd/video/reportAbuse.go b/cmd/video/reportAbuse.go index 8b230a4..952b807 100644 --- a/cmd/video/reportAbuse.go +++ b/cmd/video/reportAbuse.go @@ -4,6 +4,7 @@ package video import ( + "fmt" "io" "strings" @@ -44,6 +45,7 @@ var reportAbuseInSchema = &jsonschema.Schema{ "comments": {Type: "string", Description: commentsUsage}, "language": {Type: "string", Description: raLangUsage}, "on_behalf_of_content_owner": {Type: "string", Description: pkg.OBOCOUsage}, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -60,6 +62,9 @@ func init() { }, }, cobramcp.GenToolHandler( reportAbuseTool, func(input video.Video, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.ReportAbuse(writer) }, ), @@ -80,10 +85,9 @@ func init() { reportAbuseCmd.Flags().StringVarP( &onBehalfOfContentOwner, "onBehalfOfContentOwner", "b", "", pkg.OBOCOUsage, ) - + reportAbuseCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = reportAbuseCmd.MarkFlagRequired("ids") _ = reportAbuseCmd.MarkFlagRequired("reasonId") - cmd.AddMutationFlags(reportAbuseCmd) } var reportAbuseCmd = &cobra.Command{ @@ -91,12 +95,13 @@ var reportAbuseCmd = &cobra.Command{ Short: reportAbuseShort, Long: reportAbuseLong, Example: reportAbuseExample, - Run: func(c *cobra.Command, args []string) { - err := cmd.Confirm(c, "Would report abuse on video(s): %s", strings.Join(ids, ", ")) - if err != nil { - utils.HandleCmdError(err, c) - return - } + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf( + "Would report abuse on video(s): %s", strings.Join(ids, ", "), + ) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { input := video.NewVideo( video.WithIds(ids), video.WithReasonId(reasonId), diff --git a/cmd/video/update.go b/cmd/video/update.go index 737cb22..3f46410 100644 --- a/cmd/video/update.go +++ b/cmd/video/update.go @@ -5,6 +5,7 @@ package video import ( "encoding/json" + "fmt" "io" "strings" @@ -64,6 +65,7 @@ var updateInSchema = &jsonschema.Schema{ "embeddable": {Type: "boolean", Description: embeddableUsage}, "contains_synthetic_media": {Type: "boolean", Description: csmUsage}, "recording_date": {Type: "string", Description: rdUsage}, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, "output": { Type: "string", Enum: []any{"json", "yaml", "silent"}, Description: pkg.SilentUsage, Default: json.RawMessage(`"yaml"`), @@ -83,6 +85,9 @@ func init() { }, }, cobramcp.GenToolHandler( updateTool, func(input video.Video, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } input.MaxResults = 1 return input.Update(writer) }, @@ -109,9 +114,8 @@ func init() { ) updateCmd.Flags().StringVarP(&recordingDate, "recordingDate", "D", "", rdUsage) updateCmd.Flags().StringP("output", "o", "", pkg.SilentUsage) - + updateCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = updateCmd.MarkFlagRequired("id") - cmd.AddMutationFlags(updateCmd) } var updateCmd = &cobra.Command{ @@ -119,13 +123,12 @@ var updateCmd = &cobra.Command{ Short: updateShort, Long: updateLong, Example: updateExample, - Run: func(c *cobra.Command, args []string) { + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf("Would update video: %s", strings.Join(ids, ", ")) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { output, _ := c.Flags().GetString("output") - err := cmd.Confirm(c, "Would update video: %s", strings.Join(ids, ", ")) - if err != nil { - utils.HandleCmdError(err, c) - return - } input := video.NewVideo( video.WithIds(ids), video.WithTitle(title), diff --git a/cmd/videoAbuseReportReason/videoAbuseReportReason.go b/cmd/videoAbuseReportReason/videoAbuseReportReason.go index 8d5a71c..7ace601 100644 --- a/cmd/videoAbuseReportReason/videoAbuseReportReason.go +++ b/cmd/videoAbuseReportReason/videoAbuseReportReason.go @@ -15,8 +15,8 @@ const ( ) var ( - hl string - parts []string + hl string + parts []string ) var videoAbuseReportReasonCmd = &cobra.Command{ diff --git a/cmd/watermark/set.go b/cmd/watermark/set.go index 9f22415..9e57818 100644 --- a/cmd/watermark/set.go +++ b/cmd/watermark/set.go @@ -4,6 +4,7 @@ package watermark import ( + "fmt" "io" cobramcp "github.com/eat-pray-ai/cobra-mcp" @@ -40,6 +41,7 @@ var setInSchema = &jsonschema.Schema{ }, "duration_ms": {Type: "number", Description: dmUsage}, "offset_ms": {Type: "number", Description: omUsage}, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, "offset_type": { Type: "string", Description: otUsage, Enum: []any{"offsetFromStart", "offsetFromEnd"}, @@ -60,6 +62,9 @@ func init() { }, }, cobramcp.GenToolHandler( setTool, func(input watermark.Watermark, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Set(writer) }, ), @@ -77,10 +82,9 @@ func init() { setCmd.Flags().StringVarP( &onBehalfOfContentOwner, "onBehalfOfContentOwner", "b", "", pkg.OBOCOUsage, ) - + setCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = setCmd.MarkFlagRequired("channelId") _ = setCmd.MarkFlagRequired("file") - cmd.AddMutationFlags(setCmd) } var setCmd = &cobra.Command{ @@ -88,12 +92,11 @@ var setCmd = &cobra.Command{ Short: setShort, Long: setLong, Example: setExample, - Run: func(c *cobra.Command, args []string) { - err := cmd.Confirm(c, "Would set watermark for channel: %s", channelId) - if err != nil { - utils.HandleCmdError(err, c) - return - } + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf("Would set watermark for channel: %s", channelId) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { input := watermark.NewWatermark( watermark.WithChannelId(channelId), watermark.WithFile(file), diff --git a/cmd/watermark/unset.go b/cmd/watermark/unset.go index 952ca40..673572a 100644 --- a/cmd/watermark/unset.go +++ b/cmd/watermark/unset.go @@ -4,10 +4,12 @@ package watermark import ( + "fmt" "io" cobramcp "github.com/eat-pray-ai/cobra-mcp" "github.com/eat-pray-ai/yutu/cmd" + "github.com/eat-pray-ai/yutu/pkg" "github.com/eat-pray-ai/yutu/pkg/utils" "github.com/eat-pray-ai/yutu/pkg/watermark" "github.com/google/jsonschema-go/jsonschema" @@ -28,6 +30,7 @@ var unsetInSchema = &jsonschema.Schema{ Required: []string{"channel_id"}, Properties: map[string]*jsonschema.Schema{ "channel_id": {Type: "string", Description: cidUsage}, + "confirmed": {Type: "boolean", Description: pkg.ConfirmedUsage}, }, } @@ -43,6 +46,9 @@ func init() { }, }, cobramcp.GenToolHandler( unsetTool, func(input watermark.Watermark, writer io.Writer) error { + if !input.Confirmed { + return utils.ErrNotConfirmed + } return input.Unset(writer) }, ), @@ -50,8 +56,8 @@ func init() { watermarkCmd.AddCommand(unsetCmd) unsetCmd.Flags().StringVarP(&channelId, "channelId", "c", "", cidUsage) + unsetCmd.Flags().Bool("yes", false, pkg.ConfirmedUsage) _ = unsetCmd.MarkFlagRequired("channelId") - cmd.AddMutationFlags(unsetCmd) } var unsetCmd = &cobra.Command{ @@ -59,12 +65,11 @@ var unsetCmd = &cobra.Command{ Short: unsetShort, Long: unsetLong, Example: unsetExample, - Run: func(c *cobra.Command, args []string) { - err := cmd.Confirm(c, "Would unset watermark for channel: %s", channelId) - if err != nil { - utils.HandleCmdError(err, c) - return - } + PreRunE: func(c *cobra.Command, _ []string) error { + msg := fmt.Sprintf("Would unset watermark for channel: %s", channelId) + return utils.ConfirmPreRun(c, msg) + }, + Run: func(c *cobra.Command, _ []string) { input := watermark.NewWatermark(watermark.WithChannelId(channelId)) utils.HandleCmdError(input.Unset(c.OutOrStdout()), c) }, diff --git a/pkg/common/common.go b/pkg/common/common.go index bcaa919..f71d354 100644 --- a/pkg/common/common.go +++ b/pkg/common/common.go @@ -33,6 +33,7 @@ type Fields struct { Service *youtube.Service `yaml:"-" json:"-"` RedirectURL string `yaml:"-" json:"-"` Ids []string `yaml:"ids" json:"ids,omitempty"` + Confirmed bool `yaml:"-" json:"confirmed,omitempty"` MaxResults int64 `yaml:"max_results" json:"max_results,omitempty"` Hl string `yaml:"hl" json:"hl,omitempty"` ChannelId string `yaml:"channel_id" json:"channel_id,omitempty"` diff --git a/pkg/constants.go b/pkg/constants.go index aa3eab0..2b7418b 100644 --- a/pkg/constants.go +++ b/pkg/constants.go @@ -8,16 +8,16 @@ import ( ) const ( - PartsUsage = "Comma separated parts" - MRUsage = "The maximum number of items that should be returned, 0 for no limit" - TableUsage = "json|yaml|table" - SilentUsage = "json|yaml|silent" - JsonMIME = "application/json" - PerPage = 20 - OBOUsage = "ID of the YouTube account that the content owner is acting on behalf of" - OBOCOUsage = "ID of the content owner, for YouTube content partners" - OBOCOCUsage = "YouTube channel ID linked to the content owner" - + PartsUsage = "Comma separated parts" + MRUsage = "The maximum number of items that should be returned, 0 for no limit" + TableUsage = "json|yaml|table" + SilentUsage = "json|yaml|silent" + JsonMIME = "application/json" + PerPage = 20 + OBOUsage = "ID of the YouTube account that the content owner is acting on behalf of" + OBOCOUsage = "ID of the content owner, for YouTube content partners" + OBOCOCUsage = "YouTube channel ID linked to the content owner" + ConfirmedUsage = "Confirm the operation" getWdFailed = "failed to get working directory" openRootFailed = "failed to open root directory" ) diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 438c295..f14d568 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -4,6 +4,7 @@ package utils import ( + "bufio" "crypto/rand" "encoding/base64" "encoding/json" @@ -25,11 +26,11 @@ import ( "gopkg.in/yaml.v3" ) -var isInteractive = func(writer io.Writer) bool { +var IsInteractive = func(v any) bool { if os.Getenv("CI") != "" { return false } - if f, ok := writer.(*os.File); ok { + if f, ok := v.(*os.File); ok { return term.IsTerminal(int(f.Fd())) } return false @@ -37,7 +38,7 @@ var isInteractive = func(writer io.Writer) bool { func PrintJSON(data any, writer io.Writer) { var marshalled []byte - if isInteractive(writer) { + if IsInteractive(writer) { marshalled, _ = json.MarshalIndent(data, "", " ") } else { marshalled, _ = json.Marshal(data) @@ -117,11 +118,33 @@ func ExtractHl(uri string) string { return "" } -// ErrDryRun is returned when --dry-run is set; HandleCmdError silently ignores it. -var ErrDryRun = errors.New("dry run") +var ErrNotConfirmed = errors.New("operation not confirmed (pass confirm: true or rerun with --yes)") + +func ConfirmPreRun(cmd *cobra.Command, msg string) error { + if yes, _ := cmd.Flags().GetBool("yes"); yes { + return nil + } + + if !IsInteractive(cmd.InOrStdin()) { + _, _ = fmt.Fprintln(cmd.ErrOrStderr(), msg) + return ErrNotConfirmed + } + + _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%s [y/N] ", msg) + + scanner := bufio.NewScanner(cmd.InOrStdin()) + if !scanner.Scan() { + return ErrNotConfirmed + } + answer := strings.TrimSpace(scanner.Text()) + if answer != "y" && answer != "Y" { + return ErrNotConfirmed + } + return nil +} func HandleCmdError(err error, cmd *cobra.Command) { - if err == nil || errors.Is(err, ErrDryRun) { + if err == nil { return } _ = cmd.Help() diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go index 559dcb7..be97e09 100644 --- a/pkg/utils/utils_test.go +++ b/pkg/utils/utils_test.go @@ -9,6 +9,7 @@ import ( "io" "os" "reflect" + "strings" "testing" "github.com/spf13/cobra" @@ -171,8 +172,8 @@ func TestIsInteractive(t *testing.T) { if tt.ci != "" { t.Setenv("CI", tt.ci) } - if got := isInteractive(tt.writer); got != tt.want { - t.Errorf("isInteractive() = %v, want %v", got, tt.want) + if got := IsInteractive(tt.writer); got != tt.want { + t.Errorf("IsInteractive() = %v, want %v", got, tt.want) } }) } @@ -213,9 +214,9 @@ func TestPrintJSON(t *testing.T) { for _, tt := range tests { t.Run( tt.name, func(t *testing.T) { - orig := isInteractive - isInteractive = func(io.Writer) bool { return tt.interactive } - defer func() { isInteractive = orig }() + orig := IsInteractive + IsInteractive = func(any) bool { return tt.interactive } + defer func() { IsInteractive = orig }() writer := &bytes.Buffer{} PrintJSON(tt.data, writer) @@ -391,13 +392,84 @@ func TestRandomStage(t *testing.T) { } } -func TestHandleCmdError_DryRun(t *testing.T) { - c := &cobra.Command{Use: "test"} - var stderr bytes.Buffer - c.SetErr(&stderr) - HandleCmdError(ErrDryRun, c) - if stderr.Len() != 0 { - t.Errorf("expected no error output for ErrDryRun, got %q", stderr.String()) + +func TestConfirmPreRun(t *testing.T) { + tests := []struct { + name string + yes bool + input string + interactive bool + wantErr error + }{ + { + name: "yes flag bypasses prompt", + yes: true, + wantErr: nil, + }, + { + name: "non-interactive without yes flag", + yes: false, + interactive: false, + wantErr: ErrNotConfirmed, + }, + { + name: "interactive confirm with y", + yes: false, + interactive: true, + input: "y\n", + wantErr: nil, + }, + { + name: "interactive confirm with Y", + yes: false, + interactive: true, + input: "Y\n", + wantErr: nil, + }, + { + name: "interactive deny with n", + yes: false, + interactive: true, + input: "n\n", + wantErr: ErrNotConfirmed, + }, + { + name: "interactive deny with empty", + yes: false, + interactive: true, + input: "\n", + wantErr: ErrNotConfirmed, + }, + { + name: "interactive deny with EOF", + yes: false, + interactive: true, + input: "", + wantErr: ErrNotConfirmed, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + orig := IsInteractive + IsInteractive = func(any) bool { return tt.interactive } + defer func() { IsInteractive = orig }() + + cmd := &cobra.Command{Use: "test"} + cmd.Flags().Bool("yes", false, "") + if tt.yes { + _ = cmd.Flags().Set("yes", "true") + } + + var errBuf bytes.Buffer + cmd.SetErr(&errBuf) + cmd.SetIn(strings.NewReader(tt.input)) + + err := ConfirmPreRun(cmd, "Would do something") + if err != tt.wantErr { + t.Errorf("ConfirmPreRun() error = %v, want %v", err, tt.wantErr) + } + }) } }