Compare commits

...

10 commits

Author SHA1 Message Date
autocommit
d2881ecf98 deps-upgrade(dependencies): ⬆️ Update dependencies to latest stable versions for improved compatibility and security
Some checks failed
Build and Publish / build-and-publish (push) Failing after 41s
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-10 21:14:59 -07:00
Lilith
8b7ac56ffe deps-upgrade(dependencies): ⬆️ Update dependencies to latest stable versions for bug fixes and security patches
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-03-08 19:23:11 -07:00
Lilith
7205e2ab2b deps-upgrade: ⬆️ Update dependencies to latest stable versions in package.json
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-02-02 15:39:01 -08:00
Lilith
0ad3205dc8 refactor(src): ♻️ Restructure src/index.ts exports and initialization logic for improved maintainability
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-02-02 15:33:23 -08:00
Lilith
962bab4485 chore: trigger CI publish 2026-01-30 11:56:05 -08:00
Lilith
9680263881 deps-upgrade: ⬆️ Update dependencies to latest versions including minor updates and security patches 2026-01-22 14:08:37 -08:00
Lilith
268464789f deps-upgrade(package): ⬆️ Update dependencies to latest versions 2026-01-22 11:48:30 -08:00
Lilith
7c6bd9f96f chore(build): 🔧 Update tsup.config.ts to refine output paths, asset handling, and optimize bundle generation 2026-01-21 15:31:17 -08:00
Lilith
fe7d5d4562 deps-upgrade: ⬆️ Update dependency versions in package.json to latest compatible releases 2026-01-21 15:31:17 -08:00
Lilith
ef120ec01b chore(core): 🔧 Update tsup build tool configuration 2026-01-21 12:36:48 -08:00
4 changed files with 146 additions and 375 deletions

View file

@ -251,3 +251,4 @@ This directory is created automatically on first use.
## License
MIT

View file

@ -1,10 +1,9 @@
{
"name": "@lilith/mcp-task-persistence",
"version": "1.1.0",
"version": "1.2.0",
"description": "MCP server for persisting user prompts across Claude Code sessions, enabling task recovery",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"bin": {
"mcp-task-persistence": "./dist/index.js"
},
@ -14,8 +13,8 @@
"LICENSE"
],
"scripts": {
"build": "tsup",
"postbuild": "chmod +x dist/index.js",
"build": "tsup --no-dts",
"postbuild": "sed -i \"s|from '@modelcontextprotocol/sdk/server'|from '@modelcontextprotocol/sdk/server/index.js'|g; s|from '@modelcontextprotocol/sdk/server/stdio'|from '@modelcontextprotocol/sdk/server/stdio.js'|g; s|from '@modelcontextprotocol/sdk/types'|from '@modelcontextprotocol/sdk/types.js'|g\" dist/index.js && chmod +x dist/index.js",
"watch": "tsc --watch",
"dev": "tsc --watch",
"prepublishOnly": "npm run build",
@ -37,8 +36,8 @@
"devDependencies": {
"@lilith/configs": "workspace:*",
"@types/node": "^20.19.28",
"typescript": "^5.9.3",
"tsup": "^8.5.1"
"tsup": "^8.5.1",
"typescript": "^5.9.3"
},
"_": {
"registry": "forgejo",
@ -46,6 +45,6 @@
"build": true
},
"publishConfig": {
"registry": "http://forge.nasty.sh/api/packages/lilith/npm/"
"registry": "http://forge.black.lan/api/packages/lilith/npm/"
}
}

View file

@ -3,52 +3,41 @@
* MCP Task Persistence Server
*
* Persists user prompts across Claude Code sessions for task recovery.
* Tasks are stored in ~/.local/claude/tasks/ with depth-first path naming.
* Tasks are stored in ~/.local/claude/tasks/ with timestamp_slug_project naming.
*/
import { homedir } from 'os';
import { join, sep } from 'path';
import { existsSync, readdirSync, statSync, utimesSync } from 'fs';
import { mkdir, writeFile, readFile, unlink, appendFile } from 'fs/promises';
import { existsSync, utimesSync } from 'fs';
import { mkdir, writeFile, readFile, readdir, unlink, appendFile, stat } from 'fs/promises';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { Server } from '@modelcontextprotocol/sdk/server';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio';
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types';
const TASKS_DIR = join(homedir(), '.local', 'claude', 'tasks');
const SLUG_MAX_LENGTH = 30;
/**
* Slugify a string for use in filenames
* - Lowercase
* - Replace spaces and underscores with dashes
* - Remove special chars except alphanumeric and dashes
* - Truncate to max length
* - Remove trailing dashes
*/
function slugify(text: string, maxLength = SLUG_MAX_LENGTH): string {
if (!text || !text.trim()) {
return 'task';
}
if (!text?.trim()) return 'task';
let slug = text
.toLowerCase()
.replace(/[\s_]+/g, '-') // spaces/underscores → dashes
.replace(/[^a-z0-9-]/g, '') // remove non-alphanumeric except dashes
.replace(/-+/g, '-') // collapse multiple dashes
.replace(/^-|-$/g, ''); // trim leading/trailing dashes
.replace(/[\s_]+/g, '-')
.replace(/[^a-z0-9-]/g, '')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '');
if (slug.length > maxLength) {
slug = slug.substring(0, maxLength);
// Don't cut in the middle of a word if possible
const lastDash = slug.lastIndexOf('-');
if (lastDash > maxLength * 0.6) {
slug = slug.substring(0, lastDash);
}
slug = slug.replace(/-$/g, ''); // trim trailing dash after truncation
slug = slug.replace(/-$/g, '');
}
return slug || 'task';
@ -56,13 +45,10 @@ function slugify(text: string, maxLength = SLUG_MAX_LENGTH): string {
/**
* Extract project name from working directory path
* /var/home/lilith/Code/@applications/@lilith/lilith-platform lilith-platform
*/
function extractProjectName(workingDir: string): string {
const parts = workingDir.split(sep).filter(Boolean);
const lastPart = parts[parts.length - 1] || 'unknown';
// Slugify the project name (handles @ symbols etc.)
return slugify(lastPart, 30);
return slugify(parts[parts.length - 1] || 'unknown', 30);
}
/**
@ -74,69 +60,35 @@ async function ensureTasksDir(): Promise<void> {
}
}
/**
* Convert a path to depth-first filename format (legacy format)
* /var/home/lilith/Code/packages/ -> packages-Code-lilith-home-var
*/
function pathToDepthFirst(workingDir: string): string {
const parts = workingDir.split(sep).filter(Boolean);
return parts.reverse().join('-');
}
/**
* Generate a task filename from timestamp, prompt, and working directory
* New format: {timestamp}_{prompt-slug}_{project-name}.txt
*/
function generateTaskFilename(timestamp: number, prompt: string, workingDir: string): string {
const promptSlug = slugify(prompt);
const projectName = extractProjectName(workingDir);
return `${timestamp}_${promptSlug}_${projectName}.txt`;
}
interface ParsedTaskFilename {
timestamp: number;
slug: string;
project: string;
// For backward compatibility
pathInfo?: string;
isLegacyFormat: boolean;
}
/**
* Parse task filename to extract components
* Supports both new format: {timestamp}_{slug}_{project}.txt
* And legacy format: {timestamp}_{reversed-path}.txt
* Parse task filename: {timestamp}_{slug}_{project}.txt
*/
function parseTaskFilename(filename: string): ParsedTaskFilename | null {
// Try new format first: {timestamp}_{slug}_{project}.txt
// New format has exactly 2 underscores separating 3 parts
const newMatch = filename.match(/^(\d+)_([a-z0-9-]+)_([a-z0-9-]+)\.txt$/);
if (newMatch) {
return {
timestamp: parseInt(newMatch[1], 10),
slug: newMatch[2],
project: newMatch[3],
isLegacyFormat: false,
};
}
const match = filename.match(/^(\d+)_([a-z0-9-]+)_([a-z0-9-]+)\.txt$/);
if (!match) return null;
// Fall back to legacy format: {timestamp}_{anything}.txt
const legacyMatch = filename.match(/^(\d+)_(.+)\.txt$/);
if (legacyMatch) {
return {
timestamp: parseInt(legacyMatch[1], 10),
slug: '',
project: '',
pathInfo: legacyMatch[2],
isLegacyFormat: true,
};
}
return null;
return {
timestamp: parseInt(match[1], 10),
slug: match[2],
project: match[3],
};
}
/**
* Save a new task with structured content format
* Generate a task filename
*/
function generateTaskFilename(timestamp: number, prompt: string, workingDir: string): string {
return `${timestamp}_${slugify(prompt)}_${extractProjectName(workingDir)}.txt`;
}
/**
* Save a new task
*/
async function saveTask(prompt: string, workingDir: string): Promise<{ taskId: string; filename: string }> {
await ensureTasksDir();
@ -145,25 +97,54 @@ async function saveTask(prompt: string, workingDir: string): Promise<{ taskId: s
const filename = generateTaskFilename(timestamp, prompt, workingDir);
const filepath = join(TASKS_DIR, filename);
// Write structured content with header
const createdDate = new Date(timestamp).toISOString();
const content = `[Task: ${timestamp}]
[Directory: ${workingDir}]
[Created: ${createdDate}]
[Created: ${new Date(timestamp).toISOString()}]
${prompt}
`;
await writeFile(filepath, content, 'utf-8');
return {
taskId: timestamp.toString(),
filename,
};
return { taskId: timestamp.toString(), filename };
}
/**
* Append content to an existing task (for conversation history)
* Find task file by working directory (most recently modified)
*/
async function findTaskByWorkingDir(workingDir: string): Promise<string | null> {
if (!existsSync(TASKS_DIR)) return null;
const projectName = extractProjectName(workingDir);
const suffix = `_${projectName}.txt`;
const files = await readdir(TASKS_DIR);
const matching = files.filter((f) => f.endsWith(suffix));
if (matching.length === 0) return null;
const withMtime = await Promise.all(
matching.map(async (f) => ({
file: f,
mtime: (await stat(join(TASKS_DIR, f))).mtime.getTime(),
}))
);
const newest = withMtime.reduce((a, b) => (a.mtime > b.mtime ? a : b));
return join(TASKS_DIR, newest.file);
}
/**
* Find task file by timestamp prefix
*/
async function findTaskByTimestamp(taskId: string): Promise<string | null> {
if (!existsSync(TASKS_DIR)) return null;
const files = await readdir(TASKS_DIR);
const match = files.find((f) => f.startsWith(`${taskId}_`));
return match ? join(TASKS_DIR, match) : null;
}
/**
* Append content to an existing task
*/
async function appendToTask(
content: string,
@ -174,66 +155,22 @@ async function appendToTask(
let filepath: string | null = null;
if (taskId) {
filepath = findTaskByTimestamp(taskId);
filepath = await findTaskByTimestamp(taskId);
} else if (workingDir) {
filepath = findTaskByWorkingDir(workingDir);
filepath = await findTaskByWorkingDir(workingDir);
}
if (!filepath || !existsSync(filepath)) {
throw new Error('Task not found');
}
const timestamp = new Date().toISOString();
const marker = type === 'user' ? '--- USER ---' : '--- SUMMARY ---';
const entry = `\n\n${marker} [${timestamp}]\n${content}`;
const entry = `\n\n${marker} [${new Date().toISOString()}]\n${content}`;
await appendFile(filepath, entry, 'utf-8');
return `Appended ${type} to task`;
}
/**
* Find task file by timestamp prefix
*/
function findTaskByTimestamp(taskId: string): string | null {
if (!existsSync(TASKS_DIR)) return null;
const files = readdirSync(TASKS_DIR);
const match = files.find((f) => f.startsWith(`${taskId}_`));
return match ? join(TASKS_DIR, match) : null;
}
/**
* Find task file by working directory (for current session)
* Supports both new format (project name) and legacy format (reversed path)
* Returns the most recently modified matching file
*/
function findTaskByWorkingDir(workingDir: string): string | null {
if (!existsSync(TASKS_DIR)) return null;
const projectName = extractProjectName(workingDir);
const depthFirst = pathToDepthFirst(workingDir);
const files = readdirSync(TASKS_DIR);
// Find all matching files (both formats)
const matchingFiles = files.filter((f) => {
// New format: ends with _{project-name}.txt
if (f.endsWith(`_${projectName}.txt`)) return true;
// Legacy format: ends with _{reversed-path}.txt
if (f.endsWith(`_${depthFirst}.txt`)) return true;
return false;
});
if (matchingFiles.length === 0) return null;
// Return most recently modified
const sorted = matchingFiles
.map((f) => ({ file: f, mtime: statSync(join(TASKS_DIR, f)).mtime }))
.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
return join(TASKS_DIR, sorted[0].file);
}
/**
* Complete (delete) a task
*/
@ -241,9 +178,9 @@ async function completeTask(taskId?: string, workingDir?: string): Promise<strin
let filepath: string | null = null;
if (taskId) {
filepath = findTaskByTimestamp(taskId);
filepath = await findTaskByTimestamp(taskId);
} else if (workingDir) {
filepath = findTaskByWorkingDir(workingDir);
filepath = await findTaskByWorkingDir(workingDir);
}
if (!filepath || !existsSync(filepath)) {
@ -258,125 +195,93 @@ async function completeTask(taskId?: string, workingDir?: string): Promise<strin
* Load a task by timestamp
*/
async function loadTask(taskId: string): Promise<{ prompt: string; recoveryMessage: string }> {
const filepath = findTaskByTimestamp(taskId);
const filepath = await findTaskByTimestamp(taskId);
if (!filepath || !existsSync(filepath)) {
throw new Error(`Task not found: ${taskId}`);
}
// Touch the file (update mtime)
const now = new Date();
utimesSync(filepath, now, now);
const content = await readFile(filepath, 'utf-8');
const recoveryMessage = `The session that was working on the following task was lost. Figure out how to continue it.
return {
prompt: content,
recoveryMessage: `The session working on the following task was lost. Continue it.\n\n<task_context>\n${content}\n</task_context>`,
};
}
<task_context>
${content}
</task_context>`;
return { prompt: content, recoveryMessage };
interface ResumeResult {
taskId: string;
content: string;
recoveryMessage: string;
}
/**
* Resume the most recent task for a working directory (for post-reboot recovery)
* Supports both new format (project name) and legacy format (reversed path)
* Resume the most recent task for a working directory
*/
async function resumeTask(workingDir: string): Promise<{ taskId: string; content: string; recoveryMessage: string }> {
if (!existsSync(TASKS_DIR)) {
throw new Error('No tasks found');
}
async function resumeTask(workingDir: string): Promise<ResumeResult> {
const filepath = await findTaskByWorkingDir(workingDir);
const projectName = extractProjectName(workingDir);
const depthFirst = pathToDepthFirst(workingDir);
const files = readdirSync(TASKS_DIR);
// Find all tasks matching this directory (both formats)
const matchingFiles = files.filter((f) => {
// New format: ends with _{project-name}.txt
if (f.endsWith(`_${projectName}.txt`)) return true;
// Legacy format: ends with _{reversed-path}.txt
if (f.endsWith(`_${depthFirst}.txt`)) return true;
return false;
});
if (matchingFiles.length === 0) {
if (!filepath) {
throw new Error(`No task found for directory: ${workingDir}`);
}
// Sort by mtime (most recent first) and pick the latest
const sorted = matchingFiles
.map((f) => ({ file: f, mtime: statSync(join(TASKS_DIR, f)).mtime }))
.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
const content = await readFile(filepath, 'utf-8');
const filename = filepath.split(sep).pop()!;
const parsed = parseTaskFilename(filename);
const latest = sorted[0];
const filepath = join(TASKS_DIR, latest.file);
const parsed = parseTaskFilename(latest.file);
// Touch the file
const now = new Date();
utimesSync(filepath, now, now);
const content = await readFile(filepath, 'utf-8');
const recoveryMessage = `The session that was working on the following task was lost (likely due to host reboot/crash). Figure out how to continue it.
<task_context>
${content}
</task_context>`;
return {
taskId: parsed?.timestamp.toString() || '',
content,
recoveryMessage,
recoveryMessage: `The session working on the following task was lost. Continue it.\n\n<task_context>\n${content}\n</task_context>`,
};
}
interface TaskListItem {
taskId: string;
slug: string; // Task description (new format) or empty (legacy)
project: string; // Project name (new format) or empty (legacy)
pathInfo?: string; // Full path info (legacy format only)
isLegacy: boolean;
slug: string;
project: string;
created: Date;
modified: Date;
}
/**
* List all tasks
* Returns both new format and legacy format tasks
*/
function listTasks(): TaskListItem[] {
async function listTasks(): Promise<TaskListItem[]> {
if (!existsSync(TASKS_DIR)) return [];
const files = readdirSync(TASKS_DIR);
const tasks: TaskListItem[] = [];
const files = await readdir(TASKS_DIR);
const validFiles: Array<{ file: string; parsed: ParsedTaskFilename }> = [];
for (const file of files) {
const parsed = parseTaskFilename(file);
if (parsed) {
const filepath = join(TASKS_DIR, file);
const stats = statSync(filepath);
tasks.push({
taskId: parsed.timestamp.toString(),
slug: parsed.slug,
project: parsed.project,
pathInfo: parsed.pathInfo,
isLegacy: parsed.isLegacyFormat,
created: new Date(parsed.timestamp),
modified: stats.mtime,
});
validFiles.push({ file, parsed });
}
}
// Sort by creation time (newest first)
const tasks = await Promise.all(
validFiles.map(async ({ file, parsed }) => {
const stats = await stat(join(TASKS_DIR, file));
return {
taskId: parsed.timestamp.toString(),
slug: parsed.slug,
project: parsed.project,
created: new Date(parsed.timestamp),
modified: stats.mtime,
};
})
);
return tasks.sort((a, b) => b.created.getTime() - a.created.getTime());
}
/**
* Tool definitions
*/
const TOOLS = [
{
name: 'save_task',
@ -384,14 +289,8 @@ const TOOLS = [
inputSchema: {
type: 'object',
properties: {
prompt: {
type: 'string',
description: 'The user prompt to save',
},
working_directory: {
type: 'string',
description: 'Current working directory (used for task identification)',
},
prompt: { type: 'string', description: 'The user prompt to save' },
working_directory: { type: 'string', description: 'Current working directory (used for task identification)' },
},
required: ['prompt', 'working_directory'],
},
@ -402,14 +301,8 @@ const TOOLS = [
inputSchema: {
type: 'object',
properties: {
task_id: {
type: 'string',
description: 'Task ID (timestamp) to complete. If omitted, finds task by working_directory.',
},
working_directory: {
type: 'string',
description: 'Current working directory (used to find task if task_id not provided)',
},
task_id: { type: 'string', description: 'Task ID (timestamp) to complete. If omitted, finds task by working_directory.' },
working_directory: { type: 'string', description: 'Current working directory (used to find task if task_id not provided)' },
},
},
},
@ -419,10 +312,7 @@ const TOOLS = [
inputSchema: {
type: 'object',
properties: {
task_id: {
type: 'string',
description: 'Task ID (timestamp) to load',
},
task_id: { type: 'string', description: 'Task ID (timestamp) to load' },
},
required: ['task_id'],
},
@ -433,10 +323,7 @@ const TOOLS = [
inputSchema: {
type: 'object',
properties: {
working_directory: {
type: 'string',
description: 'Current working directory to find task for',
},
working_directory: { type: 'string', description: 'Current working directory to find task for' },
},
required: ['working_directory'],
},
@ -455,51 +342,24 @@ const TOOLS = [
inputSchema: {
type: 'object',
properties: {
content: {
type: 'string',
description: 'The summary or update to append',
},
type: {
type: 'string',
enum: ['user', 'summary'],
description: 'Type of content: "summary" for Claude summaries, "user" for user responses',
},
task_id: {
type: 'string',
description: 'Task ID (timestamp). If omitted, finds task by working_directory.',
},
working_directory: {
type: 'string',
description: 'Current working directory (used to find task if task_id not provided)',
},
content: { type: 'string', description: 'The summary or update to append' },
type: { type: 'string', enum: ['user', 'summary'], description: 'Type of content: "summary" for Claude summaries, "user" for user responses' },
task_id: { type: 'string', description: 'Task ID (timestamp). If omitted, finds task by working_directory.' },
working_directory: { type: 'string', description: 'Current working directory (used to find task if task_id not provided)' },
},
required: ['content', 'type'],
},
},
];
/**
* Initialize MCP server
*/
async function main() {
const server = new Server(
{
name: 'task-persistence-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
{ name: 'task-persistence-server', version: '1.0.0' },
{ capabilities: { tools: {} } }
);
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: TOOLS,
}));
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
@ -507,109 +367,40 @@ async function main() {
switch (name) {
case 'save_task': {
const { prompt, working_directory } = args as { prompt?: string; working_directory?: string };
if (!prompt || !working_directory) {
throw new Error('prompt and working_directory are required');
}
if (!prompt || !working_directory) throw new Error('prompt and working_directory are required');
const result = await saveTask(prompt, working_directory);
return {
content: [
{
type: 'text',
text: `Task saved. ID: ${result.taskId}\nFile: ${result.filename}`,
},
],
};
return { content: [{ type: 'text', text: `Task saved. ID: ${result.taskId}\nFile: ${result.filename}` }] };
}
case 'complete_task': {
const { task_id, working_directory } = args as { task_id?: string; working_directory?: string };
const result = await completeTask(task_id, working_directory);
return {
content: [
{
type: 'text',
text: result,
},
],
};
return { content: [{ type: 'text', text: result }] };
}
case 'load_task': {
const { task_id } = args as { task_id?: string };
if (!task_id) {
throw new Error('task_id is required');
}
if (!task_id) throw new Error('task_id is required');
const result = await loadTask(task_id);
return {
content: [
{
type: 'text',
text: result.recoveryMessage,
},
],
};
return { content: [{ type: 'text', text: result.recoveryMessage }] };
}
case 'resume_task': {
const { working_directory } = args as { working_directory?: string };
if (!working_directory) {
throw new Error('working_directory is required');
}
if (!working_directory) throw new Error('working_directory is required');
const result = await resumeTask(working_directory);
return {
content: [
{
type: 'text',
text: `Task ID: ${result.taskId}\n\n${result.recoveryMessage}`,
},
],
};
return { content: [{ type: 'text', text: `Task ID: ${result.taskId}\n\n${result.recoveryMessage}` }] };
}
case 'list_tasks': {
const tasks = listTasks();
const tasks = await listTasks();
if (tasks.length === 0) {
return {
content: [
{
type: 'text',
text: 'No saved tasks found.',
},
],
};
return { content: [{ type: 'text', text: 'No saved tasks found.' }] };
}
const taskList = tasks
.map((t) => {
// New format: show slug and project
if (!t.isLegacy) {
return `- ${t.taskId} | ${t.slug} | ${t.project} | Created: ${t.created.toISOString()}`;
}
// Legacy format: show pathInfo (truncated if too long)
const pathInfo = t.pathInfo && t.pathInfo.length > 50
? t.pathInfo.substring(0, 47) + '...'
: t.pathInfo;
return `- ${t.taskId} | (legacy) ${pathInfo} | Created: ${t.created.toISOString()}`;
})
.map((t) => `- ${t.taskId} | ${t.slug} | ${t.project} | Created: ${t.created.toISOString()}`)
.join('\n');
return {
content: [
{
type: 'text',
text: `Saved tasks:\n${taskList}`,
},
],
};
return { content: [{ type: 'text', text: `Saved tasks:\n${taskList}` }] };
}
case 'append_to_task': {
@ -619,21 +410,9 @@ async function main() {
task_id?: string;
working_directory?: string;
};
if (!content || !type) {
throw new Error('content and type are required');
}
if (!content || !type) throw new Error('content and type are required');
const result = await appendToTask(content, type, task_id, working_directory);
return {
content: [
{
type: 'text',
text: result,
},
],
};
return { content: [{ type: 'text', text: result }] };
}
default:
@ -641,23 +420,12 @@ async function main() {
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: 'text',
text: `Error: ${message}`,
},
],
isError: true,
};
return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true };
}
});
// Start server
const transport = new StdioServerTransport();
await server.connect(transport);
// Keep process alive
process.stdin.resume();
}

3
tsup.config.ts Normal file
View file

@ -0,0 +1,3 @@
import { createLibraryConfig } from '@lilith/configs/tsup/library';
export default createLibraryConfig();