feat(cli): skill install targets + remove custom install in favor of vercel-labs/skills (#177)

## Summary

**Skill install targets (original):**
- Add project-level skill install targets: Windsurf, Cline, Roo Code, Trae (opt-in via flag)
- Split install logic into global vs project-level
- Fix lint false positive: timed tags with `data-composition-id` no longer flagged by media rule

**Skill system cleanup (folded from #189):**
- Delete `install-skills.ts` (~485 lines) — remove custom installation wrapper entirely
- Strip skill logic from `init` — no more project-level `.claude/skills/` copies, no `--skip-skills` flag; replaced with post-scaffold message: `npx skills add heygen-com/hyperframes`
- Front-load SKILL.md trigger words — all 5 skill descriptions rewritten so activation language comes first (~150 chars)
- Update CLAUDE.md — install instructions now point to [vercel-labs/skills](https://github.com/vercel-labs/skills)
- Fix `.claude/settings.json` — pre-commit hook changed from `pnpm` to `bun`

## Test plan

- [ ] `npx hyperframes skills` → "Unknown command skills"
- [ ] `npx hyperframes init test --template blank --non-interactive --skip-transcribe` → prints `npx skills add heygen-com/hyperframes`
- [ ] `grep -r "install-skills" packages/cli/src/` → no results
- [ ] All 5 `skills/*/SKILL.md` have front-loaded descriptions

🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
Vance Ingalls
2026-04-02 00:47:25 -07:00
committed by GitHub
parent 37404f23da
commit ad2d63db32
11 changed files with 42 additions and 551 deletions
+8 -107
View File
@@ -23,70 +23,6 @@ import { fetchRemoteTemplate } from "../templates/remote.js";
import { trackInitTemplate } from "../telemetry/events.js";
import { hasFFmpeg } from "../whisper/manager.js";
// ---------------------------------------------------------------------------
// Install skills silently after scaffolding
// ---------------------------------------------------------------------------
async function installSkills(interactive: boolean): Promise<void> {
try {
const { installAllSkills, TARGETS } = await import("./install-skills.js");
let selectedTargets: string[] | undefined;
if (interactive) {
const choices = await clack.multiselect({
message: "Install skills for:",
options: TARGETS.map((t) => ({
value: t.flag,
label: t.name,
hint: t.dir,
})),
initialValues: TARGETS.filter((t) => t.defaultEnabled).map((t) => t.flag),
required: false,
});
if (clack.isCancel(choices)) {
return;
}
selectedTargets = choices as string[];
if (selectedTargets.length === 0) {
clack.log.info(c.dim("Skipping skills installation"));
return;
}
}
const spin = interactive ? clack.spinner() : null;
spin?.start("Installing AI coding skills...");
const result = await installAllSkills(selectedTargets, {
onProgress: (msg) => spin?.message(msg),
});
if (result.count > 0) {
const msg = `${result.count} skills installed (${result.targets.join(", ")})`;
if (spin) {
spin.stop(c.success(msg));
} else {
console.log(c.success(msg));
}
if (result.skipped.length > 0) {
const skipMsg = `Skipped: ${result.skipped.join(", ")} (repo not accessible)`;
if (interactive) {
clack.log.warn(c.dim(skipMsg));
} else {
console.log(c.dim(` ${skipMsg}`));
}
}
} else {
spin?.stop(c.dim("No skills installed"));
}
} catch {
if (interactive) {
clack.log.warn(c.dim("Skills install skipped (no git or network)"));
}
}
}
interface VideoMeta {
durationSeconds: number;
width: number;
@@ -218,12 +154,6 @@ function getSharedTemplateDir(): string {
return resolveAssetDir(["..", "templates", "_shared"], ["templates", "_shared"]);
}
function getBundledSkillsDir(): string {
// In dev: cli/src/commands/ → repo root skills/
// In built: cli/dist/ → cli/dist/skills/
return resolveAssetDir(["..", "..", "..", "..", "skills"], ["skills"]);
}
function patchVideoSrc(
dir: string,
videoFilename: string | undefined,
@@ -405,20 +335,6 @@ async function scaffoldProject(
}
}
}
// Copy project-level skills (.claude/skills/) for immediate availability
const skillsSrcDir = getBundledSkillsDir();
if (existsSync(skillsSrcDir)) {
const projectSkills = ["hyperframes-compose", "hyperframes-captions", "hyperframes-cli"];
for (const skill of projectSkills) {
const src = join(skillsSrcDir, skill);
if (existsSync(src)) {
const dest = resolve(destDir, ".claude", "skills", skill);
mkdirSync(dest, { recursive: true });
cpSync(src, dest, { recursive: true });
}
}
}
}
// ---------------------------------------------------------------------------
@@ -447,10 +363,6 @@ export default defineCommand({
description: "Path to an audio file (MP3, WAV, M4A)",
alias: "a",
},
"skip-skills": {
type: "boolean",
description: "Skip AI coding skills installation",
},
"skip-transcribe": {
type: "boolean",
description: "Skip whisper transcription",
@@ -474,7 +386,6 @@ export default defineCommand({
const templateFlag = args.template;
const videoFlag = args.video;
const audioFlag = args.audio;
const skipSkills = args["skip-skills"] === true;
const skipTranscribe = args["skip-transcribe"] === true;
const nonInteractive = args["non-interactive"] === true;
const modelFlag = args.model;
@@ -578,11 +489,6 @@ export default defineCommand({
await patchTranscript(destDir, transcriptFile);
}
// Skills
if (!skipSkills) {
await installSkills(false);
}
console.log(c.success(`Created ${c.accent(name + "/")}`));
for (const f of readdirSync(destDir).filter((f) => !f.startsWith("."))) {
console.log(` ${c.accent(f)}`);
@@ -590,18 +496,18 @@ export default defineCommand({
console.log();
console.log("Get started:");
console.log();
console.log(` ${c.accent("1.")} Open this project with your AI coding agent:`);
console.log(` ${c.accent("1.")} Install AI coding skills (one-time):`);
console.log(` ${c.accent("npx skills add heygen-com/hyperframes")}`);
console.log();
console.log(` ${c.accent("2.")} Open this project with your AI coding agent:`);
console.log(
` ${c.accent(`cd ${name}`)} then start ${c.accent("Claude Code")}, ${c.accent("Cursor")}, or your preferred agent`,
);
console.log(
` ${c.dim("AI skills are installed — your agent knows how to create and edit compositions.")}`,
);
console.log();
console.log(` ${c.accent("2.")} Preview in the browser:`);
console.log(` ${c.accent("3.")} Preview in the browser:`);
console.log(` ${c.accent(`cd ${name}`)} && ${c.accent("npx hyperframes preview")}`);
console.log();
console.log(` ${c.accent("3.")} Render to MP4 when ready:`);
console.log(` ${c.accent("4.")} Render to MP4 when ready:`);
console.log(` ${c.accent(`cd ${name}`)} && ${c.accent("npx hyperframes render")}`);
console.log();
console.log(` ${c.dim("Full docs: hyperframes.heygen.com")}`);
@@ -774,17 +680,12 @@ export default defineCommand({
await patchTranscript(destDir, transcriptFile);
}
// 5. Install AI coding skills
if (!skipSkills) {
await installSkills(true);
}
const files = readdirSync(destDir);
clack.note(files.map((f) => c.accent(f)).join("\n"), c.success(`Created ${name}/`));
clack.log.message(
`${c.dim("Tip:")} Open this project with ${c.accent("Claude Code")}, ${c.accent("Cursor")}, or your preferred AI agent.\n` +
`${c.dim(" AI skills are installed — your agent knows how to create and edit compositions.")}`,
`${c.dim("Tip:")} Install AI coding skills: ${c.accent("npx skills add heygen-com/hyperframes")}\n` +
`${c.dim(" Then open this project with")} ${c.accent("Claude Code")}${c.dim(",")} ${c.accent("Cursor")}${c.dim(", or your preferred agent.")}`,
);
// Auto-launch studio preview