fix(core,cli): parse figma 403 body, batch asset fetch, fix NO_TOKEN box

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>
This commit is contained in:
Vance Ingalls
2026-07-09 15:14:57 -07:00
co-authored by Claude Fable 5
parent 1bb7688347
commit 4fc699fee6
8 changed files with 400 additions and 84 deletions
+10 -1
View File
@@ -46,7 +46,16 @@ export function label(name: string, value: string): string {
export function errorBox(title: string, hint?: string, suggestion?: string): void {
console.error(`\n${c.error("\u2717")} ${c.bold(title)}`);
if (hint) console.error(`\n ${c.dim(hint)}`);
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();
}