UNPKG

onerios-mcp-server

Version:

OneriosMCP server providing memory, backlog management, file operations, and utility functions for enhanced AI assistant capabilities

314 lines (313 loc) 10.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BacklogDataSchema = exports.IssueSchema = exports.TaskSchema = exports.IssueStatus = exports.TaskStatus = void 0; exports.createIssue = createIssue; exports.listIssues = listIssues; exports.selectIssue = selectIssue; exports.initializeIssue = initializeIssue; exports.updateIssueStatus = updateIssueStatus; exports.addTask = addTask; exports.listTasks = listTasks; exports.updateTaskStatus = updateTaskStatus; exports.getBacklogStats = getBacklogStats; const zod_1 = require("zod"); const fs_1 = require("fs"); const uuid_1 = require("uuid"); // Status enums var TaskStatus; (function (TaskStatus) { TaskStatus["NEW"] = "New"; TaskStatus["IN_WORK"] = "InWork"; TaskStatus["DONE"] = "Done"; })(TaskStatus || (exports.TaskStatus = TaskStatus = {})); var IssueStatus; (function (IssueStatus) { IssueStatus["NEW"] = "New"; IssueStatus["IN_WORK"] = "InWork"; IssueStatus["DONE"] = "Done"; })(IssueStatus || (exports.IssueStatus = IssueStatus = {})); // Schemas for validation exports.TaskSchema = zod_1.z.object({ id: zod_1.z.string(), title: zod_1.z.string(), description: zod_1.z.string().optional(), status: zod_1.z.nativeEnum(TaskStatus), created: zod_1.z.string().datetime(), updated: zod_1.z.string().datetime() }); exports.IssueSchema = zod_1.z.object({ name: zod_1.z.string(), description: zod_1.z.string().optional(), status: zod_1.z.nativeEnum(IssueStatus), tasks: zod_1.z.record(zod_1.z.string(), exports.TaskSchema), created: zod_1.z.string().datetime(), updated: zod_1.z.string().datetime() }); exports.BacklogDataSchema = zod_1.z.object({ issues: zod_1.z.record(zod_1.z.string(), exports.IssueSchema), activeIssue: zod_1.z.string().optional() }); // Default file path const DEFAULT_BACKLOG_FILE = 'backlog.json'; /** * Get the backlog file path from environment or use default */ function getBacklogFilePath() { return process.env.BACKLOG_FILE || DEFAULT_BACKLOG_FILE; } /** * Load backlog data from file */ async function loadBacklogData() { const filePath = getBacklogFilePath(); try { const fileExists = await fs_1.promises.access(filePath).then(() => true).catch(() => false); if (!fileExists) { return { issues: {} }; } const content = await fs_1.promises.readFile(filePath, 'utf-8'); const data = JSON.parse(content); return exports.BacklogDataSchema.parse(data); } catch (error) { console.error(`Error loading backlog data: ${error}`); return { issues: {} }; } } /** * Save backlog data to file */ async function saveBacklogData(data) { const filePath = getBacklogFilePath(); try { await fs_1.promises.writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8'); } catch (error) { console.error(`Error saving backlog data: ${error}`); throw new Error(`Failed to save backlog data: ${error}`); } } /** * Generate a short unique ID for tasks */ function generateTaskId() { return (0, uuid_1.v4)().substring(0, 8); } /** * Get current ISO datetime string */ function getCurrentDateTime() { return new Date().toISOString(); } /** * Create a new issue */ async function createIssue(name, description = '', status = IssueStatus.NEW) { const data = await loadBacklogData(); if (data.issues[name]) { throw new Error(`Issue '${name}' already exists`); } const now = getCurrentDateTime(); data.issues[name] = { name, description, status, tasks: {}, created: now, updated: now }; // Set as active issue data.activeIssue = name; await saveBacklogData(data); return `Successfully created issue: ${name} with status: ${status}`; } /** * List all issues */ async function listIssues() { const data = await loadBacklogData(); if (Object.keys(data.issues).length === 0) { return "No issues found. Use 'createIssue' to create a new issue."; } const result = ["Available issues:"]; for (const [issueName, issue] of Object.entries(data.issues)) { const taskCount = Object.keys(issue.tasks).length; const activeMarker = issueName === data.activeIssue ? " (active)" : ""; const descriptionPreview = issue.description.length > 30 ? issue.description.substring(0, 30) + "..." : issue.description; result.push(`- ${issueName}${activeMarker}: Status: ${issue.status}, Tasks: ${taskCount}`); if (descriptionPreview) { result.push(` Description: ${descriptionPreview}`); } } return result.join('\n'); } /** * Select an issue as active */ async function selectIssue(name) { const data = await loadBacklogData(); if (!data.issues[name]) { throw new Error(`Issue '${name}' not found`); } data.activeIssue = name; await saveBacklogData(data); return `Selected issue: ${name}`; } /** * Initialize or reset an issue */ async function initializeIssue(name, description = '', status = IssueStatus.NEW) { const data = await loadBacklogData(); const now = getCurrentDateTime(); const existingIssue = data.issues[name]; data.issues[name] = { name, description, status, tasks: {}, created: existingIssue?.created || now, updated: now }; // Set as active issue data.activeIssue = name; await saveBacklogData(data); return `Successfully initialized issue: ${name} with status: ${status}`; } /** * Update issue status */ async function updateIssueStatus(name, status) { const data = await loadBacklogData(); if (!data.issues[name]) { throw new Error(`Issue '${name}' not found`); } const oldStatus = data.issues[name].status; data.issues[name].status = status; data.issues[name].updated = getCurrentDateTime(); await saveBacklogData(data); return `Successfully updated issue '${name}' status from '${oldStatus}' to '${status}'`; } /** * Add a task to the active issue */ async function addTask(title, description = '') { const data = await loadBacklogData(); if (!data.activeIssue) { throw new Error("No active issue. Please select an issue using 'selectIssue' first"); } if (!data.issues[data.activeIssue]) { throw new Error(`Active issue '${data.activeIssue}' not found`); } const taskId = generateTaskId(); const now = getCurrentDateTime(); data.issues[data.activeIssue].tasks[taskId] = { id: taskId, title, description, status: TaskStatus.NEW, created: now, updated: now }; data.issues[data.activeIssue].updated = now; await saveBacklogData(data); return `Successfully added task: ${title} (ID: ${taskId}) to issue '${data.activeIssue}'`; } /** * List tasks in the active issue, optionally filtered by status */ async function listTasks(statusFilter) { const data = await loadBacklogData(); if (!data.activeIssue) { throw new Error("No active issue. Please select an issue using 'selectIssue' first"); } if (!data.issues[data.activeIssue]) { throw new Error(`Active issue '${data.activeIssue}' not found`); } const issue = data.issues[data.activeIssue]; const tasks = Object.values(issue.tasks); if (tasks.length === 0) { return `No tasks found in issue '${data.activeIssue}'`; } const filteredTasks = statusFilter ? tasks.filter(task => task.status === statusFilter) : tasks; if (filteredTasks.length === 0) { return statusFilter ? `No tasks found with status '${statusFilter}' in issue '${data.activeIssue}'` : "No tasks found"; } const result = [`Tasks for issue: ${data.activeIssue}`]; if (issue.description) { result.push(`Issue description: ${issue.description}`); } for (const task of filteredTasks) { result.push(`\nID: ${task.id}`); result.push(`Title: ${task.title}`); result.push(`Status: ${task.status}`); if (task.description) { result.push(`Description: ${task.description}`); } } return result.join('\n'); } /** * Update task status */ async function updateTaskStatus(taskId, status) { const data = await loadBacklogData(); if (!data.activeIssue) { throw new Error("No active issue. Please select an issue using 'selectIssue' first"); } if (!data.issues[data.activeIssue]) { throw new Error(`Active issue '${data.activeIssue}' not found`); } const task = data.issues[data.activeIssue].tasks[taskId]; if (!task) { throw new Error(`Task with ID '${taskId}' not found in issue '${data.activeIssue}'`); } const oldStatus = task.status; task.status = status; task.updated = getCurrentDateTime(); data.issues[data.activeIssue].updated = getCurrentDateTime(); await saveBacklogData(data); return `Successfully updated task '${task.title}' (ID: ${taskId}) status from '${oldStatus}' to '${status}'`; } /** * Get backlog statistics */ async function getBacklogStats() { const data = await loadBacklogData(); const issueCount = Object.keys(data.issues).length; let totalTasks = 0; let tasksByStatus = { [TaskStatus.NEW]: 0, [TaskStatus.IN_WORK]: 0, [TaskStatus.DONE]: 0 }; let issuesByStatus = { [IssueStatus.NEW]: 0, [IssueStatus.IN_WORK]: 0, [IssueStatus.DONE]: 0 }; for (const issue of Object.values(data.issues)) { issuesByStatus[issue.status]++; const tasks = Object.values(issue.tasks); totalTasks += tasks.length; for (const task of tasks) { tasksByStatus[task.status]++; } } const result = [ "📊 Backlog Statistics", `Total Issues: ${issueCount}`, `Total Tasks: ${totalTasks}`, "", "Issues by Status:", ` • New: ${issuesByStatus[IssueStatus.NEW]}`, ` • In Work: ${issuesByStatus[IssueStatus.IN_WORK]}`, ` • Done: ${issuesByStatus[IssueStatus.DONE]}`, "", "Tasks by Status:", ` • New: ${tasksByStatus[TaskStatus.NEW]}`, ` • In Work: ${tasksByStatus[TaskStatus.IN_WORK]}`, ` • Done: ${tasksByStatus[TaskStatus.DONE]}` ]; if (data.activeIssue) { result.push(`\nActive Issue: ${data.activeIssue}`); } return result.join('\n'); }