@@ -64,7 +64,7 @@ const (
6464var DriveAddComment = common.Shortcut {
6565 Service : "drive" ,
6666 Command : "+add-comment" ,
67- Description : "Add a full-document or local comment to doc/docx/sheet, also supports wiki URL resolving to doc/docx/sheet" ,
67+ Description : "Add a comment to doc/docx/sheet/slides , also supports wiki URL resolving to doc/docx/sheet/slides " ,
6868 Risk : "write" ,
6969 Scopes : []string {
7070 "docx:document:readonly" ,
@@ -73,12 +73,12 @@ var DriveAddComment = common.Shortcut{
7373 },
7474 AuthTypes : []string {"user" , "bot" },
7575 Flags : []common.Flag {
76- {Name : "doc" , Desc : "document URL/token, sheet URL, or wiki URL that resolves to doc/docx/sheet" , Required : true },
77- {Name : "type" , Desc : "document type: doc, docx, sheet (required when --doc is a bare token; auto-detected for URLs)" , Enum : []string {"doc" , "docx" , "sheet" }},
76+ {Name : "doc" , Desc : "document URL/token, sheet/slides URL, or wiki URL that resolves to doc/docx/sheet/slides " , Required : true },
77+ {Name : "type" , Desc : "document type: doc, docx, sheet, slides (required when --doc is a bare token; auto-detected for URLs)" , Enum : []string {"doc" , "docx" , "sheet" , "slides " }},
7878 {Name : "content" , Desc : "reply_elements JSON string" , Required : true },
7979 {Name : "full-comment" , Type : "bool" , Desc : "create a full-document comment; also the default when no location is provided" },
8080 {Name : "selection-with-ellipsis" , Desc : "target content locator (plain text or 'start...end')" },
81- {Name : "block-id" , Desc : "for docx: anchor block ID; for sheet: <sheetId>!<cell> (e.g. a281f9!D6)" },
81+ {Name : "block-id" , Desc : "for docx: anchor block ID; for sheet: <sheetId>!<cell> (e.g. a281f9!D6); for slides: <slide-block-type>!<xml-id> (e.g. shape!bPq) " },
8282 },
8383 Validate : func (ctx context.Context , runtime * common.RuntimeContext ) error {
8484 docRef , err := parseCommentDocRef (runtime .Str ("doc" ), runtime .Str ("type" ))
@@ -104,6 +104,18 @@ var DriveAddComment = common.Shortcut{
104104 }
105105 return nil
106106 }
107+ if docRef .Kind == "slides" {
108+ if _ , _ , err := parseSlidesBlockRef (runtime .Str ("block-id" )); err != nil {
109+ return err
110+ }
111+ if runtime .Bool ("full-comment" ) {
112+ return output .ErrValidation ("--full-comment is not applicable for slide comments; use --block-id <slide-block-type>!<xml-id>" )
113+ }
114+ if strings .TrimSpace (runtime .Str ("selection-with-ellipsis" )) != "" {
115+ return output .ErrValidation ("--selection-with-ellipsis is not applicable for slide comments; use --block-id <slide-block-type>!<xml-id>" )
116+ }
117+ return nil
118+ }
107119
108120 selection := runtime .Str ("selection-with-ellipsis" )
109121 blockID := strings .TrimSpace (runtime .Str ("block-id" ))
@@ -116,15 +128,17 @@ var DriveAddComment = common.Shortcut{
116128
117129 mode := resolveCommentMode (runtime .Bool ("full-comment" ), selection , blockID )
118130 if mode == commentModeLocal && docRef .Kind == "doc" {
119- return output .ErrValidation ("local comments only support docx and sheet ; old doc format only supports full comments" )
131+ return output .ErrValidation ("local comments only support docx, sheet, and slides ; old doc format only supports full comments" )
120132 }
121133
122134 return nil
123135 },
124136 DryRun : func (ctx context.Context , runtime * common.RuntimeContext ) * common.DryRunAPI {
125137 docRef , _ := parseCommentDocRef (runtime .Str ("doc" ), runtime .Str ("type" ))
126138 replyElements , _ := parseCommentReplyElements (runtime .Str ("content" ))
139+ selection := runtime .Str ("selection-with-ellipsis" )
127140 blockID := strings .TrimSpace (runtime .Str ("block-id" ))
141+ mode := resolveCommentMode (runtime .Bool ("full-comment" ), selection , blockID )
128142
129143 // For wiki URLs, resolve the actual target type via API so dry-run
130144 // matches real execution behavior instead of guessing from --block-id.
@@ -133,11 +147,12 @@ var DriveAddComment = common.Shortcut{
133147 isWiki := false
134148 if docRef .Kind == "wiki" {
135149 isWiki = true
136- target , err := resolveCommentTarget (ctx , runtime , runtime .Str ("doc" ), commentModeFull )
137- if err == nil {
138- resolvedKind = target .FileType
139- resolvedToken = target .FileToken
150+ target , err := resolveCommentTarget (ctx , runtime , runtime .Str ("doc" ), mode )
151+ if err != nil {
152+ return common .NewDryRunAPI ().Set ("error" , err .Error ())
140153 }
154+ resolvedKind = target .FileType
155+ resolvedToken = target .FileToken
141156 }
142157
143158 // Sheet comment dry-run.
@@ -146,7 +161,7 @@ var DriveAddComment = common.Shortcut{
146161 if anchor == nil {
147162 anchor = & sheetAnchor {SheetID : "<sheetId>" , Col : 0 , Row : 0 }
148163 }
149- commentBody := buildCommentCreateV2Request ("sheet" , "" , replyElements , anchor )
164+ commentBody := buildCommentCreateV2Request ("sheet" , "" , "" , replyElements , anchor )
150165 desc := "1-step request: create sheet comment"
151166 if isWiki {
152167 desc = "2-step orchestration: resolve wiki -> create sheet comment"
@@ -157,15 +172,28 @@ var DriveAddComment = common.Shortcut{
157172 Body (commentBody ).
158173 Set ("file_token" , resolvedToken )
159174 }
175+ if resolvedKind == "slides" {
176+ slideAnchorBlockID , slideBlockType , err := parseSlidesBlockRef (blockID )
177+ if err != nil {
178+ return common .NewDryRunAPI ().Set ("error" , err .Error ())
179+ }
180+ commentBody := buildCommentCreateV2Request ("slides" , slideAnchorBlockID , slideBlockType , replyElements , nil )
181+ desc := "1-step request: create slide block comment"
182+ if isWiki {
183+ desc = "2-step orchestration: resolve wiki -> create slide block comment"
184+ }
185+ return common .NewDryRunAPI ().
186+ Desc (desc ).
187+ POST ("/open-apis/drive/v1/files/:file_token/new_comments" ).
188+ Body (commentBody ).
189+ Set ("file_token" , resolvedToken )
190+ }
160191
161192 // Doc/docx comment dry-run.
162- selection := runtime .Str ("selection-with-ellipsis" )
163- mode := resolveCommentMode (runtime .Bool ("full-comment" ), selection , blockID )
164-
165193 createPath := "/open-apis/drive/v1/files/:file_token/new_comments"
166- commentBody := buildCommentCreateV2Request (resolvedKind , "" , replyElements , nil )
194+ commentBody := buildCommentCreateV2Request (resolvedKind , "" , "" , replyElements , nil )
167195 if mode == commentModeLocal {
168- commentBody = buildCommentCreateV2Request (resolvedKind , anchorBlockIDForDryRun (blockID ), replyElements , nil )
196+ commentBody = buildCommentCreateV2Request (resolvedKind , anchorBlockIDForDryRun (blockID ), "" , replyElements , nil )
169197 }
170198
171199 mcpEndpoint := common .MCPEndpoint (runtime .Config .Brand )
@@ -240,6 +268,9 @@ var DriveAddComment = common.Shortcut{
240268 if docRef .Kind == "sheet" {
241269 return executeSheetComment (runtime , docRef )
242270 }
271+ if docRef .Kind == "slides" {
272+ return executeSlidesComment (runtime , docRef )
273+ }
243274
244275 selection := runtime .Str ("selection-with-ellipsis" )
245276 blockID := strings .TrimSpace (runtime .Str ("block-id" ))
@@ -254,6 +285,9 @@ var DriveAddComment = common.Shortcut{
254285 if target .FileType == "sheet" {
255286 return executeSheetComment (runtime , commentDocRef {Kind : "sheet" , Token : target .FileToken })
256287 }
288+ if target .FileType == "slides" {
289+ return executeSlidesComment (runtime , commentDocRef {Kind : "slides" , Token : target .FileToken })
290+ }
257291
258292 replyElements , err := parseCommentReplyElements (runtime .Str ("content" ))
259293 if err != nil {
@@ -283,9 +317,9 @@ var DriveAddComment = common.Shortcut{
283317 }
284318
285319 requestPath := fmt .Sprintf ("/open-apis/drive/v1/files/%s/new_comments" , validate .EncodePathSegment (target .FileToken ))
286- requestBody := buildCommentCreateV2Request (target .FileType , "" , replyElements , nil )
320+ requestBody := buildCommentCreateV2Request (target .FileType , "" , "" , replyElements , nil )
287321 if mode == commentModeLocal {
288- requestBody = buildCommentCreateV2Request (target .FileType , blockID , replyElements , nil )
322+ requestBody = buildCommentCreateV2Request (target .FileType , blockID , "" , replyElements , nil )
289323 }
290324
291325 if mode == commentModeLocal {
@@ -358,14 +392,17 @@ func parseCommentDocRef(input, docType string) (commentDocRef, error) {
358392 if token , ok := extractURLToken (raw , "/sheets/" ); ok {
359393 return commentDocRef {Kind : "sheet" , Token : token }, nil
360394 }
395+ if token , ok := extractURLToken (raw , "/slides/" ); ok {
396+ return commentDocRef {Kind : "slides" , Token : token }, nil
397+ }
361398 if token , ok := extractURLToken (raw , "/docx/" ); ok {
362399 return commentDocRef {Kind : "docx" , Token : token }, nil
363400 }
364401 if token , ok := extractURLToken (raw , "/doc/" ); ok {
365402 return commentDocRef {Kind : "doc" , Token : token }, nil
366403 }
367404 if strings .Contains (raw , "://" ) {
368- return commentDocRef {}, output .ErrValidation ("unsupported --doc input %q: use a doc/docx/sheet URL, a token with --type, or a wiki URL that resolves to doc/docx/sheet" , raw )
405+ return commentDocRef {}, output .ErrValidation ("unsupported --doc input %q: use a doc/docx/sheet/slides URL, a token with --type, or a wiki URL that resolves to doc/docx/sheet/slides " , raw )
369406 }
370407 if strings .ContainsAny (raw , "/?#" ) {
371408 return commentDocRef {}, output .ErrValidation ("unsupported --doc input %q: use a token with --type, or a wiki URL" , raw )
@@ -374,7 +411,7 @@ func parseCommentDocRef(input, docType string) (commentDocRef, error) {
374411 // Bare token: --type is required.
375412 docType = strings .TrimSpace (docType )
376413 if docType == "" {
377- return commentDocRef {}, output .ErrValidation ("--type is required when --doc is a bare token (allowed values: doc, docx, sheet)" )
414+ return commentDocRef {}, output .ErrValidation ("--type is required when --doc is a bare token (allowed values: doc, docx, sheet, slides )" )
378415 }
379416 return commentDocRef {Kind : docType , Token : raw }, nil
380417}
@@ -385,9 +422,9 @@ func resolveCommentTarget(ctx context.Context, runtime *common.RuntimeContext, i
385422 return resolvedCommentTarget {}, err
386423 }
387424
388- if docRef .Kind == "docx" || docRef .Kind == "doc" || docRef .Kind == "sheet" {
389- if mode == commentModeLocal && docRef .Kind != "docx" && docRef .Kind != "sheet" {
390- return resolvedCommentTarget {}, output .ErrValidation ("local comments only support docx and sheet ; old doc format only supports full comments" )
425+ if docRef .Kind == "docx" || docRef .Kind == "doc" || docRef .Kind == "sheet" || docRef . Kind == "slides" {
426+ if mode == commentModeLocal && docRef .Kind != "docx" && docRef .Kind != "sheet" && docRef . Kind != "slides" {
427+ return resolvedCommentTarget {}, output .ErrValidation ("local comments only support docx, sheet, and slides ; old doc format only supports full comments" )
391428 }
392429 return resolvedCommentTarget {
393430 DocID : docRef .Token ,
@@ -414,6 +451,12 @@ func resolveCommentTarget(ctx context.Context, runtime *common.RuntimeContext, i
414451 if objType == "" || objToken == "" {
415452 return resolvedCommentTarget {}, output .Errorf (output .ExitAPI , "api_error" , "wiki get_node returned incomplete node data" )
416453 }
454+ if objType == "slides" && mode == commentModeFull {
455+ return resolvedCommentTarget {}, output .ErrValidation ("wiki resolved to %q, but slide comments require --block-id <slide-block-type>!<xml-id>; --full-comment is not applicable" , objType )
456+ }
457+ if objType == "slides" && strings .TrimSpace (runtime .Str ("selection-with-ellipsis" )) != "" {
458+ return resolvedCommentTarget {}, output .ErrValidation ("wiki resolved to %q, but --selection-with-ellipsis is not applicable for slide comments; use --block-id <slide-block-type>!<xml-id>" , objType )
459+ }
417460 if objType == "sheet" {
418461 // Sheet comments are handled via the sheet fast path in Execute.
419462 fmt .Fprintf (runtime .IO ().ErrOut , "Resolved wiki to %s: %s\n " , objType , common .MaskToken (objToken ))
@@ -425,11 +468,21 @@ func resolveCommentTarget(ctx context.Context, runtime *common.RuntimeContext, i
425468 WikiToken : docRef .Token ,
426469 }, nil
427470 }
471+ if objType == "slides" {
472+ fmt .Fprintf (runtime .IO ().ErrOut , "Resolved wiki to %s: %s\n " , objType , common .MaskToken (objToken ))
473+ return resolvedCommentTarget {
474+ DocID : objToken ,
475+ FileToken : objToken ,
476+ FileType : "slides" ,
477+ ResolvedBy : "wiki" ,
478+ WikiToken : docRef .Token ,
479+ }, nil
480+ }
428481 if mode == commentModeLocal && objType != "docx" {
429- return resolvedCommentTarget {}, output .ErrValidation ("wiki resolved to %q, but local comments only support docx and sheet ; for sheet use --block-id <sheetId>!<cell>" , objType )
482+ return resolvedCommentTarget {}, output .ErrValidation ("wiki resolved to %q, but local comments only support docx, sheet, and slides ; for sheet use --block-id <sheetId>!<cell>, for slides use --block-id <slide-block-type>!<xml-id >" , objType )
430483 }
431484 if mode == commentModeFull && objType != "docx" && objType != "doc" {
432- return resolvedCommentTarget {}, output .ErrValidation ("wiki resolved to %q, but comments only support doc/docx/sheet" , objType )
485+ return resolvedCommentTarget {}, output .ErrValidation ("wiki resolved to %q, but comments only support doc/docx/sheet/slides " , objType )
433486 }
434487
435488 fmt .Fprintf (runtime .IO ().ErrOut , "Resolved wiki to %s: %s\n " , objType , common .MaskToken (objToken ))
@@ -606,7 +659,7 @@ type sheetAnchor struct {
606659 Row int
607660}
608661
609- func buildCommentCreateV2Request (fileType , blockID string , replyElements []map [string ]interface {}, sheet * sheetAnchor ) map [string ]interface {} {
662+ func buildCommentCreateV2Request (fileType , blockID , slideBlockType string , replyElements []map [string ]interface {}, sheet * sheetAnchor ) map [string ]interface {} {
610663 body := map [string ]interface {}{
611664 "file_type" : fileType ,
612665 "reply_elements" : replyElements ,
@@ -621,6 +674,9 @@ func buildCommentCreateV2Request(fileType, blockID string, replyElements []map[s
621674 body ["anchor" ] = map [string ]interface {}{
622675 "block_id" : blockID ,
623676 }
677+ if strings .TrimSpace (slideBlockType ) != "" {
678+ body ["anchor" ].(map [string ]interface {})["slide_block_type" ] = strings .TrimSpace (slideBlockType )
679+ }
624680 }
625681 return body
626682}
@@ -632,6 +688,24 @@ func anchorBlockIDForDryRun(blockID string) string {
632688 return "<anchor_block_id>"
633689}
634690
691+ func parseSlidesBlockRef (blockID string ) (string , string , error ) {
692+ blockID = strings .TrimSpace (blockID )
693+ if blockID == "" {
694+ return "" , "" , output .ErrValidation ("slide comments require --block-id in <slide-block-type>!<xml-id> format" )
695+ }
696+
697+ parts := strings .SplitN (blockID , "!" , 2 )
698+ if len (parts ) != 2 {
699+ return "" , "" , output .ErrValidation ("slide --block-id must be <slide-block-type>!<xml-id> (e.g. shape!bPq), got %q" , blockID )
700+ }
701+ parsedType := strings .TrimSpace (parts [0 ])
702+ parsedID := strings .TrimSpace (parts [1 ])
703+ if parsedType == "" || parsedID == "" {
704+ return "" , "" , output .ErrValidation ("slide --block-id must be <slide-block-type>!<xml-id> (e.g. shape!bPq), got %q" , blockID )
705+ }
706+ return parsedID , parsedType , nil
707+ }
708+
635709func firstNonEmptyString (values ... string ) string {
636710 for _ , value := range values {
637711 if strings .TrimSpace (value ) != "" {
@@ -703,7 +777,7 @@ func executeSheetComment(runtime *common.RuntimeContext, docRef commentDocRef) e
703777 }
704778
705779 requestPath := fmt .Sprintf ("/open-apis/drive/v1/files/%s/new_comments" , validate .EncodePathSegment (docRef .Token ))
706- requestBody := buildCommentCreateV2Request ("sheet" , "" , replyElements , anchor )
780+ requestBody := buildCommentCreateV2Request ("sheet" , "" , "" , replyElements , anchor )
707781
708782 fmt .Fprintf (runtime .IO ().ErrOut , "Creating sheet comment in %s (sheet=%s, col=%d, row=%d)\n " ,
709783 common .MaskToken (docRef .Token ), anchor .SheetID , anchor .Col , anchor .Row )
@@ -727,6 +801,43 @@ func executeSheetComment(runtime *common.RuntimeContext, docRef commentDocRef) e
727801 return nil
728802}
729803
804+ func executeSlidesComment (runtime * common.RuntimeContext , docRef commentDocRef ) error {
805+ replyElements , err := parseCommentReplyElements (runtime .Str ("content" ))
806+ if err != nil {
807+ return err
808+ }
809+
810+ blockID , slideBlockType , err := parseSlidesBlockRef (runtime .Str ("block-id" ))
811+ if err != nil {
812+ return err
813+ }
814+
815+ requestPath := fmt .Sprintf ("/open-apis/drive/v1/files/%s/new_comments" , validate .EncodePathSegment (docRef .Token ))
816+ requestBody := buildCommentCreateV2Request ("slides" , blockID , slideBlockType , replyElements , nil )
817+
818+ fmt .Fprintf (runtime .IO ().ErrOut , "Creating slide block comment in %s (block_id=%s, slide_block_type=%s)\n " ,
819+ common .MaskToken (docRef .Token ), blockID , slideBlockType )
820+
821+ data , err := runtime .CallAPI ("POST" , requestPath , nil , requestBody )
822+ if err != nil {
823+ return err
824+ }
825+
826+ out := map [string ]interface {}{
827+ "comment_id" : data ["comment_id" ],
828+ "file_token" : docRef .Token ,
829+ "file_type" : "slides" ,
830+ "comment_mode" : "slide_block" ,
831+ "anchor_block_id" : blockID ,
832+ "slide_block_type" : slideBlockType ,
833+ }
834+ if createdAt := firstPresentValue (data , "created_at" , "create_time" ); createdAt != nil {
835+ out ["created_at" ] = createdAt
836+ }
837+ runtime .Out (out , nil )
838+ return nil
839+ }
840+
730841func extractURLToken (raw , marker string ) (string , bool ) {
731842 idx := strings .Index (raw , marker )
732843 if idx < 0 {
0 commit comments