mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-05 10:14:30 +00:00
Adds `hyperframes catalog` for browsing the registry: - Default: non-interactive table output (agent-friendly) - --type block/component and --tag filters - --json for machine-readable output - --human-friendly for interactive picker that installs on select Registered in cli.ts, help.ts, documented in docs/packages/cli.mdx.
151 lines
4.8 KiB
TypeScript
151 lines
4.8 KiB
TypeScript
import { defineCommand } from "citty";
|
|
import type { Example } from "./_examples.js";
|
|
|
|
export const examples: Example[] = [
|
|
["List all blocks and components", "hyperframes catalog"],
|
|
["List blocks only", "hyperframes catalog --type block"],
|
|
["Filter by tag", "hyperframes catalog --type block --tag social"],
|
|
["Machine-readable JSON", "hyperframes catalog --json"],
|
|
["Interactive picker (install on select)", "hyperframes catalog --human-friendly"],
|
|
];
|
|
|
|
import * as clack from "@clack/prompts";
|
|
import { type ItemType } from "@hyperframes/core";
|
|
import { c } from "../ui/colors.js";
|
|
import { listRegistryItems, loadAllItems } from "../registry/resolver.js";
|
|
import { loadProjectConfig, DEFAULT_PROJECT_CONFIG } from "../utils/projectConfig.js";
|
|
import { resolve } from "node:path";
|
|
import { runAdd } from "./add.js";
|
|
|
|
export default defineCommand({
|
|
meta: {
|
|
name: "catalog",
|
|
description: "Browse and install blocks and components from the registry",
|
|
},
|
|
args: {
|
|
type: {
|
|
type: "string",
|
|
description: 'Filter by type: "block" or "component"',
|
|
},
|
|
tag: {
|
|
type: "string",
|
|
description: "Filter by tag (e.g. social, transition, text)",
|
|
},
|
|
json: {
|
|
type: "boolean",
|
|
description: "Print matching items as JSON to stdout",
|
|
},
|
|
"human-friendly": {
|
|
type: "boolean",
|
|
description: "Interactive picker — select an item to install",
|
|
},
|
|
},
|
|
async run({ args }) {
|
|
const json = args.json === true;
|
|
const interactive = args["human-friendly"] === true;
|
|
const dir = resolve(process.cwd());
|
|
const config = loadProjectConfig(dir) ?? DEFAULT_PROJECT_CONFIG;
|
|
|
|
let typeFilter: ItemType | undefined;
|
|
if (args.type === "block") typeFilter = "hyperframes:block";
|
|
else if (args.type === "component") typeFilter = "hyperframes:component";
|
|
else if (args.type) {
|
|
console.error(`Invalid --type: "${args.type}". Use "block" or "component".`);
|
|
process.exit(1);
|
|
}
|
|
|
|
const entries = await listRegistryItems(typeFilter ? { type: typeFilter } : undefined, {
|
|
baseUrl: config.registry,
|
|
});
|
|
const filtered = entries.filter((e) => e.type !== "hyperframes:example");
|
|
|
|
if (filtered.length === 0) {
|
|
if (json) console.log("[]");
|
|
else console.log("No items found in registry.");
|
|
return;
|
|
}
|
|
|
|
const items = await loadAllItems(filtered, { baseUrl: config.registry });
|
|
|
|
const tagFilter = args.tag?.toLowerCase();
|
|
const matching = tagFilter
|
|
? items.filter((item) => item.tags?.some((t) => t.toLowerCase() === tagFilter))
|
|
: items;
|
|
|
|
if (matching.length === 0) {
|
|
if (json) console.log("[]");
|
|
else console.log(`No items match tag "${args.tag}".`);
|
|
return;
|
|
}
|
|
|
|
if (json) {
|
|
const output = matching.map((item) => ({
|
|
name: item.name,
|
|
type: item.type.replace("hyperframes:", ""),
|
|
title: item.title,
|
|
description: item.description,
|
|
tags: item.tags ?? [],
|
|
...("dimensions" in item && item.dimensions ? { dimensions: item.dimensions } : {}),
|
|
...("duration" in item && item.duration ? { duration: item.duration } : {}),
|
|
}));
|
|
console.log(JSON.stringify(output, null, 2));
|
|
return;
|
|
}
|
|
|
|
if (interactive) {
|
|
const options = matching.map((item) => ({
|
|
value: item.name,
|
|
label: item.name,
|
|
hint: item.description,
|
|
}));
|
|
|
|
const selected = await clack.select({
|
|
message: `${matching.length} items available — pick one to install`,
|
|
options,
|
|
});
|
|
|
|
if (clack.isCancel(selected)) {
|
|
clack.cancel("Cancelled.");
|
|
process.exit(0);
|
|
}
|
|
|
|
const result = await runAdd({
|
|
name: selected as string,
|
|
projectDir: dir,
|
|
skipClipboard: false,
|
|
});
|
|
|
|
console.log("");
|
|
console.log(`${c.success("✓")} Installed ${c.accent(result.name)} (${result.type})`);
|
|
for (const file of result.written) {
|
|
const rel = file.replace(dir + "/", "");
|
|
console.log(` ${c.dim(rel)}`);
|
|
}
|
|
if (result.snippet) {
|
|
console.log("");
|
|
console.log(c.dim("Include snippet:"));
|
|
console.log(` ${result.snippet}`);
|
|
}
|
|
return;
|
|
}
|
|
|
|
const NAME_COL = 28;
|
|
const TYPE_COL = 12;
|
|
console.log(
|
|
`${c.bold("Name".padEnd(NAME_COL))}${c.bold("Type".padEnd(TYPE_COL))}${c.bold("Description")}`,
|
|
);
|
|
console.log("-".repeat(80));
|
|
|
|
for (const item of matching) {
|
|
const type = item.type.replace("hyperframes:", "");
|
|
const tags = item.tags?.length ? c.dim(` [${item.tags.join(", ")}]`) : "";
|
|
console.log(
|
|
`${c.cyan(item.name.padEnd(NAME_COL))}${type.padEnd(TYPE_COL)}${item.description}${tags}`,
|
|
);
|
|
}
|
|
|
|
console.log("");
|
|
console.log(c.dim(`${matching.length} items. Run "hyperframes add <name>" to install.`));
|
|
},
|
|
});
|