@@ -25,7 +25,57 @@ async def list_boards():
2525
2626 return json .dumps (response .json ())
2727
28-
28+ @tool
29+ @safe_tool
30+ async def list_board_cards (board_id : int , stack_id : Optional [int ] = None ):
31+ """
32+ List all cards in a Deck board with their metadata.
33+ Each card includes its id (needed for add_card_comment, add_card_label, assign_card_to_user, delete_card),
34+ title, description, stack, labels, assignees, due date, archived status, and done status.
35+ Use this tool to find cards when the agent only knows a card by name or context, not by id.
36+ :param board_id: the id of the board (obtainable with list_boards)
37+ :param stack_id: optional - filter to cards in this specific stack only (obtainable with list_boards)
38+ :return: list of cards with id, title, description, stack_id, stack_title, labels, assignees, due_date, archived, done, comments_count
39+ """
40+ response = await nc ._session ._create_adapter ().request ('GET' , f"{ nc .app_cfg .endpoint } /index.php/apps/deck/api/v1.0/boards/{ board_id } /stacks" , headers = {
41+ "Content-Type" : "application/json" ,
42+ "OCS-APIREQUEST" : "true" ,
43+ })
44+ stacks = response .json ()
45+
46+ cards = []
47+ for stack in stacks :
48+ if stack_id is not None and stack ['id' ] != stack_id :
49+ continue
50+ for card in stack .get ('cards' , []):
51+ labels = []
52+ for label in card .get ('labels' , []):
53+ labels .append ({
54+ 'id' : label ['id' ],
55+ 'title' : label ['title' ],
56+ 'color' : label ['color' ],
57+ })
58+ assignees = []
59+ for assignment in card .get ('assignedUsers' , []):
60+ participant = assignment .get ('participant' , {})
61+ assignees .append ({
62+ 'uid' : participant .get ('uid' ),
63+ 'displayname' : participant .get ('displayname' ),
64+ })
65+ cards .append ({
66+ 'id' : card ['id' ],
67+ 'title' : card ['title' ],
68+ 'description' : card .get ('description' , '' ),
69+ 'stack_id' : stack ['id' ],
70+ 'stack_title' : stack ['title' ],
71+ 'labels' : labels ,
72+ 'assignees' : assignees ,
73+ 'due_date' : card .get ('duedate' ),
74+ 'archived' : card .get ('archived' , False ),
75+ 'done' : card .get ('done' ),
76+ 'comments_count' : card .get ('commentsCount' , 0 ),
77+ })
78+ return json .dumps (cards )
2979
3080 @tool
3181 @dangerous_tool
@@ -65,7 +115,7 @@ async def add_card_label(board_id: int, stack_id: int, card_id: int, label_id: i
65115 Add a label to a card
66116 :param board_id: the id of the board (obtainable with list_boards)
67117 :param stack_id: the id of the stack (obtainable with list_boards)
68- :param card_id: the id of the card (obtainable with list_boards )
118+ :param card_id: the id of the card (obtainable with list_board_cards )
69119 :param label_id: the id of the label to add (obtainable with list_boards - labels are listed in board details)
70120 :return: success confirmation
71121 """
@@ -85,7 +135,7 @@ async def assign_card_to_user(board_id: int, stack_id: int, card_id: int, user_i
85135 Assign a card to a user
86136 :param board_id: the id of the board (obtainable with list_boards)
87137 :param stack_id: the id of the stack (obtainable with list_boards)
88- :param card_id: the id of the card (obtainable with list_boards )
138+ :param card_id: the id of the card (obtainable with list_board_cards )
89139 :param user_id: the user id to assign the card to
90140 :return: success confirmation
91141 """
@@ -105,7 +155,7 @@ async def delete_card(board_id: int, stack_id: int, card_id: int):
105155 Delete a card from a board
106156 :param board_id: the id of the board (obtainable with list_boards)
107157 :param stack_id: the id of the stack (obtainable with list_boards)
108- :param card_id: the id of the card to delete (obtainable with list_boards )
158+ :param card_id: the id of the card to delete (obtainable with list_board_cards )
109159 :return: success confirmation
110160 """
111161 response = await nc ._session ._create_adapter ().request ('DELETE' , f"{ nc .app_cfg .endpoint } /index.php/apps/deck/api/v1.0/boards/{ board_id } /stacks/{ stack_id } /cards/{ card_id } " , headers = {
@@ -115,16 +165,80 @@ async def delete_card(board_id: int, stack_id: int, card_id: int):
115165
116166 return json .dumps (response .json ())
117167
168+ # --- Card Comments (OCS API) ---
169+
170+ @tool
171+ @safe_tool
172+ async def list_card_comments (card_id : int , limit : int = 20 , offset : int = 0 ):
173+ """
174+ List all comments on a Deck card
175+ :param card_id: the id of the card (obtainable with list_board_cards)
176+ :param limit: maximum number of comments to return (default 20)
177+ :param offset: pagination offset (default 0)
178+ :return: list of comments with id, message, author, and creation date
179+ """
180+ return json .dumps (await nc .ocs ('GET' , f'/ocs/v2.php/apps/deck/api/v1.0/cards/{ card_id } /comments' , params = {
181+ 'limit' : limit ,
182+ 'offset' : offset ,
183+ }))
184+
185+ @tool
186+ @dangerous_tool
187+ async def add_card_comment (card_id : int , message : str , parent_id : Optional [int ] = None ):
188+ """
189+ Add a comment to a Deck card
190+ :param card_id: the id of the card (obtainable with list_board_cards)
191+ :param message: the comment text (max 1000 characters)
192+ :param parent_id: optional id of a parent comment for threaded replies (obtainable with list_card_comments)
193+ :return: the created comment
194+ """
195+ message_with_ai_note = f"{ message } \n \n Posted by Nextcloud AI Assistant."
196+ payload = {'message' : message_with_ai_note }
197+ if parent_id is not None :
198+ payload ['parentId' ] = parent_id
199+ return json .dumps (await nc .ocs ('POST' , f'/ocs/v2.php/apps/deck/api/v1.0/cards/{ card_id } /comments' , json = payload ))
200+
201+ @tool
202+ @dangerous_tool
203+ async def update_card_comment (card_id : int , comment_id : int , message : str ):
204+ """
205+ Update an existing comment on a Deck card. Only the comment author can update their own comments.
206+ :param card_id: the id of the card (obtainable with list_board_cards)
207+ :param comment_id: the id of the comment to update (obtainable with list_card_comments)
208+ :param message: the new comment text (max 1000 characters)
209+ :return: the updated comment
210+ """
211+ message_with_ai_note = f"{ message } \n \n Edited by Nextcloud AI Assistant."
212+ return json .dumps (await nc .ocs ('PUT' , f'/ocs/v2.php/apps/deck/api/v1.0/cards/{ card_id } /comments/{ comment_id } ' , json = {
213+ 'message' : message_with_ai_note ,
214+ }))
215+
216+ @tool
217+ @dangerous_tool
218+ async def delete_card_comment (card_id : int , comment_id : int ):
219+ """
220+ Delete a comment from a Deck card. Only the comment author can delete their own comments.
221+ :param card_id: the id of the card (obtainable with list_board_cards)
222+ :param comment_id: the id of the comment to delete (obtainable with list_card_comments)
223+ :return: confirmation of deletion
224+ """
225+ return json .dumps (await nc .ocs ('DELETE' , f'/ocs/v2.php/apps/deck/api/v1.0/cards/{ card_id } /comments/{ comment_id } ' ))
226+
118227 return [
119228 list_boards ,
229+ list_board_cards ,
120230 add_card ,
121231 add_card_label ,
122232 assign_card_to_user ,
123- delete_card
233+ delete_card ,
234+ list_card_comments ,
235+ add_card_comment ,
236+ update_card_comment ,
237+ delete_card_comment ,
124238 ]
125239
126240def get_category_name ():
127241 return "Deck"
128242
129243async def is_available (nc : AsyncNextcloudApp ):
130- return 'deck' in await nc .capabilities
244+ return 'deck' in await nc .capabilities
0 commit comments