UNPKG

story-weaver-ai

Version:

A narrative development system for AI-driven storytelling with Jungian psychology

182 lines (160 loc) 5.83 kB
/** * Story Weaver MCP Server - Refine Story Tool * * @author Sean Pavlak * @github https://github.com/seanpavlak/cursor-story-master * * Tool for refining stories based on analysis and Jungian psychological principles. */ import fs from 'fs/promises'; import path from 'path'; import { spawnSync } from 'child_process'; import { fileURLToPath } from 'url'; import logger from '../logger.js'; // Get the directory path for the module const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const rootDir = path.resolve(__dirname, '../../..'); /** * Refine a story based on analysis and Jungian psychological principles */ export const refineStoryTool = { name: 'refine_story', description: 'Refine a story based on analysis, focusing on structure, character development, themes, and psychological depth.', parameters: { type: 'object', properties: { input_file: { type: 'string', description: 'Path to the story file to refine' }, analysis_file: { type: 'string', description: 'Path to the analysis file to use for refinement' }, output_file: { type: 'string', description: 'Path to save the refined story (default: refined_story.md)' }, focus_areas: { type: 'array', items: { type: 'string' }, description: 'Areas to focus on (e.g., "pacing", "characters", "themes")' }, tone_adjustment: { type: 'string', description: 'Desired tone adjustment (e.g., "darker", "lighter", "more hopeful")' }, guidance: { type: 'string', description: 'Additional refinement guidance or specific instructions' } }, required: ['input_file', 'analysis_file'] }, async handler(params) { try { logger.info(`Refining story file: ${params.input_file} with analysis: ${params.analysis_file}`); // Default output file if not specified const outputFile = params.output_file || 'refined_story.md'; // Set up command arguments const args = [ path.join(rootDir, 'bin/story-weaver.js'), 'refine-story', '-i', params.input_file, '-a', params.analysis_file, '-o', outputFile ]; // Add focus areas if specified if (params.focus_areas && params.focus_areas.length > 0) { args.push('-f', ...params.focus_areas); } // Add tone adjustment if specified if (params.tone_adjustment) { args.push('-t', params.tone_adjustment); } // Add additional guidance if specified if (params.guidance) { args.push('-g', params.guidance); } // Execute the command logger.debug(`Executing command: ${args.join(' ')}`); const result = spawnSync('node', args, { encoding: 'utf8', cwd: process.cwd() }); if (result.error) { throw new Error(`Failed to execute: ${result.error.message}`); } if (result.status !== 0) { throw new Error(`Command failed with status ${result.status}: ${result.stderr}`); } // Check if the refined story file was created await fs.access(outputFile); // Get file statistics to determine size changes const originalStats = await fs.stat(params.input_file); const refinedStats = await fs.stat(outputFile); // Calculate changes as percentages const sizeDifference = refinedStats.size - originalStats.size; const percentChange = ((sizeDifference / originalStats.size) * 100).toFixed(2); // Read a sample of the refined story for preview const previewContent = await readSample(outputFile, 500); // Return the refinement results return { status: 'success', message: `Story refinement complete. Results saved to ${outputFile}`, file_path: outputFile, changes: { original_size: originalStats.size, refined_size: refinedStats.size, difference: sizeDifference, percent_change: `${percentChange}%`, increased: sizeDifference > 0, decreased: sizeDifference < 0 }, focus_areas: params.focus_areas || [], tone_adjustment: params.tone_adjustment || null, preview: previewContent }; } catch (error) { logger.error(`Story refinement failed: ${error.message}`); return { status: 'error', message: `Failed to refine story: ${error.message}` }; } } }; /** * Read a sample of the content from a file * @param {string} filePath - Path to the file * @param {number} maxChars - Maximum number of characters to read * @returns {string} Sample content */ async function readSample(filePath, maxChars = 500) { try { const handle = await fs.open(filePath, 'r'); const buffer = Buffer.alloc(maxChars); const { bytesRead } = await handle.read(buffer, 0, maxChars, 0); await handle.close(); let content = buffer.toString('utf8', 0, bytesRead); // If we didn't read the entire file, add ellipsis if (bytesRead === maxChars) { // Try to end at a sentence or word boundary const lastPeriod = content.lastIndexOf('.'); const lastSpace = content.lastIndexOf(' '); let endIndex = Math.max(lastPeriod, lastSpace); if (endIndex < content.length - 20) { // Ensure we're not cutting off too early content = content.substring(0, endIndex + 1) + '...'; } else { content = content + '...'; } } return content; } catch (error) { logger.error(`Failed to read sample: ${error.message}`); return '[Preview unavailable]'; } }