Merge pull request #4 from AstroSteveo/codex/investigate-intuitive-prompt-toggling
Add CLI helpers for managing Copilot configuration
This commit is contained in:
commit
1f02d732b4
24
CONFIG.md
24
CONFIG.md
@ -47,7 +47,11 @@ This creates:
|
||||
|
||||
### 2. Enable Desired Items
|
||||
|
||||
Edit the configuration file to set items to `true` that you want to include:
|
||||
You can enable items either by editing the YAML file directly or by using the CLI helpers that toggle entries for you.
|
||||
|
||||
#### Option A: Edit the configuration file manually
|
||||
|
||||
Set items to `true` in the configuration file to include them:
|
||||
|
||||
```yaml
|
||||
version: "1.0"
|
||||
@ -72,6 +76,24 @@ collections:
|
||||
csharp-dotnet-development: false
|
||||
```
|
||||
|
||||
#### Option B: Manage items from the CLI (recommended for quick toggles)
|
||||
|
||||
```bash
|
||||
# Inspect what is enabled in the default configuration file
|
||||
node /path/to/awesome-copilot/awesome-copilot.js list instructions
|
||||
|
||||
# Enable a single prompt
|
||||
node /path/to/awesome-copilot/awesome-copilot.js toggle prompts create-readme on
|
||||
|
||||
# Disable everything in a section
|
||||
node /path/to/awesome-copilot/awesome-copilot.js toggle instructions all off
|
||||
|
||||
# Work with a named configuration file
|
||||
node /path/to/awesome-copilot/awesome-copilot.js list prompts --config team.config.yml
|
||||
```
|
||||
|
||||
The CLI prints the number of enabled items and estimates the combined size of their instructions/prompts so you can avoid exceeding Copilot Agent's context window. If the total size approaches a risky threshold, you'll see a warning.
|
||||
|
||||
### 3. Apply Configuration
|
||||
|
||||
```bash
|
||||
|
||||
10
README.md
10
README.md
@ -63,7 +63,7 @@ Use our configuration system to manage all customizations in one place:
|
||||
```bash
|
||||
node /path/to/awesome-copilot/awesome-copilot.js init
|
||||
```
|
||||
4. **Edit the configuration** to enable the items you want:
|
||||
4. **Edit the configuration** to enable the items you want (or use the new CLI helpers):
|
||||
```yaml
|
||||
collections:
|
||||
frontend-web-dev: true # Enable entire collection
|
||||
@ -72,6 +72,14 @@ Use our configuration system to manage all customizations in one place:
|
||||
instructions:
|
||||
typescript-best-practices: true
|
||||
```
|
||||
```bash
|
||||
# List what is currently enabled
|
||||
node /path/to/awesome-copilot/awesome-copilot.js list instructions
|
||||
|
||||
# Enable or disable individual items without editing YAML by hand
|
||||
node /path/to/awesome-copilot/awesome-copilot.js toggle prompts create-readme on
|
||||
node /path/to/awesome-copilot/awesome-copilot.js toggle instructions all off
|
||||
```
|
||||
5. **Apply the configuration** to copy files to your project:
|
||||
```bash
|
||||
node /path/to/awesome-copilot/awesome-copilot.js apply
|
||||
|
||||
@ -7,52 +7,58 @@ const { parseCollectionYaml } = require("./yaml-parser");
|
||||
/**
|
||||
* Simple YAML parser for configuration files
|
||||
*/
|
||||
function parseConfigYamlContent(content) {
|
||||
const lines = content.split("\n");
|
||||
const result = {};
|
||||
let currentSection = null;
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
|
||||
// Skip comments and empty lines
|
||||
if (!trimmed || trimmed.startsWith("#")) continue;
|
||||
|
||||
if (!trimmed.includes(":")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const colonIndex = trimmed.indexOf(":");
|
||||
const key = trimmed.substring(0, colonIndex).trim();
|
||||
let value = trimmed.substring(colonIndex + 1).trim();
|
||||
|
||||
// Remove quotes if present
|
||||
if ((value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
|
||||
// Handle sections (no value)
|
||||
if (!value) {
|
||||
currentSection = key;
|
||||
if (!result[currentSection]) {
|
||||
result[currentSection] = {};
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle boolean values
|
||||
if (value === "true") value = true;
|
||||
else if (value === "false") value = false;
|
||||
|
||||
if (currentSection) {
|
||||
result[currentSection][key] = value;
|
||||
} else {
|
||||
result[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseConfigYaml(filePath) {
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, "utf8");
|
||||
const lines = content.split("\n");
|
||||
const result = {};
|
||||
let currentSection = null;
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
|
||||
// Skip comments and empty lines
|
||||
if (!trimmed || trimmed.startsWith("#")) continue;
|
||||
|
||||
// Handle key-value pairs
|
||||
if (trimmed.includes(":")) {
|
||||
const colonIndex = trimmed.indexOf(":");
|
||||
const key = trimmed.substring(0, colonIndex).trim();
|
||||
let value = trimmed.substring(colonIndex + 1).trim();
|
||||
|
||||
// Remove quotes if present
|
||||
if ((value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
|
||||
// Handle sections (no value)
|
||||
if (!value) {
|
||||
currentSection = key;
|
||||
if (!result[currentSection]) {
|
||||
result[currentSection] = {};
|
||||
}
|
||||
} else {
|
||||
// Handle boolean values
|
||||
if (value === "true") value = true;
|
||||
else if (value === "false") value = false;
|
||||
|
||||
if (currentSection) {
|
||||
result[currentSection][key] = value;
|
||||
} else {
|
||||
result[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return parseConfigYamlContent(content);
|
||||
} catch (error) {
|
||||
console.error(`Error parsing config file ${filePath}: ${error.message}`);
|
||||
return null;
|
||||
@ -230,4 +236,8 @@ if (require.main === module) {
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { applyConfig, parseConfigYaml };
|
||||
module.exports = {
|
||||
applyConfig,
|
||||
parseConfigYaml,
|
||||
parseConfigYamlContent
|
||||
};
|
||||
|
||||
@ -1,28 +1,65 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { generateConfig } = require("./generate-config");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const { applyConfig } = require("./apply-config");
|
||||
const {
|
||||
DEFAULT_CONFIG_PATH,
|
||||
CONFIG_SECTIONS,
|
||||
SECTION_METADATA,
|
||||
loadConfig,
|
||||
saveConfig,
|
||||
ensureConfigStructure,
|
||||
countEnabledItems,
|
||||
getAllAvailableItems
|
||||
} = require("./config-manager");
|
||||
|
||||
const CONFIG_FLAG_ALIASES = ["--config", "-c"];
|
||||
const CONTEXT_WARNING_CHAR_LIMIT = {
|
||||
instructions: 90000,
|
||||
prompts: 45000,
|
||||
chatmodes: 30000
|
||||
};
|
||||
|
||||
const numberFormatter = new Intl.NumberFormat("en-US");
|
||||
|
||||
const commands = {
|
||||
init: {
|
||||
description: "Initialize a new project with awesome-copilot configuration",
|
||||
usage: "awesome-copilot init [config-file]",
|
||||
action: async (args) => {
|
||||
const configFile = args[0] || "awesome-copilot.config.yml";
|
||||
const configFile = args[0] || DEFAULT_CONFIG_PATH;
|
||||
const { initializeProject } = require("./initialize-project");
|
||||
await initializeProject(configFile);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
apply: {
|
||||
description: "Apply configuration and copy files to project",
|
||||
usage: "awesome-copilot apply [config-file]",
|
||||
action: async (args) => {
|
||||
const configFile = args[0] || "awesome-copilot.config.yml";
|
||||
const configFile = args[0] || DEFAULT_CONFIG_PATH;
|
||||
await applyConfig(configFile);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
list: {
|
||||
description: "List items in the configuration with their enabled status",
|
||||
usage: "awesome-copilot list [section] [--config <file>]",
|
||||
action: (args) => {
|
||||
handleListCommand(args);
|
||||
}
|
||||
},
|
||||
|
||||
toggle: {
|
||||
description: "Enable or disable prompts, instructions, chat modes, or collections",
|
||||
usage: "awesome-copilot toggle <section> <name|all> [on|off] [--config <file>]",
|
||||
action: (args) => {
|
||||
handleToggleCommand(args);
|
||||
}
|
||||
},
|
||||
|
||||
help: {
|
||||
description: "Show help information",
|
||||
usage: "awesome-copilot help",
|
||||
@ -39,22 +76,24 @@ function showHelp() {
|
||||
console.log("Usage: awesome-copilot <command> [options]");
|
||||
console.log("");
|
||||
console.log("Commands:");
|
||||
|
||||
|
||||
for (const [name, cmd] of Object.entries(commands)) {
|
||||
console.log(` ${name.padEnd(10)} ${cmd.description}`);
|
||||
console.log(` ${' '.repeat(10)} ${cmd.usage}`);
|
||||
console.log(` ${" ".repeat(10)} ${cmd.usage}`);
|
||||
console.log("");
|
||||
}
|
||||
|
||||
|
||||
console.log("Examples:");
|
||||
console.log(" awesome-copilot init # Create default config file");
|
||||
console.log(" awesome-copilot init my-config.yml # Create named config file");
|
||||
console.log(" awesome-copilot apply # Apply default config");
|
||||
console.log(" awesome-copilot apply my-config.yml # Apply specific config");
|
||||
console.log(" awesome-copilot init # Create default config file");
|
||||
console.log(" awesome-copilot init my-config.yml # Create named config file");
|
||||
console.log(" awesome-copilot apply # Apply default config");
|
||||
console.log(" awesome-copilot list instructions # See which instructions are enabled");
|
||||
console.log(" awesome-copilot toggle prompts create-readme on # Enable a specific prompt");
|
||||
console.log(" awesome-copilot toggle instructions all off --config team.yml # Disable all instructions");
|
||||
console.log("");
|
||||
console.log("Workflow:");
|
||||
console.log(" 1. Run 'awesome-copilot init' to create a configuration file");
|
||||
console.log(" 2. Edit the configuration file to enable desired items");
|
||||
console.log(" 2. Use 'awesome-copilot list' and 'awesome-copilot toggle' to manage enabled items");
|
||||
console.log(" 3. Run 'awesome-copilot apply' to copy files to your project");
|
||||
}
|
||||
|
||||
@ -65,21 +104,228 @@ function showError(message) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function handleListCommand(rawArgs) {
|
||||
const { args, configPath } = extractConfigOption(rawArgs);
|
||||
|
||||
let sectionsToShow = CONFIG_SECTIONS;
|
||||
if (args.length > 0) {
|
||||
const requestedSection = validateSectionType(args[0]);
|
||||
sectionsToShow = [requestedSection];
|
||||
}
|
||||
|
||||
const { config } = loadConfig(configPath);
|
||||
const sanitizedConfig = ensureConfigStructure(config);
|
||||
|
||||
console.log(`📄 Configuration: ${configPath}`);
|
||||
|
||||
sectionsToShow.forEach(section => {
|
||||
const availableItems = getAllAvailableItems(section);
|
||||
const enabledCount = countEnabledItems(sanitizedConfig[section]);
|
||||
const { totalCharacters } = calculateSectionFootprint(section, sanitizedConfig[section]);
|
||||
const headingParts = [
|
||||
`${SECTION_METADATA[section].label} (${enabledCount}/${availableItems.length} enabled)`
|
||||
];
|
||||
|
||||
if (totalCharacters > 0 && section !== "collections") {
|
||||
headingParts.push(`~${formatNumber(totalCharacters)} chars`);
|
||||
}
|
||||
|
||||
console.log(`\n${headingParts.join(", ")}`);
|
||||
|
||||
if (!availableItems.length) {
|
||||
console.log(" (no items available)");
|
||||
return;
|
||||
}
|
||||
|
||||
availableItems.forEach(itemName => {
|
||||
const isEnabled = Boolean(sanitizedConfig[section]?.[itemName]);
|
||||
console.log(` [${isEnabled ? "✓" : " "}] ${itemName}`);
|
||||
});
|
||||
});
|
||||
|
||||
console.log("\nUse 'awesome-copilot toggle' to enable or disable specific items.");
|
||||
}
|
||||
|
||||
function handleToggleCommand(rawArgs) {
|
||||
const { args, configPath } = extractConfigOption(rawArgs);
|
||||
|
||||
if (args.length < 2) {
|
||||
throw new Error("Usage: awesome-copilot toggle <section> <name|all> [on|off] [--config <file>]");
|
||||
}
|
||||
|
||||
const section = validateSectionType(args[0]);
|
||||
const itemName = args[1];
|
||||
const stateArg = args[2];
|
||||
const desiredState = stateArg ? parseStateToken(stateArg) : null;
|
||||
|
||||
const availableItems = getAllAvailableItems(section);
|
||||
const availableSet = new Set(availableItems);
|
||||
if (!availableItems.length) {
|
||||
throw new Error(`No ${SECTION_METADATA[section].label.toLowerCase()} available to toggle.`);
|
||||
}
|
||||
|
||||
const { config, header } = loadConfig(configPath);
|
||||
const configCopy = {
|
||||
...config,
|
||||
[section]: { ...config[section] }
|
||||
};
|
||||
const sectionState = configCopy[section];
|
||||
|
||||
if (itemName === "all") {
|
||||
if (desiredState === null) {
|
||||
throw new Error("Specify 'on' or 'off' when toggling all items.");
|
||||
}
|
||||
availableItems.forEach(item => {
|
||||
sectionState[item] = desiredState;
|
||||
});
|
||||
console.log(`${desiredState ? "Enabled" : "Disabled"} all ${SECTION_METADATA[section].label.toLowerCase()}.`);
|
||||
|
||||
if (section === "instructions" && desiredState) {
|
||||
console.log("⚠️ Enabling every instruction can exceed Copilot Agent's context window. Consider enabling only what you need.");
|
||||
}
|
||||
} else {
|
||||
if (!availableSet.has(itemName)) {
|
||||
const suggestion = findClosestMatch(itemName, availableItems);
|
||||
if (suggestion) {
|
||||
throw new Error(`Unknown ${SECTION_METADATA[section].singular} '${itemName}'. Did you mean '${suggestion}'?`);
|
||||
}
|
||||
throw new Error(`Unknown ${SECTION_METADATA[section].singular} '${itemName}'.`);
|
||||
}
|
||||
|
||||
const currentState = Boolean(sectionState[itemName]);
|
||||
const newState = desiredState === null ? !currentState : desiredState;
|
||||
sectionState[itemName] = newState;
|
||||
console.log(`${newState ? "Enabled" : "Disabled"} ${SECTION_METADATA[section].singular} '${itemName}'.`);
|
||||
}
|
||||
|
||||
const sanitizedConfig = ensureConfigStructure(configCopy);
|
||||
saveConfig(configPath, sanitizedConfig, header);
|
||||
|
||||
const enabledCount = countEnabledItems(sanitizedConfig[section]);
|
||||
const totalAvailable = availableItems.length;
|
||||
const { totalCharacters } = calculateSectionFootprint(section, sanitizedConfig[section]);
|
||||
|
||||
console.log(`${SECTION_METADATA[section].label}: ${enabledCount}/${totalAvailable} enabled.`);
|
||||
if (totalCharacters > 0 && section !== "collections") {
|
||||
console.log(`Estimated ${SECTION_METADATA[section].label.toLowerCase()} context size: ${formatNumber(totalCharacters)} characters.`);
|
||||
}
|
||||
maybeWarnAboutContext(section, totalCharacters);
|
||||
console.log("Run 'awesome-copilot apply' to copy updated selections into your project.");
|
||||
}
|
||||
|
||||
function extractConfigOption(rawArgs) {
|
||||
const args = [...rawArgs];
|
||||
let configPath = DEFAULT_CONFIG_PATH;
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i];
|
||||
if (CONFIG_FLAG_ALIASES.includes(arg)) {
|
||||
if (i === args.length - 1) {
|
||||
throw new Error("Missing configuration file after --config flag.");
|
||||
}
|
||||
configPath = args[i + 1];
|
||||
args.splice(i, 2);
|
||||
i -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (args.length > 0) {
|
||||
const potentialPath = args[args.length - 1];
|
||||
if (isConfigFilePath(potentialPath)) {
|
||||
configPath = potentialPath;
|
||||
args.pop();
|
||||
}
|
||||
}
|
||||
|
||||
return { args, configPath };
|
||||
}
|
||||
|
||||
function isConfigFilePath(value) {
|
||||
if (typeof value !== "string") {
|
||||
return false;
|
||||
}
|
||||
return value.endsWith(".yml") || value.endsWith(".yaml") || value.includes("/") || value.includes("\\");
|
||||
}
|
||||
|
||||
function validateSectionType(input) {
|
||||
const normalized = String(input || "").toLowerCase();
|
||||
if (!SECTION_METADATA[normalized]) {
|
||||
throw new Error(`Unknown section '${input}'. Expected one of: ${CONFIG_SECTIONS.join(", ")}.`);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function parseStateToken(token) {
|
||||
const normalized = token.toLowerCase();
|
||||
if (["on", "enable", "enabled", "true", "yes", "y"].includes(normalized)) {
|
||||
return true;
|
||||
}
|
||||
if (["off", "disable", "disabled", "false", "no", "n"].includes(normalized)) {
|
||||
return false;
|
||||
}
|
||||
throw new Error("State must be 'on' or 'off'.");
|
||||
}
|
||||
|
||||
function calculateSectionFootprint(section, state = {}) {
|
||||
const meta = SECTION_METADATA[section];
|
||||
if (!meta || section === "collections") {
|
||||
return { totalCharacters: 0 };
|
||||
}
|
||||
|
||||
let totalCharacters = 0;
|
||||
|
||||
for (const [name, enabled] of Object.entries(state)) {
|
||||
if (!enabled) continue;
|
||||
|
||||
const filePath = path.join(__dirname, meta.dir, `${name}${meta.ext}`);
|
||||
try {
|
||||
const stats = fs.statSync(filePath);
|
||||
totalCharacters += stats.size;
|
||||
} catch (error) {
|
||||
// If the file no longer exists we skip it but continue gracefully.
|
||||
}
|
||||
}
|
||||
|
||||
return { totalCharacters };
|
||||
}
|
||||
|
||||
function maybeWarnAboutContext(section, totalCharacters) {
|
||||
const limit = CONTEXT_WARNING_CHAR_LIMIT[section];
|
||||
if (!limit || totalCharacters <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (totalCharacters >= limit) {
|
||||
console.log(`⚠️ Warning: Estimated ${SECTION_METADATA[section].label.toLowerCase()} size ${formatNumber(totalCharacters)} characters exceeds the recommended limit of ${formatNumber(limit)} characters. Copilot Agent may truncate or crash.`);
|
||||
} else if (totalCharacters >= limit * 0.8) {
|
||||
console.log(`⚠️ Heads up: Estimated ${SECTION_METADATA[section].label.toLowerCase()} size ${formatNumber(totalCharacters)} characters is approaching the recommended limit (${formatNumber(limit)} characters).`);
|
||||
}
|
||||
}
|
||||
|
||||
function formatNumber(value) {
|
||||
return numberFormatter.format(Math.round(value));
|
||||
}
|
||||
|
||||
function findClosestMatch(target, candidates) {
|
||||
const normalizedTarget = target.toLowerCase();
|
||||
return candidates.find(candidate => candidate.toLowerCase().includes(normalizedTarget));
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
|
||||
if (args.length === 0) {
|
||||
showHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const command = args[0];
|
||||
const commandArgs = args.slice(1);
|
||||
|
||||
|
||||
if (!commands[command]) {
|
||||
showError(`Unknown command: ${command}`);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
await commands[command].action(commandArgs);
|
||||
} catch (error) {
|
||||
@ -91,4 +337,4 @@ if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
||||
module.exports = { main };
|
||||
module.exports = { main };
|
||||
|
||||
172
config-manager.js
Normal file
172
config-manager.js
Normal file
@ -0,0 +1,172 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const { parseConfigYamlContent } = require("./apply-config");
|
||||
const { objectToYaml, generateConfigHeader, getAvailableItems } = require("./generate-config");
|
||||
|
||||
const DEFAULT_CONFIG_PATH = "awesome-copilot.config.yml";
|
||||
const SECTION_METADATA = {
|
||||
prompts: { dir: "prompts", ext: ".prompt.md", label: "Prompts", singular: "prompt" },
|
||||
instructions: { dir: "instructions", ext: ".instructions.md", label: "Instructions", singular: "instruction" },
|
||||
chatmodes: { dir: "chatmodes", ext: ".chatmode.md", label: "Chat Modes", singular: "chat mode" },
|
||||
collections: { dir: "collections", ext: ".collection.yml", label: "Collections", singular: "collection" }
|
||||
};
|
||||
const CONFIG_SECTIONS = Object.keys(SECTION_METADATA);
|
||||
|
||||
function loadConfig(configPath = DEFAULT_CONFIG_PATH) {
|
||||
if (!fs.existsSync(configPath)) {
|
||||
throw new Error(`Configuration file not found: ${configPath}`);
|
||||
}
|
||||
|
||||
const rawContent = fs.readFileSync(configPath, "utf8");
|
||||
const { header, body } = splitHeaderAndBody(rawContent);
|
||||
const parsed = parseConfigYamlContent(body || "");
|
||||
const config = ensureConfigStructure(parsed || {});
|
||||
|
||||
return { config, header };
|
||||
}
|
||||
|
||||
function saveConfig(configPath, config, header) {
|
||||
const ensuredConfig = ensureConfigStructure(config || {});
|
||||
const sortedConfig = sortConfigSections(ensuredConfig);
|
||||
const yamlContent = objectToYaml(sortedConfig);
|
||||
const headerContent = formatHeader(header);
|
||||
|
||||
fs.writeFileSync(configPath, headerContent + yamlContent);
|
||||
}
|
||||
|
||||
function splitHeaderAndBody(content) {
|
||||
const lines = content.split("\n");
|
||||
const headerLines = [];
|
||||
let firstBodyIndex = 0;
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const trimmed = lines[i].trim();
|
||||
if (trimmed === "" || trimmed.startsWith("#")) {
|
||||
headerLines.push(lines[i]);
|
||||
firstBodyIndex = i + 1;
|
||||
} else {
|
||||
firstBodyIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const header = headerLines.join("\n");
|
||||
const body = lines.slice(firstBodyIndex).join("\n");
|
||||
|
||||
return { header, body };
|
||||
}
|
||||
|
||||
function ensureConfigStructure(config) {
|
||||
const sanitized = typeof config === "object" && config !== null ? { ...config } : {};
|
||||
|
||||
if (!sanitized.version) {
|
||||
sanitized.version = "1.0";
|
||||
}
|
||||
|
||||
const project = typeof sanitized.project === "object" && sanitized.project !== null ? { ...sanitized.project } : {};
|
||||
if (project.output_directory === undefined) {
|
||||
project.output_directory = ".awesome-copilot";
|
||||
}
|
||||
sanitized.project = project;
|
||||
|
||||
CONFIG_SECTIONS.forEach(section => {
|
||||
sanitized[section] = sanitizeSection(sanitized[section]);
|
||||
});
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
function sanitizeSection(section) {
|
||||
if (!section || typeof section !== "object") {
|
||||
return {};
|
||||
}
|
||||
|
||||
const sanitized = {};
|
||||
for (const [key, value] of Object.entries(section)) {
|
||||
sanitized[key] = toBoolean(value);
|
||||
}
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
function toBoolean(value) {
|
||||
if (typeof value === "boolean") {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeof value === "string") {
|
||||
const normalized = value.trim().toLowerCase();
|
||||
if (normalized === "true") return true;
|
||||
if (normalized === "false") return false;
|
||||
}
|
||||
|
||||
return Boolean(value);
|
||||
}
|
||||
|
||||
function sortConfigSections(config) {
|
||||
const sorted = { ...config };
|
||||
|
||||
CONFIG_SECTIONS.forEach(section => {
|
||||
sorted[section] = sortObjectKeys(sorted[section]);
|
||||
});
|
||||
|
||||
return sorted;
|
||||
}
|
||||
|
||||
function sortObjectKeys(obj) {
|
||||
if (!obj || typeof obj !== "object") {
|
||||
return {};
|
||||
}
|
||||
|
||||
return Object.keys(obj)
|
||||
.sort((a, b) => a.localeCompare(b))
|
||||
.reduce((acc, key) => {
|
||||
acc[key] = obj[key];
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function formatHeader(existingHeader) {
|
||||
const header = existingHeader && existingHeader.trim().length > 0
|
||||
? existingHeader
|
||||
: generateConfigHeader();
|
||||
|
||||
let normalized = header;
|
||||
|
||||
if (!normalized.endsWith("\n")) {
|
||||
normalized += "\n";
|
||||
}
|
||||
if (!normalized.endsWith("\n\n")) {
|
||||
normalized += "\n";
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function countEnabledItems(section = {}) {
|
||||
return Object.values(section).filter(Boolean).length;
|
||||
}
|
||||
|
||||
function getAllAvailableItems(type) {
|
||||
const meta = SECTION_METADATA[type];
|
||||
|
||||
if (!meta) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return getAvailableItems(path.join(__dirname, meta.dir), meta.ext);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
DEFAULT_CONFIG_PATH,
|
||||
CONFIG_SECTIONS,
|
||||
SECTION_METADATA,
|
||||
loadConfig,
|
||||
saveConfig,
|
||||
splitHeaderAndBody,
|
||||
ensureConfigStructure,
|
||||
sortObjectKeys,
|
||||
countEnabledItems,
|
||||
getAllAvailableItems
|
||||
};
|
||||
@ -8,7 +8,7 @@ const path = require("path");
|
||||
*/
|
||||
function generateConfig(outputPath = "awesome-copilot.config.yml") {
|
||||
const rootDir = __dirname;
|
||||
|
||||
|
||||
// Get all available items
|
||||
const prompts = getAvailableItems(path.join(rootDir, "prompts"), ".prompt.md");
|
||||
const instructions = getAvailableItems(path.join(rootDir, "instructions"), ".instructions.md");
|
||||
@ -46,25 +46,8 @@ function generateConfig(outputPath = "awesome-copilot.config.yml") {
|
||||
config.collections[item] = false;
|
||||
});
|
||||
|
||||
// Convert to YAML format manually (since we don't want to add dependencies)
|
||||
const yamlContent = objectToYaml(config);
|
||||
|
||||
// Add header comment
|
||||
const header = `# Awesome Copilot Configuration File
|
||||
# Generated on ${new Date().toISOString()}
|
||||
#
|
||||
# This file allows you to enable/disable specific prompts, instructions,
|
||||
# chat modes, and collections for your project.
|
||||
#
|
||||
# Set items to 'true' to include them in your project
|
||||
# Set items to 'false' to exclude them
|
||||
#
|
||||
# After configuring, run: awesome-copilot apply
|
||||
#
|
||||
|
||||
`;
|
||||
|
||||
const fullContent = header + yamlContent;
|
||||
const fullContent = generateConfigHeader() + yamlContent;
|
||||
|
||||
fs.writeFileSync(outputPath, fullContent);
|
||||
console.log(`Configuration file generated: ${outputPath}`);
|
||||
@ -108,13 +91,21 @@ function objectToYaml(obj, indent = 0) {
|
||||
return yaml;
|
||||
}
|
||||
|
||||
// CLI usage
|
||||
if (require.main === module) {
|
||||
const outputPath = process.argv[2] || "awesome-copilot.config.yml";
|
||||
generateConfig(outputPath);
|
||||
}
|
||||
function generateConfigHeader(date = new Date()) {
|
||||
return `# Awesome Copilot Configuration File
|
||||
# Generated on ${date.toISOString()}
|
||||
#
|
||||
# This file allows you to enable/disable specific prompts, instructions,
|
||||
# chat modes, and collections for your project.
|
||||
#
|
||||
# Set items to 'true' to include them in your project
|
||||
# Set items to 'false' to exclude them
|
||||
#
|
||||
# After configuring, run: awesome-copilot apply
|
||||
#
|
||||
|
||||
module.exports = { generateConfig, getAvailableItems };
|
||||
`;
|
||||
}
|
||||
|
||||
// CLI usage
|
||||
if (require.main === module) {
|
||||
@ -122,4 +113,9 @@ if (require.main === module) {
|
||||
generateConfig(outputPath);
|
||||
}
|
||||
|
||||
module.exports = { generateConfig, getAvailableItems };
|
||||
module.exports = {
|
||||
generateConfig,
|
||||
getAvailableItems,
|
||||
objectToYaml,
|
||||
generateConfigHeader
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user