diff --git a/README.md b/README.md index 37b00ca..39fa683 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ We welcome contributions! Please see our [Contributing Guidelines](CONTRIBUTING. ## 🌟 Getting Started -1. **Browse the Collections**: Check out our comprehensive lists of [collections](README.collections.md), [prompts](README.prompts.md), [instructions](README.instructions.md), and [chat modes](README.chatmodes.md). +1. **Browse the Collections**: Check out our comprehensive lists of [prompts](README.prompts.md), [instructions](README.instructions.md), [chat modes](README.chatmodes.md), and [collections](README.collections.md). 2. **Add to your editor**: Click the "Install" button to install to VS Code, or copy the file contents for other editors. 3. **Start Using**: Copy prompts to use with `/` commands, let instructions enhance your coding experience, or activate chat modes for specialized assistance. diff --git a/create-collection.js b/create-collection.js index 0f1eb67..a4ef3cc 100644 --- a/create-collection.js +++ b/create-collection.js @@ -2,45 +2,91 @@ const fs = require("fs"); const path = require("path"); +const readline = require("readline"); -function createCollectionTemplate(collectionId) { - if (!collectionId) { - console.error("Collection ID is required"); - console.log("Usage: node create-collection.js "); - process.exit(1); - } +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout +}); - // Validate collection ID format - if (!/^[a-z0-9-]+$/.test(collectionId)) { - console.error("Collection ID must contain only lowercase letters, numbers, and hyphens"); - process.exit(1); - } +function prompt(question) { + return new Promise((resolve) => { + rl.question(question, resolve); + }); +} - const collectionsDir = path.join(__dirname, "collections"); - const filePath = path.join(collectionsDir, `${collectionId}.collection.yml`); +async function createCollectionTemplate() { + try { + console.log("šŸŽÆ Collection Creator"); + console.log("This tool will help you create a new collection manifest.\n"); - // Check if file already exists - if (fs.existsSync(filePath)) { - console.error(`Collection ${collectionId} already exists at ${filePath}`); - process.exit(1); - } + // Get collection ID + let collectionId; + if (process.argv[2]) { + collectionId = process.argv[2]; + } else { + collectionId = await prompt("Collection ID (lowercase, hyphens only): "); + } - // Ensure collections directory exists - if (!fs.existsSync(collectionsDir)) { - fs.mkdirSync(collectionsDir, { recursive: true }); - } + // Validate collection ID format + if (!collectionId) { + console.error("āŒ Collection ID is required"); + process.exit(1); + } - // Create a friendly name from the ID - const friendlyName = collectionId - .split("-") - .map(word => word.charAt(0).toUpperCase() + word.slice(1)) - .join(" "); + if (!/^[a-z0-9-]+$/.test(collectionId)) { + console.error("āŒ Collection ID must contain only lowercase letters, numbers, and hyphens"); + process.exit(1); + } - // Template content - const template = `id: ${collectionId} -name: ${friendlyName} -description: A collection of related prompts, instructions, and chat modes for ${friendlyName.toLowerCase()}. -tags: [${collectionId.split("-").slice(0, 3).join(", ")}] # Add relevant tags + const collectionsDir = path.join(__dirname, "collections"); + const filePath = path.join(collectionsDir, `${collectionId}.collection.yml`); + + // Check if file already exists + if (fs.existsSync(filePath)) { + console.log(`āš ļø Collection ${collectionId} already exists at ${filePath}`); + console.log("šŸ’” Please edit that file instead or choose a different ID."); + process.exit(1); + } + + // Ensure collections directory exists + if (!fs.existsSync(collectionsDir)) { + fs.mkdirSync(collectionsDir, { recursive: true }); + } + + // Get collection name + const defaultName = collectionId + .split("-") + .map(word => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "); + + let collectionName = await prompt(`Collection name (default: ${defaultName}): `); + if (!collectionName.trim()) { + collectionName = defaultName; + } + + // Get description + const defaultDescription = `A collection of related prompts, instructions, and chat modes for ${collectionName.toLowerCase()}.`; + let description = await prompt(`Description (default: ${defaultDescription}): `); + if (!description.trim()) { + description = defaultDescription; + } + + // Get tags + let tags = []; + const tagInput = await prompt("Tags (comma-separated, or press Enter for defaults): "); + if (tagInput.trim()) { + tags = tagInput.split(",").map(tag => tag.trim()).filter(tag => tag); + } else { + // Generate some default tags from the collection ID + tags = collectionId.split("-").slice(0, 3); + } + + // Template content + const template = `id: ${collectionId} +name: ${collectionName} +description: ${description} +tags: [${tags.join(", ")}] items: # Add your collection items here # Example: @@ -55,22 +101,23 @@ display: show_badge: false # set to true to show collection badge on items `; - try { fs.writeFileSync(filePath, template); console.log(`āœ… Created collection template: ${filePath}`); - console.log("\nNext steps:"); + console.log("\nšŸ“ Next steps:"); console.log("1. Edit the collection manifest to add your items"); console.log("2. Update the name, description, and tags as needed"); console.log("3. Run 'node validate-collections.js' to validate"); console.log("4. Run 'node update-readme.js' to generate documentation"); - console.log("\nCollection template contents:"); + console.log("\nšŸ“„ Collection template contents:"); console.log(template); + } catch (error) { - console.error(`Error creating collection template: ${error.message}`); + console.error(`āŒ Error creating collection template: ${error.message}`); process.exit(1); + } finally { + rl.close(); } } -// Get collection ID from command line arguments -const collectionId = process.argv[2]; -createCollectionTemplate(collectionId); \ No newline at end of file +// Run the interactive creation process +createCollectionTemplate(); \ No newline at end of file diff --git a/update-readme.js b/update-readme.js index dda4726..b1a42e0 100755 --- a/update-readme.js +++ b/update-readme.js @@ -2,6 +2,7 @@ const fs = require("fs"); const path = require("path"); +const { parseCollectionYaml } = require("./yaml-parser"); // Template sections for the README const TEMPLATES = { @@ -68,6 +69,9 @@ Curated collections of related prompts, instructions, and chat modes organized a }; // Add error handling utility +/** + * Safe file operation wrapper + */ function safeFileOperation(operation, filePath, defaultValue = null) { try { return operation(); @@ -77,110 +81,6 @@ function safeFileOperation(operation, filePath, defaultValue = null) { } } -/** - * Simple YAML parser for collection manifests - * Handles our specific YAML structure without external dependencies - */ -function parseCollectionYaml(filePath) { - return safeFileOperation( - () => { - const content = fs.readFileSync(filePath, "utf8"); - const lines = content.split("\n"); - const result = {}; - let currentKey = null; - let currentArray = null; - let currentObject = null; - - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - const trimmed = line.trim(); - - if (!trimmed || trimmed.startsWith("#")) continue; - - const leadingSpaces = line.length - line.trimLeft().length; - - // Handle array items starting with - - if (trimmed.startsWith("- ")) { - if (currentKey === "items") { - if (!currentArray) { - currentArray = []; - result[currentKey] = currentArray; - } - - // Parse item object - const item = {}; - currentArray.push(item); - currentObject = item; - - // Handle inline properties on same line as - - const restOfLine = trimmed.substring(2).trim(); - if (restOfLine) { - const colonIndex = restOfLine.indexOf(":"); - if (colonIndex > -1) { - const key = restOfLine.substring(0, colonIndex).trim(); - const value = restOfLine.substring(colonIndex + 1).trim(); - item[key] = value; - } - } - } else if (currentKey === "tags") { - if (!currentArray) { - currentArray = []; - result[currentKey] = currentArray; - } - const value = trimmed.substring(2).trim(); - currentArray.push(value); - } - } - // Handle key-value pairs - else if (trimmed.includes(":")) { - const colonIndex = trimmed.indexOf(":"); - const key = trimmed.substring(0, colonIndex).trim(); - let value = trimmed.substring(colonIndex + 1).trim(); - - if (leadingSpaces === 0) { - // Top-level property - currentKey = key; - currentArray = null; - currentObject = null; - - if (value) { - // Handle array format [item1, item2, item3] - if (value.startsWith("[") && value.endsWith("]")) { - const arrayContent = value.slice(1, -1); - if (arrayContent.trim()) { - result[key] = arrayContent.split(",").map(item => item.trim()); - } else { - result[key] = []; - } - currentKey = null; // Reset since we handled the array - } else { - result[key] = value; - } - } else if (key === "items" || key === "tags") { - // Will be populated by array items - result[key] = []; - currentArray = result[key]; - } else if (key === "display") { - result[key] = {}; - currentObject = result[key]; - } - } else if (currentObject && leadingSpaces > 0) { - // Property of current object (e.g., display properties) - currentObject[key] = value === "true" ? true : value === "false" ? false : value; - } else if (currentArray && currentObject && leadingSpaces > 2) { - // Property of array item object - currentObject[key] = value; - } - } - } - - return result; - }, - filePath, - null - ); -} - function extractTitle(filePath) { return safeFileOperation( () => { @@ -533,10 +433,10 @@ function generateChatModesSection(chatmodesDir) { * Generate the collections section with a table of all collections */ function generateCollectionsSection(collectionsDir) { - // Check if collections directory exists + // Check if collections directory exists, create it if it doesn't if (!fs.existsSync(collectionsDir)) { - console.log("Collections directory does not exist"); - return ""; + console.log("Collections directory does not exist, creating it..."); + fs.mkdirSync(collectionsDir, { recursive: true }); } // Get all collection files diff --git a/validate-collections.js b/validate-collections.js index 969d0f3..745dfaa 100644 --- a/validate-collections.js +++ b/validate-collections.js @@ -2,8 +2,10 @@ const fs = require("fs"); const path = require("path"); +const { parseCollectionYaml } = require("./yaml-parser"); -// Simple YAML parser (same as in update-readme.js) +// Maximum number of items allowed in a collection +const MAX_COLLECTION_ITEMS = 50; function safeFileOperation(operation, filePath, defaultValue = null) { try { return operation(); @@ -13,106 +15,6 @@ function safeFileOperation(operation, filePath, defaultValue = null) { } } -function parseCollectionYaml(filePath) { - return safeFileOperation( - () => { - const content = fs.readFileSync(filePath, "utf8"); - const lines = content.split("\n"); - const result = {}; - let currentKey = null; - let currentArray = null; - let currentObject = null; - - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - const trimmed = line.trim(); - - if (!trimmed || trimmed.startsWith("#")) continue; - - const leadingSpaces = line.length - line.trimLeft().length; - - // Handle array items starting with - - if (trimmed.startsWith("- ")) { - if (currentKey === "items") { - if (!currentArray) { - currentArray = []; - result[currentKey] = currentArray; - } - - // Parse item object - const item = {}; - currentArray.push(item); - currentObject = item; - - // Handle inline properties on same line as - - const restOfLine = trimmed.substring(2).trim(); - if (restOfLine) { - const colonIndex = restOfLine.indexOf(":"); - if (colonIndex > -1) { - const key = restOfLine.substring(0, colonIndex).trim(); - const value = restOfLine.substring(colonIndex + 1).trim(); - item[key] = value; - } - } - } else if (currentKey === "tags") { - if (!currentArray) { - currentArray = []; - result[currentKey] = currentArray; - } - const value = trimmed.substring(2).trim(); - currentArray.push(value); - } - } - // Handle key-value pairs - else if (trimmed.includes(":")) { - const colonIndex = trimmed.indexOf(":"); - const key = trimmed.substring(0, colonIndex).trim(); - let value = trimmed.substring(colonIndex + 1).trim(); - - if (leadingSpaces === 0) { - // Top-level property - currentKey = key; - currentArray = null; - currentObject = null; - - if (value) { - // Handle array format [item1, item2, item3] - if (value.startsWith("[") && value.endsWith("]")) { - const arrayContent = value.slice(1, -1); - if (arrayContent.trim()) { - result[key] = arrayContent.split(",").map(item => item.trim()); - } else { - result[key] = []; - } - currentKey = null; // Reset since we handled the array - } else { - result[key] = value; - } - } else if (key === "items" || key === "tags") { - // Will be populated by array items - result[key] = []; - currentArray = result[key]; - } else if (key === "display") { - result[key] = {}; - currentObject = result[key]; - } - } else if (currentObject && leadingSpaces > 0) { - // Property of current object (e.g., display properties) - currentObject[key] = value === "true" ? true : value === "false" ? false : value; - } else if (currentArray && currentObject && leadingSpaces > 2) { - // Property of array item object - currentObject[key] = value; - } - } - } - - return result; - }, - filePath, - null - ); -} - // Validation functions function validateCollectionId(id) { if (!id || typeof id !== "string") { @@ -177,8 +79,8 @@ function validateCollectionItems(items) { if (items.length < 1) { return "At least one item is required"; } - if (items.length > 50) { - return "Maximum 50 items allowed"; + if (items.length > MAX_COLLECTION_ITEMS) { + return `Maximum ${MAX_COLLECTION_ITEMS} items allowed`; } for (let i = 0; i < items.length; i++) { diff --git a/yaml-parser.js b/yaml-parser.js new file mode 100644 index 0000000..b209908 --- /dev/null +++ b/yaml-parser.js @@ -0,0 +1,113 @@ +// Simple YAML parser for collection files +const fs = require("fs"); + +function safeFileOperation(operation, filePath, defaultValue = null) { + try { + return operation(); + } catch (error) { + console.error(`Error processing file ${filePath}: ${error.message}`); + return defaultValue; + } +} + +function parseCollectionYaml(filePath) { + return safeFileOperation( + () => { + const content = fs.readFileSync(filePath, "utf8"); + const lines = content.split("\n"); + const result = {}; + let currentKey = null; + let currentArray = null; + let currentObject = null; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const trimmed = line.trim(); + + if (!trimmed || trimmed.startsWith("#")) continue; + + const leadingSpaces = line.length - line.trimLeft().length; + + // Handle array items starting with - + if (trimmed.startsWith("- ")) { + if (currentKey === "items") { + if (!currentArray) { + currentArray = []; + result[currentKey] = currentArray; + } + + // Parse item object + const item = {}; + currentArray.push(item); + currentObject = item; + + // Handle inline properties on same line as - + const restOfLine = trimmed.substring(2).trim(); + if (restOfLine) { + const colonIndex = restOfLine.indexOf(":"); + if (colonIndex > -1) { + const key = restOfLine.substring(0, colonIndex).trim(); + const value = restOfLine.substring(colonIndex + 1).trim(); + item[key] = value; + } + } + } else if (currentKey === "tags") { + if (!currentArray) { + currentArray = []; + result[currentKey] = currentArray; + } + const value = trimmed.substring(2).trim(); + currentArray.push(value); + } + } + // Handle key-value pairs + else if (trimmed.includes(":")) { + const colonIndex = trimmed.indexOf(":"); + const key = trimmed.substring(0, colonIndex).trim(); + let value = trimmed.substring(colonIndex + 1).trim(); + + if (leadingSpaces === 0) { + // Top-level property + currentKey = key; + currentArray = null; + currentObject = null; + + if (value) { + // Handle array format [item1, item2, item3] + if (value.startsWith("[") && value.endsWith("]")) { + const arrayContent = value.slice(1, -1); + if (arrayContent.trim()) { + result[key] = arrayContent.split(",").map(item => item.trim()); + } else { + result[key] = []; + } + currentKey = null; // Reset since we handled the array + } else { + result[key] = value; + } + } else if (key === "items" || key === "tags") { + // Will be populated by array items + result[key] = []; + currentArray = result[key]; + } else if (key === "display") { + result[key] = {}; + currentObject = result[key]; + } + } else if (currentObject && leadingSpaces > 0) { + // Property of current object (e.g., display properties) + currentObject[key] = value === "true" ? true : value === "false" ? false : value; + } else if (currentArray && currentObject && leadingSpaces > 2) { + // Property of array item object + currentObject[key] = value; + } + } + } + + return result; + }, + filePath, + null + ); +} + +module.exports = { parseCollectionYaml, safeFileOperation }; \ No newline at end of file