mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-11 21:57:14 +03:00
Restore prefix toggle for i- prefixed skill bundles
Re-adds the toggle in the install section that lets users download bundles with all user-invokable skills prefixed with i- (e.g. /i-audit) to avoid naming conflicts. Build now produces both unprefixed and prefixed variants for all providers and universal ZIP. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
b628e208e3
commit
ee7eeae301
@@ -34,7 +34,10 @@ function getFilePath(type, provider, id) {
|
||||
}
|
||||
|
||||
const VALID_ID = /^[a-zA-Z0-9_-]+$/;
|
||||
const ALLOWED_PROVIDERS = ['cursor', 'claude-code', 'gemini', 'codex', 'agents', 'universal'];
|
||||
const ALLOWED_PROVIDERS = [
|
||||
'cursor', 'claude-code', 'gemini', 'codex', 'agents', 'universal',
|
||||
'cursor-prefixed', 'claude-code-prefixed', 'gemini-prefixed', 'codex-prefixed', 'agents-prefixed', 'universal-prefixed',
|
||||
];
|
||||
|
||||
export default function handler(req, res) {
|
||||
try {
|
||||
|
||||
@@ -6,7 +6,10 @@ const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const PROJECT_ROOT = join(__dirname, "../../..");
|
||||
|
||||
const ALLOWED_PROVIDERS = ['cursor', 'claude-code', 'gemini', 'codex', 'agents', 'universal'];
|
||||
const ALLOWED_PROVIDERS = [
|
||||
'cursor', 'claude-code', 'gemini', 'codex', 'agents', 'universal',
|
||||
'cursor-prefixed', 'claude-code-prefixed', 'gemini-prefixed', 'codex-prefixed', 'agents-prefixed', 'universal-prefixed',
|
||||
];
|
||||
|
||||
export default function handler(req, res) {
|
||||
try {
|
||||
|
||||
+4
-1
@@ -219,7 +219,10 @@ document.addEventListener("click", (e) => {
|
||||
const bundleBtn = e.target.closest("[data-bundle]");
|
||||
if (bundleBtn) {
|
||||
const provider = bundleBtn.dataset.bundle;
|
||||
window.location.href = `/api/download/bundle/${provider}`;
|
||||
const prefixToggle = document.getElementById('prefix-toggle');
|
||||
const usePrefixed = prefixToggle && prefixToggle.checked;
|
||||
const bundleName = usePrefixed ? `${provider}-prefixed` : provider;
|
||||
window.location.href = `/api/download/bundle/${bundleName}`;
|
||||
}
|
||||
|
||||
// Handle copy button clicks
|
||||
|
||||
@@ -2208,6 +2208,85 @@ code {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Download options / prefix toggle */
|
||||
.download-options {
|
||||
margin-bottom: var(--spacing-lg);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.prefix-toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
cursor: pointer;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
background: var(--color-cream);
|
||||
border: 1px solid var(--color-mist);
|
||||
border-radius: 8px;
|
||||
transition: border-color var(--duration-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.prefix-toggle:hover {
|
||||
border-color: var(--color-ash);
|
||||
}
|
||||
|
||||
.prefix-toggle input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.prefix-toggle-slider {
|
||||
position: relative;
|
||||
width: 44px;
|
||||
height: 24px;
|
||||
background: var(--color-mist);
|
||||
border-radius: 24px;
|
||||
transition: background var(--duration-fast) var(--ease-out);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.prefix-toggle-slider::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 3px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background: var(--color-paper);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
|
||||
transition: transform var(--duration-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.prefix-toggle input:checked + .prefix-toggle-slider {
|
||||
background: var(--color-accent);
|
||||
}
|
||||
|
||||
.prefix-toggle input:checked + .prefix-toggle-slider::after {
|
||||
transform: translateX(20px);
|
||||
}
|
||||
|
||||
.prefix-toggle input:focus-visible + .prefix-toggle-slider {
|
||||
outline: 2px solid var(--color-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.prefix-toggle-label {
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-charcoal);
|
||||
}
|
||||
|
||||
.prefix-toggle-label code {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.8125rem;
|
||||
background: var(--color-accent-dim);
|
||||
color: var(--color-accent);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.download-tip {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--color-ash);
|
||||
|
||||
+1
-3850
File diff suppressed because one or more lines are too long
+10
-1
@@ -277,7 +277,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="download-tip" data-reveal>Once installed, keep the <a href="/cheatsheet">command cheatsheet</a> handy for quick reference.</p>
|
||||
<div class="download-options" data-reveal>
|
||||
<label class="prefix-toggle">
|
||||
<input type="checkbox" id="prefix-toggle" />
|
||||
<span class="prefix-toggle-slider"></span>
|
||||
<span class="prefix-toggle-label">
|
||||
Prefix skills with <code>/i-</code> to avoid conflicts
|
||||
</span>
|
||||
</label>
|
||||
<p class="download-tip">Once installed, keep the <a href="/cheatsheet">command cheatsheet</a> handy for quick reference.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 5. CHANGELOG -->
|
||||
|
||||
+17
-6
@@ -130,8 +130,8 @@ async function buildStaticSite() {
|
||||
/**
|
||||
* Assemble universal directory from all provider outputs
|
||||
*/
|
||||
function assembleUniversal(distDir) {
|
||||
const universalDir = path.join(distDir, 'universal');
|
||||
function assembleUniversal(distDir, suffix = '') {
|
||||
const universalDir = path.join(distDir, `universal${suffix}`);
|
||||
|
||||
// Clean and recreate
|
||||
if (fs.existsSync(universalDir)) {
|
||||
@@ -147,7 +147,7 @@ function assembleUniversal(distDir) {
|
||||
];
|
||||
|
||||
for (const { provider, configDir } of providerMappings) {
|
||||
const src = path.join(distDir, provider, configDir);
|
||||
const src = path.join(distDir, `${provider}${suffix}`, configDir);
|
||||
const dest = path.join(universalDir, configDir);
|
||||
if (fs.existsSync(src)) {
|
||||
copyDirSync(src, dest);
|
||||
@@ -156,10 +156,11 @@ function assembleUniversal(distDir) {
|
||||
|
||||
// Add a visible README so macOS users don't see an empty folder
|
||||
// (all provider dirs are dotfiles, hidden by default in Finder)
|
||||
const prefixNote = suffix ? '\nSkills in this bundle are prefixed with i- (e.g. /i-audit) to avoid conflicts.\n' : '';
|
||||
fs.writeFileSync(path.join(universalDir, 'README.txt'),
|
||||
`Impeccable — Design fluency for AI harnesses
|
||||
https://impeccable.style
|
||||
|
||||
${prefixNote}
|
||||
This folder contains skills for all supported tools:
|
||||
|
||||
.cursor/ → Cursor
|
||||
@@ -172,7 +173,8 @@ To install, copy the relevant folder(s) into your project root.
|
||||
These are hidden folders (dotfiles) — press Cmd+Shift+. in Finder to see them.
|
||||
`);
|
||||
|
||||
console.log(`✓ Assembled universal directory (${providerMappings.length} providers)`);
|
||||
const label = suffix ? ' (prefixed)' : '';
|
||||
console.log(`✓ Assembled universal${label} directory (${providerMappings.length} providers)`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -210,8 +212,17 @@ async function build() {
|
||||
transformCodex(skills, DIST_DIR, patterns);
|
||||
transformAgents(skills, DIST_DIR, patterns);
|
||||
|
||||
// Assemble universal directory
|
||||
// Transform for each provider (prefixed with i-)
|
||||
const prefixOptions = { prefix: 'i-', outputSuffix: '-prefixed' };
|
||||
transformCursor(skills, DIST_DIR, patterns, prefixOptions);
|
||||
transformClaudeCode(skills, DIST_DIR, patterns, prefixOptions);
|
||||
transformGemini(skills, DIST_DIR, patterns, prefixOptions);
|
||||
transformCodex(skills, DIST_DIR, patterns, prefixOptions);
|
||||
transformAgents(skills, DIST_DIR, patterns, prefixOptions);
|
||||
|
||||
// Assemble universal directory (unprefixed and prefixed)
|
||||
assembleUniversal(DIST_DIR);
|
||||
assembleUniversal(DIST_DIR, '-prefixed');
|
||||
|
||||
// Create ZIP bundles (individual + universal)
|
||||
await createAllZips(DIST_DIR);
|
||||
|
||||
@@ -13,7 +13,8 @@ import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceho
|
||||
* @param {Object} options - Optional settings
|
||||
*/
|
||||
export function transformAgents(skills, distDir, patterns = null, options = {}) {
|
||||
const agentsDir = path.join(distDir, 'agents');
|
||||
const { prefix = '', outputSuffix = '' } = options;
|
||||
const agentsDir = path.join(distDir, `agents${outputSuffix}`);
|
||||
const skillsDir = path.join(agentsDir, '.agents/skills');
|
||||
|
||||
cleanDir(agentsDir);
|
||||
@@ -21,7 +22,7 @@ export function transformAgents(skills, distDir, patterns = null, options = {})
|
||||
|
||||
let refCount = 0;
|
||||
for (const skill of skills) {
|
||||
const skillName = skill.name;
|
||||
const skillName = skill.userInvokable ? `${prefix}${skill.name}` : skill.name;
|
||||
const skillDir = path.join(skillsDir, skillName);
|
||||
|
||||
const frontmatterObj = {
|
||||
@@ -60,5 +61,6 @@ export function transformAgents(skills, distDir, patterns = null, options = {})
|
||||
|
||||
const userInvokableCount = skills.filter(s => s.userInvokable).length;
|
||||
const refInfo = refCount > 0 ? ` (${refCount} reference files)` : '';
|
||||
console.log(`✓ Agents: ${skills.length} skills (${userInvokableCount} user-invokable)${refInfo}`);
|
||||
const prefixInfo = prefix ? ` [${prefix}prefixed]` : '';
|
||||
console.log(`✓ Agents${prefixInfo}: ${skills.length} skills (${userInvokableCount} user-invokable)${refInfo}`);
|
||||
}
|
||||
|
||||
+13
-2
@@ -53,13 +53,24 @@ export async function createAllZips(distDir) {
|
||||
|
||||
const providers = ['cursor', 'claude-code', 'gemini', 'codex', 'agents'];
|
||||
|
||||
// Create individual provider ZIPs
|
||||
// Create unprefixed ZIPs
|
||||
for (const provider of providers) {
|
||||
const providerDir = path.join(distDir, provider);
|
||||
await createProviderZip(providerDir, distDir, provider);
|
||||
}
|
||||
|
||||
// Create universal ZIP
|
||||
// Create unprefixed universal ZIP
|
||||
const universalDir = path.join(distDir, 'universal');
|
||||
await createProviderZip(universalDir, distDir, 'universal');
|
||||
|
||||
// Create prefixed ZIPs
|
||||
console.log(' --- prefixed variants ---');
|
||||
for (const provider of providers) {
|
||||
const providerDir = path.join(distDir, `${provider}-prefixed`);
|
||||
await createProviderZip(providerDir, distDir, `${provider}-prefixed`);
|
||||
}
|
||||
|
||||
// Create prefixed universal ZIP
|
||||
const universalPrefixedDir = path.join(distDir, 'universal-prefixed');
|
||||
await createProviderZip(universalPrefixedDir, distDir, 'universal-prefixed');
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
// Only allow alphanumeric, hyphens, and underscores in IDs
|
||||
export const VALID_ID = /^[a-zA-Z0-9_-]+$/;
|
||||
|
||||
export const ALLOWED_PROVIDERS = ['cursor', 'claude-code', 'gemini', 'codex', 'agents', 'universal'];
|
||||
export const ALLOWED_PROVIDERS = [
|
||||
'cursor', 'claude-code', 'gemini', 'codex', 'agents', 'universal',
|
||||
'cursor-prefixed', 'claude-code-prefixed', 'gemini-prefixed', 'codex-prefixed', 'agents-prefixed', 'universal-prefixed',
|
||||
];
|
||||
export const ALLOWED_TYPES = ['skill', 'command'];
|
||||
|
||||
export function isValidId(id) {
|
||||
|
||||
Reference in New Issue
Block a user