MCP Tools for Obsidian

import { type } from "arktype"; import { existsSync, mkdirSync } from "fs"; import { appendFile } from "fs/promises"; import { homedir, platform } from "os"; import { dirname, resolve } from "path"; /** * Determines the appropriate log directory path based on the current operating system. * @param appName - The name of the application to use in the log directory path. * @returns The full path to the log directory for the current operating system. * @throws {Error} If the current operating system is not supported. */ export function getLogFilePath(appName: string, fileName: string) { switch (platform()) { case "darwin": // macOS return resolve(homedir(), "Library", "Logs", appName, fileName); case "win32": // Windows return resolve(homedir(), "AppData", "Local", "Logs", appName, fileName); case "linux": // Linux return resolve(homedir(), ".local", "share", "logs", appName, fileName); default: throw new Error("Unsupported operating system"); } } const ensureDirSync = (dirPath: string) => { if (!existsSync(dirPath)) { mkdirSync(dirPath, { recursive: true }); } }; const logLevels = ["DEBUG", "INFO", "WARN", "ERROR", "FATAL"] as const; export const logLevelSchema = type.enumerated(...logLevels); export type LogLevel = typeof logLevelSchema.infer; const formatMessage = ( level: LogLevel, message: unknown, meta: Record<string, unknown>, ) => { const timestamp = new Date().toISOString(); const metaStr = Object.keys(meta).length ? `\n${JSON.stringify(meta, null, 2)}` : ""; return `${timestamp} [${level.padEnd(5)}] ${JSON.stringify( message, )}${metaStr}\n`; }; const loggerConfigSchema = type({ appName: "string", filename: "string", level: logLevelSchema, }); export const loggerConfigMorph = loggerConfigSchema.pipe((config) => { const filename = getLogFilePath(config.appName, config.filename); const levels = logLevels.slice(logLevels.indexOf(config.level)); return { ...config, levels, filename }; }); export type InputLoggerConfig = typeof loggerConfigSchema.infer; export type FullLoggerConfig = typeof loggerConfigMorph.infer; /** * Creates a logger instance with configurable options for logging to a file. * The logger provides methods for logging messages at different log levels (DEBUG, INFO, WARN, ERROR, FATAL). * @param config - An object with configuration options for the logger. * @param config.filepath - The file path to use for logging to a file. * @param config.level - The minimum log level to log messages. * @returns An object with logging methods (debug, info, warn, error, fatal). */ export function createLogger(inputConfig: InputLoggerConfig) { let config: FullLoggerConfig = loggerConfigMorph.assert(inputConfig); let logMeta: Record<string, unknown> = {}; const queue: Promise<void>[] = []; const log = (level: LogLevel, message: unknown, meta?: typeof logMeta) => { if (!config.levels.includes(level)) return; ensureDirSync(dirname(getLogFilePath(config.appName, config.filename))); queue.push( appendFile( config.filename, formatMessage(level, message, { ...logMeta, ...(meta ?? {}) }), ), ); }; const debug = (message: unknown, meta?: typeof logMeta) => log("DEBUG", message, meta); const info = (message: unknown, meta?: typeof logMeta) => log("INFO", message, meta); const warn = (message: unknown, meta?: typeof logMeta) => log("WARN", message, meta); const error = (message: unknown, meta?: typeof logMeta) => log("ERROR", message, meta); const fatal = (message: unknown, meta?: typeof logMeta) => log("FATAL", message, meta); const logger = { debug, info, warn, error, fatal, flush() { return Promise.all(queue); }, get config(): FullLoggerConfig { return { ...config }; }, /** * Updates the configuration of the logger instance. * @param newConfig - A partial configuration object to merge with the existing configuration. * This method updates the log levels based on the new configuration level, and then merges the new configuration with the existing configuration. */ set config(newConfig: Partial<InputLoggerConfig>) { config = loggerConfigMorph.assert({ ...config, ...newConfig }); logger.debug("Updated logger configuration", { config }); }, set meta(newMeta: Record<string, unknown>) { logMeta = newMeta; }, }; return logger; }