feat(cli): add Windsurf, Cline, Roo, Trae skill install targets

- Add project-level skill targets: Windsurf, Cline, Roo Code, Trae
- Split install logic into global vs project-level paths
- Fix lint false positive for timed tags with data-composition-id
- Update CLAUDE.md with new install examples and supported targets

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vance Ingalls
2026-04-01 22:29:11 -07:00
co-authored by Claude Opus 4.6
parent efb09e33ba
commit 6956531ecc
4 changed files with 119 additions and 9 deletions
Submodule .claude/worktrees/agent-a692178b added at 1e4c101fb4
+6 -2
View File
@@ -39,11 +39,15 @@ The skills encode HyperFrames-specific patterns (e.g., required `class="clip"` o
### Installing skills
```bash
npx hyperframes skills # install all to Claude, Gemini, Codex
npx hyperframes skills --claude # Claude Code only
npx hyperframes skills # install to Claude, Gemini, Codex (global)
npx hyperframes skills --claude # Claude Code only
npx hyperframes skills --windsurf # Windsurf (project-level)
npx hyperframes skills --cursor --cline --roo --trae # multiple project targets
npx skills add greensock/gsap-skills # alternative: via skills CLI
```
Supported targets: Claude Code, Gemini CLI, Codex CLI (global, enabled by default), Cursor, Windsurf, Cline, Roo Code, Trae (project-level, opt-in via flag).
## Project Overview
Open-source video rendering framework: write HTML, render video.
+111 -6
View File
@@ -20,7 +20,7 @@ function execFileAsync(
}
// ---------------------------------------------------------------------------
// Target CLI tools — each has a global skills directory
// Target CLI tools — each has a skills directory (global or project-level)
// ---------------------------------------------------------------------------
interface Target {
@@ -29,15 +29,19 @@ interface Target {
/** Agent name for `npx skills add -a <agent>` */
skillsAgent: string;
dir: string;
/** Global targets install to ~/.<agent>/skills/; project targets install to ./<agent>/skills/ */
isGlobal: boolean;
defaultEnabled: boolean;
}
const TARGETS: Target[] = [
// -- Global targets (enabled by default) --
{
name: "Claude Code",
flag: "claude",
skillsAgent: "claude-code",
dir: join(homedir(), ".claude", "skills"),
isGlobal: true,
defaultEnabled: true,
},
{
@@ -45,6 +49,7 @@ const TARGETS: Target[] = [
flag: "gemini",
skillsAgent: "gemini-cli",
dir: join(homedir(), ".gemini", "skills"),
isGlobal: true,
defaultEnabled: true,
},
{
@@ -52,8 +57,10 @@ const TARGETS: Target[] = [
flag: "codex",
skillsAgent: "codex",
dir: join(homedir(), ".codex", "skills"),
isGlobal: true,
defaultEnabled: true,
},
// -- Project-level targets (opt-in via flag) --
{
name: "Cursor",
flag: "cursor",
@@ -61,6 +68,47 @@ const TARGETS: Target[] = [
get dir() {
return join(process.cwd(), ".cursor", "skills");
},
isGlobal: false,
defaultEnabled: false,
},
{
name: "Windsurf",
flag: "windsurf",
skillsAgent: "windsurf",
get dir() {
return join(process.cwd(), ".windsurf", "skills");
},
isGlobal: false,
defaultEnabled: false,
},
{
name: "Cline",
flag: "cline",
skillsAgent: "cline",
get dir() {
return join(process.cwd(), ".cline", "skills");
},
isGlobal: false,
defaultEnabled: false,
},
{
name: "Roo Code",
flag: "roo",
skillsAgent: "roo",
get dir() {
return join(process.cwd(), ".roo", "skills");
},
isGlobal: false,
defaultEnabled: false,
},
{
name: "Trae",
flag: "trae",
skillsAgent: "trae",
get dir() {
return join(process.cwd(), ".trae", "skills");
},
isGlobal: false,
defaultEnabled: false,
},
];
@@ -234,6 +282,29 @@ async function fallbackInstall(targets: Target[]): Promise<{
return { count: allInstalled.length, installed: allInstalled, skipped };
}
// ---------------------------------------------------------------------------
// Shared helpers
// ---------------------------------------------------------------------------
function splitAgents(targets: Target[]): { global: string[]; project: string[] } {
return {
global: targets.filter((t) => t.isGlobal).map((t) => t.skillsAgent),
project: targets.filter((t) => !t.isGlobal).map((t) => t.skillsAgent),
};
}
async function installSource(
repo: string,
agents: { global: string[]; project: string[] },
): Promise<void> {
if (agents.global.length > 0) {
await runSkillsAdd(repo, agents.global, true);
}
if (agents.project.length > 0) {
await runSkillsAdd(repo, agents.project, false);
}
}
// ---------------------------------------------------------------------------
// Programmatic API — used by init command
// ---------------------------------------------------------------------------
@@ -247,8 +318,8 @@ export async function installAllSkills(
const targets = targetNames
? TARGETS.filter((t) => targetNames.includes(t.flag))
: TARGETS.filter((t) => t.defaultEnabled);
const agents = targets.map((t) => t.skillsAgent);
const progress = options?.onProgress;
const agents = splitAgents(targets);
// Try npx skills add first
if (hasNpx()) {
@@ -257,7 +328,7 @@ export async function installAllSkills(
for (const source of SOURCES) {
try {
progress?.(`Installing ${source.name} skills...`);
await runSkillsAdd(source.repo, agents, true);
await installSource(source.repo, agents);
count += 1;
} catch {
skipped.push(source.name);
@@ -294,7 +365,7 @@ async function runInstall({ args }: { args: Record<string, unknown> }): Promise<
clack.intro(c.bold("hyperframes skills"));
const targets = resolveTargets(args);
const agents = targets.map((t) => t.skillsAgent);
const agents = splitAgents(targets);
// Try npx skills add
if (hasNpx()) {
@@ -305,7 +376,7 @@ async function runInstall({ args }: { args: Record<string, unknown> }): Promise<
const spinner = clack.spinner();
spinner.start(`Installing ${source.name} skills...`);
try {
await runSkillsAdd(source.repo, agents, true);
await installSource(source.repo, agents);
installed.push(source.name);
spinner.stop(c.success(`${source.name} skills installed`));
} catch {
@@ -364,7 +435,25 @@ async function runInstall({ args }: { args: Record<string, unknown> }): Promise<
export default defineCommand({
meta: {
name: "skills",
description: "Install HyperFrames and GSAP skills for AI coding tools",
description: `Install HyperFrames and GSAP skills for AI coding tools
Global targets (enabled by default):
--claude Claude Code (~/.claude/skills/)
--gemini Gemini CLI (~/.gemini/skills/)
--codex Codex CLI (~/.codex/skills/)
Project-level targets (opt-in):
--cursor Cursor (.cursor/skills/)
--windsurf Windsurf (.windsurf/skills/)
--cline Cline (.cline/skills/)
--roo Roo Code (.roo/skills/)
--trae Trae (.trae/skills/)
Examples:
hyperframes skills # install to Claude, Gemini, Codex
hyperframes skills --windsurf # install to Windsurf (project-level)
hyperframes skills --cursor --cline # install to Cursor + Cline
hyperframes skills --claude --windsurf # mix global + project targets`,
},
args: {
claude: { type: "boolean", description: "Install to Claude Code (~/.claude/skills/)" },
@@ -374,6 +463,22 @@ export default defineCommand({
type: "boolean",
description: "Install to Cursor (.cursor/skills/ in current project)",
},
windsurf: {
type: "boolean",
description: "Install to Windsurf (.windsurf/skills/ in current project)",
},
cline: {
type: "boolean",
description: "Install to Cline (.cline/skills/ in current project)",
},
roo: {
type: "boolean",
description: "Install to Roo Code (.roo/skills/ in current project)",
},
trae: {
type: "boolean",
description: "Install to Trae (.trae/skills/ in current project)",
},
"human-friendly": { type: "boolean", description: "Enable interactive terminal UI" },
},
run: runInstall,
+1 -1
View File
@@ -82,7 +82,7 @@ export const mediaRules: Array<(ctx: LintContext) => HyperframeLintFinding[]> =
const timedTagPositions: Array<{ name: string; start: number; id?: string }> = [];
for (const tag of tags) {
if (tag.name === "video" || tag.name === "audio") continue;
if (readAttr(tag.raw, "data-start")) {
if (readAttr(tag.raw, "data-start") && !readAttr(tag.raw, "data-composition-id")) {
timedTagPositions.push({
name: tag.name,
start: tag.index,