4343 docStatusJQ string
4444
4545 docOpenNoBrowser bool
46+
47+ docCommentAddContent string
48+ docCommentAddAttention bool
49+ docCommentAddOutput string
50+ docCommentAddJQ string
51+ docCommentGetOutput string
52+ docCommentGetJQ string
53+ docCommentEditContent string
54+ docCommentEditAttention string
55+ docCommentEditOutput string
56+ docCommentEditJQ string
57+ docCommentDeleteYes bool
58+ docCommentDeleteOutput string
59+ docCommentDeleteJQ string
4660)
4761
4862var documentCmd = & cobra.Command {
@@ -140,6 +154,51 @@ Pass --no-browser (or -n) to print the URL without launching the browser.`,
140154 RunE : runDocumentOpen ,
141155}
142156
157+ var documentCommentCmd = & cobra.Command {
158+ Use : "comment" ,
159+ Short : "Manage document comments" ,
160+ }
161+
162+ var documentCommentAddCmd = & cobra.Command {
163+ Use : "add <docid>" ,
164+ Short : "Add a comment to a document" ,
165+ Long : `Add a comment to a document via POST /api/v1/documents/{docid}/comments.
166+
167+ The comment body is provided with --content (required). Pass --attention
168+ to mark the comment as important.` ,
169+ Args : cobra .ExactArgs (1 ),
170+ RunE : runDocumentCommentAdd ,
171+ }
172+
173+ var documentCommentGetCmd = & cobra.Command {
174+ Use : "get <docid>" ,
175+ Short : "Get comments on a document" ,
176+ Long : `List comments on a document via GET /api/v1/documents/{docid}/comments.` ,
177+ Args : cobra .ExactArgs (1 ),
178+ RunE : runDocumentCommentGet ,
179+ }
180+
181+ var documentCommentEditCmd = & cobra.Command {
182+ Use : "edit <docid> <seq>" ,
183+ Short : "Update a comment" ,
184+ Long : `Update a comment via PATCH /api/v1/documents/{docid}/comments/{seq}.
185+
186+ At least one of --content / --attention must be provided. --attention accepts
187+ "0" (normal) or "1" (important); omit to leave unchanged.` ,
188+ Args : cobra .ExactArgs (2 ),
189+ RunE : runDocumentCommentEdit ,
190+ }
191+
192+ var documentCommentDeleteCmd = & cobra.Command {
193+ Use : "delete <docid> <seq>" ,
194+ Short : "Delete a comment" ,
195+ Long : `Delete a comment via DELETE /api/v1/documents/{docid}/comments/{seq}.
196+
197+ By default the command prompts for confirmation. Pass --yes to skip it.` ,
198+ Args : cobra .ExactArgs (2 ),
199+ RunE : runDocumentCommentDelete ,
200+ }
201+
143202var documentDownloadCmd = & cobra.Command {
144203 Use : "download <docid>" ,
145204 Short : "Download a document as PDF" ,
@@ -164,6 +223,11 @@ func init() {
164223 documentCmd .AddCommand (documentStatusCmd )
165224 documentCmd .AddCommand (documentDownloadCmd )
166225 documentCmd .AddCommand (documentOpenCmd )
226+ documentCmd .AddCommand (documentCommentCmd )
227+ documentCommentCmd .AddCommand (documentCommentAddCmd )
228+ documentCommentCmd .AddCommand (documentCommentGetCmd )
229+ documentCommentCmd .AddCommand (documentCommentEditCmd )
230+ documentCommentCmd .AddCommand (documentCommentDeleteCmd )
167231
168232 f := documentSearchCmd .Flags ()
169233 f .StringVar (& docSearchBody , "body" , "" , "search condition JSON: inline, file path, or - for stdin" )
@@ -201,6 +265,27 @@ func init() {
201265
202266 of := documentOpenCmd .Flags ()
203267 of .BoolVarP (& docOpenNoBrowser , "no-browser" , "n" , false , "print the URL without launching the browser" )
268+
269+ caf := documentCommentAddCmd .Flags ()
270+ caf .StringVar (& docCommentAddContent , "content" , "" , "comment content (required)" )
271+ caf .BoolVar (& docCommentAddAttention , "attention" , false , "mark as important comment (attentionflg=1)" )
272+ caf .StringVarP (& docCommentAddOutput , "output" , "o" , "" , "output format: table|json (default: table on TTY, json otherwise)" )
273+ caf .StringVar (& docCommentAddJQ , "jq" , "" , "apply a gojq filter to the JSON response (forces JSON output)" )
274+
275+ cgf := documentCommentGetCmd .Flags ()
276+ cgf .StringVarP (& docCommentGetOutput , "output" , "o" , "" , "output format: table|json (default: table on TTY, json otherwise)" )
277+ cgf .StringVar (& docCommentGetJQ , "jq" , "" , "apply a gojq filter to the JSON response (forces JSON output)" )
278+
279+ cef := documentCommentEditCmd .Flags ()
280+ cef .StringVar (& docCommentEditContent , "content" , "" , "new comment content (omit to leave unchanged)" )
281+ cef .StringVar (& docCommentEditAttention , "attention" , "" , "new attention flag: 0 (normal) | 1 (important); omit to leave unchanged" )
282+ cef .StringVarP (& docCommentEditOutput , "output" , "o" , "" , "output format: table|json (default: table on TTY, json otherwise)" )
283+ cef .StringVar (& docCommentEditJQ , "jq" , "" , "apply a gojq filter to the JSON response (forces JSON output)" )
284+
285+ cdf := documentCommentDeleteCmd .Flags ()
286+ cdf .BoolVarP (& docCommentDeleteYes , "yes" , "y" , false , "skip the interactive confirmation prompt" )
287+ cdf .StringVarP (& docCommentDeleteOutput , "output" , "o" , "" , "output format: table|json (default: table on TTY, json otherwise)" )
288+ cdf .StringVar (& docCommentDeleteJQ , "jq" , "" , "apply a gojq filter to the JSON response (forces JSON output)" )
204289}
205290
206291func runDocumentSearch (cmd * cobra.Command , args []string ) error {
@@ -440,6 +525,157 @@ func runDocumentOpen(_ *cobra.Command, args []string) error {
440525 return nil
441526}
442527
528+ func runDocumentCommentAdd (cmd * cobra.Command , args []string ) error {
529+ docID , err := parseDocID (args [0 ])
530+ if err != nil {
531+ return err
532+ }
533+ if docCommentAddContent == "" {
534+ return fmt .Errorf ("--content is required" )
535+ }
536+ client , err := newClientFromFlags (cmd .Context ())
537+ if err != nil {
538+ return err
539+ }
540+ req := xpoint.AddCommentRequest {Content : docCommentAddContent }
541+ if docCommentAddAttention {
542+ req .AttentionFlg = 1
543+ }
544+ res , err := client .AddComment (cmd .Context (), docID , req )
545+ if err != nil {
546+ return err
547+ }
548+ return render (res , resolveOutputFormat (docCommentAddOutput ), docCommentAddJQ , func () error {
549+ w := tabwriter .NewWriter (os .Stdout , 0 , 0 , 2 , ' ' , 0 )
550+ defer w .Flush ()
551+ fmt .Fprintln (w , "DOCID\t SEQ\t MESSAGE_TYPE\t MESSAGE" )
552+ fmt .Fprintf (w , "%d\t %d\t %d\t %s\n " , res .DocID , res .Seq , res .MessageType , res .Message )
553+ return nil
554+ })
555+ }
556+
557+ func runDocumentCommentGet (cmd * cobra.Command , args []string ) error {
558+ docID , err := parseDocID (args [0 ])
559+ if err != nil {
560+ return err
561+ }
562+ client , err := newClientFromFlags (cmd .Context ())
563+ if err != nil {
564+ return err
565+ }
566+ res , err := client .GetComments (cmd .Context (), docID )
567+ if err != nil {
568+ return err
569+ }
570+ return render (res , resolveOutputFormat (docCommentGetOutput ), docCommentGetJQ , func () error {
571+ w := tabwriter .NewWriter (os .Stdout , 0 , 0 , 2 , ' ' , 0 )
572+ defer w .Flush ()
573+ fmt .Fprintln (w , "SEQ\t ATTENTION\t WRITER\t WRITE_DATE\t CONTENT" )
574+ for _ , cm := range res .CommentList {
575+ attention := "-"
576+ if cm .AttentionFlg {
577+ attention = "*"
578+ }
579+ fmt .Fprintf (w , "%s\t %s\t %s\t %s\t %s\n " , cm .SeqNo , attention , cm .WriterName , cm .WriteDate , cm .Content )
580+ }
581+ return nil
582+ })
583+ }
584+
585+ func runDocumentCommentEdit (cmd * cobra.Command , args []string ) error {
586+ docID , err := parseDocID (args [0 ])
587+ if err != nil {
588+ return err
589+ }
590+ seq , err := parseSeq (args [1 ])
591+ if err != nil {
592+ return err
593+ }
594+ if ! cmd .Flags ().Changed ("content" ) && ! cmd .Flags ().Changed ("attention" ) {
595+ return fmt .Errorf ("at least one of --content / --attention is required" )
596+ }
597+ req := xpoint.UpdateCommentRequest {}
598+ if cmd .Flags ().Changed ("content" ) {
599+ v := docCommentEditContent
600+ req .Content = & v
601+ }
602+ if cmd .Flags ().Changed ("attention" ) {
603+ switch docCommentEditAttention {
604+ case "0" :
605+ v := 0
606+ req .AttentionFlg = & v
607+ case "1" :
608+ v := 1
609+ req .AttentionFlg = & v
610+ default :
611+ return fmt .Errorf ("--attention must be 0 or 1, got %q" , docCommentEditAttention )
612+ }
613+ }
614+ client , err := newClientFromFlags (cmd .Context ())
615+ if err != nil {
616+ return err
617+ }
618+ res , err := client .UpdateComment (cmd .Context (), docID , seq , req )
619+ if err != nil {
620+ return err
621+ }
622+ return render (res , resolveOutputFormat (docCommentEditOutput ), docCommentEditJQ , func () error {
623+ w := tabwriter .NewWriter (os .Stdout , 0 , 0 , 2 , ' ' , 0 )
624+ defer w .Flush ()
625+ fmt .Fprintln (w , "DOCID\t SEQ\t MESSAGE_TYPE\t MESSAGE" )
626+ fmt .Fprintf (w , "%d\t %d\t %d\t %s\n " , res .DocID , res .Seq , res .MessageType , res .Message )
627+ return nil
628+ })
629+ }
630+
631+ func runDocumentCommentDelete (cmd * cobra.Command , args []string ) error {
632+ docID , err := parseDocID (args [0 ])
633+ if err != nil {
634+ return err
635+ }
636+ seq , err := parseSeq (args [1 ])
637+ if err != nil {
638+ return err
639+ }
640+ if ! docCommentDeleteYes && ! confirmDeleteComment (docID , seq ) {
641+ return fmt .Errorf ("aborted" )
642+ }
643+ client , err := newClientFromFlags (cmd .Context ())
644+ if err != nil {
645+ return err
646+ }
647+ res , err := client .DeleteComment (cmd .Context (), docID , seq )
648+ if err != nil {
649+ return err
650+ }
651+ return render (res , resolveOutputFormat (docCommentDeleteOutput ), docCommentDeleteJQ , func () error {
652+ w := tabwriter .NewWriter (os .Stdout , 0 , 0 , 2 , ' ' , 0 )
653+ defer w .Flush ()
654+ fmt .Fprintln (w , "DOCID\t SEQ\t MESSAGE_TYPE\t MESSAGE" )
655+ fmt .Fprintf (w , "%d\t %d\t %d\t %s\n " , res .DocID , res .Seq , res .MessageType , res .Message )
656+ return nil
657+ })
658+ }
659+
660+ func parseSeq (s string ) (int , error ) {
661+ n , err := strconv .Atoi (strings .TrimSpace (s ))
662+ if err != nil || n <= 0 {
663+ return 0 , fmt .Errorf ("invalid seq %q: must be a positive integer" , s )
664+ }
665+ return n , nil
666+ }
667+
668+ func confirmDeleteComment (docID , seq int ) bool {
669+ fmt .Fprintf (os .Stderr , "Really delete comment seq=%d on document %d? [y/N]: " , seq , docID )
670+ var ans string
671+ _ , _ = fmt .Fscanln (os .Stdin , & ans )
672+ switch strings .ToLower (strings .TrimSpace (ans )) {
673+ case "y" , "yes" :
674+ return true
675+ }
676+ return false
677+ }
678+
443679// resolveDownloadPath decides the on-disk path for a downloaded PDF.
444680//
445681// When out is empty, the server-provided filename is used in the current
0 commit comments