From 49e333e9de828ed8f6098167fb634b838f59ac5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Fri, 3 Apr 2026 00:11:56 +0200 Subject: [PATCH] feat(cli): add skills command that installs all without selection (#192) * feat(cli): add skills command that installs all without selection Wraps `npx skills add --all -g` so users don't need to manually select skills or targets. Just run `hyperframes skills` and everything gets installed to all supported AI tools. Co-Authored-By: Claude Opus 4.6 (1M context) * fix(cli): fix typecheck errors in skills command Use spawn instead of execFile to avoid stdio type mismatch. Co-Authored-By: Claude Opus 4.6 (1M context) --------- Co-authored-by: Claude Opus 4.6 (1M context) --- packages/cli/src/cli.ts | 1 + packages/cli/src/commands/skills.ts | 76 +++++++++++++++++++++++++++++ packages/cli/src/help.ts | 7 +-- 3 files changed, 78 insertions(+), 6 deletions(-) create mode 100644 packages/cli/src/commands/skills.ts diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 798ef2b7d..b91a8880c 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -37,6 +37,7 @@ const subCommands = { docs: () => import("./commands/docs.js").then((m) => m.default), doctor: () => import("./commands/doctor.js").then((m) => m.default), upgrade: () => import("./commands/upgrade.js").then((m) => m.default), + skills: () => import("./commands/skills.js").then((m) => m.default), telemetry: () => import("./commands/telemetry.js").then((m) => m.default), validate: () => import("./commands/validate.js").then((m) => m.default), }; diff --git a/packages/cli/src/commands/skills.ts b/packages/cli/src/commands/skills.ts new file mode 100644 index 000000000..384895fc9 --- /dev/null +++ b/packages/cli/src/commands/skills.ts @@ -0,0 +1,76 @@ +import { defineCommand } from "citty"; +import { execFileSync, spawn } from "node:child_process"; +import * as clack from "@clack/prompts"; +import { c } from "../ui/colors.js"; + +function hasNpx(): boolean { + try { + execFileSync("npx", ["--version"], { stdio: "ignore", timeout: 5000 }); + return true; + } catch { + return false; + } +} + +function runSkillsAdd(repo: string): Promise { + return new Promise((resolve, reject) => { + const child = spawn("npx", ["skills", "add", repo, "--all", "-g"], { + stdio: "ignore", + timeout: 120_000, + }); + child.on("close", (code) => { + if (code === 0) resolve(); + else reject(new Error(`npx skills add exited with code ${code}`)); + }); + child.on("error", reject); + }); +} + +const SOURCES = [ + { name: "HyperFrames", repo: "heygen-com/hyperframes" }, + { name: "GSAP", repo: "greensock/gsap-skills" }, +]; + +export default defineCommand({ + meta: { + name: "skills", + description: "Install HyperFrames and GSAP skills for AI coding tools", + }, + args: {}, + async run() { + clack.intro(c.bold("hyperframes skills")); + + if (!hasNpx()) { + clack.log.error(c.error("npx not found. Install Node.js and retry.")); + clack.outro(c.warn("No skills installed.")); + return; + } + + const installed: string[] = []; + const skipped: string[] = []; + + for (const source of SOURCES) { + const spinner = clack.spinner(); + spinner.start(`Installing ${source.name} skills...`); + try { + await runSkillsAdd(source.repo); + installed.push(source.name); + spinner.stop(c.success(`${source.name} skills installed`)); + } catch { + skipped.push(source.name); + spinner.stop(c.dim(`${source.name} skills skipped (unavailable)`)); + } + } + + if (skipped.length > 0) { + console.log(` ${c.dim("Skipped:")} ${skipped.join(", ")}`); + console.log(); + } + + if (installed.length > 0) { + clack.outro(c.success(`${installed.join(" + ")} skills installed.`)); + } else { + clack.outro(c.warn("No skills installed.")); + } + }, +}); diff --git a/packages/cli/src/help.ts b/packages/cli/src/help.ts index bd30f5c48..f13d85334 100644 --- a/packages/cli/src/help.ts +++ b/packages/cli/src/help.ts @@ -87,12 +87,7 @@ async function loadExamples(name: string): Promise { // Commands without their own file (e.g. listed in help but not yet a real command) const STATIC_EXAMPLES: Record = { - skills: [ - ["Install skills to all supported AI tools", "hyperframes skills"], - ["Install to Claude Code only", "hyperframes skills --claude"], - ["Install to Cursor (project-level)", "hyperframes skills --cursor"], - ["Install to specific tools", "hyperframes skills --claude --gemini"], - ], + skills: [["Install all skills to all supported AI tools", "hyperframes skills"]], }; // ── Render root help ───────────────────────────────────────────────────────