@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.
@@ -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
- }