@@ -9,18 +9,15 @@ import {
99import fs from 'fs/promises' ;
1010import path from 'path' ;
1111import { fileURLToPath } from 'url' ;
12- import { exec } from 'child_process' ;
13- import util from 'util' ;
14-
15- const execPromise = util . promisify ( exec ) ;
12+ import piml from 'piml' ;
1613
1714const __filename = fileURLToPath ( import . meta. url ) ;
1815const __dirname = path . dirname ( __filename ) ;
1916
2017// Constants
2118const POSTS_DIR = path . resolve ( __dirname , '../../public/posts' ) ;
2219const POSTS_JSON = path . join ( POSTS_DIR , 'posts.json' ) ;
23- const PROJECT_ROOT = path . resolve ( __dirname , '../../' ) ;
20+ const LOGS_DIR = path . resolve ( __dirname , '../../public/logs ' ) ;
2421
2522// Helper functions
2623async function readPosts ( ) {
@@ -83,17 +80,71 @@ async function createPost(args) {
8380 posts . unshift ( newPost ) ; // Add to beginning
8481 await writePosts ( posts ) ;
8582
86- // Generate RSS and Sitemap
83+ return `Blog post "${ title } " created successfully at ${ filePath } .` ;
84+ }
85+
86+ async function createDiscoveryLog ( args ) {
87+ const { type, title, slug, rating, description, content, date, ...otherFields } = args ;
88+
89+ // Validate slug
90+ if ( ! / ^ [ a - z 0 - 9 - ] + $ / . test ( slug ) ) {
91+ throw new Error ( 'Slug must contain only lowercase letters, numbers, and hyphens.' ) ;
92+ }
93+
94+ const typeLower = type . toLowerCase ( ) ;
95+ const categoryDir = path . join ( LOGS_DIR , typeLower ) ;
96+ const pimlPath = path . join ( categoryDir , `${ typeLower } .piml` ) ;
97+
98+ // Ensure directory exists
99+ await fs . mkdir ( categoryDir , { recursive : true } ) ;
100+
101+ // Read existing PIML
102+ let logs = [ ] ;
87103 try {
88- await execPromise ( 'npm run generate-rss' , { cwd : PROJECT_ROOT } ) ;
89- await execPromise ( 'npm run generate-sitemap' , { cwd : PROJECT_ROOT } ) ;
104+ const pimlString = await fs . readFile ( pimlPath , 'utf-8' ) ;
105+ const parsed = piml . parse ( pimlString ) ;
106+ logs = parsed . logs || [ ] ;
90107 } catch ( error ) {
91- console . error ( 'Error generating RSS/Sitemap:' , error ) ;
92- // Don't fail the whole operation if generation fails, but log it
93- return `Blog post "${ title } " created at ${ filePath } , but RSS/Sitemap generation failed: ${ error . message } ` ;
108+ if ( error . code !== 'ENOENT' ) {
109+ throw error ;
110+ }
111+ }
112+
113+ // Check if log exists
114+ if ( logs . some ( item => item . slug === slug ) ) {
115+ throw new Error ( `Log with slug "${ slug } " already exists in ${ typeLower } .` ) ;
116+ }
117+
118+ // Create new item
119+ const today = new Date ( ) ;
120+ const dateStr = date || today . getFullYear ( ) + '-' +
121+ String ( today . getMonth ( ) + 1 ) . padStart ( 2 , '0' ) + '-' +
122+ String ( today . getDate ( ) ) . padStart ( 2 , '0' ) ;
123+
124+ const newItem = {
125+ category : type . charAt ( 0 ) . toUpperCase ( ) + type . slice ( 1 ) ,
126+ date : dateStr ,
127+ rating : parseInt ( rating , 10 ) ,
128+ slug,
129+ title,
130+ description,
131+ ...otherFields
132+ } ;
133+
134+ // Add to beginning
135+ logs . unshift ( newItem ) ;
136+
137+ // Write back PIML
138+ const newPimlString = piml . stringify ( { logs } ) ;
139+ await fs . writeFile ( pimlPath , newPimlString , 'utf-8' ) ;
140+
141+ // Create detailed content if provided
142+ if ( content ) {
143+ const txtPath = path . join ( categoryDir , `${ slug } .txt` ) ;
144+ await fs . writeFile ( txtPath , content , 'utf-8' ) ;
94145 }
95146
96- return `Blog post "${ title } " created successfully at ${ filePath } . RSS and Sitemap updated .` ;
147+ return `Discovery log "${ title } " created successfully in ${ typeLower } .` ;
97148}
98149
99150// Server setup
@@ -153,6 +204,60 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
153204 required : [ "title" , "slug" , "description" , "content" ] ,
154205 } ,
155206 } ,
207+ {
208+ name : "create_discovery_log" ,
209+ description : "Add a new log to discovery logs. This updates the .piml file and creates an optional .txt file." ,
210+ inputSchema : {
211+ type : "object" ,
212+ properties : {
213+ type : {
214+ type : "string" ,
215+ description : "Log category (e.g., 'book', 'movie', 'video', 'game', 'article', 'music', 'series', 'food', 'websites', 'tools', 'event')" ,
216+ enum : [ "book" , "movie" , "video" , "game" , "article" , "music" , "series" , "food" , "websites" , "tools" , "event" ]
217+ } ,
218+ title : {
219+ type : "string" ,
220+ description : "The title of the log entry" ,
221+ } ,
222+ slug : {
223+ type : "string" ,
224+ description : "The URL-friendly slug for the log (lowercase, hyphens only)" ,
225+ } ,
226+ date : {
227+ type : "string" ,
228+ description : "Date of the discovery (YYYY-MM-DD). Defaults to today." ,
229+ } ,
230+ rating : {
231+ type : "number" ,
232+ description : "Rating from 1 to 5" ,
233+ minimum : 1 ,
234+ maximum : 5
235+ } ,
236+ description : {
237+ type : "string" ,
238+ description : "A short description/summary of the discovery" ,
239+ } ,
240+ content : {
241+ type : "string" ,
242+ description : "Optional detailed thoughts (will be saved in a .txt file)" ,
243+ } ,
244+ link : {
245+ type : "string" ,
246+ description : "Optional URL link to the item" ,
247+ } ,
248+ image : {
249+ type : "string" ,
250+ description : "Optional URL/path to a cover image" ,
251+ } ,
252+ artist : { type : "string" , description : "Artist name (for music)" } ,
253+ author : { type : "string" , description : "Author name (for books/articles)" } ,
254+ director : { type : "string" , description : "Director name (for movies/series)" } ,
255+ platform : { type : "string" , description : "Platform name (for games/tools)" } ,
256+ album : { type : "string" , description : "Album name (for music)" } ,
257+ } ,
258+ required : [ "type" , "title" , "slug" , "rating" , "description" ] ,
259+ } ,
260+ } ,
156261 ] ,
157262 } ;
158263} ) ;
@@ -182,6 +287,30 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
182287 } ;
183288 }
184289 }
290+
291+ if ( request . params . name === "create_discovery_log" ) {
292+ try {
293+ const result = await createDiscoveryLog ( request . params . arguments ) ;
294+ return {
295+ content : [
296+ {
297+ type : "text" ,
298+ text : result ,
299+ } ,
300+ ] ,
301+ } ;
302+ } catch ( error ) {
303+ return {
304+ content : [
305+ {
306+ type : "text" ,
307+ text : `Error creating discovery log: ${ error . message } ` ,
308+ } ,
309+ ] ,
310+ isError : true ,
311+ } ;
312+ }
313+ }
185314 throw new Error ( "Tool not found" ) ;
186315} ) ;
187316
0 commit comments