refactor(cli): colocate --help examples in command files (#202)

Move per-command examples from the centralized `help.ts` record into
each command file as `export const examples: Example[]`. help.ts now
dynamically imports them at --help time.

This means adding a new command and its examples happens in one file
instead of two, reducing the chance of forgetting examples.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
James Russo
2026-04-02 14:11:38 -07:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 4bb01fd1b6
commit 5e8ff36675
17 changed files with 132 additions and 86 deletions
+2 -2
View File
@@ -87,8 +87,8 @@ Always run both on changed files before committing. The lefthook pre-commit hook
When adding a new CLI command:
1. Define the command in `packages/cli/src/commands/<name>.ts` using `defineCommand` from citty
2. Register it in `packages/cli/src/cli.ts` under `subCommands` (lazy-loaded)
3. **Add examples to `packages/cli/src/help.ts`** in the `COMMAND_EXAMPLES` record — every command must have `--help` examples
2. **Export `examples`** in the same file — `export const examples: Example[] = [...]` (import `Example` from `./_examples.js`). These are displayed by `--help`.
3. Register it in `packages/cli/src/cli.ts` under `subCommands` (lazy-loaded)
4. Validate by running `npx tsx packages/cli/src/cli.ts <name> --help` and verifying the examples section appears
## Key Concepts
+6
View File
@@ -0,0 +1,6 @@
/**
* Shared type for CLI command examples.
* Each command file exports `examples` using this type.
* help.ts dynamically imports them at --help time.
*/
export type Example = [comment: string, command: string];
+7
View File
@@ -1,5 +1,12 @@
import { defineCommand } from "citty";
import type { Example } from "./_examples.js";
import { existsSync, statSync } from "node:fs";
export const examples: Example[] = [
["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"],
];
import { resolve, join } from "node:path";
import { resolveProject } from "../utils/project.js";
import { loadProducer } from "../utils/producer.js";
+7
View File
@@ -1,6 +1,13 @@
import { defineCommand } from "citty";
import type { Example } from "./_examples.js";
import * as clack from "@clack/prompts";
import { c } from "../ui/colors.js";
export const examples: Example[] = [
["Find or download Chrome for rendering", "hyperframes browser ensure"],
["Print the Chrome executable path", "hyperframes browser path"],
["Remove cached Chrome download", "hyperframes browser clear"],
];
import { formatBytes } from "../ui/format.js";
import {
ensureBrowser,
@@ -1,5 +1,11 @@
import { defineCommand } from "citty";
import type { Example } from "./_examples.js";
import { readFileSync } from "node:fs";
export const examples: Example[] = [
["List compositions in the current project", "hyperframes compositions"],
["Output as JSON", "hyperframes compositions --json"],
];
import { c } from "../ui/colors.js";
import { ensureDOMParser } from "../utils/dom.js";
import { resolveProject } from "../utils/project.js";
+8
View File
@@ -1,5 +1,13 @@
import { defineCommand } from "citty";
import type { Example } from "./_examples.js";
import { readFileSync, existsSync } from "node:fs";
export const examples: Example[] = [
["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"],
];
import { resolve, dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { c } from "../ui/colors.js";
+3
View File
@@ -1,5 +1,8 @@
import { defineCommand } from "citty";
import type { Example } from "./_examples.js";
import { execSync } from "node:child_process";
export const examples: Example[] = [["Check system dependencies", "hyperframes doctor"]];
import { freemem, platform } from "node:os";
import { c } from "../ui/colors.js";
import { findBrowser } from "../browser/manager.js";
+6
View File
@@ -1,5 +1,11 @@
import { defineCommand } from "citty";
import type { Example } from "./_examples.js";
import { readFileSync, readdirSync, statSync } from "node:fs";
export const examples: Example[] = [
["Show project metadata", "hyperframes info"],
["Output as JSON", "hyperframes info --json"],
];
import { join } from "node:path";
import { parseHtml } from "@hyperframes/core";
import { c } from "../ui/colors.js";
+9
View File
@@ -1,4 +1,13 @@
import { defineCommand, runCommand } from "citty";
import type { Example } from "./_examples.js";
export const examples: Example[] = [
["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"],
];
import {
existsSync,
mkdirSync,
+8
View File
@@ -1,5 +1,13 @@
import { defineCommand } from "citty";
import type { Example } from "./_examples.js";
import { c } from "../ui/colors.js";
export const examples: Example[] = [
["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"],
];
import { formatLintFindings } from "../utils/lintFormat.js";
import { lintProject } from "../utils/lintProject.js";
import { resolveProject } from "../utils/project.js";
+7
View File
@@ -1,5 +1,12 @@
import { defineCommand } from "citty";
import type { Example } from "./_examples.js";
import { spawn } from "node:child_process";
export const examples: Example[] = [
["Preview the current project", "hyperframes preview"],
["Preview a specific project directory", "hyperframes preview ./my-video"],
["Use a custom port", "hyperframes preview --port 8080"],
];
import { existsSync, lstatSync, symlinkSync, unlinkSync, readlinkSync, mkdirSync } from "node:fs";
import { resolve, dirname, basename, join } from "node:path";
import { fileURLToPath } from "node:url";
+9
View File
@@ -1,5 +1,14 @@
import { defineCommand } from "citty";
import type { Example } from "./_examples.js";
import { existsSync, mkdirSync, statSync } from "node:fs";
export const examples: Example[] = [
["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"],
];
import { cpus, freemem } from "node:os";
import { resolve, dirname, join } from "node:path";
import { resolveProject } from "../utils/project.js";
+7
View File
@@ -1,5 +1,12 @@
import { defineCommand } from "citty";
import type { Example } from "./_examples.js";
import { c } from "../ui/colors.js";
export const examples: Example[] = [
["Check current telemetry status", "hyperframes telemetry status"],
["Disable telemetry", "hyperframes telemetry disable"],
["Enable telemetry", "hyperframes telemetry enable"],
];
import { readConfig, writeConfig, CONFIG_PATH } from "../telemetry/config.js";
function runEnable(): void {
+10
View File
@@ -1,5 +1,15 @@
import { defineCommand } from "citty";
import type { Example } from "./_examples.js";
import { existsSync, writeFileSync } from "node:fs";
export const examples: Example[] = [
["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"],
];
import { resolve, join, extname } from "node:path";
import * as clack from "@clack/prompts";
import { c } from "../ui/colors.js";
+10
View File
@@ -1,5 +1,15 @@
import { defineCommand } from "citty";
import type { Example } from "./_examples.js";
import { existsSync, readFileSync } from "node:fs";
export const examples: Example[] = [
["Generate speech from text", 'hyperframes tts "Welcome to HyperFrames"'],
["Choose a voice", 'hyperframes tts "Hello world" --voice am_adam'],
["Save to a specific file", 'hyperframes tts "Intro" --voice bf_emma --output narration.wav'],
["Adjust speech speed", 'hyperframes tts "Slow and clear" --speed 0.8'],
["Read text from a file", "hyperframes tts script.txt"],
["List available voices", "hyperframes tts --list"],
];
import { resolve, extname } from "node:path";
import * as clack from "@clack/prompts";
import { c } from "../ui/colors.js";
+7
View File
@@ -1,6 +1,13 @@
import { defineCommand } from "citty";
import type { Example } from "./_examples.js";
import * as clack from "@clack/prompts";
import { c } from "../ui/colors.js";
export const examples: Example[] = [
["Check for updates interactively", "hyperframes upgrade"],
["Check for updates without prompting", "hyperframes upgrade --check"],
["Show upgrade commands directly", "hyperframes upgrade --yes"],
];
import { VERSION } from "../version.js";
import { checkForUpdate, withMeta } from "../utils/updateCheck.js";
+20 -84
View File
@@ -62,7 +62,7 @@ const GROUPS: Group[] = [
];
// ── Root-level examples ────────────────────────────────────────────────────
type Example = [comment: string, command: string];
import type { Example } from "./commands/_examples.js";
const ROOT_EXAMPLES: Example[] = [
["Create a new project", "hyperframes init my-video"],
@@ -73,93 +73,26 @@ const ROOT_EXAMPLES: Example[] = [
["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"],
],
// ── Per-command examples loaded from command files ────────────────────────
// Each command file exports `examples: Example[]`. This function dynamically
// imports them so examples live next to the command they document.
async function loadExamples(name: string): Promise<Example[] | undefined> {
try {
const mod = await import(`./commands/${name}.js`);
return mod.examples;
} catch {
return undefined;
}
}
// Commands without their own file (e.g. listed in help but not yet a real command)
const STATIC_EXAMPLES: Record<string, Example[]> = {
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"],
],
tts: [
["Generate speech from text", 'hyperframes tts "Welcome to HyperFrames"'],
["Choose a voice", 'hyperframes tts "Hello world" --voice am_adam'],
["Save to a specific file", 'hyperframes tts "Intro" --voice bf_emma --output narration.wav'],
["Adjust speech speed", 'hyperframes tts "Slow and clear" --speed 0.8'],
["Read text from a file", "hyperframes tts script.txt"],
["List available voices", "hyperframes tts --list"],
],
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 ───────────────────────────────────────────────────────
@@ -218,7 +151,10 @@ export async function showUsage(cmd: CommandDef, parent?: CommandDef): Promise<v
console.log(usage + "\n");
const name = meta?.name;
if (name && COMMAND_EXAMPLES[name]) {
console.log(formatExamples(COMMAND_EXAMPLES[name]) + "\n");
if (name) {
const examples = STATIC_EXAMPLES[name] ?? (await loadExamples(name));
if (examples) {
console.log(formatExamples(examples) + "\n");
}
}
}