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

* 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>

* refactor(cli): use shared colors, lazy-load help, fix description drift

- Replace hand-rolled ANSI helpers with existing ui/colors.ts (fixes
  non-conformant NO_COLOR handling)
- Add cyan and gray to shared color module
- Lazy-load help.ts via dynamic import to avoid allocating help data
  on non-help invocations
- Fix description drift: benchmark and transcribe descriptions now
  match their command meta.description
- Unify tuple order: ROOT_EXAMPLES now uses [comment, command] to
  match COMMAND_EXAMPLES
- Remove redundant comment restating type annotation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): resolve typecheck error in showUsage generic signature

The lazy-load wrapper needs to cast CommandDef<T> to CommandDef when
forwarding to the help module, since TypeScript's generic variance
makes the direct assignment incompatible.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(cli): remove kubectl references from help comments

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
James Russo
2026-04-01 12:55:26 -07:00
committed by GitHub
co-authored by Claude Opus 4.6
parent eb94e6cf77
commit 2d30654632
6 changed files with 232 additions and 22 deletions
+11 -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 type { ArgsDef, CommandDef } from "citty";
const isHelp = process.argv.includes("--help") || process.argv.includes("-h");
@@ -91,4 +92,13 @@ process.on("exit", () => {
_flushSync?.();
});
runMain(main);
// Lazy-load help renderer — avoids allocating help data on non-help invocations
async function showUsage<T extends ArgsDef>(
cmd: CommandDef<T>,
parent?: CommandDef<T>,
): Promise<void> {
const { showUsage: impl } = await import("./help.js");
return impl(cmd as CommandDef, parent as CommandDef | undefined);
}
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: {
+216
View File
@@ -0,0 +1,216 @@
/**
* Custom help renderer for the hyperframes CLI.
*
* Root-level: grouped command categories + examples.
* Subcommands: citty's standard USAGE/ARGUMENTS/OPTIONS + appended examples.
*/
import { renderUsage } from "citty";
import type { CommandDef } from "citty";
import { c } from "./ui/colors.js";
import { VERSION } from "./version.js";
// ── 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 preset fps/quality/worker configs 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, or import an existing transcript",
],
],
},
{
title: "Settings",
commands: [["telemetry", "Manage anonymous usage telemetry"]],
},
];
// ── Root-level examples ────────────────────────────────────────────────────
type Example = [comment: string, command: string];
const ROOT_EXAMPLES: Example[] = [
["Create a new project", "hyperframes init my-video"],
["Start the live preview studio", "hyperframes preview"],
["Render to MP4", "hyperframes render -o out.mp4"],
["Transparent WebM overlay", "hyperframes render --format webm -o out.webm"],
["Validate your composition", "hyperframes lint"],
["Check system dependencies", "hyperframes doctor"],
];
// ── Per-command examples (comment + command style: comment + command) ────────────────
const COMMAND_EXAMPLES: Record<string, Example[]> = {
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(
`${c.bold("hyperframes")} ${c.dim(`v${VERSION}`)} — Create and render HTML video compositions`,
);
lines.push("");
lines.push(`${c.bold("Usage:")} hyperframes ${c.cyan("<command>")} [options]`);
lines.push("");
for (const group of GROUPS) {
lines.push(c.bold(`${group.title}:`));
for (const [name, desc] of group.commands) {
lines.push(` ${c.cyan(name.padEnd(NAME_COL))}${desc}`);
}
lines.push("");
}
lines.push(c.bold("Examples:"));
for (const [comment, command] of ROOT_EXAMPLES) {
lines.push(` ${c.dim("$")} ${command.padEnd(CMD_COL)} ${c.dim(comment)}`);
}
lines.push("");
lines.push(`Run ${c.cyan("hyperframes <command> --help")} for more information about a command.`);
return lines.join("\n");
}
// ── Format examples section (comment + command style) ────────────────────────────────
function formatExamples(examples: Example[]): string {
const lines: string[] = [];
lines.push(c.bold("Examples:"));
for (const [comment, command] of examples) {
lines.push(` ${c.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) {
console.log(renderRootHelp() + "\n");
return;
}
const meta = await (typeof cmd.meta === "function" ? cmd.meta() : cmd.meta);
const usage = await renderUsage(cmd, parent);
console.log(usage + "\n");
const name = meta?.name;
if (name && COMMAND_EXAMPLES[name]) {
console.log(formatExamples(COMMAND_EXAMPLES[name]) + "\n");
}
}
+2
View File
@@ -17,6 +17,8 @@ export const c = {
dim: wrap(pc.dim),
bold: wrap(pc.bold),
accent: wrap(teal),
cyan: wrap(pc.cyan),
gray: wrap(pc.gray),
progress: wrap(pc.magenta),
reset: isColorSupported ? pc.reset : (s: string) => s,
};