prompt-scheduler-mcp
Version:
MCP server for managing Augment Prompt Scheduler tasks with local workspace storage and real-time countdown features
259 lines • 10.1 kB
JavaScript
import * as fs from 'fs/promises';
import * as path from 'path';
import * as crypto from 'crypto';
export class PromptSchedulerManager {
tasksFilePath;
configFilePath;
workspaceId;
constructor(workspaceId) {
// Use workspace-specific storage inside the workspace directory
this.workspaceId = workspaceId || this.detectWorkspace();
// Store tasks inside the workspace directory in a .augment-scheduler folder
const workspaceDataDir = path.join(this.workspaceId, '.augment-scheduler');
this.tasksFilePath = path.join(workspaceDataDir, 'tasks.json');
this.configFilePath = path.join(workspaceDataDir, 'config.json');
console.error(`MCP Server using workspace: ${this.workspaceId}`);
console.error(`Tasks will be stored in: ${this.tasksFilePath}`);
this.ensureDataDirectory();
}
detectWorkspace() {
// Priority order for workspace detection:
// 1. Environment variables from MCP client
// 2. Current working directory (where MCP client is running)
// 3. Look for VS Code workspace indicators
const cwd = process.cwd();
// Check for VS Code specific environment variables
if (process.env.VSCODE_CWD) {
console.error(`Using VSCODE_CWD: ${process.env.VSCODE_CWD}`);
return process.env.VSCODE_CWD;
}
// Check for workspace path from MCP client environment
if (process.env.WORKSPACE_PATH) {
console.error(`Using WORKSPACE_PATH: ${process.env.WORKSPACE_PATH}`);
return process.env.WORKSPACE_PATH;
}
// Use current working directory (where MCP client is running)
// This should match where the user is working
console.error(`Using current working directory: ${cwd}`);
return cwd;
}
getWorkspaceHash(workspaceId) {
// Use the same hashing algorithm as the VS Code extension
return crypto.createHash('md5').update(workspaceId).digest('hex').substring(0, 8);
}
async ensureDataDirectory() {
const dataDir = path.dirname(this.tasksFilePath);
try {
await fs.access(dataDir);
}
catch {
await fs.mkdir(dataDir, { recursive: true });
}
}
setWorkspace(workspaceId) {
this.workspaceId = workspaceId;
// Store tasks inside the workspace directory in a .augment-scheduler folder
const workspaceDataDir = path.join(workspaceId, '.augment-scheduler');
this.tasksFilePath = path.join(workspaceDataDir, 'tasks.json');
this.configFilePath = path.join(workspaceDataDir, 'config.json');
console.error(`MCP Server switched to workspace: ${workspaceId}`);
console.error(`Tasks will be stored in: ${this.tasksFilePath}`);
}
getWorkspaceInfo() {
return {
workspaceId: this.workspaceId,
tasksFile: this.tasksFilePath,
configFile: this.configFilePath
};
}
generateTaskId() {
return `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
async loadTasks() {
try {
const data = await fs.readFile(this.tasksFilePath, 'utf-8');
return JSON.parse(data);
}
catch (error) {
// File doesn't exist or is invalid, return empty array
return [];
}
}
async saveTasks(tasks) {
await this.ensureDataDirectory();
await fs.writeFile(this.tasksFilePath, JSON.stringify(tasks, null, 2));
}
async createTask(taskData) {
try {
const tasks = await this.loadTasks();
const newTask = {
id: this.generateTaskId(),
title: taskData.title,
prompt: taskData.prompt,
priority: taskData.priority || 'medium',
createdAt: Date.now(),
tags: taskData.tags || [],
isActive: taskData.isActive !== false,
executionCount: 0,
maxExecutions: taskData.maxExecutions || -1,
executionMode: taskData.executionMode || 'infinite',
idleThresholdMinutes: taskData.idleThresholdMinutes || 5,
idleStartTime: undefined,
lastStatusCheck: 'unknown'
};
tasks.push(newTask);
await this.saveTasks(tasks);
return { success: true, task: newTask };
}
catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
async listTasks(options = {}) {
try {
let tasks = await this.loadTasks();
if (options.activeOnly) {
tasks = tasks.filter(task => task.isActive);
}
if (options.priority) {
tasks = tasks.filter(task => task.priority === options.priority);
}
return { success: true, tasks };
}
catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
async getTask(id) {
try {
const tasks = await this.loadTasks();
const task = tasks.find(t => t.id === id);
if (!task) {
return { success: false, error: 'Task not found' };
}
return { success: true, task };
}
catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
async updateTask(updateData) {
try {
const tasks = await this.loadTasks();
const taskIndex = tasks.findIndex(t => t.id === updateData.id);
if (taskIndex === -1) {
return { success: false, error: 'Task not found' };
}
// Update the task with provided data
const updatedTask = { ...tasks[taskIndex] };
Object.keys(updateData).forEach(key => {
if (key !== 'id' && updateData[key] !== undefined) {
updatedTask[key] = updateData[key];
}
});
tasks[taskIndex] = updatedTask;
await this.saveTasks(tasks);
return { success: true, task: updatedTask };
}
catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
async deleteTask(id) {
try {
const tasks = await this.loadTasks();
const taskIndex = tasks.findIndex(t => t.id === id);
if (taskIndex === -1) {
return { success: false, error: 'Task not found' };
}
tasks.splice(taskIndex, 1);
await this.saveTasks(tasks);
return { success: true };
}
catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
async executeTask(id) {
try {
const tasks = await this.loadTasks();
const task = tasks.find(t => t.id === id);
if (!task) {
return { success: false, error: 'Task not found' };
}
if (!task.isActive) {
return { success: false, error: 'Task is not active' };
}
// Update execution count and timestamp
task.executionCount++;
task.lastExecuted = Date.now();
// Handle execution limits
if (task.executionMode === 'once' && task.executionCount >= 1) {
task.isActive = false;
}
else if (task.executionMode === 'limited' && task.maxExecutions > 0 && task.executionCount >= task.maxExecutions) {
task.isActive = false;
}
await this.saveTasks(tasks);
// Create an execution request file that the VS Code extension can monitor
const executionRequest = {
taskId: id,
prompt: task.prompt,
timestamp: Date.now(),
title: task.title
};
const executionDir = path.join(path.dirname(this.tasksFilePath), 'executions');
await fs.mkdir(executionDir, { recursive: true });
const executionFile = path.join(executionDir, `execution_${Date.now()}_${id}.json`);
await fs.writeFile(executionFile, JSON.stringify(executionRequest, null, 2));
return {
success: true,
message: `Task "${task.title}" queued for execution. Execution count: ${task.executionCount}`
};
}
catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
async getSchedulerStatus() {
try {
const tasks = await this.loadTasks();
const activeTasks = tasks.filter(t => t.isActive).length;
const completedTasks = tasks.filter(t => !t.isActive).length;
const lastActivity = Math.max(...tasks.map(t => t.lastExecuted || 0));
const status = {
totalTasks: tasks.length,
activeTasks,
completedTasks,
lastActivity: lastActivity > 0 ? lastActivity : undefined,
isRunning: true // Assume running if we can access the data
};
return { success: true, status };
}
catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
}
//# sourceMappingURL=scheduler-manager.js.map