figma-relai 0.7.2 → 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,7 +16,7 @@ function createServer() {
16
16
  return new McpServer(
17
17
  {
18
18
  name: "Relai",
19
- version: "0.7.2"
19
+ version: "0.7.4"
20
20
  },
21
21
  {
22
22
  instructions: `
@@ -889,11 +889,7 @@ async function resolveFileIdentity(sendCommand) {
889
889
  statuses.push(res.status);
890
890
  const meta = res.json.meta ?? {};
891
891
  if (res.ok && typeof meta.file_key === "string") {
892
- main = {
893
- fileKey: meta.file_key,
894
- fileName: meta.file_name ?? rootName,
895
- via: c.key
896
- };
892
+ main = { fileKey: meta.file_key, via: c.key };
897
893
  break;
898
894
  }
899
895
  }
@@ -905,16 +901,18 @@ async function resolveFileIdentity(sendCommand) {
905
901
  denied ? "Figma rejected the token on every probe (401/403) \u2014 it is expired or lacks the file content read scope. Re-run `npx -y figma-relai@latest login`." : "None of this file's published component keys resolved."
906
902
  );
907
903
  }
908
- const identity = { fileKey: main.fileKey, fileName: main.fileName };
909
- if (rootName && rootName !== main.fileName) {
910
- const res = await figmaApi(token, `/files/${main.fileKey}?branch_data=true&depth=1`);
911
- const branches = res.json.branches ?? [];
904
+ const file = await figmaApi(token, `/files/${main.fileKey}?branch_data=true&depth=1`);
905
+ const identity = {
906
+ fileKey: main.fileKey,
907
+ fileName: typeof file.json.name === "string" ? file.json.name : rootName
908
+ };
909
+ if (rootName && rootName !== identity.fileName) {
910
+ const branches = file.json.branches ?? [];
912
911
  const hit = branches.find((b) => b.name === rootName);
913
912
  if (hit?.key) {
914
913
  identity.branchKey = hit.key;
915
914
  identity.branchName = hit.name;
916
915
  }
917
- if (typeof res.json.name === "string") identity.fileName = res.json.name;
918
916
  }
919
917
  try {
920
918
  await sendCommand("set_file_identity", { identity, componentKey: main.via });
@@ -1887,7 +1885,7 @@ import { z as z12 } from "zod";
1887
1885
  function register11(server, sendCommand) {
1888
1886
  server.tool(
1889
1887
  "import_from_library",
1890
- "Import a component, style, or variable from an enabled team library by its key. Component keys come from the design system or get_local_components; imported components can then be instantiated with manage_components.",
1888
+ "Import a component, style, or variable from an enabled team library by its key. Keys come from get_design_system's libraryCatalog, which resolves each library this file draws from and lists everything it publishes \u2014 not just what the file already uses. If the library you want is not in that catalog, it is listed under librariesNotCatalogued with what to do about it. Imported components can then be instantiated with manage_components.",
1891
1889
  {
1892
1890
  kind: z12.enum(["component", "style", "variable"]),
1893
1891
  key: z12.string().describe("Library key")
@@ -2197,13 +2195,15 @@ function register15(server, sendCommand) {
2197
2195
  var design_system_exports = {};
2198
2196
  __export(design_system_exports, {
2199
2197
  discoverLibraryFiles: () => discoverLibraryFiles,
2198
+ librariesNotCatalogued: () => librariesNotCatalogued,
2199
+ probeFailureNote: () => probeFailureNote,
2200
2200
  register: () => register16
2201
2201
  });
2202
2202
  import { z as z17 } from "zod";
2203
2203
  function register16(server, sendCommand) {
2204
2204
  server.tool(
2205
2205
  "get_design_system",
2206
- "Inventory the design system available to this file \u2014 call this BEFORE building UI, then prefer instantiating existing components (manage_components action:instantiate takes local and library keys) and binding existing variables over drawing raw shapes. Reports: local components/styles/variable collections with usage counts, remote components/styles the file already uses, enabled libraries' variable collections, and \u2014 with a token stored (`npx figma-relai login`) \u2014 the FULL published catalog of every library this file consumes, resolved automatically. What the file already uses is never the whole library: a component the file has not placed yet is invisible until this catalog is read. Results are cached per session \u2014 pass refresh:true after big library changes.",
2206
+ "Inventory the design system available to this file \u2014 call this BEFORE building UI, then prefer instantiating existing components (manage_components action:instantiate takes local and library keys) and binding existing variables over drawing raw shapes. Reports: local components/styles/variable collections with usage counts, remote components/styles the file already uses, enabled libraries' variable collections, and \u2014 with a token stored (`npx figma-relai login`) \u2014 the FULL published catalog of every library this file consumes, resolved automatically. What the file already uses is never the whole library: a component the file has not placed yet is invisible until this catalog is read. A library the file has drawn from NOTHING at all cannot be resolved either \u2014 those are named under librariesNotCatalogued, with the one step that fixes it. Results are cached per session \u2014 pass refresh:true after big library changes.",
2207
2207
  {
2208
2208
  refresh: z17.boolean().optional().describe("Rescan instead of using the session cache"),
2209
2209
  libraryFileUrl: z17.string().optional().describe(
@@ -2219,6 +2219,10 @@ function register16(server, sendCommand) {
2219
2219
  12e4
2220
2220
  );
2221
2221
  data.libraryCatalog = await fetchLibraryCatalogs(libraryFileUrl, data);
2222
+ if (!libraryFileUrl) {
2223
+ const missed = librariesNotCatalogued(data);
2224
+ if (missed.length) data.librariesNotCatalogued = missedNote(missed);
2225
+ }
2222
2226
  const truncNotes = [];
2223
2227
  const components = data.components;
2224
2228
  for (const [group, entry] of Object.entries(components ?? {})) {
@@ -2310,9 +2314,10 @@ function apiFor(token) {
2310
2314
  return { ok: res.ok, status: res.status, json };
2311
2315
  };
2312
2316
  }
2313
- function authFailure(statuses) {
2314
- if (statuses.length === 0 || !statuses.every((s) => s === 401 || s === 403)) return null;
2315
- return "Figma rejected the token on every probe (401/403) \u2014 it is expired or lacks the file content read scope. Re-run `npx -y figma-relai@latest login`, then retry.";
2317
+ function probeFailureNote(statuses) {
2318
+ const denied = statuses.filter((s) => s === 401 || s === 403).length;
2319
+ if (denied === 0) return null;
2320
+ return denied === statuses.length ? "Figma rejected the token on every probe (401/403) \u2014 it is expired or lacks the file content read scope. Re-run `npx -y figma-relai@latest login`, then retry." : `Figma refused ${denied} of ${statuses.length} probes with 401/403 \u2014 the token works, but it cannot read the libraries this file draws from (they belong to a team or organisation it has no access to). Ask for access to that library file, or pass libraryFileUrl for one you can read.`;
2316
2321
  }
2317
2322
  var metaOf = (r) => r.json.meta ?? {};
2318
2323
  async function discoverLibraryFiles(api, scan) {
@@ -2336,7 +2341,7 @@ async function discoverLibraryFiles(api, scan) {
2336
2341
  }
2337
2342
  }
2338
2343
  if (found.size === 0) {
2339
- const denied = authFailure(statuses);
2344
+ const denied = probeFailureNote(statuses);
2340
2345
  if (denied) throw new Error(denied);
2341
2346
  }
2342
2347
  return [...found].map(([fileKey, via]) => ({ fileKey, via }));
@@ -2378,6 +2383,29 @@ async function catalogOne(api, fileKey) {
2378
2383
  }))
2379
2384
  };
2380
2385
  }
2386
+ function librariesNotCatalogued(data) {
2387
+ const catalog = data.libraryCatalog;
2388
+ if (!catalog?.libraries) return [];
2389
+ const norm = (s) => s.trim().toLowerCase();
2390
+ const seen = new Set(catalog.libraries.map((l) => norm(l.name ?? "")));
2391
+ const enabled = data.variables?.libraryCollections;
2392
+ const names = /* @__PURE__ */ new Set();
2393
+ for (const c of enabled ?? []) {
2394
+ const name = c.libraryName?.trim();
2395
+ if (name && !seen.has(norm(name))) names.add(name);
2396
+ }
2397
+ return [...names];
2398
+ }
2399
+ function missedNote(names) {
2400
+ return {
2401
+ libraries: names,
2402
+ why: "Enabled, but absent from the catalog above \u2014 usually because this file has not placed a single component or style from them, so there is no key to resolve their file from.",
2403
+ // The remedy is the designer's, not the agent's: one instance is enough,
2404
+ // and only while the scan runs — the keys it yields stay valid after.
2405
+ how: "Ask the designer to drag any one component from that library onto the canvas (any page), then call this tool again with refresh:true. The instance can be deleted once the catalog is read \u2014 the keys remain valid. Or pass libraryFileUrl with the library file's figma.com URL.",
2406
+ ignoreIf: "A library that publishes only variables always lands here; its contents are already in variables.libraryCollections."
2407
+ };
2408
+ }
2381
2409
  async function fetchLibraryCatalogs(libraryFileUrl, scan) {
2382
2410
  const token = loadToken();
2383
2411
  if (!token) {
@@ -2398,8 +2426,9 @@ async function fetchLibraryCatalogs(libraryFileUrl, scan) {
2398
2426
  return { note: err.message };
2399
2427
  }
2400
2428
  if (!targets.length) {
2429
+ const remote = (scan.components?.remoteUsed?.items?.length ?? 0) + (scan.styles?.remoteUsed?.items?.length ?? 0);
2401
2430
  return {
2402
- note: "No library file could be resolved from the keys this file holds \u2014 every candidate 404'd (unpublished, or from a library this token cannot read). Pass libraryFileUrl explicitly."
2431
+ note: remote ? "No library file could be resolved from the keys this file holds \u2014 every candidate 404'd (unpublished, or from a library this token cannot read). Pass libraryFileUrl explicitly." : "This file uses no remote components or styles, so there is no key to resolve a library from. Either it draws from none, or it publishes its own \u2014 check components.local. Pass libraryFileUrl to catalog a specific library anyway."
2403
2432
  };
2404
2433
  }
2405
2434
  }
@@ -4446,4 +4475,4 @@ export {
4446
4475
  listToolCatalog,
4447
4476
  registerPrompts
4448
4477
  };
4449
- //# sourceMappingURL=chunk-EU6JIYHS.js.map
4478
+ //# sourceMappingURL=chunk-BW7VTSKX.js.map