@ufira/vibma 0.3.2 → 1.0.0-rc1

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.
@@ -43,11 +43,20 @@ var flexJson = (inner) => import_zod.z.preprocess((v) => {
43
43
  // src/tools/endpoint.ts
44
44
  function pickFields(obj, fields) {
45
45
  if (fields.includes("*")) return obj;
46
- const keep = /* @__PURE__ */ new Set([...fields, "id", "name", "type"]);
46
+ const identity = ["id", "name", "type"];
47
+ const keep = /* @__PURE__ */ new Set([...fields, ...identity]);
47
48
  const out = {};
48
49
  for (const key of Object.keys(obj)) {
49
50
  if (keep.has(key)) out[key] = obj[key];
50
51
  }
52
+ const requested = fields.filter((f) => !identity.includes(f));
53
+ if (requested.length > 0) {
54
+ const found = requested.filter((f) => f in obj);
55
+ if (found.length === 0) {
56
+ const available = Object.keys(obj).filter((k) => !identity.includes(k));
57
+ out._warning = `Requested fields [${requested.join(", ")}] not found. Available: [${available.join(", ")}]`;
58
+ }
59
+ }
51
60
  return out;
52
61
  }
53
62
  function paginate(items, offset = 0, limit = 100) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/tools/endpoint.ts","../../src/utils/coercion.ts"],"sourcesContent":["/**\n * Shared endpoint infrastructure.\n *\n * Every resource endpoint follows the same contract:\n * create → items[{...}] → { results: [{id}, ...] }\n * get → { id, fields? } → resource object (field-filtered)\n * list → { filters?, fields?, offset, limit } → { totalCount, returned, offset, limit, items: [...] }\n * update → items[{...}] → { results: [\"ok\", ...] }\n * delete → { id } or items[{id}] → \"ok\" or { results: [\"ok\", ...] }\n *\n * MCP side: endpointSchema()\n * Figma side: createDispatcher() + paginate()\n */\n\nimport { z } from \"zod\";\nimport { flexJson } from \"../utils/coercion\";\nimport type { Capabilities } from \"./types\";\n\n// ─── Method Types ────────────────────────────────────────────────\n\nexport type EndpointMethod = \"create\" | \"get\" | \"list\" | \"update\" | \"delete\";\n\n// ─── Response Types ──────────────────────────────────────────────\n\n/** Batch response envelope (create, update, delete). Produced by batchHandler. */\nexport interface BatchResponse<T = { id: string }> {\n results: Array<T | \"ok\" | { error: string }>;\n warnings?: string[];\n deferred?: string;\n}\n\n/** Paginated list response envelope. */\nexport interface ListResponse<T = Record<string, any>> {\n totalCount: number;\n returned: number;\n offset: number;\n limit: number;\n items: T[];\n}\n\n// ─── Discriminated Param Types ───────────────────────────────────\n//\n// Each endpoint specializes these with its own create/update item\n// types and list filters. Example:\n//\n// type StyleParams = EndpointParams<StyleCreateItem, StyleUpdateItem, { type?: string }>;\n//\n\nexport type EndpointParams<\n TCreate = Record<string, any>,\n TUpdate = Record<string, any>,\n TListFilters = Record<string, never>,\n> =\n | { method: \"create\"; items: TCreate[] }\n | { method: \"get\"; id: string; fields?: string[] }\n | ({ method: \"list\"; fields?: string[]; offset?: number; limit?: number } & TListFilters)\n | { method: \"update\"; items: TUpdate[] }\n | { method: \"delete\"; id?: string; items?: Array<{ id: string }> };\n\n// ─── Helpers ────────────────────────────────────────────────────\n\n/**\n * Top-level field filter for get/list responses.\n * Always preserves identity fields (id, name, type).\n * Pass [\"*\"] to return all fields.\n */\nexport function pickFields(obj: Record<string, any>, fields: string[]): Record<string, any> {\n if (fields.includes(\"*\")) return obj;\n const keep = new Set([...fields, \"id\", \"name\", \"type\"]);\n const out: Record<string, any> = {};\n for (const key of Object.keys(obj)) {\n if (keep.has(key)) out[key] = obj[key];\n }\n return out;\n}\n\n/**\n * Paginate an array of items. Default limit: 100.\n * Call from list handlers after assembling the full result set.\n */\nexport function paginate<T>(items: T[], offset = 0, limit = 100): ListResponse<T> {\n const sliced = items.slice(offset, offset + limit);\n return { totalCount: items.length, returned: sliced.length, offset, limit, items: sliced };\n}\n\n// ─── Schema Builder ──────────────────────────────────────────────\n\n/** Maps each endpoint method to the minimum tier required to use it. */\nexport type MethodTier = \"read\" | \"create\" | \"edit\";\n\nconst DEFAULT_TIERS: Record<string, MethodTier> = {\n get: \"read\", list: \"read\", create: \"create\", update: \"edit\", delete: \"edit\",\n};\n\n/**\n * Build standard endpoint Zod schema fields.\n *\n * Always includes `method`. Auto-adds:\n * - `id` when get/delete are in the method list\n * - `fields` when get or list are in the method list\n * - `offset`/`limit` when list is in the method list\n * Merge endpoint-specific fields (items, list filters) via `extra`.\n *\n * When `caps` is provided, methods are filtered by tier — only methods\n * whose tier is enabled appear in the enum.\n */\nexport function endpointSchema(\n methods: string[],\n capsOrExtra?: Capabilities | Record<string, z.ZodTypeAny>,\n extraOrTiers?: Record<string, z.ZodTypeAny>,\n methodTiers?: Record<string, MethodTier>,\n): Record<string, z.ZodTypeAny> {\n // Overload resolution: (methods, extra?) or (methods, caps, extra?, tiers?)\n let caps: Capabilities | undefined;\n let extra: Record<string, z.ZodTypeAny> | undefined;\n\n if (capsOrExtra && (\"create\" in capsOrExtra) && (\"edit\" in capsOrExtra)\n && typeof (capsOrExtra as any).create === \"boolean\") {\n caps = capsOrExtra as Capabilities;\n extra = extraOrTiers;\n } else {\n extra = capsOrExtra as Record<string, z.ZodTypeAny> | undefined;\n // methodTiers and extraOrTiers are unused in the legacy call signature\n }\n\n // Filter methods by capabilities\n let filtered = methods;\n if (caps) {\n const tiers = { ...DEFAULT_TIERS, ...methodTiers };\n filtered = methods.filter(m => {\n const tier = tiers[m] ?? \"edit\"; // unknown methods default to edit\n if (tier === \"read\") return true;\n if (tier === \"create\") return caps!.create;\n if (tier === \"edit\") return caps!.edit;\n return false;\n });\n }\n\n const schema: Record<string, z.ZodTypeAny> = {\n method: z.enum(filtered as [string, ...string[]]),\n };\n if (filtered.includes(\"get\") || filtered.includes(\"delete\")) {\n schema.id = z.string().optional().describe(\"Resource ID (get, delete)\");\n }\n if (filtered.includes(\"get\") || filtered.includes(\"list\")) {\n schema.fields = flexJson(z.array(z.string())).optional()\n .describe('Property whitelist (get/list). Identity fields (id, name, type) always included. Omit for stubs on list, full detail on get. Pass [\"*\"] for all fields.');\n }\n if (filtered.includes(\"list\")) {\n schema.offset = z.coerce.number().optional().describe(\"Skip N items for pagination (default 0)\");\n schema.limit = z.coerce.number().optional().describe(\"Max items per page (default 100)\");\n }\n return { ...schema, ...extra };\n}\n\n// ─── Figma Dispatcher ────────────────────────────────────────────\n\ntype MethodHandlers = Record<string, (params: any) => Promise<any>>;\n\n/**\n * Create a Figma handler that dispatches on `params.method`.\n * Only methods with registered handlers are allowed.\n * Automatically applies `fields` filtering on get responses.\n */\nexport function createDispatcher(handlers: MethodHandlers) {\n const supported = Object.keys(handlers).join(\", \");\n return async (params: any): Promise<any> => {\n const method = params.method as EndpointMethod;\n const handler = handlers[method];\n if (!handler) throw new Error(`Method '${method}' not supported. Available: ${supported}`);\n let result = await handler(params);\n // Auto-apply fields filtering on get responses (full detail by default)\n if (method === \"get\" && params.fields?.length && result && typeof result === \"object\") {\n result = pickFields(result, params.fields);\n }\n return result;\n };\n}\n","import { z } from \"zod\";\n\n// AI agents (Claude, GPT, etc.) frequently pass numbers as strings\n// (\"10\" instead of 10), booleans as strings (\"true\" instead of true),\n// and objects/arrays as JSON strings. These helpers add resilient\n// coercion so tools don't fail on valid-but-mistyped input.\n\n/** Coerce \"true\"/\"false\"/\"1\"/\"0\" strings to boolean */\nexport const flexBool = <T extends z.ZodTypeAny>(inner: T) =>\n z.preprocess((v) => {\n if (v === \"true\" || v === \"1\") return true;\n if (v === \"false\" || v === \"0\") return false;\n return v;\n }, inner);\n\n/** Coerce JSON strings to parsed values (for objects/arrays that agents may stringify) */\nexport const flexJson = <T extends z.ZodTypeAny>(inner: T) =>\n z.preprocess((v) => {\n if (typeof v === \"string\") {\n try {\n return JSON.parse(v);\n } catch {\n return v;\n }\n }\n return v;\n }, inner);\n\n/** Coerce numeric strings only when they're valid numbers (safe for use inside unions) */\nexport const flexNum = <T extends z.ZodTypeAny>(inner: T) =>\n z.preprocess((v) => {\n if (typeof v === \"string\") {\n const n = Number(v);\n if (!isNaN(n) && v.trim() !== \"\") return n;\n }\n return v;\n }, inner);\n"],"mappings":"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","names":["import_zod"]}
1
+ {"version":3,"sources":["../../src/tools/endpoint.ts","../../src/utils/coercion.ts"],"sourcesContent":["/**\n * Shared endpoint infrastructure.\n *\n * Every resource endpoint follows the same contract:\n * create → items[{...}] → { results: [{id}, ...] }\n * get → { id, fields? } → resource object (field-filtered)\n * list → { filters?, fields?, offset, limit } → { totalCount, returned, offset, limit, items: [...] }\n * update → items[{...}] → { results: [\"ok\", ...] }\n * delete → { id } or items[{id}] → \"ok\" or { results: [\"ok\", ...] }\n *\n * MCP side: endpointSchema()\n * Figma side: createDispatcher() + paginate()\n */\n\nimport { z } from \"zod\";\nimport { flexJson } from \"../utils/coercion\";\nimport type { Capabilities } from \"./types\";\n\n// ─── Method Types ────────────────────────────────────────────────\n\nexport type EndpointMethod = \"create\" | \"get\" | \"list\" | \"update\" | \"delete\";\n\n// ─── Response Types ──────────────────────────────────────────────\n\n/** Batch response envelope (create, update, delete). Produced by batchHandler. */\nexport interface BatchResponse<T = { id: string }> {\n results: Array<T | \"ok\" | { error: string }>;\n warnings?: string[];\n deferred?: string;\n}\n\n/** Paginated list response envelope. */\nexport interface ListResponse<T = Record<string, any>> {\n totalCount: number;\n returned: number;\n offset: number;\n limit: number;\n items: T[];\n}\n\n// ─── Discriminated Param Types ───────────────────────────────────\n//\n// Each endpoint specializes these with its own create/update item\n// types and list filters. Example:\n//\n// type StyleParams = EndpointParams<StyleCreateItem, StyleUpdateItem, { type?: string }>;\n//\n\nexport type EndpointParams<\n TCreate = Record<string, any>,\n TUpdate = Record<string, any>,\n TListFilters = Record<string, never>,\n> =\n | { method: \"create\"; items: TCreate[] }\n | { method: \"get\"; id: string; fields?: string[] }\n | ({ method: \"list\"; fields?: string[]; offset?: number; limit?: number } & TListFilters)\n | { method: \"update\"; items: TUpdate[] }\n | { method: \"delete\"; id?: string; items?: Array<{ id: string }> };\n\n// ─── Helpers ────────────────────────────────────────────────────\n\n/**\n * Top-level field filter for get/list responses.\n * Always preserves identity fields (id, name, type).\n * Pass [\"*\"] to return all fields.\n */\nexport function pickFields(obj: Record<string, any>, fields: string[]): Record<string, any> {\n if (fields.includes(\"*\")) return obj;\n const identity = [\"id\", \"name\", \"type\"];\n const keep = new Set([...fields, ...identity]);\n const out: Record<string, any> = {};\n for (const key of Object.keys(obj)) {\n if (keep.has(key)) out[key] = obj[key];\n }\n // Warn when all non-identity requested fields were unknown\n const requested = fields.filter(f => !identity.includes(f));\n if (requested.length > 0) {\n const found = requested.filter(f => f in obj);\n if (found.length === 0) {\n const available = Object.keys(obj).filter(k => !identity.includes(k));\n out._warning = `Requested fields [${requested.join(\", \")}] not found. Available: [${available.join(\", \")}]`;\n }\n }\n return out;\n}\n\n/**\n * Paginate an array of items. Default limit: 100.\n * Call from list handlers after assembling the full result set.\n */\nexport function paginate<T>(items: T[], offset = 0, limit = 100): ListResponse<T> {\n const sliced = items.slice(offset, offset + limit);\n return { totalCount: items.length, returned: sliced.length, offset, limit, items: sliced };\n}\n\n// ─── Schema Builder ──────────────────────────────────────────────\n\n/** Maps each endpoint method to the minimum tier required to use it. */\nexport type MethodTier = \"read\" | \"create\" | \"edit\";\n\nconst DEFAULT_TIERS: Record<string, MethodTier> = {\n get: \"read\", list: \"read\", create: \"create\", update: \"edit\", delete: \"edit\",\n};\n\n/**\n * Build standard endpoint Zod schema fields.\n *\n * Always includes `method`. Auto-adds:\n * - `id` when get/delete are in the method list\n * - `fields` when get or list are in the method list\n * - `offset`/`limit` when list is in the method list\n * Merge endpoint-specific fields (items, list filters) via `extra`.\n *\n * When `caps` is provided, methods are filtered by tier — only methods\n * whose tier is enabled appear in the enum.\n */\nexport function endpointSchema(\n methods: string[],\n capsOrExtra?: Capabilities | Record<string, z.ZodTypeAny>,\n extraOrTiers?: Record<string, z.ZodTypeAny>,\n methodTiers?: Record<string, MethodTier>,\n): Record<string, z.ZodTypeAny> {\n // Overload resolution: (methods, extra?) or (methods, caps, extra?, tiers?)\n let caps: Capabilities | undefined;\n let extra: Record<string, z.ZodTypeAny> | undefined;\n\n if (capsOrExtra && (\"create\" in capsOrExtra) && (\"edit\" in capsOrExtra)\n && typeof (capsOrExtra as any).create === \"boolean\") {\n caps = capsOrExtra as Capabilities;\n extra = extraOrTiers;\n } else {\n extra = capsOrExtra as Record<string, z.ZodTypeAny> | undefined;\n // methodTiers and extraOrTiers are unused in the legacy call signature\n }\n\n // Filter methods by capabilities\n let filtered = methods;\n if (caps) {\n const tiers = { ...DEFAULT_TIERS, ...methodTiers };\n filtered = methods.filter(m => {\n const tier = tiers[m] ?? \"edit\"; // unknown methods default to edit\n if (tier === \"read\") return true;\n if (tier === \"create\") return caps!.create;\n if (tier === \"edit\") return caps!.edit;\n return false;\n });\n }\n\n const schema: Record<string, z.ZodTypeAny> = {\n method: z.enum(filtered as [string, ...string[]]),\n };\n if (filtered.includes(\"get\") || filtered.includes(\"delete\")) {\n schema.id = z.string().optional().describe(\"Resource ID (get, delete)\");\n }\n if (filtered.includes(\"get\") || filtered.includes(\"list\")) {\n schema.fields = flexJson(z.array(z.string())).optional()\n .describe('Property whitelist (get/list). Identity fields (id, name, type) always included. Omit for stubs on list, full detail on get. Pass [\"*\"] for all fields.');\n }\n if (filtered.includes(\"list\")) {\n schema.offset = z.coerce.number().optional().describe(\"Skip N items for pagination (default 0)\");\n schema.limit = z.coerce.number().optional().describe(\"Max items per page (default 100)\");\n }\n return { ...schema, ...extra };\n}\n\n// ─── Figma Dispatcher ────────────────────────────────────────────\n\ntype MethodHandlers = Record<string, (params: any) => Promise<any>>;\n\n/**\n * Create a Figma handler that dispatches on `params.method`.\n * Only methods with registered handlers are allowed.\n * Automatically applies `fields` filtering on get responses.\n */\nexport function createDispatcher(handlers: MethodHandlers) {\n const supported = Object.keys(handlers).join(\", \");\n return async (params: any): Promise<any> => {\n const method = params.method as EndpointMethod;\n const handler = handlers[method];\n if (!handler) throw new Error(`Method '${method}' not supported. Available: ${supported}`);\n let result = await handler(params);\n // Auto-apply fields filtering on get responses (full detail by default)\n if (method === \"get\" && params.fields?.length && result && typeof result === \"object\") {\n result = pickFields(result, params.fields);\n }\n return result;\n };\n}\n","import { z } from \"zod\";\n\n// AI agents (Claude, GPT, etc.) frequently pass numbers as strings\n// (\"10\" instead of 10), booleans as strings (\"true\" instead of true),\n// and objects/arrays as JSON strings. These helpers add resilient\n// coercion so tools don't fail on valid-but-mistyped input.\n\n/** Coerce \"true\"/\"false\"/\"1\"/\"0\" strings to boolean */\nexport const flexBool = <T extends z.ZodTypeAny>(inner: T) =>\n z.preprocess((v) => {\n if (v === \"true\" || v === \"1\") return true;\n if (v === \"false\" || v === \"0\") return false;\n return v;\n }, inner);\n\n/** Coerce JSON strings to parsed values (for objects/arrays that agents may stringify) */\nexport const flexJson = <T extends z.ZodTypeAny>(inner: T) =>\n z.preprocess((v) => {\n if (typeof v === \"string\") {\n try {\n return JSON.parse(v);\n } catch {\n return v;\n }\n }\n return v;\n }, inner);\n\n/** Coerce numeric strings only when they're valid numbers (safe for use inside unions) */\nexport const flexNum = <T extends z.ZodTypeAny>(inner: T) =>\n z.preprocess((v) => {\n if (typeof v === \"string\") {\n const n = Number(v);\n if (!isNaN(n) && v.trim() !== \"\") return n;\n }\n return v;\n }, inner);\n"],"mappings":"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","names":["import_zod"]}
@@ -17,11 +17,20 @@ var flexJson = (inner) => z.preprocess((v) => {
17
17
  // src/tools/endpoint.ts
18
18
  function pickFields(obj, fields) {
19
19
  if (fields.includes("*")) return obj;
20
- const keep = /* @__PURE__ */ new Set([...fields, "id", "name", "type"]);
20
+ const identity = ["id", "name", "type"];
21
+ const keep = /* @__PURE__ */ new Set([...fields, ...identity]);
21
22
  const out = {};
22
23
  for (const key of Object.keys(obj)) {
23
24
  if (keep.has(key)) out[key] = obj[key];
24
25
  }
26
+ const requested = fields.filter((f) => !identity.includes(f));
27
+ if (requested.length > 0) {
28
+ const found = requested.filter((f) => f in obj);
29
+ if (found.length === 0) {
30
+ const available = Object.keys(obj).filter((k) => !identity.includes(k));
31
+ out._warning = `Requested fields [${requested.join(", ")}] not found. Available: [${available.join(", ")}]`;
32
+ }
33
+ }
25
34
  return out;
26
35
  }
27
36
  function paginate(items, offset = 0, limit = 100) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/tools/endpoint.ts","../../src/utils/coercion.ts"],"sourcesContent":["/**\n * Shared endpoint infrastructure.\n *\n * Every resource endpoint follows the same contract:\n * create → items[{...}] → { results: [{id}, ...] }\n * get → { id, fields? } → resource object (field-filtered)\n * list → { filters?, fields?, offset, limit } → { totalCount, returned, offset, limit, items: [...] }\n * update → items[{...}] → { results: [\"ok\", ...] }\n * delete → { id } or items[{id}] → \"ok\" or { results: [\"ok\", ...] }\n *\n * MCP side: endpointSchema()\n * Figma side: createDispatcher() + paginate()\n */\n\nimport { z } from \"zod\";\nimport { flexJson } from \"../utils/coercion\";\nimport type { Capabilities } from \"./types\";\n\n// ─── Method Types ────────────────────────────────────────────────\n\nexport type EndpointMethod = \"create\" | \"get\" | \"list\" | \"update\" | \"delete\";\n\n// ─── Response Types ──────────────────────────────────────────────\n\n/** Batch response envelope (create, update, delete). Produced by batchHandler. */\nexport interface BatchResponse<T = { id: string }> {\n results: Array<T | \"ok\" | { error: string }>;\n warnings?: string[];\n deferred?: string;\n}\n\n/** Paginated list response envelope. */\nexport interface ListResponse<T = Record<string, any>> {\n totalCount: number;\n returned: number;\n offset: number;\n limit: number;\n items: T[];\n}\n\n// ─── Discriminated Param Types ───────────────────────────────────\n//\n// Each endpoint specializes these with its own create/update item\n// types and list filters. Example:\n//\n// type StyleParams = EndpointParams<StyleCreateItem, StyleUpdateItem, { type?: string }>;\n//\n\nexport type EndpointParams<\n TCreate = Record<string, any>,\n TUpdate = Record<string, any>,\n TListFilters = Record<string, never>,\n> =\n | { method: \"create\"; items: TCreate[] }\n | { method: \"get\"; id: string; fields?: string[] }\n | ({ method: \"list\"; fields?: string[]; offset?: number; limit?: number } & TListFilters)\n | { method: \"update\"; items: TUpdate[] }\n | { method: \"delete\"; id?: string; items?: Array<{ id: string }> };\n\n// ─── Helpers ────────────────────────────────────────────────────\n\n/**\n * Top-level field filter for get/list responses.\n * Always preserves identity fields (id, name, type).\n * Pass [\"*\"] to return all fields.\n */\nexport function pickFields(obj: Record<string, any>, fields: string[]): Record<string, any> {\n if (fields.includes(\"*\")) return obj;\n const keep = new Set([...fields, \"id\", \"name\", \"type\"]);\n const out: Record<string, any> = {};\n for (const key of Object.keys(obj)) {\n if (keep.has(key)) out[key] = obj[key];\n }\n return out;\n}\n\n/**\n * Paginate an array of items. Default limit: 100.\n * Call from list handlers after assembling the full result set.\n */\nexport function paginate<T>(items: T[], offset = 0, limit = 100): ListResponse<T> {\n const sliced = items.slice(offset, offset + limit);\n return { totalCount: items.length, returned: sliced.length, offset, limit, items: sliced };\n}\n\n// ─── Schema Builder ──────────────────────────────────────────────\n\n/** Maps each endpoint method to the minimum tier required to use it. */\nexport type MethodTier = \"read\" | \"create\" | \"edit\";\n\nconst DEFAULT_TIERS: Record<string, MethodTier> = {\n get: \"read\", list: \"read\", create: \"create\", update: \"edit\", delete: \"edit\",\n};\n\n/**\n * Build standard endpoint Zod schema fields.\n *\n * Always includes `method`. Auto-adds:\n * - `id` when get/delete are in the method list\n * - `fields` when get or list are in the method list\n * - `offset`/`limit` when list is in the method list\n * Merge endpoint-specific fields (items, list filters) via `extra`.\n *\n * When `caps` is provided, methods are filtered by tier — only methods\n * whose tier is enabled appear in the enum.\n */\nexport function endpointSchema(\n methods: string[],\n capsOrExtra?: Capabilities | Record<string, z.ZodTypeAny>,\n extraOrTiers?: Record<string, z.ZodTypeAny>,\n methodTiers?: Record<string, MethodTier>,\n): Record<string, z.ZodTypeAny> {\n // Overload resolution: (methods, extra?) or (methods, caps, extra?, tiers?)\n let caps: Capabilities | undefined;\n let extra: Record<string, z.ZodTypeAny> | undefined;\n\n if (capsOrExtra && (\"create\" in capsOrExtra) && (\"edit\" in capsOrExtra)\n && typeof (capsOrExtra as any).create === \"boolean\") {\n caps = capsOrExtra as Capabilities;\n extra = extraOrTiers;\n } else {\n extra = capsOrExtra as Record<string, z.ZodTypeAny> | undefined;\n // methodTiers and extraOrTiers are unused in the legacy call signature\n }\n\n // Filter methods by capabilities\n let filtered = methods;\n if (caps) {\n const tiers = { ...DEFAULT_TIERS, ...methodTiers };\n filtered = methods.filter(m => {\n const tier = tiers[m] ?? \"edit\"; // unknown methods default to edit\n if (tier === \"read\") return true;\n if (tier === \"create\") return caps!.create;\n if (tier === \"edit\") return caps!.edit;\n return false;\n });\n }\n\n const schema: Record<string, z.ZodTypeAny> = {\n method: z.enum(filtered as [string, ...string[]]),\n };\n if (filtered.includes(\"get\") || filtered.includes(\"delete\")) {\n schema.id = z.string().optional().describe(\"Resource ID (get, delete)\");\n }\n if (filtered.includes(\"get\") || filtered.includes(\"list\")) {\n schema.fields = flexJson(z.array(z.string())).optional()\n .describe('Property whitelist (get/list). Identity fields (id, name, type) always included. Omit for stubs on list, full detail on get. Pass [\"*\"] for all fields.');\n }\n if (filtered.includes(\"list\")) {\n schema.offset = z.coerce.number().optional().describe(\"Skip N items for pagination (default 0)\");\n schema.limit = z.coerce.number().optional().describe(\"Max items per page (default 100)\");\n }\n return { ...schema, ...extra };\n}\n\n// ─── Figma Dispatcher ────────────────────────────────────────────\n\ntype MethodHandlers = Record<string, (params: any) => Promise<any>>;\n\n/**\n * Create a Figma handler that dispatches on `params.method`.\n * Only methods with registered handlers are allowed.\n * Automatically applies `fields` filtering on get responses.\n */\nexport function createDispatcher(handlers: MethodHandlers) {\n const supported = Object.keys(handlers).join(\", \");\n return async (params: any): Promise<any> => {\n const method = params.method as EndpointMethod;\n const handler = handlers[method];\n if (!handler) throw new Error(`Method '${method}' not supported. Available: ${supported}`);\n let result = await handler(params);\n // Auto-apply fields filtering on get responses (full detail by default)\n if (method === \"get\" && params.fields?.length && result && typeof result === \"object\") {\n result = pickFields(result, params.fields);\n }\n return result;\n };\n}\n","import { z } from \"zod\";\n\n// AI agents (Claude, GPT, etc.) frequently pass numbers as strings\n// (\"10\" instead of 10), booleans as strings (\"true\" instead of true),\n// and objects/arrays as JSON strings. These helpers add resilient\n// coercion so tools don't fail on valid-but-mistyped input.\n\n/** Coerce \"true\"/\"false\"/\"1\"/\"0\" strings to boolean */\nexport const flexBool = <T extends z.ZodTypeAny>(inner: T) =>\n z.preprocess((v) => {\n if (v === \"true\" || v === \"1\") return true;\n if (v === \"false\" || v === \"0\") return false;\n return v;\n }, inner);\n\n/** Coerce JSON strings to parsed values (for objects/arrays that agents may stringify) */\nexport const flexJson = <T extends z.ZodTypeAny>(inner: T) =>\n z.preprocess((v) => {\n if (typeof v === \"string\") {\n try {\n return JSON.parse(v);\n } catch {\n return v;\n }\n }\n return v;\n }, inner);\n\n/** Coerce numeric strings only when they're valid numbers (safe for use inside unions) */\nexport const flexNum = <T extends z.ZodTypeAny>(inner: T) =>\n z.preprocess((v) => {\n if (typeof v === \"string\") {\n const n = Number(v);\n if (!isNaN(n) && v.trim() !== \"\") return n;\n }\n return v;\n }, inner);\n"],"mappings":"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","names":["z","z"]}
1
+ {"version":3,"sources":["../../src/tools/endpoint.ts","../../src/utils/coercion.ts"],"sourcesContent":["/**\n * Shared endpoint infrastructure.\n *\n * Every resource endpoint follows the same contract:\n * create → items[{...}] → { results: [{id}, ...] }\n * get → { id, fields? } → resource object (field-filtered)\n * list → { filters?, fields?, offset, limit } → { totalCount, returned, offset, limit, items: [...] }\n * update → items[{...}] → { results: [\"ok\", ...] }\n * delete → { id } or items[{id}] → \"ok\" or { results: [\"ok\", ...] }\n *\n * MCP side: endpointSchema()\n * Figma side: createDispatcher() + paginate()\n */\n\nimport { z } from \"zod\";\nimport { flexJson } from \"../utils/coercion\";\nimport type { Capabilities } from \"./types\";\n\n// ─── Method Types ────────────────────────────────────────────────\n\nexport type EndpointMethod = \"create\" | \"get\" | \"list\" | \"update\" | \"delete\";\n\n// ─── Response Types ──────────────────────────────────────────────\n\n/** Batch response envelope (create, update, delete). Produced by batchHandler. */\nexport interface BatchResponse<T = { id: string }> {\n results: Array<T | \"ok\" | { error: string }>;\n warnings?: string[];\n deferred?: string;\n}\n\n/** Paginated list response envelope. */\nexport interface ListResponse<T = Record<string, any>> {\n totalCount: number;\n returned: number;\n offset: number;\n limit: number;\n items: T[];\n}\n\n// ─── Discriminated Param Types ───────────────────────────────────\n//\n// Each endpoint specializes these with its own create/update item\n// types and list filters. Example:\n//\n// type StyleParams = EndpointParams<StyleCreateItem, StyleUpdateItem, { type?: string }>;\n//\n\nexport type EndpointParams<\n TCreate = Record<string, any>,\n TUpdate = Record<string, any>,\n TListFilters = Record<string, never>,\n> =\n | { method: \"create\"; items: TCreate[] }\n | { method: \"get\"; id: string; fields?: string[] }\n | ({ method: \"list\"; fields?: string[]; offset?: number; limit?: number } & TListFilters)\n | { method: \"update\"; items: TUpdate[] }\n | { method: \"delete\"; id?: string; items?: Array<{ id: string }> };\n\n// ─── Helpers ────────────────────────────────────────────────────\n\n/**\n * Top-level field filter for get/list responses.\n * Always preserves identity fields (id, name, type).\n * Pass [\"*\"] to return all fields.\n */\nexport function pickFields(obj: Record<string, any>, fields: string[]): Record<string, any> {\n if (fields.includes(\"*\")) return obj;\n const identity = [\"id\", \"name\", \"type\"];\n const keep = new Set([...fields, ...identity]);\n const out: Record<string, any> = {};\n for (const key of Object.keys(obj)) {\n if (keep.has(key)) out[key] = obj[key];\n }\n // Warn when all non-identity requested fields were unknown\n const requested = fields.filter(f => !identity.includes(f));\n if (requested.length > 0) {\n const found = requested.filter(f => f in obj);\n if (found.length === 0) {\n const available = Object.keys(obj).filter(k => !identity.includes(k));\n out._warning = `Requested fields [${requested.join(\", \")}] not found. Available: [${available.join(\", \")}]`;\n }\n }\n return out;\n}\n\n/**\n * Paginate an array of items. Default limit: 100.\n * Call from list handlers after assembling the full result set.\n */\nexport function paginate<T>(items: T[], offset = 0, limit = 100): ListResponse<T> {\n const sliced = items.slice(offset, offset + limit);\n return { totalCount: items.length, returned: sliced.length, offset, limit, items: sliced };\n}\n\n// ─── Schema Builder ──────────────────────────────────────────────\n\n/** Maps each endpoint method to the minimum tier required to use it. */\nexport type MethodTier = \"read\" | \"create\" | \"edit\";\n\nconst DEFAULT_TIERS: Record<string, MethodTier> = {\n get: \"read\", list: \"read\", create: \"create\", update: \"edit\", delete: \"edit\",\n};\n\n/**\n * Build standard endpoint Zod schema fields.\n *\n * Always includes `method`. Auto-adds:\n * - `id` when get/delete are in the method list\n * - `fields` when get or list are in the method list\n * - `offset`/`limit` when list is in the method list\n * Merge endpoint-specific fields (items, list filters) via `extra`.\n *\n * When `caps` is provided, methods are filtered by tier — only methods\n * whose tier is enabled appear in the enum.\n */\nexport function endpointSchema(\n methods: string[],\n capsOrExtra?: Capabilities | Record<string, z.ZodTypeAny>,\n extraOrTiers?: Record<string, z.ZodTypeAny>,\n methodTiers?: Record<string, MethodTier>,\n): Record<string, z.ZodTypeAny> {\n // Overload resolution: (methods, extra?) or (methods, caps, extra?, tiers?)\n let caps: Capabilities | undefined;\n let extra: Record<string, z.ZodTypeAny> | undefined;\n\n if (capsOrExtra && (\"create\" in capsOrExtra) && (\"edit\" in capsOrExtra)\n && typeof (capsOrExtra as any).create === \"boolean\") {\n caps = capsOrExtra as Capabilities;\n extra = extraOrTiers;\n } else {\n extra = capsOrExtra as Record<string, z.ZodTypeAny> | undefined;\n // methodTiers and extraOrTiers are unused in the legacy call signature\n }\n\n // Filter methods by capabilities\n let filtered = methods;\n if (caps) {\n const tiers = { ...DEFAULT_TIERS, ...methodTiers };\n filtered = methods.filter(m => {\n const tier = tiers[m] ?? \"edit\"; // unknown methods default to edit\n if (tier === \"read\") return true;\n if (tier === \"create\") return caps!.create;\n if (tier === \"edit\") return caps!.edit;\n return false;\n });\n }\n\n const schema: Record<string, z.ZodTypeAny> = {\n method: z.enum(filtered as [string, ...string[]]),\n };\n if (filtered.includes(\"get\") || filtered.includes(\"delete\")) {\n schema.id = z.string().optional().describe(\"Resource ID (get, delete)\");\n }\n if (filtered.includes(\"get\") || filtered.includes(\"list\")) {\n schema.fields = flexJson(z.array(z.string())).optional()\n .describe('Property whitelist (get/list). Identity fields (id, name, type) always included. Omit for stubs on list, full detail on get. Pass [\"*\"] for all fields.');\n }\n if (filtered.includes(\"list\")) {\n schema.offset = z.coerce.number().optional().describe(\"Skip N items for pagination (default 0)\");\n schema.limit = z.coerce.number().optional().describe(\"Max items per page (default 100)\");\n }\n return { ...schema, ...extra };\n}\n\n// ─── Figma Dispatcher ────────────────────────────────────────────\n\ntype MethodHandlers = Record<string, (params: any) => Promise<any>>;\n\n/**\n * Create a Figma handler that dispatches on `params.method`.\n * Only methods with registered handlers are allowed.\n * Automatically applies `fields` filtering on get responses.\n */\nexport function createDispatcher(handlers: MethodHandlers) {\n const supported = Object.keys(handlers).join(\", \");\n return async (params: any): Promise<any> => {\n const method = params.method as EndpointMethod;\n const handler = handlers[method];\n if (!handler) throw new Error(`Method '${method}' not supported. Available: ${supported}`);\n let result = await handler(params);\n // Auto-apply fields filtering on get responses (full detail by default)\n if (method === \"get\" && params.fields?.length && result && typeof result === \"object\") {\n result = pickFields(result, params.fields);\n }\n return result;\n };\n}\n","import { z } from \"zod\";\n\n// AI agents (Claude, GPT, etc.) frequently pass numbers as strings\n// (\"10\" instead of 10), booleans as strings (\"true\" instead of true),\n// and objects/arrays as JSON strings. These helpers add resilient\n// coercion so tools don't fail on valid-but-mistyped input.\n\n/** Coerce \"true\"/\"false\"/\"1\"/\"0\" strings to boolean */\nexport const flexBool = <T extends z.ZodTypeAny>(inner: T) =>\n z.preprocess((v) => {\n if (v === \"true\" || v === \"1\") return true;\n if (v === \"false\" || v === \"0\") return false;\n return v;\n }, inner);\n\n/** Coerce JSON strings to parsed values (for objects/arrays that agents may stringify) */\nexport const flexJson = <T extends z.ZodTypeAny>(inner: T) =>\n z.preprocess((v) => {\n if (typeof v === \"string\") {\n try {\n return JSON.parse(v);\n } catch {\n return v;\n }\n }\n return v;\n }, inner);\n\n/** Coerce numeric strings only when they're valid numbers (safe for use inside unions) */\nexport const flexNum = <T extends z.ZodTypeAny>(inner: T) =>\n z.preprocess((v) => {\n if (typeof v === \"string\") {\n const n = Number(v);\n if (!isNaN(n) && v.trim() !== \"\") return n;\n }\n return v;\n }, inner);\n"],"mappings":";AAcA,SAAS,KAAAA,UAAS;;;ACdlB,SAAS,SAAS;AAgBX,IAAM,WAAW,CAAyB,UAC/C,EAAE,WAAW,CAAC,MAAM;AAClB,MAAI,OAAO,MAAM,UAAU;AACzB,QAAI;AACF,aAAO,KAAK,MAAM,CAAC;AAAA,IACrB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT,GAAG,KAAK;;;ADwCH,SAAS,WAAW,KAA0B,QAAuC;AAC1F,MAAI,OAAO,SAAS,GAAG,EAAG,QAAO;AACjC,QAAM,WAAW,CAAC,MAAM,QAAQ,MAAM;AACtC,QAAM,OAAO,oBAAI,IAAI,CAAC,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAC7C,QAAM,MAA2B,CAAC;AAClC,aAAW,OAAO,OAAO,KAAK,GAAG,GAAG;AAClC,QAAI,KAAK,IAAI,GAAG,EAAG,KAAI,GAAG,IAAI,IAAI,GAAG;AAAA,EACvC;AAEA,QAAM,YAAY,OAAO,OAAO,OAAK,CAAC,SAAS,SAAS,CAAC,CAAC;AAC1D,MAAI,UAAU,SAAS,GAAG;AACxB,UAAM,QAAQ,UAAU,OAAO,OAAK,KAAK,GAAG;AAC5C,QAAI,MAAM,WAAW,GAAG;AACtB,YAAM,YAAY,OAAO,KAAK,GAAG,EAAE,OAAO,OAAK,CAAC,SAAS,SAAS,CAAC,CAAC;AACpE,UAAI,WAAW,qBAAqB,UAAU,KAAK,IAAI,CAAC,4BAA4B,UAAU,KAAK,IAAI,CAAC;AAAA,IAC1G;AAAA,EACF;AACA,SAAO;AACT;AAMO,SAAS,SAAY,OAAY,SAAS,GAAG,QAAQ,KAAsB;AAChF,QAAM,SAAS,MAAM,MAAM,QAAQ,SAAS,KAAK;AACjD,SAAO,EAAE,YAAY,MAAM,QAAQ,UAAU,OAAO,QAAQ,QAAQ,OAAO,OAAO,OAAO;AAC3F;AAOA,IAAM,gBAA4C;AAAA,EAChD,KAAK;AAAA,EAAQ,MAAM;AAAA,EAAQ,QAAQ;AAAA,EAAU,QAAQ;AAAA,EAAQ,QAAQ;AACvE;AAcO,SAAS,eACd,SACA,aACA,cACA,aAC8B;AAE9B,MAAI;AACJ,MAAI;AAEJ,MAAI,eAAgB,YAAY,eAAiB,UAAU,eACpD,OAAQ,YAAoB,WAAW,WAAW;AACvD,WAAO;AACP,YAAQ;AAAA,EACV,OAAO;AACL,YAAQ;AAAA,EAEV;AAGA,MAAI,WAAW;AACf,MAAI,MAAM;AACR,UAAM,QAAQ,EAAE,GAAG,eAAe,GAAG,YAAY;AACjD,eAAW,QAAQ,OAAO,OAAK;AAC7B,YAAM,OAAO,MAAM,CAAC,KAAK;AACzB,UAAI,SAAS,OAAQ,QAAO;AAC5B,UAAI,SAAS,SAAU,QAAO,KAAM;AACpC,UAAI,SAAS,OAAQ,QAAO,KAAM;AAClC,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAEA,QAAM,SAAuC;AAAA,IAC3C,QAAQC,GAAE,KAAK,QAAiC;AAAA,EAClD;AACA,MAAI,SAAS,SAAS,KAAK,KAAK,SAAS,SAAS,QAAQ,GAAG;AAC3D,WAAO,KAAKA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,2BAA2B;AAAA,EACxE;AACA,MAAI,SAAS,SAAS,KAAK,KAAK,SAAS,SAAS,MAAM,GAAG;AACzD,WAAO,SAAS,SAASA,GAAE,MAAMA,GAAE,OAAO,CAAC,CAAC,EAAE,SAAS,EACpD,SAAS,yJAAyJ;AAAA,EACvK;AACA,MAAI,SAAS,SAAS,MAAM,GAAG;AAC7B,WAAO,SAASA,GAAE,OAAO,OAAO,EAAE,SAAS,EAAE,SAAS,yCAAyC;AAC/F,WAAO,QAAQA,GAAE,OAAO,OAAO,EAAE,SAAS,EAAE,SAAS,kCAAkC;AAAA,EACzF;AACA,SAAO,EAAE,GAAG,QAAQ,GAAG,MAAM;AAC/B;AAWO,SAAS,iBAAiB,UAA0B;AACzD,QAAM,YAAY,OAAO,KAAK,QAAQ,EAAE,KAAK,IAAI;AACjD,SAAO,OAAO,WAA8B;AAC1C,UAAM,SAAS,OAAO;AACtB,UAAM,UAAU,SAAS,MAAM;AAC/B,QAAI,CAAC,QAAS,OAAM,IAAI,MAAM,WAAW,MAAM,+BAA+B,SAAS,EAAE;AACzF,QAAI,SAAS,MAAM,QAAQ,MAAM;AAEjC,QAAI,WAAW,SAAS,OAAO,QAAQ,UAAU,UAAU,OAAO,WAAW,UAAU;AACrF,eAAS,WAAW,QAAQ,OAAO,MAAM;AAAA,IAC3C;AACA,WAAO;AAAA,EACT;AACF;","names":["z","z"]}