refactor and add list_snippets

This commit is contained in:
pablof7z
2025-03-31 12:26:50 +01:00
parent 6d0e5a8e79
commit f74736191a
15 changed files with 494 additions and 190 deletions

1
.gitignore vendored
View File

@@ -32,3 +32,4 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Finder (MacOS) folder config
.DS_Store
mcp-code

View File

@@ -1,43 +1,32 @@
import { Command } from 'commander';
import { ndk } from '../ndk.js';
import { knownUsers } from '../users.js';
import { identifierToPubkeys } from '../lib/nostr/utils.js';
import { Command } from "commander";
import { toPubkeys, formatUser } from "../lib/converters/index.js";
export function registerFindUserCommand(program: Command): void {
program
.command('find-user')
.description('Find a user by identifier')
.argument('<query>', 'User identifier to search for')
// Create a command for finding a user
const findUserCommand = new Command("find-user")
.description("Find a user by name, npub, or other profile information")
.argument("<query>", "The search query to find a user")
.action(async (query: string) => {
try {
const pubkeys = identifierToPubkeys(query);
// Find matching pubkeys
const pubkeys = toPubkeys(query);
if (pubkeys.length > 0) {
const result = pubkeys.map(formatUser).join('\n\n---\n\n');
console.log(result);
} else {
console.log("No user found matching the query.");
if (pubkeys.length === 0) {
console.log(`No users found matching query: ${query}`);
return;
}
// Format and display each matching user
console.log(`Found ${pubkeys.length} matching users:`);
for (let i = 0; i < pubkeys.length; i++) {
if (i > 0) console.log("\n---\n");
const pubkey = pubkeys[i];
if (pubkey) {
console.log(formatUser(pubkey));
}
}
} catch (error) {
console.error('Error executing find-user command:', error);
process.exit(1);
console.error("Error:", error);
}
});
}
// Helper function to format user profiles
function formatUser(pubkey: string) {
const profile = knownUsers[pubkey]?.profile;
const user = ndk.getUser({ pubkey });
const keys: Record<string, string> = {
Npub: user.npub,
};
if (profile?.name) keys.Name = profile.name;
if (profile?.about) keys.About = profile.about;
if (profile?.picture) keys.Picture = profile.picture;
return Object.entries(keys)
.map(([key, value]) => `${key}: ${value}`)
.join("\n");
}
export default findUserCommand;

View File

@@ -1,5 +1,4 @@
import { Command } from 'commander';
import { registerFindUserCommand } from './find-user.js';
import { registerFindSnippetsCommand } from './find-snippets.js';
import { registerWotCommand } from './wot.js';
import { registerListUsernamesCommand } from './list-usernames.js';
@@ -17,7 +16,6 @@ program
// Register all commands
registerMcpCommand(program);
registerFindUserCommand(program);
registerFindSnippetsCommand(program);
registerWotCommand(program);
registerListUsernamesCommand(program);

View File

@@ -1,4 +1,4 @@
import { Command } from 'commander';
import type { Command } from 'commander';
import { readConfig } from "../config.js";
import { addCreatePubkeyCommand } from "../logic/create-pubkey.js";
import { addFindSnippetsCommand } from "../logic/find_snippets.js";
@@ -6,6 +6,8 @@ import { addFindUserCommand } from "../logic/find_user.js";
import { addListUsernamesCommand } from "../logic/list_usernames.js";
import { addPublishCodeSnippetCommand } from "../logic/publish-code-snippet.js";
import { addPublishCommand } from "../logic/publish.js";
import { addListSnippetsCommand } from "../logic/list_snippets.js";
import { addFetchSnippetByIdCommand } from "../logic/fetch_snippet_by_id.js";
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -20,6 +22,8 @@ const commandMap: Record<string, CommandFunction> = {
"find-user": addFindUserCommand,
"find-snippets": addFindSnippetsCommand,
"list-usernames": addListUsernamesCommand,
"list-snippets": addListSnippetsCommand,
"fetch-snippet-by-id": addFetchSnippetByIdCommand,
};
// Global server instance
@@ -70,5 +74,7 @@ export function registerMcpCommands(server: McpServer) {
addFindUserCommand(server);
addFindSnippetsCommand(server);
addListUsernamesCommand(server);
addListSnippetsCommand(server);
addFetchSnippetByIdCommand(server);
}
}

7
lib/cache/snippets.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
import type { CodeSnippet } from "../types/index.js";
/**
* In-memory cache to store snippets by ID for efficient retrieval
* Used by list_snippets and fetch_snippet_by_id commands
*/
export const snippetsCache: Record<string, CodeSnippet> = {};

3
lib/converters/index.ts Normal file
View File

@@ -0,0 +1,3 @@
// Export all converters
export * from './snippets.js';
export * from './users.js';

195
lib/converters/snippets.ts Normal file
View File

@@ -0,0 +1,195 @@
import type { NDKEvent } from "@nostr-dev-kit/ndk";
import { knownUsers } from "../../users.js";
import type { CodeSnippet } from "../types/index.js";
/**
* Converts an NDKEvent into a CodeSnippet
* @param event NDKEvent of kind 1337
* @returns CodeSnippet object
*/
export function toSnippet(event: NDKEvent): CodeSnippet {
const title = event.tagValue("title") ?? event.tagValue("name");
const description = event.tagValue("description") ?? "";
const language = event.tagValue("l");
const tags = event.tags
.filter((tag) => tag[0] === "t" && tag[1] !== undefined)
.map((tag) => tag[1] as string);
return {
id: event.id,
title: title || "Untitled",
description,
code: event.content,
language: language || "text",
pubkey: event.pubkey,
createdAt: event.created_at || 0,
tags,
};
}
/**
* Converts a CodeSnippet to a formatted metadata string (without code)
* @param snippet CodeSnippet object
* @returns Formatted metadata string
*/
export function toMetadataString(snippet: CodeSnippet): string {
const { title, description, language, tags, id, pubkey, createdAt } = snippet;
const profile = knownUsers[pubkey]?.profile;
const returns = [
`ID: ${id}`,
`Title: ${title}`,
`Description: ${description}`,
`Language: ${language}`,
`Tags: ${tags.join(", ")}`,
`Created: ${new Date(createdAt * 1000).toISOString()}`,
`Pubkey: ${pubkey}`,
];
if (profile?.name) returns.push(`Author: ${profile.name}`);
return returns.join("\n");
}
/**
* Formats a single snippet for display
* @param snippet The snippet to format
* @returns Formatted string representation
*/
export function formatSnippet(snippet: CodeSnippet): string {
return formatSnippets([snippet]);
}
/**
* Formats snippets for display
* @param snippets Array of code snippets
* @returns Formatted string representation
*/
export function formatSnippets(snippets: CodeSnippet[]): string {
return snippets
.map((snippet) => {
const author = knownUsers[snippet.pubkey];
const keys: Record<string, string> = {
Title: snippet.title,
Language: snippet.language,
Tags: snippet.tags.join(", "),
Code: snippet.code,
};
if (author?.profile?.name) keys.Author = author.profile.name;
return Object.entries(keys)
.map(([key, value]) => `${key}: ${value}`)
.join("\n");
})
.join("\n\n---\n\n");
}
/**
* Formats partial match snippets for display
* @param snippets Array of code snippets
* @returns Formatted string representation
*/
export function formatPartialMatches(snippets: CodeSnippet[]): string {
if (snippets.length === 0) return "";
let text =
"\n\nSome other events not included in this result since they had less in common with your search, here is a list of the events that had partial matches:\n\n";
text += snippets
.map((snippet) => {
return ` * ${snippet.title}:\n Tags: ${snippet.tags.join(", ")}`;
})
.join("\n");
return text;
}
/**
* Parses metadata from a file content string
* @param fileContent String containing metadata and code sections
* @returns Object with metadata and code
*/
export function parseMetadataFromString(fileContent: string): {
metadata: { title: string; description: string; language: string; tags: string[] };
code: string
} {
// Match the metadata and code sections
const metadataRegex = /^---METADATA---([\s\S]*?)(?=^---CODE---$)(^---CODE---$)([\s\S]*)$/m;
const matches = fileContent.match(metadataRegex);
if (!matches || matches.length < 4) {
throw new Error("Invalid file format: metadata section not found");
}
const metadataSection = matches[1] || "";
let codeSection = matches[3] || "";
// Remove leading newline from code section if present
if (codeSection.startsWith("\n")) {
codeSection = codeSection.substring(1);
}
// Parse each field with proper multiline flag
const titleMatch = metadataSection.match(/^Title:\s*(.+)$/m);
const title = titleMatch?.[1] ? titleMatch[1].trim() : "";
// Extract description which can be multiline but should stop at Language: or Tags:
const descriptionLines = [];
let inDescription = false;
// Process line by line
const lines = metadataSection.split('\n');
for (const line of lines) {
if (line.trim().startsWith('Description:')) {
inDescription = true;
const content = line.replace(/^Description:\s*/, '').trim();
if (content) {
descriptionLines.push(content);
}
} else if (line.trim().startsWith('Language:') || line.trim().startsWith('Tags:')) {
inDescription = false;
} else if (inDescription) {
descriptionLines.push(line);
}
}
const description = descriptionLines.join('\n').trim();
const languageMatch = metadataSection.match(/^Language:\s*(.+)$/m);
const language = languageMatch?.[1] ? languageMatch[1].trim() : "";
const tagsMatch = metadataSection.match(/^Tags:\s*(.+)$/m);
const tagsString = tagsMatch?.[1] ? tagsMatch[1].trim() : "";
const tags = tagsString.split(',').map(tag => tag.trim()).filter(Boolean);
return {
metadata: {
title,
description,
language,
tags
},
code: codeSection
};
}
/**
* Creates a string with metadata and code sections
*/
export function createMetadataString(
title: string,
description: string,
language: string,
tags: string[],
code: string
): string {
return `---METADATA---
# Edit the metadata below. Keep the format exactly as shown (Title:, Description:, Language:, Tags:)
# Description needs to be at least 140 characters and Tags need at least 5 entries
# Don't remove the ---METADATA--- and ---CODE--- markers!
Title: ${title}
Description: ${description}
Language: ${language}
Tags: ${tags.join(', ')}
---CODE---
${code}`;
}

44
lib/converters/users.ts Normal file
View File

@@ -0,0 +1,44 @@
import { ndk } from "../../ndk.js";
import { knownUsers } from "../../users.js";
import { queryUser } from "../../users.js";
/**
* Converts an identifier (pubkey, npub, or name) to pubkeys
* @param identifier The identifier to convert
* @returns Array of pubkeys
*/
export function toPubkeys(identifier: string): string[] {
// If it's an npub, convert directly
if (identifier.startsWith("npub")) {
return [ndk.getUser({ npub: identifier }).pubkey];
}
// If it's a hex pubkey, return as is
if (identifier.length === 64 && /^[0-9a-f]+$/i.test(identifier)) {
return [identifier];
}
// Otherwise, search by profile name or other attributes
return queryUser(identifier);
}
/**
* Format user profile data for display
* @param pubkey User public key
* @returns Formatted string representation
*/
export function formatUser(pubkey: string): string {
const profile = knownUsers[pubkey]?.profile;
const user = ndk.getUser({ pubkey });
const keys: Record<string, string> = {
Npub: user.npub,
};
if (profile?.name) keys.Name = profile.name;
if (profile?.about) keys.About = profile.about;
if (profile?.picture) keys.Picture = profile.picture;
return Object.entries(keys)
.map(([key, value]) => `${key}: ${value}`)
.join("\n");
}

View File

@@ -1,9 +1,9 @@
import type { NDKEvent, NDKFilter } from "@nostr-dev-kit/ndk";
import { ndk } from "../../ndk.js";
import { knownUsers } from "../../users.js";
import type { CodeSnippet, FindSnippetsParams } from "../types/index.js";
import { log } from "../utils/log.js";
import { SNIPPET_KIND, eventToSnippet, identifierToPubkeys } from "./utils.js";
import { SNIPPET_KIND, identifierToPubkeys } from "./utils.js";
import { formatPartialMatches, formatSnippets, toSnippet } from "../converters/index.js";
/**
* Get code snippets from Nostr events of kind 1337
@@ -87,50 +87,11 @@ export async function getSnippets(params: FindSnippetsParams = {}): Promise<{
}
// Convert events to snippets
const snippets = selectedEvents.map(eventToSnippet);
const otherSnippets = notSelectedEvents.map(eventToSnippet);
const snippets = selectedEvents.map(toSnippet);
const otherSnippets = notSelectedEvents.map(toSnippet);
return { snippets, otherSnippets };
}
/**
* Format snippets for display
* @param snippets Array of code snippets
* @returns Formatted string representation
*/
export function formatSnippets(snippets: CodeSnippet[]): string {
return snippets
.map((snippet) => {
const author = knownUsers[snippet.pubkey];
const keys: Record<string, string> = {
Title: snippet.title,
Language: snippet.language,
Tags: snippet.tags.join(", "),
Code: snippet.code,
};
if (author?.profile?.name) keys.Author = author.profile.name;
return Object.entries(keys)
.map(([key, value]) => `${key}: ${value}`)
.join("\n");
})
.join("\n\n---\n\n");
}
/**
* Format partial match snippets for display
* @param snippets Array of code snippets
* @returns Formatted string representation
*/
export function formatPartialMatches(snippets: CodeSnippet[]): string {
if (snippets.length === 0) return "";
let text =
"\n\nSome other events not included in this result since they had less in common with your search, here is a list of the events that had partial matches:\n\n";
text += snippets
.map((snippet) => {
return ` * ${snippet.title}:\n Tags: ${snippet.tags.join(", ")}`;
})
.join("\n");
return text;
}
// Re-export formatters for backward compatibility
export { formatSnippets, formatPartialMatches };

View File

@@ -1,9 +1,8 @@
import type { NDKEvent, NDKSigner } from "@nostr-dev-kit/ndk";
import { NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
import { ndk } from "../../ndk.js";
import { queryUser } from "../../users.js";
import type { CodeSnippet } from "../types/index.js";
import { getUser } from "../../config.js";
import { toPubkeys, toSnippet } from "../converters/index.js";
export const SNIPPET_KIND = 1337;
@@ -32,45 +31,5 @@ export async function getSigner(username?: string): Promise<NDKSigner> {
return new NDKPrivateKeySigner(userData.nsec);
}
/**
* Converts an identifier (pubkey, npub, or name) to pubkeys
* @param identifier The identifier to convert
* @returns Array of pubkeys
*/
export function identifierToPubkeys(identifier: string): string[] {
// If it's an npub, convert directly
if (identifier.startsWith("npub")) {
return [ndk.getUser({ npub: identifier }).pubkey];
}
// If it's a hex pubkey, return as is
if (identifier.length === 64 && /^[0-9a-f]+$/i.test(identifier)) {
return [identifier];
}
// Otherwise, search by profile name or other attributes
return queryUser(identifier);
}
/**
* Converts an NDKEvent into a CodeSnippet
* @param event NDKEvent of kind 1337
* @returns CodeSnippet object
*/
export function eventToSnippet(event: NDKEvent): CodeSnippet {
const title = event.tagValue("title") ?? event.tagValue("name");
const language = event.tagValue("l");
const tags = event.tags
.filter((tag) => tag[0] === "t" && tag[1] !== undefined)
.map((tag) => tag[1] as string);
return {
id: event.id,
title: title || "Untitled",
code: event.content,
language: language || "text",
pubkey: event.pubkey,
createdAt: event.created_at || 0,
tags,
};
}
// Re-export converter functions for backward compatibility
export { toPubkeys as identifierToPubkeys, toSnippet as eventToSnippet };

View File

@@ -17,6 +17,7 @@ export interface FindSnippetsParams {
export type CodeSnippet = {
id: string;
title: string;
description: string;
code: string;
language: string;
pubkey: string;

View File

@@ -0,0 +1,78 @@
import { z } from "zod";
import { formatSnippets } from "../lib/converters/index.js";
import { ndk } from "../ndk.js";
import { SNIPPET_KIND } from "../lib/nostr/utils.js";
import { log } from "../lib/utils/log.js";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { toSnippet } from "../lib/converters/index.js";
/**
* Fetch a snippet by its ID
* @param id Snippet ID to fetch
* @returns The snippet content or an error message
*/
export async function fetchSnippetById(id: string): Promise<{
content: Array<{ type: "text"; text: string }>;
}> {
try {
log(`Fetching snippet with ID: ${id}`);
// Create filter for the specific event ID
const filter = {
kinds: [SNIPPET_KIND as number],
ids: [id],
};
// Fetch the event
const events = await ndk.fetchEvents(filter);
const event = Array.from(events)[0]; // Get the first (and should be only) event
if (!event) {
return {
content: [
{
type: "text",
text: `No snippet found with ID: ${id}`,
},
],
};
}
// Convert event to snippet
const snippet = toSnippet(event);
// Format the snippet for display
const formattedSnippet = formatSnippets([snippet]);
return {
content: [
{
type: "text",
text: formattedSnippet,
},
],
};
} catch (error: unknown) {
const errorMessage =
error instanceof Error ? error.message : String(error);
return {
content: [
{
type: "text",
text: `Error fetching snippet: ${errorMessage}`,
},
],
};
}
}
export function addFetchSnippetByIdCommand(server: McpServer) {
server.tool(
"fetch_snippet_by_id",
"Fetch and display a snippet by its ID",
{
id: z.string().describe("ID of the snippet to fetch"),
},
async ({ id }) => fetchSnippetById(id)
);
}

View File

@@ -1,44 +1,52 @@
import { z } from "zod";
import { ndk } from "../ndk.js";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { knownUsers } from "../users.js";
import { queryUser } from "../users.js";
import { toPubkeys, formatUser } from "../lib/converters/index.js";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
/**
* Find a user by name, npub, or other profile information
* @param query The search query to find a user
* @returns Results with formatted user information
* @param query Search query to find a user
* @returns Formatted user data or error message
*/
export async function findUser(query: string) {
export async function findUser(query: string): Promise<{
content: Array<{ type: "text"; text: string }>;
}> {
try {
const pubkeys = queryUser(query);
// Convert the query to pubkeys
const pubkeys = toPubkeys(query);
if (pubkeys.length === 0) {
return {
content: [
{
type: "text" as const,
text: "No users found matching the query.",
type: "text",
text: `No users found matching: ${query}`,
},
],
};
}
// Format the found users for display
// Format user data for each pubkey
const formattedUsers = pubkeys.map(formatUser).join("\n\n---\n\n");
return {
content: [
{
type: "text" as const,
text: `Found ${pubkeys.length} users:\n\n${formattedUsers}`,
type: "text",
text: formattedUsers,
},
],
};
} catch (error: unknown) {
const errorMessage =
error instanceof Error ? error.message : String(error);
throw new Error(`Failed to find user: ${errorMessage}`);
return {
content: [
{
type: "text",
text: `Error finding user: ${errorMessage}`,
},
],
};
}
}
@@ -49,29 +57,7 @@ export function addFindUserCommand(server: McpServer) {
{
query: z.string().describe("The search query to find a user"),
},
async ({ query }) => {
return findUser(query);
}
async ({ query }) => findUser(query)
);
}
/**
* Format user profile data for display
* @param pubkey User public key
* @returns Formatted string representation
*/
function formatUser(pubkey: string): string {
const profile = knownUsers[pubkey]?.profile;
const user = ndk.getUser({ pubkey });
const keys: Record<string, string> = {
Npub: user.npub,
};
if (profile?.name) keys.Name = profile.name;
if (profile?.about) keys.About = profile.about;
if (profile?.picture) keys.Picture = profile.picture;
return Object.entries(keys)
.map(([key, value]) => `${key}: ${value}`)
.join("\n");
}

88
logic/list_snippets.ts Normal file
View File

@@ -0,0 +1,88 @@
import { z } from "zod";
import type { CodeSnippet, FindSnippetsParams } from "../lib/types/index.js";
import { getSnippets } from "../lib/nostr/snippets.js";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { toMetadataString } from "../lib/converters/index.js";
/**
* List snippets with metadata
* @param params Parameters to filter snippets
* @returns List of snippet metadata
*/
export async function listSnippets(
params: FindSnippetsParams = {}
): Promise<{ content: Array<{ type: "text", text: string }> }> {
try {
const result = await getSnippets(params);
let list = result.snippets
.map(toMetadataString)
.join("\n\n------------------\n\n");
const extra = result.otherSnippets
.map(toMetadataString)
.join("\n\n------------------\n\n");
// Include partial matches if they exist
if (result.otherSnippets.length > 0) {
list += "\n\nSome other events not included in this result since they had less in common with your search, here is a list of the events that had partial matches:\n\n";
list += extra;
}
if (list.length === 0) {
return {
content: [
{
type: "text",
text: "No snippets found",
},
],
};
}
return {
content: [
{
type: "text",
text: list,
},
],
};
} catch (error: unknown) {
const errorMessage =
error instanceof Error ? error.message : String(error);
throw new Error(`Failed to list snippets: ${errorMessage}`);
}
}
export function addListSnippetsCommand(server: McpServer) {
server.tool(
"list_snippets",
"List code snippets metadata (without code content) with filtering by language and tags. Use this to get a large list of available code snippets.",
{
since: z
.number()
.optional()
.describe("Fetch snippets newer than this timestamp"),
until: z
.number()
.optional()
.describe("Fetch snippets older than this timestamp"),
authors: z
.array(z.string())
.optional()
.describe(
"List of author names to filter by (in username format!)"
),
languages: z
.array(z.string())
.optional()
.describe("List of programming languages to filter by"),
tags: z
.array(z.string())
.optional()
.describe(
"List of tags to filter by, be exhaustive, e.g. [ 'ndk', 'nostr', 'pubkey', 'signer' ], we use OR matches"
),
},
async (args) => listSnippets(args)
);
}

View File

@@ -2,15 +2,16 @@ import { NDKEvent } from "@nostr-dev-kit/ndk";
import { z } from "zod";
import { SNIPPET_KIND, getSigner } from "../lib/nostr/utils.js";
import { ndk } from "../ndk.js";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { writeFileSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { homedir, tmpdir } from "node:os";
import { readConfig, getUser } from "../config.js";
import { readConfig } from "../config.js";
import { existsSync } from "node:fs";
import * as Bun from "bun";
import { createMetadataString, parseMetadataFromString } from "../lib/converters/index.js";
function log(message: string, ...args: any[]) {
function log(message: string): void {
// append to ~/.nmcp-nostr.log
const logFilePath = join(homedir(), ".nmcp-nostr.log");
const logMessage = `${new Date().toISOString()} - ${message}\n`;
@@ -134,25 +135,12 @@ export async function publishCodeSnippet(
username?: string
): Promise<{ content: Array<{ type: "text", text: string }> }> {
try {
// Validate minimum requirements
// if (tags.length < 5) {
// throw new Error(
// "Insufficient tags. At least 5 tags are required. Please add more relevant and accurate information."
// );
// }
// if (description.length < 140) {
// throw new Error(
// "Description is too short. At least 140 characters are required. Please add more relevant and accurate information."
// );
// }
// put the code snippet in a temp file and run the command in config.editor or `code` and wait until it's closed -- then read the file and publish it
const config = readConfig();
const tempFilePath = join(tmpdir(), `snippet-${Date.now()}.${language}`);
// Create file content with metadata section for editing
const fileContent = createFileWithMetadata(title, description, language, tags, code);
const fileContent = createMetadataString(title, description, language, tags, code);
// Write the content to the temp file
writeFileSync(tempFilePath, fileContent);
@@ -163,7 +151,7 @@ export async function publishCodeSnippet(
// Spawn the editor process - first arg is the command array including both the command and its arguments
const process = Bun.spawn([...editorCommand, tempFilePath]);
log("spawned editor process to edit " + tempFilePath);
log(`spawned editor process to edit ${tempFilePath}`);
// Wait for the editor to close
await process.exited;
@@ -178,21 +166,21 @@ export async function publishCodeSnippet(
if (existsSync(tempFilePath)) {
const updatedContent = readFileSync(tempFilePath, "utf-8");
try {
log("updatedContent: " + updatedContent);
const parsed = parseMetadata(updatedContent);
log(`updatedContent: ${updatedContent}`);
const parsed = parseMetadataFromString(updatedContent);
updatedTitle = parsed.metadata.title || title;
updatedDescription = parsed.metadata.description || description;
updatedLanguage = parsed.metadata.language || language;
updatedTags = parsed.metadata.tags.length >= 5 ? parsed.metadata.tags : tags;
updatedCode = parsed.code;
} catch (error) {
log("error " + error);
log(`error ${error}`);
console.error("Error parsing metadata:", error);
// Fallback to using the file content as just code if metadata parsing fails
updatedCode = updatedContent;
}
} else {
log("tempFilePath does not exist", tempFilePath);
log(`tempFilePath does not exist ${tempFilePath}`);
}
const eventTags = [