Prefix all skills (incl. frontend-design), fix cross-references, move toggle inline

- Prefix ALL skills in prefixed bundles, not just user-invokable ones
- Add prefixSkillReferences() to update /skillname and "the skillname skill"
  cross-references in skill body text when prefixing
- Add prefix/outputSuffix support to agents transformer
- Move prefix toggle inline next to the download ZIP button for better discoverability

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-03-05 10:12:36 -08:00
co-authored by Claude Opus 4.6
parent ee7eeae301
commit aadaee5c37
9 changed files with 4010 additions and 48 deletions
+5 -3
View File
@@ -1,5 +1,5 @@
import path from 'path';
import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceholders } from '../utils.js';
import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceholders, prefixSkillReferences } from '../utils.js';
/**
* Agents Transformer (VS Code Copilot + Antigravity)
@@ -20,9 +20,10 @@ export function transformAgents(skills, distDir, patterns = null, options = {})
cleanDir(agentsDir);
ensureDir(skillsDir);
const allSkillNames = skills.map(s => s.name);
let refCount = 0;
for (const skill of skills) {
const skillName = skill.userInvokable ? `${prefix}${skill.name}` : skill.name;
const skillName = `${prefix}${skill.name}`;
const skillDir = path.join(skillsDir, skillName);
const frontmatterObj = {
@@ -41,7 +42,8 @@ export function transformAgents(skills, distDir, patterns = null, options = {})
}
const frontmatter = generateYamlFrontmatter(frontmatterObj);
const skillBody = replacePlaceholders(skill.body, 'agents');
let skillBody = replacePlaceholders(skill.body, 'agents');
if (prefix) skillBody = prefixSkillReferences(skillBody, prefix, allSkillNames);
const content = `${frontmatter}\n\n${skillBody}`;
const outputPath = path.join(skillDir, 'SKILL.md');
writeFile(outputPath, content);
+5 -3
View File
@@ -1,5 +1,5 @@
import path from 'path';
import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceholders } from '../utils.js';
import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceholders, prefixSkillReferences } from '../utils.js';
/**
* Claude Code Transformer (Skills Only)
@@ -22,9 +22,10 @@ export function transformClaudeCode(skills, distDir, patterns = null, options =
cleanDir(claudeDir);
ensureDir(skillsDir);
const allSkillNames = skills.map(s => s.name);
let refCount = 0;
for (const skill of skills) {
const skillName = skill.userInvokable ? `${prefix}${skill.name}` : skill.name;
const skillName = `${prefix}${skill.name}`;
const skillDir = path.join(skillsDir, skillName);
const frontmatterObj = {
@@ -40,7 +41,8 @@ export function transformClaudeCode(skills, distDir, patterns = null, options =
if (skill.allowedTools) frontmatterObj['allowed-tools'] = skill.allowedTools;
const frontmatter = generateYamlFrontmatter(frontmatterObj);
const skillBody = replacePlaceholders(skill.body, 'claude-code');
let skillBody = replacePlaceholders(skill.body, 'claude-code');
if (prefix) skillBody = prefixSkillReferences(skillBody, prefix, allSkillNames);
const content = `${frontmatter}\n\n${skillBody}`;
const outputPath = path.join(skillDir, 'SKILL.md');
writeFile(outputPath, content);
+4 -2
View File
@@ -1,5 +1,5 @@
import path from 'path';
import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceholders } from '../utils.js';
import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceholders, prefixSkillReferences } from '../utils.js';
/**
* Codex Transformer (Skills Only)
@@ -23,9 +23,10 @@ export function transformCodex(skills, distDir, patterns = null, options = {}) {
cleanDir(codexDir);
ensureDir(skillsDir);
const allSkillNames = skills.map(s => s.name);
let refCount = 0;
for (const skill of skills) {
const skillName = skill.userInvokable ? `${prefix}${skill.name}` : skill.name;
const skillName = `${prefix}${skill.name}`;
const skillDir = path.join(skillsDir, skillName);
const frontmatterObj = {
@@ -45,6 +46,7 @@ export function transformCodex(skills, distDir, patterns = null, options = {}) {
const frontmatter = generateYamlFrontmatter(frontmatterObj);
let skillBody = replacePlaceholders(skill.body, 'codex');
if (prefix) skillBody = prefixSkillReferences(skillBody, prefix, allSkillNames);
// For user-invokable skills, transform remaining {{argname}} to $ARGNAME
if (skill.userInvokable) {
skillBody = skillBody.replace(/\{\{([^}]+)\}\}/g, (match, argName) => {
+5 -3
View File
@@ -1,5 +1,5 @@
import path from 'path';
import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceholders } from '../utils.js';
import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceholders, prefixSkillReferences } from '../utils.js';
/**
* Cursor Transformer (Skills Only)
@@ -22,9 +22,10 @@ export function transformCursor(skills, distDir, patterns = null, options = {})
cleanDir(cursorDir);
ensureDir(skillsDir);
const allSkillNames = skills.map(s => s.name);
let refCount = 0;
for (const skill of skills) {
const skillName = skill.userInvokable ? `${prefix}${skill.name}` : skill.name;
const skillName = `${prefix}${skill.name}`;
const skillDir = path.join(skillsDir, skillName);
const frontmatterObj = {
@@ -34,7 +35,8 @@ export function transformCursor(skills, distDir, patterns = null, options = {})
if (skill.license) frontmatterObj.license = skill.license;
const frontmatter = generateYamlFrontmatter(frontmatterObj);
const skillBody = replacePlaceholders(skill.body, 'cursor');
let skillBody = replacePlaceholders(skill.body, 'cursor');
if (prefix) skillBody = prefixSkillReferences(skillBody, prefix, allSkillNames);
const content = `${frontmatter}\n\n${skillBody}`;
const outputPath = path.join(skillDir, 'SKILL.md');
writeFile(outputPath, content);
+4 -2
View File
@@ -1,5 +1,5 @@
import path from 'path';
import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceholders } from '../utils.js';
import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceholders, prefixSkillReferences } from '../utils.js';
/**
* Gemini Transformer (Skills Only)
@@ -23,9 +23,10 @@ export function transformGemini(skills, distDir, patterns = null, options = {})
cleanDir(geminiDir);
ensureDir(skillsDir);
const allSkillNames = skills.map(s => s.name);
let refCount = 0;
for (const skill of skills) {
const skillName = skill.userInvokable ? `${prefix}${skill.name}` : skill.name;
const skillName = `${prefix}${skill.name}`;
const skillDir = path.join(skillsDir, skillName);
const frontmatter = generateYamlFrontmatter({
@@ -34,6 +35,7 @@ export function transformGemini(skills, distDir, patterns = null, options = {})
});
let skillBody = replacePlaceholders(skill.body, 'gemini');
if (prefix) skillBody = prefixSkillReferences(skillBody, prefix, allSkillNames);
// For user-invokable skills, replace remaining {{arg}} placeholders with {{args}}
if (skill.userInvokable) {
skillBody = skillBody.replace(/\{\{[^}]+\}\}/g, '{{args}}');
+32
View File
@@ -294,6 +294,38 @@ export const PROVIDER_PLACEHOLDERS = {
/**
* Replace all {{placeholder}} tokens with provider-specific values
*/
/**
* Prefix skill cross-references in body text.
* Replaces patterns like `/skillname` and `the skillname skill` with prefixed versions.
*
* @param {string} content - The skill body text
* @param {string} prefix - The prefix to add (e.g., 'i-')
* @param {string[]} skillNames - Array of all skill names
*/
export function prefixSkillReferences(content, prefix, skillNames) {
if (!prefix || !skillNames || skillNames.length === 0) return content;
let result = content;
// Sort by length descending to avoid partial matches (e.g. 'teach-impeccable' before 'teach')
const sorted = [...skillNames].sort((a, b) => b.length - a.length);
for (const name of sorted) {
const prefixed = `${prefix}${name}`;
// Replace `/skillname` references (command invocations)
result = result.replace(new RegExp(`\\/(?=${escapeRegex(name)}(?:[^a-zA-Z0-9_-]|$))`, 'g'), `/${prefix}`);
// Replace `the skillname skill` references
result = result.replace(new RegExp(`the ${escapeRegex(name)} skill`, 'gi'), `the ${prefixed} skill`);
}
return result;
}
function escapeRegex(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
export function replacePlaceholders(content, provider) {
const placeholders = PROVIDER_PLACEHOLDERS[provider] || PROVIDER_PLACEHOLDERS['cursor'];