jrnl-mcp
Version:
Model Context Protocol server for jrnl CLI journal application
156 lines (155 loc) • 5.31 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.JrnlExecutor = void 0;
exports.executeJrnlCommand = executeJrnlCommand;
exports.parseJrnlJsonOutput = parseJrnlJsonOutput;
exports.executeJrnlJsonCommand = executeJrnlJsonCommand;
/**
* Utility for executing jrnl CLI commands and parsing results
*/
const child_process_1 = require("child_process");
/**
* Default options for jrnl execution
*/
const DEFAULT_OPTIONS = {
timeout: 30000, // 30 seconds
maxBuffer: 1024 * 1024 * 5, // 5MB
};
/**
* Execute a jrnl command and return the result
*
* @param args Command line arguments to pass to jrnl
* @param options Execution options
* @returns Promise with execution result
*/
async function executeJrnlCommand(args, options = {}) {
const opts = { ...DEFAULT_OPTIONS, ...options };
let commandArgs = [...args];
// If journal is specified, add the --journal flag
if (opts.journal) {
commandArgs = ["--journal", opts.journal, ...commandArgs];
}
return new Promise((resolve) => {
let stdout = "";
let stderr = "";
let killed = false;
// Spawn jrnl process with arguments
const process = (0, child_process_1.spawn)("jrnl", commandArgs);
// Set timeout if specified
const timeoutId = opts.timeout
? setTimeout(() => {
process.kill();
killed = true;
resolve({
stdout,
stderr: stderr + "\nCommand timed out after " + opts.timeout + "ms",
success: false,
});
}, opts.timeout)
: null;
// Collect stdout
process.stdout.on("data", (data) => {
stdout += data.toString();
});
// Collect stderr
process.stderr.on("data", (data) => {
stderr += data.toString();
});
// Handle process completion
process.on("close", (code) => {
if (timeoutId)
clearTimeout(timeoutId);
if (!killed) {
resolve({
stdout,
stderr,
success: code === 0,
});
}
});
// Handle errors
process.on("error", (err) => {
if (timeoutId)
clearTimeout(timeoutId);
if (!killed) {
resolve({
stdout,
stderr: stderr + "\n" + err.message,
success: false,
});
}
});
});
}
/**
* Parse JSON output from jrnl command
*
* @param output Command output string
* @returns Parsed JSON object
*/
function parseJrnlJsonOutput(output) {
try {
return JSON.parse(output.trim());
}
catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to parse jrnl output as JSON: ${error.message}`);
}
throw error;
}
}
/**
* Safely execute a jrnl command that outputs JSON and parse the result
*
* @param args Command line arguments
* @param options Execution options
* @returns Parsed JSON result
*/
async function executeJrnlJsonCommand(args, options = {}) {
// Ensure export flag is included to get JSON output
const commandArgs = [...args];
if (!commandArgs.includes("--export") && !commandArgs.includes("-j")) {
commandArgs.push("--export", "json");
}
const result = await executeJrnlCommand(commandArgs, options);
if (!result.success) {
throw new Error(`jrnl command failed: ${result.stderr}`);
}
return parseJrnlJsonOutput(result.stdout);
}
class JrnlExecutor {
async execute(args) {
const result = await executeJrnlCommand(args);
if (!result.success) {
throw new Error(`jrnl command failed: ${result.stderr}`);
}
// Clean jrnl output - remove decorative boxes and extra text
const cleaned = this.cleanJrnlOutput(result.stdout);
return cleaned;
}
cleanJrnlOutput(output) {
// Remove decorative box characters and summary lines
const lines = output.split("\n");
const cleanedLines = lines.filter((line) => {
// Filter out decorative box lines and summary text
return (!line.match(/^[┏┓┗┛━ ]+$/) &&
!line.match(/^\s*\d+ entries? found\s*$/) &&
!line.match(/^\s*no entries? found\s*$/) &&
!line.match(/^Journals defined in config/) &&
line.trim() !== "");
});
const cleanedOutput = cleanedLines.join("\n").trim();
// Check if jrnl returned "no entries found" - return empty JSON structure
if (output.includes("no entries found") || cleanedOutput === "") {
return '{"tags": {}, "entries": []}';
}
// For JSON export, try to extract just the JSON part
if (cleanedOutput.includes("{") && cleanedOutput.includes("}")) {
const jsonStart = cleanedOutput.indexOf("{");
const jsonEnd = cleanedOutput.lastIndexOf("}") + 1;
return cleanedOutput.substring(jsonStart, jsonEnd);
}
return cleanedOutput;
}
}
exports.JrnlExecutor = JrnlExecutor;