@taazkareem/clickup-mcp-server 0.6.0 → 0.6.2

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.
Files changed (46) hide show
  1. package/README.md +14 -23
  2. package/build/config.js +34 -2
  3. package/build/index.js +3 -0
  4. package/build/logger.js +8 -38
  5. package/build/mcp-tools.js +64 -0
  6. package/build/server-state.js +93 -0
  7. package/build/server.js +33 -8
  8. package/build/server.log +0 -0
  9. package/build/services/clickup/base.js +3 -0
  10. package/build/services/clickup/bulk.js +3 -0
  11. package/build/services/clickup/folder.js +3 -0
  12. package/build/services/clickup/index.js +9 -1
  13. package/build/services/clickup/list.js +3 -0
  14. package/build/services/clickup/tag.js +190 -0
  15. package/build/services/clickup/task.js +138 -0
  16. package/build/services/clickup/types.js +3 -0
  17. package/build/services/clickup/workspace.js +3 -0
  18. package/build/services/shared.js +3 -0
  19. package/build/tools/bulk-tasks.js +36 -0
  20. package/build/tools/debug.js +76 -0
  21. package/build/tools/folder.js +3 -0
  22. package/build/tools/index.js +4 -0
  23. package/build/tools/list.js +3 -0
  24. package/build/tools/logs.js +55 -0
  25. package/build/tools/tag.js +824 -0
  26. package/build/tools/task/attachments.js +3 -0
  27. package/build/tools/task/bulk-operations.js +10 -0
  28. package/build/tools/task/handlers.js +61 -2
  29. package/build/tools/task/index.js +8 -1
  30. package/build/tools/task/main.js +18 -2
  31. package/build/tools/task/single-operations.js +10 -0
  32. package/build/tools/task/utilities.js +40 -3
  33. package/build/tools/task/workspace-operations.js +222 -0
  34. package/build/tools/task.js +1554 -0
  35. package/build/tools/utils.js +3 -0
  36. package/build/tools/workspace.js +3 -0
  37. package/build/utils/color-processor.js +183 -0
  38. package/build/utils/concurrency-utils.js +3 -0
  39. package/build/utils/date-utils.js +3 -0
  40. package/build/utils/params-utils.js +39 -0
  41. package/build/utils/resolver-utils.js +3 -0
  42. package/build/utils/sponsor-analytics.js +100 -0
  43. package/build/utils/sponsor-service.js +3 -0
  44. package/build/utils/sponsor-utils.js +57 -0
  45. package/build/utils/token-utils.js +49 -0
  46. package/package.json +1 -1
@@ -0,0 +1,190 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
3
+ * SPDX-License-Identifier: MIT
4
+ *
5
+ * ClickUp Tag Service
6
+ *
7
+ * Provides access to ClickUp API endpoints for tag management:
8
+ * - Space tags (get, create, update, delete)
9
+ * - Task tags (add, remove)
10
+ */
11
+ import { BaseClickUpService } from './base.js';
12
+ /**
13
+ * ClickUp Tag Service class for managing tags
14
+ */
15
+ export class ClickUpTagService extends BaseClickUpService {
16
+ /**
17
+ * Get all tags in a space
18
+ * @param spaceId - ID of the space to get tags from
19
+ * @returns Promise with tags array
20
+ */
21
+ async getSpaceTags(spaceId) {
22
+ try {
23
+ this.logger.debug(`Getting tags for space: ${spaceId}`);
24
+ const response = await this.client.get(`/space/${spaceId}/tag`);
25
+ return {
26
+ success: true,
27
+ data: response.data.tags
28
+ };
29
+ }
30
+ catch (error) {
31
+ this.logger.error(`Failed to get tags for space: ${spaceId}`, error);
32
+ return {
33
+ success: false,
34
+ error: {
35
+ message: error.message || 'Failed to get space tags',
36
+ code: error.code,
37
+ details: error.data
38
+ }
39
+ };
40
+ }
41
+ }
42
+ /**
43
+ * Create a new tag in a space
44
+ * @param spaceId - ID of the space
45
+ * @param tagData - Tag data (name, background color, foreground color)
46
+ * @returns Promise with created tag
47
+ */
48
+ async createSpaceTag(spaceId, tagData) {
49
+ try {
50
+ this.logger.debug(`Creating tag "${tagData.tag_name}" in space: ${spaceId}`);
51
+ // Send tag data wrapped in a 'tag' object
52
+ const response = await this.client.post(`/space/${spaceId}/tag`, {
53
+ tag: {
54
+ name: tagData.tag_name,
55
+ tag_bg: tagData.tag_bg,
56
+ tag_fg: tagData.tag_fg
57
+ }
58
+ });
59
+ return {
60
+ success: true,
61
+ data: response.data.tag
62
+ };
63
+ }
64
+ catch (error) {
65
+ this.logger.error(`Failed to create tag in space: ${spaceId}`, error);
66
+ return {
67
+ success: false,
68
+ error: {
69
+ message: error.message || 'Failed to create space tag',
70
+ code: error.code,
71
+ details: error.data
72
+ }
73
+ };
74
+ }
75
+ }
76
+ /**
77
+ * Update an existing tag in a space
78
+ * @param spaceId - ID of the space
79
+ * @param tagName - Current name of the tag to update
80
+ * @param updateData - Tag data to update (name, colors)
81
+ * @returns Promise with updated tag
82
+ */
83
+ async updateSpaceTag(spaceId, tagName, updateData) {
84
+ try {
85
+ this.logger.debug(`Updating tag "${tagName}" in space: ${spaceId}`);
86
+ // Encode the tag name in the URL
87
+ const encodedTagName = encodeURIComponent(tagName);
88
+ const response = await this.client.put(`/space/${spaceId}/tag/${encodedTagName}`, updateData);
89
+ return {
90
+ success: true,
91
+ data: response.data.tag
92
+ };
93
+ }
94
+ catch (error) {
95
+ this.logger.error(`Failed to update tag "${tagName}" in space: ${spaceId}`, error);
96
+ return {
97
+ success: false,
98
+ error: {
99
+ message: error.message || 'Failed to update space tag',
100
+ code: error.code,
101
+ details: error.data
102
+ }
103
+ };
104
+ }
105
+ }
106
+ /**
107
+ * Delete a tag from a space
108
+ * @param spaceId - ID of the space
109
+ * @param tagName - Name of the tag to delete
110
+ * @returns Promise with success status
111
+ */
112
+ async deleteSpaceTag(spaceId, tagName) {
113
+ try {
114
+ this.logger.debug(`Deleting tag "${tagName}" from space: ${spaceId}`);
115
+ // Encode the tag name in the URL
116
+ const encodedTagName = encodeURIComponent(tagName);
117
+ await this.client.delete(`/space/${spaceId}/tag/${encodedTagName}`);
118
+ return {
119
+ success: true
120
+ };
121
+ }
122
+ catch (error) {
123
+ this.logger.error(`Failed to delete tag "${tagName}" from space: ${spaceId}`, error);
124
+ return {
125
+ success: false,
126
+ error: {
127
+ message: error.message || 'Failed to delete space tag',
128
+ code: error.code,
129
+ details: error.data
130
+ }
131
+ };
132
+ }
133
+ }
134
+ /**
135
+ * Add a tag to a task
136
+ * @param taskId - ID of the task
137
+ * @param tagName - Name of the tag to add
138
+ * @returns Promise with success status
139
+ */
140
+ async addTagToTask(taskId, tagName) {
141
+ try {
142
+ this.logger.debug(`Adding tag "${tagName}" to task: ${taskId}`);
143
+ // Encode the tag name in the URL
144
+ const encodedTagName = encodeURIComponent(tagName);
145
+ await this.client.post(`/task/${taskId}/tag/${encodedTagName}`, {});
146
+ return {
147
+ success: true
148
+ };
149
+ }
150
+ catch (error) {
151
+ this.logger.error(`Failed to add tag "${tagName}" to task: ${taskId}`, error);
152
+ return {
153
+ success: false,
154
+ error: {
155
+ message: error.message || 'Failed to add tag to task',
156
+ code: error.code,
157
+ details: error.data
158
+ }
159
+ };
160
+ }
161
+ }
162
+ /**
163
+ * Remove a tag from a task
164
+ * @param taskId - ID of the task
165
+ * @param tagName - Name of the tag to remove
166
+ * @returns Promise with success status
167
+ */
168
+ async removeTagFromTask(taskId, tagName) {
169
+ try {
170
+ this.logger.debug(`Removing tag "${tagName}" from task: ${taskId}`);
171
+ // Encode the tag name in the URL
172
+ const encodedTagName = encodeURIComponent(tagName);
173
+ await this.client.delete(`/task/${taskId}/tag/${encodedTagName}`);
174
+ return {
175
+ success: true
176
+ };
177
+ }
178
+ catch (error) {
179
+ this.logger.error(`Failed to remove tag "${tagName}" from task: ${taskId}`, error);
180
+ return {
181
+ success: false,
182
+ error: {
183
+ message: error.message || 'Failed to remove tag from task',
184
+ code: error.code,
185
+ details: error.data
186
+ }
187
+ };
188
+ }
189
+ }
190
+ }
@@ -1,4 +1,7 @@
1
1
  /**
2
+ * SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
3
+ * SPDX-License-Identifier: MIT
4
+ *
2
5
  * ClickUp Task Service
3
6
  *
4
7
  * Handles all operations related to tasks in ClickUp, including:
@@ -10,6 +13,7 @@
10
13
  */
11
14
  import { BaseClickUpService, ErrorCode, ClickUpServiceError } from './base.js';
12
15
  import { ListService } from './list.js';
16
+ import { estimateTokensFromObject, wouldExceedTokenLimit } from '../../utils/token-utils.js';
13
17
  export class TaskService extends BaseClickUpService {
14
18
  constructor(apiKey, teamId, baseUrl, workspaceService) {
15
19
  super(apiKey, teamId, baseUrl);
@@ -59,6 +63,27 @@ export class TaskService extends BaseClickUpService {
59
63
  if (filters.assignees && filters.assignees.length > 0) {
60
64
  filters.assignees.forEach(assignee => params.append('assignees[]', assignee));
61
65
  }
66
+ // Team tasks endpoint specific parameters
67
+ if (filters.tags && filters.tags.length > 0) {
68
+ filters.tags.forEach(tag => params.append('tags[]', tag));
69
+ }
70
+ if (filters.list_ids && filters.list_ids.length > 0) {
71
+ filters.list_ids.forEach(id => params.append('list_ids[]', id));
72
+ }
73
+ if (filters.folder_ids && filters.folder_ids.length > 0) {
74
+ filters.folder_ids.forEach(id => params.append('folder_ids[]', id));
75
+ }
76
+ if (filters.space_ids && filters.space_ids.length > 0) {
77
+ filters.space_ids.forEach(id => params.append('space_ids[]', id));
78
+ }
79
+ if (filters.archived !== undefined)
80
+ params.append('archived', String(filters.archived));
81
+ if (filters.include_closed_lists !== undefined)
82
+ params.append('include_closed_lists', String(filters.include_closed_lists));
83
+ if (filters.include_archived_lists !== undefined)
84
+ params.append('include_archived_lists', String(filters.include_archived_lists));
85
+ if (filters.include_compact_time_entries !== undefined)
86
+ params.append('include_compact_time_entries', String(filters.include_compact_time_entries));
62
87
  // Date filters
63
88
  if (filters.due_date_gt)
64
89
  params.append('due_date_gt', String(filters.due_date_gt));
@@ -560,4 +585,117 @@ export class TaskService extends BaseClickUpService {
560
585
  throw this.handleError(error, `Failed to upload attachment from URL to task ${taskId}`);
561
586
  }
562
587
  }
588
+ /**
589
+ * Format task data for summary view
590
+ * @param task The task to format
591
+ * @returns TaskSummary object
592
+ */
593
+ formatTaskSummary(task) {
594
+ return {
595
+ id: task.id,
596
+ name: task.name,
597
+ status: task.status.status,
598
+ list: {
599
+ id: task.list.id,
600
+ name: task.list.name
601
+ },
602
+ due_date: task.due_date,
603
+ url: task.url,
604
+ priority: this.extractPriorityValue(task),
605
+ tags: task.tags.map(tag => ({
606
+ name: tag.name,
607
+ tag_bg: tag.tag_bg,
608
+ tag_fg: tag.tag_fg
609
+ }))
610
+ };
611
+ }
612
+ /**
613
+ * Estimates token count for a task in JSON format
614
+ * @param task ClickUp task
615
+ * @returns Estimated token count
616
+ */
617
+ estimateTaskTokens(task) {
618
+ return estimateTokensFromObject(task);
619
+ }
620
+ /**
621
+ * Get filtered tasks across the entire team/workspace using tags and other filters
622
+ * @param filters Task filters to apply including tags, list/folder/space filtering
623
+ * @returns Either a DetailedTaskResponse or WorkspaceTasksResponse depending on detail_level
624
+ */
625
+ async getWorkspaceTasks(filters = {}) {
626
+ try {
627
+ this.logOperation('getWorkspaceTasks', { filters });
628
+ const params = this.buildTaskFilterParams(filters);
629
+ const response = await this.client.get(`/team/${this.teamId}/task`, {
630
+ params
631
+ });
632
+ const tasks = response.data.tasks;
633
+ const totalCount = tasks.length; // Note: This is just the current page count
634
+ const hasMore = totalCount === 100; // ClickUp returns max 100 tasks per page
635
+ const nextPage = (filters.page || 0) + 1;
636
+ // If the estimated token count exceeds 50,000 or detail_level is 'summary',
637
+ // return summary format for efficiency and to avoid hitting token limits
638
+ const TOKEN_LIMIT = 50000;
639
+ // Estimate tokens for the full response
640
+ let tokensExceedLimit = false;
641
+ if (filters.detail_level !== 'summary' && tasks.length > 0) {
642
+ // We only need to check token count if detailed was requested
643
+ // For summary requests, we always return summary format
644
+ // First check with a sample task - if one task exceeds the limit, we definitely need summary
645
+ const sampleTask = tasks[0];
646
+ // Check if all tasks would exceed the token limit
647
+ const estimatedTokensPerTask = this.estimateTaskTokens(sampleTask);
648
+ const estimatedTotalTokens = estimatedTokensPerTask * tasks.length;
649
+ // Add 10% overhead for the response wrapper
650
+ tokensExceedLimit = estimatedTotalTokens * 1.1 > TOKEN_LIMIT;
651
+ // Double-check with more precise estimation if we're close to the limit
652
+ if (!tokensExceedLimit && estimatedTotalTokens * 1.1 > TOKEN_LIMIT * 0.8) {
653
+ // More precise check - build a representative sample and extrapolate
654
+ tokensExceedLimit = wouldExceedTokenLimit({ tasks, total_count: totalCount, has_more: hasMore, next_page: nextPage }, TOKEN_LIMIT);
655
+ }
656
+ }
657
+ // Determine if we should return summary or detailed based on request and token limit
658
+ const shouldUseSummary = filters.detail_level === 'summary' || tokensExceedLimit;
659
+ this.logOperation('getWorkspaceTasks', {
660
+ totalTasks: tasks.length,
661
+ estimatedTokens: tasks.reduce((count, task) => count + this.estimateTaskTokens(task), 0),
662
+ usingDetailedFormat: !shouldUseSummary,
663
+ requestedFormat: filters.detail_level || 'auto'
664
+ });
665
+ if (shouldUseSummary) {
666
+ return {
667
+ summaries: tasks.map(task => this.formatTaskSummary(task)),
668
+ total_count: totalCount,
669
+ has_more: hasMore,
670
+ next_page: nextPage
671
+ };
672
+ }
673
+ return {
674
+ tasks,
675
+ total_count: totalCount,
676
+ has_more: hasMore,
677
+ next_page: nextPage
678
+ };
679
+ }
680
+ catch (error) {
681
+ this.logOperation('getWorkspaceTasks', { error: error.message, status: error.response?.status });
682
+ throw this.handleError(error, 'Failed to get workspace tasks');
683
+ }
684
+ }
685
+ /**
686
+ * Get task summaries for lightweight retrieval
687
+ * @param filters Task filters to apply
688
+ * @returns WorkspaceTasksResponse with task summaries
689
+ */
690
+ async getTaskSummaries(filters = {}) {
691
+ return this.getWorkspaceTasks({ ...filters, detail_level: 'summary' });
692
+ }
693
+ /**
694
+ * Get detailed task data
695
+ * @param filters Task filters to apply
696
+ * @returns DetailedTaskResponse with full task data
697
+ */
698
+ async getTaskDetails(filters = {}) {
699
+ return this.getWorkspaceTasks({ ...filters, detail_level: 'detailed' });
700
+ }
563
701
  }
@@ -1,4 +1,7 @@
1
1
  /**
2
+ * SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
3
+ * SPDX-License-Identifier: MIT
4
+ *
2
5
  * Common type definitions for ClickUp API entities
3
6
  */
4
7
  // Helper function to validate and convert priority values
@@ -1,4 +1,7 @@
1
1
  /**
2
+ * SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
3
+ * SPDX-License-Identifier: MIT
4
+ *
2
5
  * ClickUp Workspace Service Module
3
6
  *
4
7
  * Handles workspace hierarchy and space-related operations
@@ -1,4 +1,7 @@
1
1
  /**
2
+ * SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
3
+ * SPDX-License-Identifier: MIT
4
+ *
2
5
  * Shared Services Module
3
6
  *
4
7
  * This module maintains singleton instances of services that should be shared
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Alternative implementation for bulk tasks creation
3
+ */
4
+ import { handleCreateBulkTasks } from './task.js';
5
+ /**
6
+ * Alternative tool definition for bulk task creation to work around MCP validation issues
7
+ */
8
+ export const createTasksBulkTool = {
9
+ name: "create_tasks_bulk",
10
+ description: "Create multiple tasks in a list efficiently. You MUST provide:\n1. An array of tasks with required properties\n2. Either listId or listName to specify the target list",
11
+ inputSchema: {
12
+ type: "object",
13
+ properties: {
14
+ listId: {
15
+ type: "string",
16
+ description: "ID of list for new tasks (preferred). Use this instead of listName if you have it."
17
+ },
18
+ listName: {
19
+ type: "string",
20
+ description: "Name of list for new tasks. Only use if you don't have listId."
21
+ },
22
+ tasks: {
23
+ // Define minimally to avoid validation issues
24
+ description: "Array of tasks to create. Each task must have at least a name."
25
+ }
26
+ },
27
+ required: ["tasks"]
28
+ }
29
+ };
30
+ /**
31
+ * Handler for create_tasks_bulk tool
32
+ */
33
+ export async function handleCreateTasksBulk(parameters) {
34
+ // Use the same implementation as handleCreateBulkTasks
35
+ return handleCreateBulkTasks(parameters);
36
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Debug Tools
3
+ *
4
+ * This module provides tools for debugging and monitoring the ClickUp MCP server.
5
+ */
6
+ // In-memory log storage
7
+ const DEBUG_LOGS = [];
8
+ /**
9
+ * Log a message to the in-memory debug logs
10
+ */
11
+ export function logDebug(message, level = 'info') {
12
+ const timestamp = new Date().toISOString();
13
+ DEBUG_LOGS.push({ timestamp, message, level });
14
+ // Optional: Also print to console for development
15
+ if (level === 'error') {
16
+ console.error(`[${timestamp}] ERROR: ${message}`);
17
+ }
18
+ else if (level === 'warn') {
19
+ console.warn(`[${timestamp}] WARN: ${message}`);
20
+ }
21
+ else {
22
+ console.log(`[${timestamp}] INFO: ${message}`);
23
+ }
24
+ }
25
+ /**
26
+ * Tool definition for checking debug logs
27
+ */
28
+ export const checkDebugLogsTool = {
29
+ name: 'check_debug_logs',
30
+ description: 'Check the server debug logs collected since the server started.',
31
+ inputSchema: {
32
+ type: 'object',
33
+ properties: {}
34
+ }
35
+ };
36
+ /**
37
+ * Tool definition for clearing debug logs
38
+ */
39
+ export const clearDebugLogsTool = {
40
+ name: 'clear_debug_logs',
41
+ description: 'Clear all server debug logs.',
42
+ inputSchema: {
43
+ type: 'object',
44
+ properties: {}
45
+ }
46
+ };
47
+ /**
48
+ * Handler for the check_debug_logs tool
49
+ */
50
+ export function handleCheckDebugLogs() {
51
+ return {
52
+ content: [
53
+ {
54
+ type: "text",
55
+ text: DEBUG_LOGS.length > 0
56
+ ? DEBUG_LOGS.map(log => `[${log.timestamp}] [${log.level.toUpperCase()}] ${log.message}`).join('\n')
57
+ : "No debug logs available."
58
+ }
59
+ ]
60
+ };
61
+ }
62
+ /**
63
+ * Handler for the clear_debug_logs tool
64
+ */
65
+ export function handleClearDebugLogs() {
66
+ const count = DEBUG_LOGS.length;
67
+ DEBUG_LOGS.length = 0;
68
+ return {
69
+ content: [
70
+ {
71
+ type: "text",
72
+ text: `Cleared ${count} debug log entries.`
73
+ }
74
+ ]
75
+ };
76
+ }
@@ -1,4 +1,7 @@
1
1
  /**
2
+ * SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
3
+ * SPDX-License-Identifier: MIT
4
+ *
2
5
  * ClickUp MCP Folder Tools
3
6
  *
4
7
  * This module defines folder-related tools for creating, retrieving,
@@ -1,4 +1,7 @@
1
1
  /**
2
+ * SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
3
+ * SPDX-License-Identifier: MIT
4
+ *
2
5
  * Tools Index
3
6
  *
4
7
  * This file exports all tool definitions and handlers for the ClickUp MCP server.
@@ -9,3 +12,4 @@ export * from './workspace.js';
9
12
  export * from './task/index.js';
10
13
  export * from './list.js';
11
14
  export * from './folder.js';
15
+ export * from './tag.js';
@@ -1,4 +1,7 @@
1
1
  /**
2
+ * SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
3
+ * SPDX-License-Identifier: MIT
4
+ *
2
5
  * ClickUp MCP List Tools
3
6
  *
4
7
  * This module defines list-related tools including creating, updating,
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Simple Logs Tool
3
+ *
4
+ * Provides a basic tool for reading server logs.
5
+ */
6
+ import { promises as fs } from 'fs';
7
+ import { join, dirname } from 'path';
8
+ // Define the log file path - using __dirname to get absolute path
9
+ const LOG_FILE = join(dirname(__dirname), 'server.log');
10
+ // Tool definition
11
+ export const checkLogsTool = {
12
+ name: "check_logs",
13
+ description: "Check server logs with optional filtering by log level",
14
+ parameters: {
15
+ type: "object",
16
+ properties: {
17
+ level: {
18
+ type: "string",
19
+ enum: ["TRACE", "DEBUG", "INFO", "WARN", "ERROR"],
20
+ description: "Filter logs by level"
21
+ },
22
+ limit: {
23
+ type: "number",
24
+ description: "Maximum number of log entries to return (default: 50)"
25
+ }
26
+ }
27
+ }
28
+ };
29
+ // Simple handler implementation using async/await with promises
30
+ export async function handleCheckLogs(params) {
31
+ try {
32
+ // Read log file using promises
33
+ const content = await fs.readFile(LOG_FILE, 'utf-8');
34
+ // Split into lines and filter out empty lines
35
+ let lines = content.split('\n').filter(Boolean);
36
+ // Apply level filtering if specified
37
+ if (params?.level) {
38
+ lines = lines.filter(line => line.includes(`${params.level}:`));
39
+ }
40
+ // Apply limit (default to 50)
41
+ const limit = params?.limit || 50;
42
+ const result = lines.slice(-limit);
43
+ // Return results
44
+ return {
45
+ logs: result.length > 0 ? result : ['No matching logs found.']
46
+ };
47
+ }
48
+ catch (error) {
49
+ // Simple error handling
50
+ if (error.code === 'ENOENT') {
51
+ return { logs: ['Log file not found.'] };
52
+ }
53
+ return { logs: [`Error reading logs: ${error.message}`] };
54
+ }
55
+ }