mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-12 07:09:59 +00:00
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:
co-authored by
Claude Opus 4.6
parent
eb94e6cf77
commit
2d30654632
+11
-1
@@ -15,6 +15,7 @@ if (process.argv.includes("--version") || process.argv.includes("-V")) {
|
|||||||
// For --help we skip telemetry entirely.
|
// For --help we skip telemetry entirely.
|
||||||
|
|
||||||
import { defineCommand, runMain } from "citty";
|
import { defineCommand, runMain } from "citty";
|
||||||
|
import type { ArgsDef, CommandDef } from "citty";
|
||||||
|
|
||||||
const isHelp = process.argv.includes("--help") || process.argv.includes("-h");
|
const isHelp = process.argv.includes("--help") || process.argv.includes("-h");
|
||||||
|
|
||||||
@@ -91,4 +92,13 @@ process.on("exit", () => {
|
|||||||
_flushSync?.();
|
_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 });
|
||||||
|
|||||||
@@ -428,13 +428,7 @@ async function scaffoldProject(
|
|||||||
export default defineCommand({
|
export default defineCommand({
|
||||||
meta: {
|
meta: {
|
||||||
name: "init",
|
name: "init",
|
||||||
description: `Scaffold a new composition project
|
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)`,
|
|
||||||
},
|
},
|
||||||
args: {
|
args: {
|
||||||
name: { type: "positional", description: "Project name", required: false },
|
name: { type: "positional", description: "Project name", required: false },
|
||||||
|
|||||||
@@ -364,13 +364,7 @@ async function runInstall({ args }: { args: Record<string, unknown> }): Promise<
|
|||||||
export default defineCommand({
|
export default defineCommand({
|
||||||
meta: {
|
meta: {
|
||||||
name: "skills",
|
name: "skills",
|
||||||
description: `Install HyperFrames and GSAP skills for AI coding tools
|
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`,
|
|
||||||
},
|
},
|
||||||
args: {
|
args: {
|
||||||
claude: { type: "boolean", description: "Install to Claude Code (~/.claude/skills/)" },
|
claude: { type: "boolean", description: "Install to Claude Code (~/.claude/skills/)" },
|
||||||
|
|||||||
@@ -27,13 +27,7 @@ function defaultWorkerCount(): number {
|
|||||||
export default defineCommand({
|
export default defineCommand({
|
||||||
meta: {
|
meta: {
|
||||||
name: "render",
|
name: "render",
|
||||||
description: `Render a composition to MP4 or WebM
|
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`,
|
|
||||||
},
|
},
|
||||||
args: {
|
args: {
|
||||||
dir: {
|
dir: {
|
||||||
|
|||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,8 @@ export const c = {
|
|||||||
dim: wrap(pc.dim),
|
dim: wrap(pc.dim),
|
||||||
bold: wrap(pc.bold),
|
bold: wrap(pc.bold),
|
||||||
accent: wrap(teal),
|
accent: wrap(teal),
|
||||||
|
cyan: wrap(pc.cyan),
|
||||||
|
gray: wrap(pc.gray),
|
||||||
progress: wrap(pc.magenta),
|
progress: wrap(pc.magenta),
|
||||||
reset: isColorSupported ? pc.reset : (s: string) => s,
|
reset: isColorSupported ? pc.reset : (s: string) => s,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user