fix(cli): restore hyperframes capture <url>; move video download to --video flag

PR #1447 added `capture video` as a citty subCommand. citty's runCommand
(node_modules/.bun/citty@0.2.2/.../dist/index.mjs:209-227) treats any non-flag
positional as a subcommand-name attempt and throws E_UNKNOWN_COMMAND when it
doesn't match — there's no fallback to the parent's positional args, so
`hyperframes capture https://vercel.com` died with "Unknown command https://vercel.com".

Per James's suggestion, surface video-download as `capture --video <project>`
(a mode flag) instead of a subcommand. Citty has no issue with a positional
URL coexisting with flags. `video.ts` now exports `runVideoMode()` instead of
a `defineCommand` default export.

- `hyperframes capture <url>` works again
- `hyperframes capture --video <project> --index N` downloads video
- `hyperframes capture --video <project> --list` lists manifest
- `hyperframes capture --video <project> --video-url <url>` downloads by URL
This commit is contained in:
ukimsanov
2026-06-15 16:03:27 -07:00
parent 69aa595f38
commit f8d9f51245
2 changed files with 144 additions and 144 deletions
+42 -9
View File
@@ -8,7 +8,11 @@ export const examples: Example[] = [
["JSON output for AI agents", "hyperframes capture https://example.com --json"],
[
"Pull a video from the captured manifest by index",
"hyperframes capture video ./linear-video --index 0",
"hyperframes capture --video ./linear-video --index 0",
],
[
"List videos referenced in the captured manifest",
"hyperframes capture --video ./linear-video --list",
],
];
@@ -17,14 +21,11 @@ export default defineCommand({
name: "capture",
description: "Capture a website as editable HyperFrames components",
},
subCommands: {
video: () => import("./capture/video.js").then((m) => m.default),
},
args: {
url: {
type: "positional",
description: "Website URL to capture",
required: true,
description: "Website URL to capture (omit when using --video)",
required: false,
},
output: {
type: "string",
@@ -49,12 +50,44 @@ export default defineCommand({
description: "Output JSON (for AI agents / programmatic use)",
default: false,
},
video: {
type: "string",
description:
"Switch to video-download mode: path to a captured project directory whose video-manifest.json should be read. Pair with --index, --video-url, or --list.",
},
index: {
type: "string",
description: "(--video mode) Manifest entry index to download (0-based)",
},
"video-url": {
type: "string",
description: "(--video mode) Exact video URL to download (must match a manifest entry)",
},
list: {
type: "boolean",
description: "(--video mode) List manifest entries and exit",
default: false,
},
},
async run({ args }) {
const url = args.url as string;
if (args.video) {
const { runVideoMode } = await import("./capture/video.js");
await runVideoMode({
project: args.video as string,
index: (args.index as string | undefined) ?? null,
url: (args["video-url"] as string | undefined) ?? null,
list: args.list as boolean,
});
return;
}
// citty fires parent's run AFTER routing to a subcommand; skip when args.url is a subcommand name.
if (url === "video") return;
const url = args.url as string | undefined;
if (!url) {
console.error(
"Missing URL. Pass a website URL, or use --video <project> for video download.",
);
process.exit(1);
}
try {
new URL(url);