awesome-copilot/create-collection.js
Copilot dfe63f4542
Implement Collections feature for grouping related prompts, instructions, and chat modes (#232)
* Initial plan

* Implement core Collections feature with YAML parsing and README generation

Co-authored-by: aaronpowell <434140+aaronpowell@users.noreply.github.com>

* Complete Collections feature implementation with validation, tooling, and documentation

Co-authored-by: aaronpowell <434140+aaronpowell@users.noreply.github.com>

* Update generated README files to include collections instructions

* Fix YAML parsing logic bug: replace impossible condition with proper indentation check

Co-authored-by: aaronpowell <434140+aaronpowell@users.noreply.github.com>

* Refactor: Extract YAML parser to shared module and improve user experience

Co-authored-by: aaronpowell <434140+aaronpowell@users.noreply.github.com>

* fixing task and file permissions

* Better args handling

* Adding some more collections

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: aaronpowell <434140+aaronpowell@users.noreply.github.com>
Co-authored-by: Aaron Powell <me@aaron-powell.com>
2025-09-17 10:15:34 +10:00

162 lines
4.8 KiB
JavaScript
Executable File

#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
function prompt(question) {
return new Promise((resolve) => {
rl.question(question, resolve);
});
}
function parseArgs() {
const args = process.argv.slice(2);
const out = { id: undefined, tags: undefined };
// simple long/short option parsing
for (let i = 0; i < args.length; i++) {
const a = args[i];
if (a === '--id' || a === '-i') {
out.id = args[i + 1];
i++;
} else if (a.startsWith('--id=')) {
out.id = a.split('=')[1];
} else if (a === '--tags' || a === '-t') {
out.tags = args[i + 1];
i++;
} else if (a.startsWith('--tags=')) {
out.tags = a.split('=')[1];
} else if (!a.startsWith('-') && !out.id) {
// first positional -> id
out.id = a;
} else if (!a.startsWith('-') && out.id && !out.tags) {
// second positional -> tags
out.tags = a;
}
}
// normalize tags to string (comma separated) or undefined
if (Array.isArray(out.tags)) {
out.tags = out.tags.join(',');
}
return out;
}
async function createCollectionTemplate() {
try {
console.log("🎯 Collection Creator");
console.log("This tool will help you create a new collection manifest.\n");
// Parse CLI args and fall back to interactive prompts when missing
const parsed = parseArgs();
// Get collection ID
let collectionId = parsed.id;
if (!collectionId) {
collectionId = await prompt("Collection ID (lowercase, hyphens only): ");
}
// Validate collection ID format
if (!collectionId) {
console.error("❌ Collection ID is required");
process.exit(1);
}
if (!/^[a-z0-9-]+$/.test(collectionId)) {
console.error("❌ Collection ID must contain only lowercase letters, numbers, and hyphens");
process.exit(1);
}
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 (from CLI or prompt)
let tags = [];
let tagInput = parsed.tags;
if (!tagInput) {
tagInput = await prompt("Tags (comma-separated, or press Enter for defaults): ");
}
if (tagInput && tagInput.toString().trim()) {
tags = tagInput.toString().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:
# - path: prompts/example.prompt.md
# kind: prompt
# - path: instructions/example.instructions.md
# kind: instruction
# - path: chatmodes/example.chatmode.md
# kind: chat-mode
display:
ordering: alpha # or "manual" to preserve the order above
show_badge: false # set to true to show collection badge on items
`;
fs.writeFileSync(filePath, template);
console.log(`✅ Created collection template: ${filePath}`);
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("\n📄 Collection template contents:");
console.log(template);
} catch (error) {
console.error(`❌ Error creating collection template: ${error.message}`);
process.exit(1);
} finally {
rl.close();
}
}
// Run the interactive creation process
createCollectionTemplate();