feat(cli): improve --help with grouped commands and per-command examples

Replaces citty's flat COMMANDS list with kubectl-style grouped categories
and adds examples to every subcommand. Root help now groups 14 commands
into 5 categories (Getting Started, Project, Tooling, AI & Integrations,
Settings). Per-command --help now appends a formatted Examples section
with practical usage patterns.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
James
2026-04-01 19:41:04 +00:00
co-authored by Claude Opus 4.6
parent eb94e6cf77
commit 88c8d32bd2
5 changed files with 227 additions and 22 deletions
+2 -1
View File
@@ -15,6 +15,7 @@ if (process.argv.includes("--version") || process.argv.includes("-V")) {
// For --help we skip telemetry entirely.
import { defineCommand, runMain } from "citty";
import { showUsage } from "./help.js";
const isHelp = process.argv.includes("--help") || process.argv.includes("-h");
@@ -91,4 +92,4 @@ process.on("exit", () => {
_flushSync?.();
});
runMain(main);
runMain(main, { showUsage });
+1 -7
View File
@@ -428,13 +428,7 @@ async function scaffoldProject(
export default defineCommand({
meta: {
name: "init",
description: `Scaffold a new composition project
Examples:
hyperframes init my-video # interactive wizard
hyperframes init my-video --template warm-grain # pick a template
hyperframes init my-video --video video.mp4 # with video file
hyperframes init my-video --non-interactive # skip prompts (CI/agents)`,
description: "Scaffold a new composition project",
},
args: {
name: { type: "positional", description: "Project name", required: false },
+1 -7
View File
@@ -364,13 +364,7 @@ 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
Examples:
hyperframes skills # install to Claude, Gemini, Codex
hyperframes skills --claude # install to Claude Code only
hyperframes skills --cursor # install to Cursor (project-level)
hyperframes skills --claude --gemini # install to specific tools`,
description: "Install HyperFrames and GSAP skills for AI coding tools",
},
args: {
claude: { type: "boolean", description: "Install to Claude Code (~/.claude/skills/)" },
+1 -7
View File
@@ -27,13 +27,7 @@ function defaultWorkerCount(): number {
export default defineCommand({
meta: {
name: "render",
description: `Render a composition to MP4 or WebM
Examples:
hyperframes render --output output.mp4
hyperframes render --format webm --output overlay.webm # transparent WebM
hyperframes render --fps 60 --quality high --output hd.mp4
hyperframes render --docker --output deterministic.mp4`,
description: "Render a composition to MP4 or WebM",
},
args: {
dir: {
+222
View File
@@ -0,0 +1,222 @@
/**
* Custom help renderer for the hyperframes CLI.
*
* Root-level: kubectl-style grouped categories + examples.
* Subcommands: citty's standard USAGE/ARGUMENTS/OPTIONS + appended examples.
*/
import { renderUsage } from "citty";
import type { CommandDef } from "citty";
import { VERSION } from "./version.js";
// ── ANSI helpers (respect NO_COLOR / TERM=dumb / non-TTY) ──────────────────
const colorEnabled =
process.env.NO_COLOR !== "1" && process.env.TERM !== "dumb" && (process.stdout.isTTY ?? false);
const esc = (open: number, close: number) => (s: string) =>
colorEnabled ? `\x1b[${open}m${s}\x1b[${close}m` : s;
const bold = esc(1, 22);
const cyan = esc(36, 39);
const dim = esc(2, 22);
const gray = esc(90, 39);
// ── Root-level command groups ──────────────────────────────────────────────
interface Group {
title: string;
commands: [name: string, description: string][];
}
const GROUPS: Group[] = [
{
title: "Getting Started",
commands: [
["init", "Scaffold a new composition project"],
["preview", "Start the studio for previewing compositions"],
["render", "Render a composition to MP4 or WebM"],
],
},
{
title: "Project",
commands: [
["lint", "Validate a composition for common mistakes"],
["info", "Print project metadata"],
["compositions", "List all compositions in a project"],
["docs", "View inline documentation in the terminal"],
],
},
{
title: "Tooling",
commands: [
["benchmark", "Render with presets and compare speed and file size"],
["browser", "Manage the Chrome browser used for rendering"],
["doctor", "Check system dependencies and environment"],
["upgrade", "Check for updates and show upgrade instructions"],
],
},
{
title: "AI & Integrations",
commands: [
["skills", "Install HyperFrames and GSAP skills for AI coding tools"],
["transcribe", "Transcribe audio/video to word-level timestamps"],
],
},
{
title: "Settings",
commands: [["telemetry", "Manage anonymous usage telemetry"]],
},
];
// ── Root-level examples ────────────────────────────────────────────────────
const ROOT_EXAMPLES: [command: string, comment: string][] = [
["hyperframes init my-video", "Create a new project"],
["hyperframes preview", "Start the live preview studio"],
["hyperframes render -o out.mp4", "Render to MP4"],
["hyperframes render --format webm -o out.webm", "Transparent WebM overlay"],
["hyperframes lint", "Validate your composition"],
["hyperframes doctor", "Check system dependencies"],
];
// ── Per-command examples (kubectl style: comment + command) ────────────────
// Each entry is [comment, command].
const COMMAND_EXAMPLES: Record<string, [comment: string, command: string][]> = {
init: [
["Create a project with the interactive wizard", "hyperframes init my-video"],
["Pick a starter template", "hyperframes init my-video --template warm-grain"],
["Start from an existing video file", "hyperframes init my-video --video clip.mp4"],
["Start from an audio file", "hyperframes init my-video --audio track.mp3"],
["Non-interactive mode (for CI or AI agents)", "hyperframes init my-video --non-interactive"],
],
preview: [
["Preview the current project", "hyperframes preview"],
["Preview a specific project directory", "hyperframes preview ./my-video"],
["Use a custom port", "hyperframes preview --port 8080"],
],
render: [
["Render to MP4", "hyperframes render --output output.mp4"],
["Render transparent WebM overlay", "hyperframes render --format webm --output overlay.webm"],
["High quality at 60fps", "hyperframes render --fps 60 --quality high --output hd.mp4"],
["Deterministic render via Docker", "hyperframes render --docker --output deterministic.mp4"],
["Parallel rendering with 4 workers", "hyperframes render --workers 4 --output fast.mp4"],
],
lint: [
["Lint the current project", "hyperframes lint"],
["Lint a specific directory", "hyperframes lint ./my-video"],
["Output findings as JSON", "hyperframes lint --json"],
["Include info-level findings", "hyperframes lint --verbose"],
],
info: [
["Show project metadata", "hyperframes info"],
["Output as JSON", "hyperframes info --json"],
],
compositions: [
["List compositions in the current project", "hyperframes compositions"],
["Output as JSON", "hyperframes compositions --json"],
],
benchmark: [
["Run benchmarks with default settings (3 runs)", "hyperframes benchmark"],
["Run 5 iterations per config", "hyperframes benchmark --runs 5"],
["Output results as JSON", "hyperframes benchmark --json"],
],
browser: [
["Find or download Chrome for rendering", "hyperframes browser ensure"],
["Print the Chrome executable path", "hyperframes browser path"],
["Remove cached Chrome download", "hyperframes browser clear"],
],
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"],
],
transcribe: [
["Transcribe an audio file", "hyperframes transcribe audio.mp3"],
["Transcribe a video file", "hyperframes transcribe video.mp4"],
[
"Use a larger model for better accuracy",
"hyperframes transcribe audio.mp3 --model medium.en",
],
["Set language to filter non-target speech", "hyperframes transcribe audio.mp3 --language en"],
["Import an existing SRT file", "hyperframes transcribe subtitles.srt"],
["Import an OpenAI Whisper JSON response", "hyperframes transcribe response.json"],
],
docs: [
["List all available topics", "hyperframes docs"],
["Read about data attributes", "hyperframes docs data-attributes"],
["Read about rendering", "hyperframes docs rendering"],
["Read about GSAP integration", "hyperframes docs gsap"],
],
doctor: [["Check system dependencies", "hyperframes doctor"]],
upgrade: [
["Check for updates interactively", "hyperframes upgrade"],
["Check for updates without prompting", "hyperframes upgrade --check"],
["Show upgrade commands directly", "hyperframes upgrade --yes"],
],
telemetry: [
["Check current telemetry status", "hyperframes telemetry status"],
["Disable telemetry", "hyperframes telemetry disable"],
["Enable telemetry", "hyperframes telemetry enable"],
],
};
// ── Render root help ───────────────────────────────────────────────────────
function renderRootHelp(): string {
const NAME_COL = 16;
const CMD_COL = 46;
const lines: string[] = [];
lines.push(
`${bold("hyperframes")} ${dim(`v${VERSION}`)} — Create and render HTML video compositions`,
);
lines.push("");
lines.push(`${bold("Usage:")} hyperframes ${cyan("<command>")} [options]`);
lines.push("");
for (const group of GROUPS) {
lines.push(bold(`${group.title}:`));
for (const [name, desc] of group.commands) {
lines.push(` ${cyan(name.padEnd(NAME_COL))}${desc}`);
}
lines.push("");
}
lines.push(bold("Examples:"));
for (const [command, comment] of ROOT_EXAMPLES) {
lines.push(` ${dim("$")} ${command.padEnd(CMD_COL)} ${dim(comment)}`);
}
lines.push("");
lines.push(`Run ${cyan("hyperframes <command> --help")} for more information about a command.`);
return lines.join("\n");
}
// ── Format examples section (kubectl style) ────────────────────────────────
function formatExamples(examples: [string, string][]): string {
const lines: string[] = [];
lines.push(bold("Examples:"));
for (const [comment, command] of examples) {
lines.push(` ${gray(`# ${comment}`)}`);
lines.push(` ${command}`);
lines.push("");
}
return lines.join("\n");
}
// ── Main showUsage override ────────────────────────────────────────────────
export async function showUsage(cmd: CommandDef, parent?: CommandDef): Promise<void> {
if (!parent) {
// Root help → custom grouped output
console.log(renderRootHelp() + "\n");
return;
}
// Subcommand help → citty's structured output + our examples
const usage = await renderUsage(cmd, parent);
console.log(usage + "\n");
const meta = await (typeof cmd.meta === "function" ? cmd.meta() : cmd.meta);
const name = meta?.name;
if (name && COMMAND_EXAMPLES[name]) {
console.log(formatExamples(COMMAND_EXAMPLES[name]) + "\n");
}
}