@grec0/memory-bank-mcp 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +420 -425
- package/dist/common/chunker.js +515 -3
- package/dist/common/embeddingService.js +51 -39
- package/dist/common/fileScanner.js +48 -29
- package/dist/common/indexManager.js +85 -46
- package/dist/common/logger.js +54 -0
- package/dist/common/vectorStore.js +47 -4
- package/dist/index.js +1 -1
- package/dist/tools/analyzeCoverage.js +66 -46
- package/dist/tools/indexCode.js +1 -0
- package/package.json +2 -1
- package/dist/common/setup.js +0 -49
- package/dist/common/utils.js +0 -215
- package/dist/operations/boardMemberships.js +0 -186
- package/dist/operations/boards.js +0 -268
- package/dist/operations/cards.js +0 -426
- package/dist/operations/comments.js +0 -249
- package/dist/operations/labels.js +0 -258
- package/dist/operations/lists.js +0 -157
- package/dist/operations/projects.js +0 -102
- package/dist/operations/tasks.js +0 -238
- package/dist/tools/board-summary.js +0 -151
- package/dist/tools/card-details.js +0 -106
- package/dist/tools/create-card-with-tasks.js +0 -81
- package/dist/tools/workflow-actions.js +0 -145
package/dist/operations/cards.js
DELETED
|
@@ -1,426 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Card operations for the MCP Kanban server
|
|
3
|
-
*
|
|
4
|
-
* This module provides functions for interacting with cards in the Planka Kanban board,
|
|
5
|
-
* including creating, retrieving, updating, moving, duplicating, and deleting cards,
|
|
6
|
-
* as well as managing card stopwatches for time tracking.
|
|
7
|
-
*/
|
|
8
|
-
import { z } from "zod";
|
|
9
|
-
import { plankaRequest } from "../common/utils.js";
|
|
10
|
-
import { PlankaCardSchema } from "../common/types.js";
|
|
11
|
-
// Schema definitions
|
|
12
|
-
/**
|
|
13
|
-
* Schema for creating a new card
|
|
14
|
-
* @property {string} listId - The ID of the list to create the card in
|
|
15
|
-
* @property {string} name - The name of the card
|
|
16
|
-
* @property {string} [description] - The description of the card
|
|
17
|
-
* @property {number} [position] - The position of the card in the list (default: 65535)
|
|
18
|
-
*/
|
|
19
|
-
export const CreateCardSchema = z.object({
|
|
20
|
-
listId: z.string().describe("List ID"),
|
|
21
|
-
name: z.string().describe("Card name"),
|
|
22
|
-
description: z.string().optional().describe("Card description"),
|
|
23
|
-
position: z.number().optional().describe("Card position (default: 65535)"),
|
|
24
|
-
});
|
|
25
|
-
/**
|
|
26
|
-
* Schema for retrieving cards from a list
|
|
27
|
-
* @property {string} listId - The ID of the list to get cards from
|
|
28
|
-
*/
|
|
29
|
-
export const GetCardsSchema = z.object({
|
|
30
|
-
listId: z.string().describe("List ID"),
|
|
31
|
-
});
|
|
32
|
-
/**
|
|
33
|
-
* Schema for retrieving a specific card
|
|
34
|
-
* @property {string} id - The ID of the card to retrieve
|
|
35
|
-
*/
|
|
36
|
-
export const GetCardSchema = z.object({
|
|
37
|
-
id: z.string().describe("Card ID"),
|
|
38
|
-
});
|
|
39
|
-
/**
|
|
40
|
-
* Schema for updating a card
|
|
41
|
-
* @property {string} id - The ID of the card to update
|
|
42
|
-
* @property {string} [name] - The new name for the card
|
|
43
|
-
* @property {string} [description] - The new description for the card
|
|
44
|
-
* @property {number} [position] - The new position for the card
|
|
45
|
-
* @property {string} [dueDate] - The due date for the card (ISO format)
|
|
46
|
-
* @property {boolean} [isCompleted] - Whether the card is completed
|
|
47
|
-
*/
|
|
48
|
-
export const UpdateCardSchema = z.object({
|
|
49
|
-
id: z.string().describe("Card ID"),
|
|
50
|
-
name: z.string().optional().describe("Card name"),
|
|
51
|
-
description: z.string().optional().describe("Card description"),
|
|
52
|
-
position: z.number().optional().describe("Card position"),
|
|
53
|
-
dueDate: z.string().optional().describe("Card due date (ISO format)"),
|
|
54
|
-
isCompleted: z.boolean().optional().describe("Whether the card is completed"),
|
|
55
|
-
});
|
|
56
|
-
export const MoveCardSchema = z.object({
|
|
57
|
-
id: z.string().describe("Card ID"),
|
|
58
|
-
listId: z.string().describe("Target list ID"),
|
|
59
|
-
position: z.number().optional().describe("Card position in the target list (default: 65535)"),
|
|
60
|
-
});
|
|
61
|
-
export const DuplicateCardSchema = z.object({
|
|
62
|
-
id: z.string().describe("Card ID to duplicate"),
|
|
63
|
-
position: z.number().optional().describe("Position for the duplicated card (default: 65535)"),
|
|
64
|
-
});
|
|
65
|
-
export const DeleteCardSchema = z.object({
|
|
66
|
-
id: z.string().describe("Card ID"),
|
|
67
|
-
});
|
|
68
|
-
// Stopwatch schemas
|
|
69
|
-
export const StartCardStopwatchSchema = z.object({
|
|
70
|
-
id: z.string().describe("Card ID"),
|
|
71
|
-
});
|
|
72
|
-
export const StopCardStopwatchSchema = z.object({
|
|
73
|
-
id: z.string().describe("Card ID"),
|
|
74
|
-
});
|
|
75
|
-
export const GetCardStopwatchSchema = z.object({
|
|
76
|
-
id: z.string().describe("Card ID"),
|
|
77
|
-
});
|
|
78
|
-
export const ResetCardStopwatchSchema = z.object({
|
|
79
|
-
id: z.string().describe("Card ID"),
|
|
80
|
-
});
|
|
81
|
-
// Response schemas
|
|
82
|
-
const CardsResponseSchema = z.object({
|
|
83
|
-
items: z.array(PlankaCardSchema),
|
|
84
|
-
included: z.record(z.any()).optional(),
|
|
85
|
-
});
|
|
86
|
-
const CardResponseSchema = z.object({
|
|
87
|
-
item: PlankaCardSchema,
|
|
88
|
-
included: z.record(z.any()).optional(),
|
|
89
|
-
});
|
|
90
|
-
// Function implementations
|
|
91
|
-
/**
|
|
92
|
-
* Creates a new card in a list
|
|
93
|
-
*
|
|
94
|
-
* @param {CreateCardOptions} options - Options for creating the card
|
|
95
|
-
* @param {string} options.listId - The ID of the list to create the card in
|
|
96
|
-
* @param {string} options.name - The name of the card
|
|
97
|
-
* @param {string} [options.description] - The description of the card
|
|
98
|
-
* @param {number} [options.position] - The position of the card in the list (default: 65535)
|
|
99
|
-
* @returns {Promise<object>} The created card
|
|
100
|
-
* @throws {Error} If the card creation fails
|
|
101
|
-
*/
|
|
102
|
-
export async function createCard(options) {
|
|
103
|
-
try {
|
|
104
|
-
const response = await plankaRequest(`/api/lists/${options.listId}/cards`, {
|
|
105
|
-
method: "POST",
|
|
106
|
-
body: {
|
|
107
|
-
name: options.name,
|
|
108
|
-
description: options.description,
|
|
109
|
-
position: options.position,
|
|
110
|
-
},
|
|
111
|
-
});
|
|
112
|
-
const parsedResponse = CardResponseSchema.parse(response);
|
|
113
|
-
return parsedResponse.item;
|
|
114
|
-
}
|
|
115
|
-
catch (error) {
|
|
116
|
-
throw new Error(`Failed to create card: ${error instanceof Error ? error.message : String(error)}`);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Retrieves all cards for a specific list
|
|
121
|
-
*
|
|
122
|
-
* @param {string} listId - The ID of the list to get cards from
|
|
123
|
-
* @returns {Promise<Array<object>>} Array of cards in the list
|
|
124
|
-
*/
|
|
125
|
-
export async function getCards(listId) {
|
|
126
|
-
try {
|
|
127
|
-
// Get all projects which includes boards
|
|
128
|
-
const projectsResponse = await plankaRequest(`/api/projects`);
|
|
129
|
-
if (!projectsResponse ||
|
|
130
|
-
typeof projectsResponse !== "object" ||
|
|
131
|
-
!("included" in projectsResponse) ||
|
|
132
|
-
!projectsResponse.included ||
|
|
133
|
-
typeof projectsResponse.included !== "object") {
|
|
134
|
-
return [];
|
|
135
|
-
}
|
|
136
|
-
const included = projectsResponse.included;
|
|
137
|
-
// Get all boards
|
|
138
|
-
if (!("boards" in included) || !Array.isArray(included.boards)) {
|
|
139
|
-
return [];
|
|
140
|
-
}
|
|
141
|
-
const boards = included.boards;
|
|
142
|
-
// Check each board for cards with the matching list ID
|
|
143
|
-
for (const board of boards) {
|
|
144
|
-
if (typeof board !== "object" || board === null || !("id" in board)) {
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
const boardId = board.id;
|
|
148
|
-
// Get the board details which includes cards
|
|
149
|
-
const boardResponse = await plankaRequest(`/api/boards/${boardId}`);
|
|
150
|
-
if (!boardResponse ||
|
|
151
|
-
typeof boardResponse !== "object" ||
|
|
152
|
-
!("included" in boardResponse) ||
|
|
153
|
-
!boardResponse.included ||
|
|
154
|
-
typeof boardResponse.included !== "object") {
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
const boardIncluded = boardResponse.included;
|
|
158
|
-
if (!("cards" in boardIncluded) ||
|
|
159
|
-
!Array.isArray(boardIncluded.cards)) {
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
162
|
-
const cards = boardIncluded.cards;
|
|
163
|
-
// Filter cards by list ID
|
|
164
|
-
const matchingCards = cards.filter((card) => typeof card === "object" &&
|
|
165
|
-
card !== null &&
|
|
166
|
-
"listId" in card &&
|
|
167
|
-
card.listId === listId);
|
|
168
|
-
if (matchingCards.length > 0) {
|
|
169
|
-
return matchingCards;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
// If we couldn't find any cards for this list ID
|
|
173
|
-
return [];
|
|
174
|
-
}
|
|
175
|
-
catch (error) {
|
|
176
|
-
// If all else fails, return an empty array
|
|
177
|
-
return [];
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Retrieves a specific card by ID
|
|
182
|
-
*
|
|
183
|
-
* @param {string} id - The ID of the card to retrieve
|
|
184
|
-
* @returns {Promise<object>} The requested card
|
|
185
|
-
*/
|
|
186
|
-
export async function getCard(id) {
|
|
187
|
-
const response = await plankaRequest(`/api/cards/${id}`);
|
|
188
|
-
const parsedResponse = CardResponseSchema.parse(response);
|
|
189
|
-
return parsedResponse.item;
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* Updates a card's properties
|
|
193
|
-
*
|
|
194
|
-
* @param {string} id - The ID of the card to update
|
|
195
|
-
* @param {Partial<Omit<CreateCardOptions, "listId">>} options - The properties to update
|
|
196
|
-
* @returns {Promise<object>} The updated card
|
|
197
|
-
*/
|
|
198
|
-
export async function updateCard(id, options) {
|
|
199
|
-
const response = await plankaRequest(`/api/cards/${id}`, {
|
|
200
|
-
method: "PATCH",
|
|
201
|
-
body: options,
|
|
202
|
-
});
|
|
203
|
-
const parsedResponse = CardResponseSchema.parse(response);
|
|
204
|
-
return parsedResponse.item;
|
|
205
|
-
}
|
|
206
|
-
/**
|
|
207
|
-
* Moves a card to a different list or position
|
|
208
|
-
*
|
|
209
|
-
* @param {string} cardId - The ID of the card to move
|
|
210
|
-
* @param {string} listId - The ID of the list to move the card to
|
|
211
|
-
* @param {number} [position=65535] - The position in the target list
|
|
212
|
-
* @param {string} [boardId] - The ID of the board (if moving between boards)
|
|
213
|
-
* @param {string} [projectId] - The ID of the project (if moving between projects)
|
|
214
|
-
* @returns {Promise<object>} The moved card
|
|
215
|
-
*/
|
|
216
|
-
export async function moveCard(cardId, listId, position = 65535, boardId, projectId) {
|
|
217
|
-
try {
|
|
218
|
-
// Use the PATCH endpoint to update the card with the new list ID and position
|
|
219
|
-
const response = await plankaRequest(`/api/cards/${cardId}`, {
|
|
220
|
-
method: "PATCH",
|
|
221
|
-
body: {
|
|
222
|
-
listId,
|
|
223
|
-
position,
|
|
224
|
-
boardId,
|
|
225
|
-
projectId,
|
|
226
|
-
},
|
|
227
|
-
});
|
|
228
|
-
// Parse and return the updated card
|
|
229
|
-
const parsedResponse = CardResponseSchema.parse(response);
|
|
230
|
-
return parsedResponse.item;
|
|
231
|
-
}
|
|
232
|
-
catch (error) {
|
|
233
|
-
throw new Error(`Failed to move card: ${error instanceof Error ? error.message : String(error)}`);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* Duplicates a card in the same list
|
|
238
|
-
*
|
|
239
|
-
* @param {string} id - The ID of the card to duplicate
|
|
240
|
-
* @param {number} [position] - The position for the duplicated card
|
|
241
|
-
* @returns {Promise<object>} The duplicated card
|
|
242
|
-
*/
|
|
243
|
-
export async function duplicateCard(id, position) {
|
|
244
|
-
try {
|
|
245
|
-
// First, get the original card to access its name
|
|
246
|
-
const originalCard = await getCard(id);
|
|
247
|
-
// Create a new card with "Copy of" prefix
|
|
248
|
-
const cardName = originalCard ? `Copy of ${originalCard.name}` : "";
|
|
249
|
-
// Get the list ID from the original card
|
|
250
|
-
const listId = originalCard ? originalCard.listId : "";
|
|
251
|
-
if (!listId) {
|
|
252
|
-
throw new Error("Could not determine list ID for card duplication");
|
|
253
|
-
}
|
|
254
|
-
// Create a new card with the same properties but with "Copy of" prefix
|
|
255
|
-
const newCard = await createCard({
|
|
256
|
-
listId,
|
|
257
|
-
name: cardName,
|
|
258
|
-
description: originalCard.description || "",
|
|
259
|
-
position: position || 65535,
|
|
260
|
-
});
|
|
261
|
-
return newCard;
|
|
262
|
-
}
|
|
263
|
-
catch (error) {
|
|
264
|
-
throw new Error(`Failed to duplicate card: ${error instanceof Error ? error.message : String(error)}`);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* Deletes a card by ID
|
|
269
|
-
*
|
|
270
|
-
* @param {string} id - The ID of the card to delete
|
|
271
|
-
* @returns {Promise<{success: boolean}>} Success indicator
|
|
272
|
-
*/
|
|
273
|
-
export async function deleteCard(id) {
|
|
274
|
-
await plankaRequest(`/api/cards/${id}`, {
|
|
275
|
-
method: "DELETE",
|
|
276
|
-
});
|
|
277
|
-
return { success: true };
|
|
278
|
-
}
|
|
279
|
-
// Stopwatch functions
|
|
280
|
-
/**
|
|
281
|
-
* Starts the stopwatch for a card to track time spent
|
|
282
|
-
*
|
|
283
|
-
* @param {string} id - The ID of the card to start the stopwatch for
|
|
284
|
-
* @returns {Promise<object>} The updated card with stopwatch information
|
|
285
|
-
*/
|
|
286
|
-
export async function startCardStopwatch(id) {
|
|
287
|
-
try {
|
|
288
|
-
// Get the current card to check if a stopwatch is already running
|
|
289
|
-
const card = await getCard(id);
|
|
290
|
-
// Calculate the stopwatch object
|
|
291
|
-
let stopwatch = {
|
|
292
|
-
startedAt: new Date().toISOString(),
|
|
293
|
-
total: 0,
|
|
294
|
-
};
|
|
295
|
-
// If there's an existing stopwatch, preserve the total time
|
|
296
|
-
if (card.stopwatch && card.stopwatch.total) {
|
|
297
|
-
stopwatch.total = card.stopwatch.total;
|
|
298
|
-
}
|
|
299
|
-
// Update the card with the new stopwatch
|
|
300
|
-
const response = await plankaRequest(`/api/cards/${id}`, {
|
|
301
|
-
method: "PATCH",
|
|
302
|
-
body: { stopwatch },
|
|
303
|
-
});
|
|
304
|
-
const parsedResponse = CardResponseSchema.parse(response);
|
|
305
|
-
return parsedResponse.item;
|
|
306
|
-
}
|
|
307
|
-
catch (error) {
|
|
308
|
-
throw new Error(`Failed to start card stopwatch: ${error instanceof Error ? error.message : String(error)}`);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
/**
|
|
312
|
-
* Stops the stopwatch for a card
|
|
313
|
-
*
|
|
314
|
-
* @param {string} id - The ID of the card to stop the stopwatch for
|
|
315
|
-
* @returns {Promise<object>} The updated card with stopwatch information
|
|
316
|
-
*/
|
|
317
|
-
export async function stopCardStopwatch(id) {
|
|
318
|
-
try {
|
|
319
|
-
// Get the current card to calculate elapsed time
|
|
320
|
-
const card = await getCard(id);
|
|
321
|
-
// If there's no stopwatch or it's not running, return the card as is
|
|
322
|
-
if (!card.stopwatch || !card.stopwatch.startedAt) {
|
|
323
|
-
return card;
|
|
324
|
-
}
|
|
325
|
-
// Calculate elapsed time
|
|
326
|
-
const startedAt = new Date(card.stopwatch.startedAt);
|
|
327
|
-
const now = new Date();
|
|
328
|
-
const elapsedSeconds = Math.floor((now.getTime() - startedAt.getTime()) / 1000);
|
|
329
|
-
// Calculate the new total time
|
|
330
|
-
const totalSeconds = (card.stopwatch.total || 0) + elapsedSeconds;
|
|
331
|
-
// Update the card with the stopped stopwatch (null startedAt but preserved total)
|
|
332
|
-
const stopwatch = {
|
|
333
|
-
startedAt: null,
|
|
334
|
-
total: totalSeconds,
|
|
335
|
-
};
|
|
336
|
-
const response = await plankaRequest(`/api/cards/${id}`, {
|
|
337
|
-
method: "PATCH",
|
|
338
|
-
body: { stopwatch },
|
|
339
|
-
});
|
|
340
|
-
const parsedResponse = CardResponseSchema.parse(response);
|
|
341
|
-
return parsedResponse.item;
|
|
342
|
-
}
|
|
343
|
-
catch (error) {
|
|
344
|
-
throw new Error(`Failed to stop card stopwatch: ${error instanceof Error ? error.message : String(error)}`);
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
/**
|
|
348
|
-
* Gets the current stopwatch time for a card
|
|
349
|
-
*
|
|
350
|
-
* @param {string} id - The ID of the card to get the stopwatch time for
|
|
351
|
-
* @returns {Promise<object>} The card's stopwatch information
|
|
352
|
-
*/
|
|
353
|
-
export async function getCardStopwatch(id) {
|
|
354
|
-
try {
|
|
355
|
-
const card = await getCard(id);
|
|
356
|
-
// If there's no stopwatch, return default values
|
|
357
|
-
if (!card.stopwatch) {
|
|
358
|
-
return {
|
|
359
|
-
isRunning: false,
|
|
360
|
-
total: 0,
|
|
361
|
-
current: 0,
|
|
362
|
-
formattedTotal: formatDuration(0),
|
|
363
|
-
formattedCurrent: formatDuration(0),
|
|
364
|
-
};
|
|
365
|
-
}
|
|
366
|
-
// Calculate current elapsed time if stopwatch is running
|
|
367
|
-
let currentElapsed = 0;
|
|
368
|
-
const isRunning = !!card.stopwatch.startedAt;
|
|
369
|
-
if (isRunning && card.stopwatch.startedAt) {
|
|
370
|
-
const startedAt = new Date(card.stopwatch.startedAt);
|
|
371
|
-
const now = new Date();
|
|
372
|
-
currentElapsed = Math.floor((now.getTime() - startedAt.getTime()) / 1000);
|
|
373
|
-
}
|
|
374
|
-
return {
|
|
375
|
-
isRunning,
|
|
376
|
-
total: card.stopwatch.total || 0,
|
|
377
|
-
current: currentElapsed,
|
|
378
|
-
startedAt: card.stopwatch.startedAt,
|
|
379
|
-
formattedTotal: formatDuration(card.stopwatch.total || 0),
|
|
380
|
-
formattedCurrent: formatDuration(currentElapsed),
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
catch (error) {
|
|
384
|
-
throw new Error(`Failed to get card stopwatch: ${error instanceof Error ? error.message : String(error)}`);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
/**
|
|
388
|
-
* Resets the stopwatch for a card
|
|
389
|
-
*
|
|
390
|
-
* @param {string} id - The ID of the card to reset the stopwatch for
|
|
391
|
-
* @returns {Promise<object>} The updated card with reset stopwatch
|
|
392
|
-
*/
|
|
393
|
-
export async function resetCardStopwatch(id) {
|
|
394
|
-
try {
|
|
395
|
-
// Set stopwatch to null to clear it
|
|
396
|
-
const response = await plankaRequest(`/api/cards/${id}`, {
|
|
397
|
-
method: "PATCH",
|
|
398
|
-
body: { stopwatch: null },
|
|
399
|
-
});
|
|
400
|
-
const parsedResponse = CardResponseSchema.parse(response);
|
|
401
|
-
return parsedResponse.item;
|
|
402
|
-
}
|
|
403
|
-
catch (error) {
|
|
404
|
-
throw new Error(`Failed to reset card stopwatch: ${error instanceof Error ? error.message : String(error)}`);
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
/**
|
|
408
|
-
* Formats a duration in seconds to a human-readable string
|
|
409
|
-
*
|
|
410
|
-
* @param {number} seconds - The duration in seconds
|
|
411
|
-
* @returns {string} Formatted duration string (e.g., "2h 30m 15s")
|
|
412
|
-
*/
|
|
413
|
-
function formatDuration(seconds) {
|
|
414
|
-
const hours = Math.floor(seconds / 3600);
|
|
415
|
-
const minutes = Math.floor((seconds % 3600) / 60);
|
|
416
|
-
const remainingSeconds = seconds % 60;
|
|
417
|
-
let result = "";
|
|
418
|
-
if (hours > 0) {
|
|
419
|
-
result += `${hours}h `;
|
|
420
|
-
}
|
|
421
|
-
if (minutes > 0 || hours > 0) {
|
|
422
|
-
result += `${minutes}m `;
|
|
423
|
-
}
|
|
424
|
-
result += `${remainingSeconds}s`;
|
|
425
|
-
return result.trim();
|
|
426
|
-
}
|
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Comment operations for the MCP Kanban server
|
|
3
|
-
*
|
|
4
|
-
* This module provides functions for interacting with comments in the Planka Kanban board,
|
|
5
|
-
* including creating, retrieving, updating, and deleting comments on cards.
|
|
6
|
-
*/
|
|
7
|
-
import { z } from "zod";
|
|
8
|
-
import { plankaRequest } from "../common/utils.js";
|
|
9
|
-
// Schema definitions
|
|
10
|
-
/**
|
|
11
|
-
* Schema for creating a new comment
|
|
12
|
-
* @property {string} cardId - The ID of the card to create the comment on
|
|
13
|
-
* @property {string} text - The text content of the comment
|
|
14
|
-
*/
|
|
15
|
-
export const CreateCommentSchema = z.object({
|
|
16
|
-
cardId: z.string().describe("Card ID"),
|
|
17
|
-
text: z.string().describe("Comment text"),
|
|
18
|
-
});
|
|
19
|
-
/**
|
|
20
|
-
* Schema for retrieving comments from a card
|
|
21
|
-
* @property {string} cardId - The ID of the card to get comments from
|
|
22
|
-
*/
|
|
23
|
-
export const GetCommentsSchema = z.object({
|
|
24
|
-
cardId: z.string().describe("Card ID"),
|
|
25
|
-
});
|
|
26
|
-
/**
|
|
27
|
-
* Schema for retrieving a specific comment
|
|
28
|
-
* @property {string} id - The ID of the comment to retrieve
|
|
29
|
-
*/
|
|
30
|
-
export const GetCommentSchema = z.object({
|
|
31
|
-
id: z.string().describe("Comment ID"),
|
|
32
|
-
});
|
|
33
|
-
/**
|
|
34
|
-
* Schema for updating a comment
|
|
35
|
-
* @property {string} id - The ID of the comment to update
|
|
36
|
-
* @property {string} text - The new text content for the comment
|
|
37
|
-
*/
|
|
38
|
-
export const UpdateCommentSchema = z.object({
|
|
39
|
-
id: z.string().describe("Comment ID"),
|
|
40
|
-
text: z.string().describe("Comment text"),
|
|
41
|
-
});
|
|
42
|
-
/**
|
|
43
|
-
* Schema for deleting a comment
|
|
44
|
-
* @property {string} id - The ID of the comment to delete
|
|
45
|
-
*/
|
|
46
|
-
export const DeleteCommentSchema = z.object({
|
|
47
|
-
id: z.string().describe("Comment ID"),
|
|
48
|
-
});
|
|
49
|
-
// Comment action schema
|
|
50
|
-
const CommentActionSchema = z.object({
|
|
51
|
-
id: z.string(),
|
|
52
|
-
type: z.literal("commentCard"),
|
|
53
|
-
data: z.object({
|
|
54
|
-
text: z.string(),
|
|
55
|
-
}),
|
|
56
|
-
cardId: z.string(),
|
|
57
|
-
userId: z.string(),
|
|
58
|
-
createdAt: z.string(),
|
|
59
|
-
updatedAt: z.string().nullable(),
|
|
60
|
-
});
|
|
61
|
-
// Response schemas
|
|
62
|
-
const CommentActionsResponseSchema = z.object({
|
|
63
|
-
items: z.array(CommentActionSchema),
|
|
64
|
-
included: z.record(z.any()).optional(),
|
|
65
|
-
});
|
|
66
|
-
const CommentActionResponseSchema = z.object({
|
|
67
|
-
item: CommentActionSchema,
|
|
68
|
-
included: z.record(z.any()).optional(),
|
|
69
|
-
});
|
|
70
|
-
// Function implementations
|
|
71
|
-
/**
|
|
72
|
-
* Creates a new comment on a card
|
|
73
|
-
*
|
|
74
|
-
* @param {CreateCommentOptions} options - Options for creating the comment
|
|
75
|
-
* @param {string} options.cardId - The ID of the card to create the comment on
|
|
76
|
-
* @param {string} options.text - The text content of the comment
|
|
77
|
-
* @returns {Promise<object>} The created comment
|
|
78
|
-
* @throws {Error} If the comment creation fails
|
|
79
|
-
*/
|
|
80
|
-
export async function createComment(options) {
|
|
81
|
-
try {
|
|
82
|
-
const response = await plankaRequest(`/api/cards/${options.cardId}/comment-actions`, {
|
|
83
|
-
method: "POST",
|
|
84
|
-
body: {
|
|
85
|
-
text: options.text,
|
|
86
|
-
},
|
|
87
|
-
});
|
|
88
|
-
const parsedResponse = CommentActionResponseSchema.parse(response);
|
|
89
|
-
return parsedResponse.item;
|
|
90
|
-
}
|
|
91
|
-
catch (error) {
|
|
92
|
-
throw new Error(`Failed to create comment: ${error instanceof Error ? error.message : String(error)}`);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Retrieves all comments for a specific card
|
|
97
|
-
*
|
|
98
|
-
* @param {string} cardId - The ID of the card to get comments for
|
|
99
|
-
* @returns {Promise<Array<object>>} Array of comments on the card
|
|
100
|
-
* @throws {Error} If retrieving comments fails
|
|
101
|
-
*/
|
|
102
|
-
export async function getComments(cardId) {
|
|
103
|
-
try {
|
|
104
|
-
const response = await plankaRequest(`/api/cards/${cardId}/actions`);
|
|
105
|
-
try {
|
|
106
|
-
// Try to parse as a CommentsResponseSchema first
|
|
107
|
-
const parsedResponse = CommentActionsResponseSchema.parse(response);
|
|
108
|
-
// Filter only comment actions
|
|
109
|
-
if (parsedResponse.items && Array.isArray(parsedResponse.items)) {
|
|
110
|
-
return parsedResponse.items.filter((item) => item.type === "commentCard");
|
|
111
|
-
}
|
|
112
|
-
return parsedResponse.items;
|
|
113
|
-
}
|
|
114
|
-
catch (parseError) {
|
|
115
|
-
// If that fails, try to parse as an array directly
|
|
116
|
-
if (Array.isArray(response)) {
|
|
117
|
-
const items = z.array(CommentActionSchema).parse(response);
|
|
118
|
-
// Filter only comment actions
|
|
119
|
-
return items.filter((item) => item.type === "commentCard");
|
|
120
|
-
}
|
|
121
|
-
// If we get here, we couldn't parse the response in any expected format
|
|
122
|
-
throw new Error(`Could not parse comments response: ${JSON.stringify(response)}`);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
catch (error) {
|
|
126
|
-
// If all else fails, return an empty array
|
|
127
|
-
return [];
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Retrieves a specific comment by ID
|
|
132
|
-
*
|
|
133
|
-
* @param {string} id - The ID of the comment to retrieve
|
|
134
|
-
* @returns {Promise<object>} The requested comment
|
|
135
|
-
* @throws {Error} If retrieving the comment fails
|
|
136
|
-
*/
|
|
137
|
-
export async function getComment(id) {
|
|
138
|
-
try {
|
|
139
|
-
// Get all projects which includes boards
|
|
140
|
-
const projectsResponse = await plankaRequest(`/api/projects`);
|
|
141
|
-
if (!projectsResponse ||
|
|
142
|
-
typeof projectsResponse !== "object" ||
|
|
143
|
-
!("included" in projectsResponse) ||
|
|
144
|
-
!projectsResponse.included ||
|
|
145
|
-
typeof projectsResponse.included !== "object") {
|
|
146
|
-
throw new Error("Failed to get projects");
|
|
147
|
-
}
|
|
148
|
-
const included = projectsResponse.included;
|
|
149
|
-
// Get all boards
|
|
150
|
-
if (!("boards" in included) || !Array.isArray(included.boards)) {
|
|
151
|
-
throw new Error("No boards found");
|
|
152
|
-
}
|
|
153
|
-
const boards = included.boards;
|
|
154
|
-
// Check each board for cards
|
|
155
|
-
for (const board of boards) {
|
|
156
|
-
if (typeof board !== "object" || board === null || !("id" in board)) {
|
|
157
|
-
continue;
|
|
158
|
-
}
|
|
159
|
-
const boardId = board.id;
|
|
160
|
-
// Get the board details which includes cards
|
|
161
|
-
const boardResponse = await plankaRequest(`/api/boards/${boardId}`);
|
|
162
|
-
if (!boardResponse ||
|
|
163
|
-
typeof boardResponse !== "object" ||
|
|
164
|
-
!("included" in boardResponse) ||
|
|
165
|
-
!boardResponse.included ||
|
|
166
|
-
typeof boardResponse.included !== "object") {
|
|
167
|
-
continue;
|
|
168
|
-
}
|
|
169
|
-
const boardIncluded = boardResponse.included;
|
|
170
|
-
if (!("cards" in boardIncluded) ||
|
|
171
|
-
!Array.isArray(boardIncluded.cards)) {
|
|
172
|
-
continue;
|
|
173
|
-
}
|
|
174
|
-
const cards = boardIncluded.cards;
|
|
175
|
-
// Check each card for the comment
|
|
176
|
-
for (const card of cards) {
|
|
177
|
-
if (typeof card !== "object" || card === null || !("id" in card)) {
|
|
178
|
-
continue;
|
|
179
|
-
}
|
|
180
|
-
const cardId = card.id;
|
|
181
|
-
// Get the card actions
|
|
182
|
-
const actionsResponse = await plankaRequest(`/api/cards/${cardId}/actions`);
|
|
183
|
-
if (!actionsResponse ||
|
|
184
|
-
typeof actionsResponse !== "object" ||
|
|
185
|
-
!("items" in actionsResponse) ||
|
|
186
|
-
!Array.isArray(actionsResponse.items)) {
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
|
-
const actions = actionsResponse.items;
|
|
190
|
-
// Find the comment with the matching ID
|
|
191
|
-
const comment = actions.find((action) => typeof action === "object" &&
|
|
192
|
-
action !== null &&
|
|
193
|
-
"id" in action &&
|
|
194
|
-
action.id === id &&
|
|
195
|
-
"type" in action &&
|
|
196
|
-
action.type === "commentCard");
|
|
197
|
-
if (comment) {
|
|
198
|
-
return comment;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
throw new Error(`Comment not found: ${id}`);
|
|
203
|
-
}
|
|
204
|
-
catch (error) {
|
|
205
|
-
throw new Error(`Failed to get comment: ${error instanceof Error ? error.message : String(error)}`);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* Updates a comment's text content
|
|
210
|
-
*
|
|
211
|
-
* @param {string} id - The ID of the comment to update
|
|
212
|
-
* @param {Partial<Omit<CreateCommentOptions, "cardId">>} options - The properties to update
|
|
213
|
-
* @param {string} options.text - The new text content for the comment
|
|
214
|
-
* @returns {Promise<object>} The updated comment
|
|
215
|
-
* @throws {Error} If updating the comment fails
|
|
216
|
-
*/
|
|
217
|
-
export async function updateComment(id, options) {
|
|
218
|
-
try {
|
|
219
|
-
const response = await plankaRequest(`/api/comment-actions/${id}`, {
|
|
220
|
-
method: "PATCH",
|
|
221
|
-
body: {
|
|
222
|
-
text: options.text,
|
|
223
|
-
},
|
|
224
|
-
});
|
|
225
|
-
const parsedResponse = CommentActionResponseSchema.parse(response);
|
|
226
|
-
return parsedResponse.item;
|
|
227
|
-
}
|
|
228
|
-
catch (error) {
|
|
229
|
-
throw new Error(`Failed to update comment: ${error instanceof Error ? error.message : String(error)}`);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
/**
|
|
233
|
-
* Deletes a comment by ID
|
|
234
|
-
*
|
|
235
|
-
* @param {string} id - The ID of the comment to delete
|
|
236
|
-
* @returns {Promise<{success: boolean}>} Success indicator
|
|
237
|
-
* @throws {Error} If deleting the comment fails
|
|
238
|
-
*/
|
|
239
|
-
export async function deleteComment(id) {
|
|
240
|
-
try {
|
|
241
|
-
await plankaRequest(`/api/comment-actions/${id}`, {
|
|
242
|
-
method: "DELETE",
|
|
243
|
-
});
|
|
244
|
-
return { success: true };
|
|
245
|
-
}
|
|
246
|
-
catch (error) {
|
|
247
|
-
throw new Error(`Failed to delete comment: ${error instanceof Error ? error.message : String(error)}`);
|
|
248
|
-
}
|
|
249
|
-
}
|