Skip to content

Commit 57d7fe2

Browse files
authored
Merge pull request #155 from Pavlinchen/feat/deck-card-comments
feat(deck): Add card discovery (list_board_cards) and card comment tools
2 parents c6ee7f9 + b0e5c87 commit 57d7fe2

1 file changed

Lines changed: 120 additions & 6 deletions

File tree

ex_app/lib/all_tools/deck.py

Lines changed: 120 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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\nPosted 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\nEdited 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

126240
def get_category_name():
127241
return "Deck"
128242

129243
async def is_available(nc: AsyncNextcloudApp):
130-
return 'deck' in await nc.capabilities
244+
return 'deck' in await nc.capabilities

0 commit comments

Comments
 (0)