Files
pbakaus_impeccable/server/lib/api-handlers.js
T
Paul BakausandClaude Opus 4.6 b0f44f83c6 Consolidate 18 skills into 1 /impeccable skill with 20 commands
Biggest change in a while. Users previously had 18 standalone skill
entries cluttering their /menu; now they have one entry (/impeccable)
that routes to 20 specialized commands via argument dispatch. The pin
mechanism (/impeccable pin audit) restores standalone shortcuts on
demand for commands users hit all the time.

## Architecture

- Single /impeccable skill with command router section in SKILL.md
- 20 commands served via reference files under source/skills/impeccable/reference/
- /impeccable pin <command> creates a lightweight redirect shim so users
  who prefer /audit, /polish, etc. can still have them
- Context gathering (teach) auto-runs on first use
- command-metadata.json is the single source of truth for command
  descriptions, argument hints, and relationships

## Site rewrite

- Docs URL: /skills renamed to /docs (with /skills permanent redirects)
- Homepage hero frames Impeccable as "one skill with 20 commands"
- "Get Started" split into 50/50 install + how-to-use with editorial
  numbered steps, /impeccable shown as the home command with three modes
- New /docs overview: home command hero card + dense category rows
  matching the old cheatsheet density, with leads-to/pairs-with/
  combines-with relationship metadata served from a shared source
- Cheatsheet merged into /docs, /cheatsheet redirects
- Magazine spread and mobile cards show /impeccable as a stacked
  namespace label above the command name at full display size
- Periodic table updated with craft/teach/extract as first-class cells
- Skill detail pages generate from reference files, with an editorial
  wrapper per command for tagline + body
- Tutorials and anti-patterns pages updated to use /impeccable <cmd>

## Build system

- Dead code removed (scripts/lib/transformers/shared.js)
- Build log wording fixed ("1 skill" not "1 skills (1 user-invocable)")
- generateApiData fallback branch removed (throws loudly if metadata
  missing instead of silently degrading)
- Commands API includes editorial tagline alongside the long description;
  UI surfaces prefer tagline for human display, description for auto-
  trigger keyword matching

## Gitignore

- Added .claude/scheduled_tasks.lock, .claude/settings.local.json to
  ignore list (local Claude Code state that should not be tracked).
- Harness skill directories (.claude/skills/, .agents/skills/, etc.)
  remain tracked by design: npx skills reads them from this repo at
  install time and they enable clean submodule use.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 19:45:17 -07:00

224 lines
6.5 KiB
JavaScript

import { readdir, readFile } from "fs/promises";
import { basename, join, dirname } from "path";
import { existsSync } from "fs";
import { fileURLToPath } from "url";
import { readPatterns, parseFrontmatter } from "../../scripts/lib/utils.js";
import { FILE_DOWNLOAD_PROVIDER_CONFIG_DIRS } from "../../lib/download-providers.js";
import {
isAllowedBundleProvider,
isAllowedFileProvider,
isAllowedType,
isValidId,
sanitizeFilename
} from "./validation.js";
// Get project root directory (works in both Node.js and Bun, including Vercel)
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const PROJECT_ROOT = join(__dirname, "..", "..");
// Helper to read file content (works in both Node.js and Bun)
async function readFileContent(filePath) {
return readFile(filePath, "utf-8");
}
// Read all skills from source/skills/ subdirectories
export async function getSkills() {
const skillsDir = join(PROJECT_ROOT, "source", "skills");
const entries = await readdir(skillsDir, { withFileTypes: true });
const skills = [];
for (const entry of entries) {
if (!entry.isDirectory()) continue;
const skillMdPath = join(skillsDir, entry.name, "SKILL.md");
if (!existsSync(skillMdPath)) continue;
const content = await readFileContent(skillMdPath);
const { frontmatter } = parseFrontmatter(content);
skills.push({
id: entry.name,
name: frontmatter.name || entry.name,
description: frontmatter.description || "No description available",
userInvocable: frontmatter['user-invocable'] === true || frontmatter['user-invocable'] === 'true',
});
}
return skills;
}
// Read a short tagline for a command from its editorial file
// (content/site/skills/<id>.md). Returns null if the file or tagline is
// missing. Taglines are used by UI surfaces that need a human-friendly
// one-liner; `description` stays optimized for auto-trigger matching.
async function readCommandTagline(id) {
const editorialPath = join(PROJECT_ROOT, "content/site/skills", `${id}.md`);
if (!existsSync(editorialPath)) return null;
try {
const raw = await readFileContent(editorialPath);
const match = raw.match(/^---\n([\s\S]*?)\n---/);
if (!match) return null;
const taglineMatch = match[1].match(/tagline:\s*"([^"]+)"/);
return taglineMatch ? taglineMatch[1] : null;
} catch {
return null;
}
}
// Read commands. After the v3.0 consolidation, commands are sub-commands of
// /impeccable. Read them from command-metadata.json and include the root
// impeccable skill itself so UI surfaces (cheatsheet, magazine spread) can
// list them.
export async function getCommands() {
const allSkills = await getSkills();
const metadataPath = join(PROJECT_ROOT, "source/skills/impeccable/scripts/command-metadata.json");
const commands = [];
const impeccable = allSkills.find(s => s.name === "impeccable");
if (impeccable) {
commands.push({
id: "impeccable",
name: "impeccable",
description: impeccable.description,
tagline: await readCommandTagline("impeccable"),
userInvocable: true,
});
}
if (existsSync(metadataPath)) {
try {
const raw = await readFileContent(metadataPath);
const metadata = JSON.parse(raw);
for (const [id, meta] of Object.entries(metadata)) {
commands.push({
id,
name: id,
description: meta.description,
tagline: await readCommandTagline(id),
userInvocable: true,
});
}
} catch (error) {
console.error("Error reading command metadata:", error);
}
}
// Fallback: return just user-invocable skills if no metadata
if (commands.length === 0) {
return allSkills.filter(s => s.userInvocable);
}
return commands;
}
// Get command/skill source content
export async function getCommandSource(id) {
if (!isValidId(id)) {
return { error: "Invalid command ID", status: 400 };
}
const skillPath = join(PROJECT_ROOT, "source", "skills", id, "SKILL.md");
try {
if (!existsSync(skillPath)) {
return null;
}
const content = await readFileContent(skillPath);
return content;
} catch (error) {
console.error("Error reading skill source:", error);
return null;
}
}
// Get the appropriate file path for a provider
export function getFilePath(type, provider, id) {
const distDir = join(PROJECT_ROOT, "dist");
const configDir = FILE_DOWNLOAD_PROVIDER_CONFIG_DIRS[provider];
if (!configDir) return null;
// Everything is a skill now
if (type === "skill" || type === "command") {
return join(distDir, provider, configDir, "skills", id, "SKILL.md");
}
return null;
}
// Handle individual file download
export async function handleFileDownload(type, provider, id) {
if (!isAllowedType(type)) {
return new Response("Invalid type", { status: 400 });
}
if (!isAllowedFileProvider(provider)) {
return new Response("Invalid provider", { status: 400 });
}
if (!isValidId(id)) {
return new Response("Invalid file ID", { status: 400 });
}
const filePath = getFilePath(type, provider, id);
if (!filePath) {
return new Response("Invalid provider", { status: 400 });
}
try {
if (!existsSync(filePath)) {
return new Response("File not found", { status: 404 });
}
const content = await readFile(filePath);
const fileName = sanitizeFilename(basename(filePath));
return new Response(content, {
headers: {
"Content-Type": "application/octet-stream",
"Content-Disposition": `attachment; filename="${fileName}"`,
},
});
} catch (error) {
console.error("Error downloading file:", error);
return new Response("Error downloading file", { status: 500 });
}
}
// Extract patterns from SKILL.md using the shared utility
export async function getPatterns() {
try {
return readPatterns(PROJECT_ROOT);
} catch (error) {
console.error("Error reading patterns:", error);
return { patterns: [], antipatterns: [] };
}
}
// Handle bundle download
export async function handleBundleDownload(provider) {
if (!isAllowedBundleProvider(provider)) {
return new Response("Invalid provider", { status: 400 });
}
const distDir = join(PROJECT_ROOT, "dist");
const zipPath = join(distDir, `${provider}.zip`);
try {
if (!existsSync(zipPath)) {
return new Response("Bundle not found", { status: 404 });
}
const content = await readFile(zipPath);
const safeProvider = sanitizeFilename(provider);
return new Response(content, {
headers: {
"Content-Type": "application/zip",
"Content-Disposition": `attachment; filename="impeccable-style-${safeProvider}.zip"`,
},
});
} catch (error) {
console.error("Error downloading bundle:", error);
return new Response("Error downloading bundle", { status: 500 });
}
}