/** * figma_inspect — Dev Mode-style compact dump of one or more frames. * * Fetches /v1/files/:key/nodes?ids=... with a depth cap, then runs * compactNode to emit layout + visual essentials only. The workhorse tool, * named after Figma Dev Mode's "Inspect" panel. * * Typical payload: 1-30 KB per frame at depth=4, far below the raw REST * response which can be 10x-100x larger. */ import { Type } from "@sinclair/typebox"; import { defineTool } from "@mariozechner/pi-coding-agent"; import { getNodes, parseFileKey, parseNodeIdFromUrl, normalizeNodeId } from "../client.js"; import { compactNode } from "../transforms.js"; export const inspectTool = defineTool({ name: "figma_inspect", label: "Figma Inspect", description: "Inspect one or more Figma frames/nodes: compact layout, fills, text, component references. Much cheaper than reading the full file. Use figma_map first to find node ids.", promptSnippet: "figma_inspect(file, nodeIds, depth) — compact layout/visual dump for targeted frames.", promptGuidelines: [ "Prefer inspecting one frame over batching multiple root-level ids.", "Start with depth=3; only increase if the tree you need is deeper.", "If truncatedChildren appears, call again with a more specific nodeId or a higher depth.", ], parameters: Type.Object({ file: Type.String({ description: "File key or URL." }), nodeIds: Type.Array( Type.String({ description: "Node id (1:234 or 1-234, or a full Figma URL with ?node-id=)." }), { minItems: 1, maxItems: 10, description: "One or more node ids to fetch." }, ), depth: Type.Optional( Type.Integer({ minimum: 0, maximum: 12, description: "Descendant depth per node. Default 3. Use 0 for just the node itself.", }), ), includeHidden: Type.Optional( Type.Boolean({ description: "Include children with visible=false. Default false." }), ), }), async execute(_id, params, signal) { const fileKey = parseFileKey(params.file); const depth = params.depth ?? 3; const ids = params.nodeIds.map((raw) => parseNodeIdFromUrl(raw) ?? normalizeNodeId(raw)); const resp = await getNodes(fileKey, ids, depth, signal); const compacted: Record = {}; for (const [nodeId, entry] of Object.entries(resp.nodes)) { if (!entry) { compacted[nodeId] = { error: "not found" }; continue; } compacted[nodeId] = compactNode(entry.document, { maxDepth: depth }); } return { content: [ { type: "text" as const, text: JSON.stringify( { file: { name: resp.name, key: fileKey, version: resp.version }, depth, nodes: compacted, }, null, 2, ), }, ], details: { fileKey, version: resp.version, depth, requestedIds: ids }, }; }, });