mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-05 00:56:23 +00:00
Extends the scope+retry work from the figma bug-bash (valid report:
9-bugs-with-repros; the skill-not-used report was discarded).
- 403-body parse (bug 4): figma returns 403 {"err":"Invalid token"} for bad
PATs (NOT 401), and 403 {"err":"Invalid scope(s)… requires X"} for missing
scopes. get() now reads the body: "Invalid token" reclassifies to BAD_TOKEN
with re-mint advice; a scope body surfaces figma's own diagnosis verbatim;
else falls back to the endpoint's scope hint. Reads both err and message
(variables endpoint uses message). One fix, honest messages for bugs 1/4/9.
- Batch asset fetch (requested): figma asset accepts multiple refs
(space-separated or comma-joined) of one file and renders them in a SINGLE
/v1/images call via new client.renderNodes — figma's documented per-minute
rate-limit workaround. runAssetImport delegates to runAssetImportMany;
cache-checks per node, batches only the misses, one index.md regen.
- NO_TOKEN box (bug 8): errorBox indented only the first hint line, mangling
the numbered setup list. Indent every line; single-line hints unchanged.
Verified live: 3 refs -> 3 imports -> 1 request; bad token -> BAD_TOKEN not
scope advice. Client suite 22, cli figma 33.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
62 lines
2.3 KiB
TypeScript
62 lines
2.3 KiB
TypeScript
import { c } from "./colors.js";
|
|
|
|
export function formatBytes(bytes: number): string {
|
|
if (bytes < 1024) return `${bytes} B`;
|
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
}
|
|
|
|
export function formatDuration(ms: number): string {
|
|
const seconds = ms / 1000;
|
|
if (seconds < 60) return `${seconds.toFixed(1)}s`;
|
|
const minutes = Math.floor(seconds / 60);
|
|
const remaining = seconds - minutes * 60;
|
|
return `${minutes}m ${remaining.toFixed(1)}s`;
|
|
}
|
|
|
|
/**
|
|
* Build the detail portion of the render-complete summary (everything after the
|
|
* file size). The output video length is shown as the primary figure, with the
|
|
* wall-clock render time explicitly labeled "rendered in" so the two are never
|
|
* confused (users were comparing the render time to ffprobe's media duration).
|
|
* Directory (png-sequence) output has no single muxed video, so it shows a frame
|
|
* count instead, or just the render time when neither is known.
|
|
*/
|
|
export function formatRenderSummaryDetail(input: {
|
|
elapsedMs: number;
|
|
outputDurationSeconds?: number;
|
|
isDirectory: boolean;
|
|
frameCount?: number;
|
|
}): string {
|
|
const middle = input.isDirectory
|
|
? input.frameCount != null
|
|
? `${input.frameCount} frames`
|
|
: undefined
|
|
: input.outputDurationSeconds != null && input.outputDurationSeconds > 0
|
|
? `${formatDuration(input.outputDurationSeconds * 1000)} video`
|
|
: undefined;
|
|
const renderTime = `rendered in ${formatDuration(input.elapsedMs)}`;
|
|
return [middle, renderTime].filter(Boolean).join(" · ");
|
|
}
|
|
|
|
export function label(name: string, value: string): string {
|
|
const pad = 14 - name.length;
|
|
return ` ${c.dim(name)}${" ".repeat(Math.max(1, pad))}${c.bold(value)}`;
|
|
}
|
|
|
|
export function errorBox(title: string, hint?: string, suggestion?: string): void {
|
|
console.error(`\n${c.error("\u2717")} ${c.bold(title)}`);
|
|
if (hint) {
|
|
// Indent EVERY hint line, not just the first \u2014 a multi-line hint (e.g. the
|
|
// NO_TOKEN numbered setup list) otherwise had line 1 indented and the rest
|
|
// flush-left, mangling the list. Single-line hints are unchanged.
|
|
const indented = hint
|
|
.split("\n")
|
|
.map((line) => ` ${line}`)
|
|
.join("\n");
|
|
console.error(`\n${c.dim(indented)}`);
|
|
}
|
|
if (suggestion) console.error(` ${c.accent(suggestion)}`);
|
|
console.error();
|
|
}
|