figmanage 1.3.8 → 1.4.0

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.
Files changed (40) hide show
  1. package/README.md +164 -133
  2. package/dist/auth/client.d.ts +1 -0
  3. package/dist/auth/health.js +2 -1
  4. package/dist/cli/comments.js +79 -2
  5. package/dist/cli/components.js +66 -0
  6. package/dist/cli/compound-commands.js +2 -0
  7. package/dist/cli/org.js +143 -5
  8. package/dist/cli/teams.js +41 -1
  9. package/dist/cli/webhooks.js +20 -1
  10. package/dist/clients/internal-api.js +1 -1
  11. package/dist/clients/public-api.js +1 -1
  12. package/dist/mcp.d.ts +1 -0
  13. package/dist/mcp.js +10 -4
  14. package/dist/operations/comments.d.ts +25 -0
  15. package/dist/operations/comments.js +22 -0
  16. package/dist/operations/compound-manager.d.ts +1 -0
  17. package/dist/operations/compound-manager.js +17 -3
  18. package/dist/operations/dev-resources.d.ts +25 -0
  19. package/dist/operations/dev-resources.js +31 -0
  20. package/dist/operations/navigate.d.ts +5 -0
  21. package/dist/operations/navigate.js +20 -0
  22. package/dist/operations/org.d.ts +68 -1
  23. package/dist/operations/org.js +112 -1
  24. package/dist/operations/teams.d.ts +9 -0
  25. package/dist/operations/teams.js +23 -0
  26. package/dist/operations/webhooks.d.ts +14 -0
  27. package/dist/operations/webhooks.js +12 -0
  28. package/dist/tools/analytics.js +2 -0
  29. package/dist/tools/comments.js +99 -3
  30. package/dist/tools/compound-manager.js +7 -2
  31. package/dist/tools/compound.js +3 -0
  32. package/dist/tools/dev-resources.d.ts +2 -0
  33. package/dist/tools/dev-resources.js +78 -0
  34. package/dist/tools/org.js +203 -8
  35. package/dist/tools/permissions.js +3 -0
  36. package/dist/tools/register.d.ts +2 -1
  37. package/dist/tools/register.js +4 -1
  38. package/dist/tools/teams.js +53 -1
  39. package/dist/tools/webhooks.js +25 -2
  40. package/package.json +1 -1
@@ -24,6 +24,16 @@ export const SEAT_KEY_TO_TYPE = {
24
24
  developer: 'dev',
25
25
  collaborator: 'collab',
26
26
  };
27
+ // -- Helpers --
28
+ /** Resolve a billing plan_id from a team's folders. */
29
+ export async function resolvePlanId(client, teamId) {
30
+ const foldersRes = await client.get(`/api/teams/${teamId}/folders`);
31
+ const folders = foldersRes.data?.meta?.folder_rows || foldersRes.data?.meta || foldersRes.data || [];
32
+ const folder = (Array.isArray(folders) ? folders : []).find((f) => f.plan_id);
33
+ if (!folder?.plan_id)
34
+ throw new Error('No billing plan found for this team. Try providing plan_id directly.');
35
+ return folder.plan_id;
36
+ }
27
37
  export async function listAdmins(config, params) {
28
38
  const orgId = requireOrgId(config, params.org_id);
29
39
  const res = await internalClient(config).get(`/api/orgs/${orgId}/admins`, { params: { include_license_admins: params.include_license_admins ?? false } });
@@ -121,7 +131,9 @@ export async function orgDomains(config, params) {
121
131
  return response;
122
132
  }
123
133
  export async function aiCreditUsage(config, params) {
124
- const res = await internalClient(config).get(`/api/plans/${params.plan_id}/ai_credits/plan_usage_summary`);
134
+ const client = internalClient(config);
135
+ const planId = params.plan_id || await resolvePlanId(client, params.team_id);
136
+ const res = await client.get(`/api/plans/${planId}/ai_credits/plan_usage_summary`);
125
137
  return res.data;
126
138
  }
127
139
  export async function exportMembers(config, params) {
@@ -202,4 +214,103 @@ export async function changeSeat(config, params) {
202
214
  new_seat: SEAT_LABELS[params.seat_type],
203
215
  };
204
216
  }
217
+ export async function activityLog(config, params) {
218
+ const orgId = requireOrgId(config, params.org_id);
219
+ const now = new Date();
220
+ const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
221
+ const reqParams = {
222
+ org_id: orgId,
223
+ page_size: String(params.page_size || 50),
224
+ start_time: params.start_time || thirtyDaysAgo.toISOString().split('T')[0],
225
+ end_time: params.end_time || now.toISOString(),
226
+ emails: params.emails || '',
227
+ };
228
+ if (params.after)
229
+ reqParams.after = params.after;
230
+ const res = await internalClient(config).get('/api/activity_logs', { params: reqParams });
231
+ const meta = res.data?.meta;
232
+ const rawLogs = Array.isArray(meta) && meta.length > 0 ? meta[0] : [];
233
+ const pagination = Array.isArray(meta) && meta.length > 1 ? meta[1] : undefined;
234
+ const entries = (Array.isArray(rawLogs) ? rawLogs : []).map((e) => ({
235
+ id: e.id,
236
+ timestamp: e.created_at,
237
+ event: e.event_name,
238
+ actor: e.actor ? { id: e.actor.id, email: e.actor.email, name: e.actor.name } : null,
239
+ team: e.metadata?.team_name || null,
240
+ ip_address: e.ip_address || null,
241
+ target: e.acted_on_type ? { type: e.acted_on_type, id_or_key: e.acted_on_id_or_key } : null,
242
+ metadata: e.metadata || null,
243
+ }));
244
+ const result = { entries };
245
+ if (pagination?.after) {
246
+ result.pagination = { after: pagination.after, column: pagination.column };
247
+ }
248
+ return result;
249
+ }
250
+ export async function listPayments(config, params) {
251
+ const orgId = requireOrgId(config, params.org_id);
252
+ const res = await internalClient(config).get(`/api/orgs/${orgId}/billing_data`);
253
+ const invoices = res.data?.meta?.invoices || [];
254
+ return invoices.filter((inv) => inv.status === 'paid');
255
+ }
256
+ // -- Org member removal --
257
+ export async function removeOrgMember(config, params) {
258
+ if (!params.confirm) {
259
+ throw new Error('Removing a member from the org is permanent and cannot be undone. ' +
260
+ 'They lose access to all teams, projects, files, and apps. ' +
261
+ 'Their drafts move to Unassigned drafts. Any paid seats are freed. ' +
262
+ 'Set confirm: true to proceed.');
263
+ }
264
+ const orgId = requireOrgId(config, params.org_id);
265
+ const client = internalClient(config);
266
+ // Resolve org_user_id
267
+ const res = await client.get(`/api/v2/orgs/${orgId}/org_users`, {
268
+ params: params.user_identifier.includes('@') ? { search_query: params.user_identifier } : {},
269
+ });
270
+ const users = res.data?.meta?.users || res.data?.meta || res.data || [];
271
+ const members = Array.isArray(users) ? users : [];
272
+ const member = members.find((m) => params.user_identifier.includes('@')
273
+ ? m.user?.email === params.user_identifier
274
+ : String(m.user_id) === String(params.user_identifier));
275
+ if (!member)
276
+ throw new Error(`User not found: ${params.user_identifier}`);
277
+ if (String(member.user_id) === String(config.userId)) {
278
+ throw new Error('Cannot remove yourself from the org.');
279
+ }
280
+ await client.delete(`/api/orgs/${orgId}/org_users`, {
281
+ data: { org_user_ids: [String(member.id)] },
282
+ });
283
+ return `Removed ${member.user?.handle || member.user?.email || params.user_identifier} from the org. This cannot be undone.`;
284
+ }
285
+ export async function createUserGroup(config, params) {
286
+ const client = internalClient(config);
287
+ let planId = params.plan_id;
288
+ if (!planId) {
289
+ if (!params.team_id)
290
+ throw new Error('Either team_id or plan_id is required to create a user group.');
291
+ planId = await resolvePlanId(client, params.team_id);
292
+ }
293
+ const res = await client.post('/api/user_groups', {
294
+ name: params.name,
295
+ description: params.description || '',
296
+ plan_id: planId,
297
+ emails: params.emails || [],
298
+ should_notify: params.should_notify ?? true,
299
+ });
300
+ return res.data?.meta || res.data;
301
+ }
302
+ export async function deleteUserGroups(config, params) {
303
+ await internalClient(config).delete('/api/user_groups', {
304
+ data: { user_group_ids: params.user_group_ids },
305
+ });
306
+ return `Deleted ${params.user_group_ids.length} user group(s).`;
307
+ }
308
+ export async function addUserGroupMembers(config, params) {
309
+ const res = await internalClient(config).put(`/api/user_groups/${params.user_group_id}/add_members`, { emails: params.emails });
310
+ return res.data?.meta || res.data;
311
+ }
312
+ export async function removeUserGroupMembers(config, params) {
313
+ await internalClient(config).put(`/api/user_groups/${params.user_group_id}/remove_members`, { user_ids: params.user_ids });
314
+ return `Removed ${params.user_ids.length} member(s) from group.`;
315
+ }
205
316
  //# sourceMappingURL=org.js.map
@@ -11,6 +11,15 @@ export declare function renameTeam(config: AuthConfig, params: {
11
11
  team_id: string;
12
12
  name: string;
13
13
  }): Promise<string>;
14
+ export declare function addTeamMember(config: AuthConfig, params: {
15
+ team_id: string;
16
+ email: string;
17
+ level?: number;
18
+ }): Promise<string>;
19
+ export declare function removeTeamMember(config: AuthConfig, params: {
20
+ team_id: string;
21
+ user_id: string;
22
+ }): Promise<string>;
14
23
  export declare function deleteTeam(config: AuthConfig, params: {
15
24
  team_id: string;
16
25
  }): Promise<string>;
@@ -10,6 +10,29 @@ export async function renameTeam(config, params) {
10
10
  await internalClient(config).put(`/api/teams/${params.team_id}`, { name: params.name });
11
11
  return `Team ${params.team_id} renamed to "${params.name}".`;
12
12
  }
13
+ export async function addTeamMember(config, params) {
14
+ await internalClient(config).post('/api/invites', {
15
+ emails: [params.email],
16
+ resource_type: 'team',
17
+ resource_id_or_key: params.team_id,
18
+ level: params.level ?? 100,
19
+ user_group_ids: [],
20
+ });
21
+ return `Invited ${params.email} to team ${params.team_id}.`;
22
+ }
23
+ export async function removeTeamMember(config, params) {
24
+ const res = await internalClient(config).get(`/api/teams/${params.team_id}/members`);
25
+ const members = res.data?.meta || res.data || [];
26
+ const list = Array.isArray(members) ? members : [];
27
+ const member = list.find((m) => String(m.id) === String(params.user_id) || m.email === params.user_id);
28
+ if (!member)
29
+ throw new Error(`User not found in team: ${params.user_id}`);
30
+ const roleId = member.team_role?.id;
31
+ if (!roleId)
32
+ throw new Error(`User has no direct team role to remove (may have org-level access).`);
33
+ await internalClient(config).delete(`/api/roles/${roleId}`);
34
+ return `Removed ${member.name || member.email} from team ${params.team_id}.`;
35
+ }
13
36
  export async function deleteTeam(config, params) {
14
37
  await internalClient(config).delete(`/api/teams/${params.team_id}`);
15
38
  return `Team ${params.team_id} deleted.`;
@@ -22,6 +22,20 @@ export declare function updateWebhook(config: AuthConfig, params: {
22
22
  description?: string;
23
23
  status?: WebhookStatus;
24
24
  }): Promise<Record<string, any>>;
25
+ export interface WebhookRequest {
26
+ id: string;
27
+ endpoint: string;
28
+ payload: Record<string, any> | null;
29
+ status: number | null;
30
+ error: string | null;
31
+ sent_at: string;
32
+ }
33
+ export declare function webhookRequests(config: AuthConfig, params: {
34
+ webhook_id: string;
35
+ }): Promise<{
36
+ count: number;
37
+ requests: WebhookRequest[];
38
+ }>;
25
39
  export declare function deleteWebhook(config: AuthConfig, params: {
26
40
  webhook_id: string;
27
41
  }): Promise<void>;
@@ -33,6 +33,18 @@ export async function updateWebhook(config, params) {
33
33
  const { passcode: _, ...safe } = res.data || {};
34
34
  return safe;
35
35
  }
36
+ export async function webhookRequests(config, params) {
37
+ const res = await publicClient(config).get(`/v2/webhooks/${params.webhook_id}/requests`);
38
+ const requests = (res.data?.requests || []).map((r) => ({
39
+ id: r.id,
40
+ endpoint: r.endpoint,
41
+ payload: r.payload || null,
42
+ status: r.response_status_code ?? null,
43
+ error: r.error_msg || null,
44
+ sent_at: r.sent_at,
45
+ }));
46
+ return { count: requests.length, requests };
47
+ }
36
48
  export async function deleteWebhook(config, params) {
37
49
  await publicClient(config).delete(`/v2/webhooks/${params.webhook_id}`);
38
50
  }
@@ -5,6 +5,7 @@ import { libraryUsage, componentUsage } from '../operations/analytics.js';
5
5
  // -- library_usage --
6
6
  defineTool({
7
7
  toolset: 'analytics',
8
+ adminOnly: true,
8
9
  auth: 'cookie',
9
10
  register(server, config) {
10
11
  server.registerTool('library_usage', {
@@ -27,6 +28,7 @@ defineTool({
27
28
  // -- component_usage --
28
29
  defineTool({
29
30
  toolset: 'analytics',
31
+ adminOnly: true,
30
32
  auth: 'cookie',
31
33
  register(server, config) {
32
34
  server.registerTool('component_usage', {
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { defineTool, toolResult, toolError, toolSummary, figmaId } from './register.js';
3
3
  import { formatApiError } from '../helpers.js';
4
- import { listComments, formatCommentsAsMarkdown, postComment, deleteComment, listCommentReactions, } from '../operations/comments.js';
4
+ import { listComments, formatCommentsAsMarkdown, postComment, deleteComment, resolveComment, editComment, addCommentReaction, removeCommentReaction, listCommentReactions, } from '../operations/comments.js';
5
5
  // -- list_comments --
6
6
  defineTool({
7
7
  toolset: 'comments',
@@ -21,7 +21,7 @@ defineTool({
21
21
  }
22
22
  if (comments.length === 0)
23
23
  return toolResult('No comments on this file.');
24
- return toolSummary(`${comments.length} comment(s).`, comments, 'Use post_comment to reply, or delete_comment to remove.');
24
+ return toolSummary(`${comments.length} comment(s).`, comments, 'Use post_comment to reply, resolve_comment to close threads, or delete_comment to remove.');
25
25
  }
26
26
  catch (e) {
27
27
  return toolError(`Failed to list comments: ${formatApiError(e)}`);
@@ -94,7 +94,7 @@ defineTool({
94
94
  const reactions = await listCommentReactions(config, { file_key, comment_id });
95
95
  if (reactions.length === 0)
96
96
  return toolResult('No reactions on this comment.');
97
- return toolSummary(`${reactions.length} reaction(s).`, reactions);
97
+ return toolSummary(`${reactions.length} reaction(s).`, reactions, 'Use add_comment_reaction or remove_comment_reaction to manage.');
98
98
  }
99
99
  catch (e) {
100
100
  return toolError(`Failed to list reactions: ${formatApiError(e)}`);
@@ -102,4 +102,100 @@ defineTool({
102
102
  });
103
103
  },
104
104
  });
105
+ // -- resolve_comment --
106
+ defineTool({
107
+ toolset: 'comments',
108
+ auth: 'cookie',
109
+ mutates: true,
110
+ register(server, config) {
111
+ server.registerTool('resolve_comment', {
112
+ description: 'Resolve or unresolve a comment thread. Resolved comments are collapsed in the Figma UI.',
113
+ inputSchema: {
114
+ file_key: figmaId.describe('File key'),
115
+ comment_id: figmaId.describe('Comment ID to resolve'),
116
+ resolved: z.boolean().optional().describe('true to resolve (default), false to unresolve'),
117
+ },
118
+ }, async ({ file_key, comment_id, resolved }) => {
119
+ try {
120
+ const msg = await resolveComment(config, { file_key, comment_id, resolved });
121
+ return toolResult(msg);
122
+ }
123
+ catch (e) {
124
+ return toolError(`Failed to resolve comment: ${formatApiError(e)}`);
125
+ }
126
+ });
127
+ },
128
+ });
129
+ // -- edit_comment --
130
+ defineTool({
131
+ toolset: 'comments',
132
+ auth: 'cookie',
133
+ mutates: true,
134
+ register(server, config) {
135
+ server.registerTool('edit_comment', {
136
+ description: 'Edit the text of an existing comment.',
137
+ inputSchema: {
138
+ file_key: figmaId.describe('File key'),
139
+ comment_id: figmaId.describe('Comment ID'),
140
+ message: z.string().describe('New comment text'),
141
+ },
142
+ }, async ({ file_key, comment_id, message }) => {
143
+ try {
144
+ const msg = await editComment(config, { file_key, comment_id, message });
145
+ return toolResult(msg);
146
+ }
147
+ catch (e) {
148
+ return toolError(`Failed to edit comment: ${formatApiError(e)}`);
149
+ }
150
+ });
151
+ },
152
+ });
153
+ // -- add_comment_reaction --
154
+ defineTool({
155
+ toolset: 'comments',
156
+ auth: 'pat',
157
+ mutates: true,
158
+ register(server, config) {
159
+ server.registerTool('add_comment_reaction', {
160
+ description: 'Add an emoji reaction to a comment.',
161
+ inputSchema: {
162
+ file_key: figmaId.describe('File key'),
163
+ comment_id: figmaId.describe('Comment ID'),
164
+ emoji: z.string().describe('Emoji shortcode (e.g. ":thumbsup:", ":heart:")'),
165
+ },
166
+ }, async ({ file_key, comment_id, emoji }) => {
167
+ try {
168
+ const result = await addCommentReaction(config, { file_key, comment_id, emoji });
169
+ return toolSummary(`Added ${result.emoji} reaction.`, result);
170
+ }
171
+ catch (e) {
172
+ return toolError(`Failed to add reaction: ${formatApiError(e)}`);
173
+ }
174
+ });
175
+ },
176
+ });
177
+ // -- remove_comment_reaction --
178
+ defineTool({
179
+ toolset: 'comments',
180
+ auth: 'pat',
181
+ mutates: true,
182
+ register(server, config) {
183
+ server.registerTool('remove_comment_reaction', {
184
+ description: 'Remove your emoji reaction from a comment.',
185
+ inputSchema: {
186
+ file_key: figmaId.describe('File key'),
187
+ comment_id: figmaId.describe('Comment ID'),
188
+ emoji: z.string().describe('Emoji shortcode to remove (e.g. ":thumbsup:")'),
189
+ },
190
+ }, async ({ file_key, comment_id, emoji }) => {
191
+ try {
192
+ await removeCommentReaction(config, { file_key, comment_id, emoji });
193
+ return toolResult(`Removed ${emoji} reaction from comment ${comment_id}.`);
194
+ }
195
+ catch (e) {
196
+ return toolError(`Failed to remove reaction: ${formatApiError(e)}`);
197
+ }
198
+ });
199
+ },
200
+ });
105
201
  //# sourceMappingURL=comments.js.map
@@ -8,21 +8,24 @@ defineTool({
8
8
  auth: 'cookie',
9
9
  mutates: true,
10
10
  destructive: true,
11
+ adminOnly: true,
11
12
  register(server, config) {
12
13
  server.registerTool('offboard_user', {
13
- description: 'Audit and optionally execute user offboarding. Default: read-only audit. With execute=true: transfers file ownership, revokes access, downgrades seat.',
14
+ description: 'Audit and optionally execute user offboarding. Default: read-only audit. With execute=true: transfers file ownership, revokes access, downgrades seat. With remove_from_org=true: also permanently removes the user from the org (cannot be undone).',
14
15
  inputSchema: {
15
16
  user_identifier: z.string().describe('Email or user_id of the user to offboard'),
16
17
  execute: z.boolean().optional().default(false).describe('Execute the offboarding (default: false, audit only)'),
17
18
  transfer_to: z.string().optional().describe('Email or user_id to transfer file ownership to (required if user owns files and execute=true)'),
19
+ remove_from_org: z.boolean().optional().default(false).describe('Permanently remove from org after offboarding (cannot be undone). Requires execute=true.'),
18
20
  org_id: figmaId.optional().describe('Org ID override (defaults to current workspace)'),
19
21
  },
20
- }, async ({ user_identifier, execute, transfer_to, org_id }) => {
22
+ }, async ({ user_identifier, execute, transfer_to, remove_from_org, org_id }) => {
21
23
  try {
22
24
  const result = await offboardUser(config, {
23
25
  user_identifier,
24
26
  execute: execute ?? false,
25
27
  transfer_to,
28
+ remove_from_org: remove_from_org ?? false,
26
29
  org_id,
27
30
  });
28
31
  const mode = (execute ?? false) ? 'Offboarding complete.' : 'Offboarding audit (read-only). Set execute=true to proceed.';
@@ -39,6 +42,7 @@ defineTool({
39
42
  toolset: 'compound',
40
43
  auth: 'cookie',
41
44
  mutates: true,
45
+ adminOnly: true,
42
46
  register(server, config) {
43
47
  server.registerTool('onboard_user', {
44
48
  description: 'Invite a user to teams and optionally share files and set seat type. Sends invite emails per team.',
@@ -74,6 +78,7 @@ defineTool({
74
78
  defineTool({
75
79
  toolset: 'compound',
76
80
  auth: 'cookie',
81
+ adminOnly: true,
77
82
  register(server, config) {
78
83
  server.registerTool('quarterly_design_ops_report', {
79
84
  description: 'Org-wide design ops snapshot: seat utilization, team activity, billing, and library adoption over a given period.',
@@ -27,6 +27,7 @@ defineTool({
27
27
  defineTool({
28
28
  toolset: 'compound',
29
29
  auth: 'cookie',
30
+ adminOnly: true,
30
31
  register(server, config) {
31
32
  server.registerTool('workspace_overview', {
32
33
  description: 'Full org snapshot: teams with member/project counts, seat breakdown, and billing summary.',
@@ -152,6 +153,7 @@ defineTool({
152
153
  defineTool({
153
154
  toolset: 'compound',
154
155
  auth: 'cookie',
156
+ adminOnly: true,
155
157
  register(server, config) {
156
158
  server.registerTool('seat_optimization', {
157
159
  description: 'Identify inactive paid seats and calculate potential savings. Fetches members, seat counts, and pricing to find optimization opportunities.',
@@ -182,6 +184,7 @@ defineTool({
182
184
  defineTool({
183
185
  toolset: 'compound',
184
186
  auth: 'cookie',
187
+ adminOnly: true,
185
188
  register(server, config) {
186
189
  server.registerTool('permission_audit', {
187
190
  description: 'Audit permissions across a team or project. Scans files for external editors, open link access, and elevated individual permissions.',
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=dev-resources.d.ts.map
@@ -0,0 +1,78 @@
1
+ import { z } from 'zod';
2
+ import { defineTool, toolResult, toolError, toolSummary, figmaId } from './register.js';
3
+ import { formatApiError } from '../helpers.js';
4
+ import { listDevResources, createDevResource, deleteDevResource, } from '../operations/dev-resources.js';
5
+ // -- list_dev_resources --
6
+ defineTool({
7
+ toolset: 'components',
8
+ auth: 'pat',
9
+ register(server, config) {
10
+ server.registerTool('list_dev_resources', {
11
+ description: 'List dev resources (links, annotations) attached to nodes in a file. Used in Dev Mode.',
12
+ inputSchema: {
13
+ file_key: figmaId.describe('File key'),
14
+ node_ids: z.array(figmaId).optional().describe('Filter by specific node IDs'),
15
+ },
16
+ }, async ({ file_key, node_ids }) => {
17
+ try {
18
+ const result = await listDevResources(config, { file_key, node_ids });
19
+ if (result.length === 0)
20
+ return toolResult('No dev resources found.');
21
+ return toolSummary(`${result.length} dev resource(s).`, result, 'Use create_dev_resource to add, or delete_dev_resource to remove.');
22
+ }
23
+ catch (e) {
24
+ return toolError(`Failed to list dev resources: ${formatApiError(e)}`);
25
+ }
26
+ });
27
+ },
28
+ });
29
+ // -- create_dev_resource --
30
+ defineTool({
31
+ toolset: 'components',
32
+ auth: 'pat',
33
+ mutates: true,
34
+ register(server, config) {
35
+ server.registerTool('create_dev_resource', {
36
+ description: 'Create a dev resource (link/annotation) on a node. Visible in Dev Mode.',
37
+ inputSchema: {
38
+ file_key: figmaId.describe('File key'),
39
+ node_id: figmaId.describe('Node ID to attach the resource to'),
40
+ name: z.string().describe('Resource name/label'),
41
+ url: z.string().describe('Resource URL'),
42
+ },
43
+ }, async ({ file_key, node_id, name, url }) => {
44
+ try {
45
+ const result = await createDevResource(config, { file_key, node_id, name, url });
46
+ return toolSummary('Created dev resource.', result);
47
+ }
48
+ catch (e) {
49
+ return toolError(`Failed to create dev resource: ${formatApiError(e)}`);
50
+ }
51
+ });
52
+ },
53
+ });
54
+ // -- delete_dev_resource --
55
+ defineTool({
56
+ toolset: 'components',
57
+ auth: 'pat',
58
+ mutates: true,
59
+ destructive: true,
60
+ register(server, config) {
61
+ server.registerTool('delete_dev_resource', {
62
+ description: 'Delete a dev resource from a file.',
63
+ inputSchema: {
64
+ file_key: figmaId.describe('File key'),
65
+ dev_resource_id: figmaId.describe('Dev resource ID (from list_dev_resources)'),
66
+ },
67
+ }, async ({ file_key, dev_resource_id }) => {
68
+ try {
69
+ await deleteDevResource(config, { file_key, dev_resource_id });
70
+ return toolResult(`Deleted dev resource ${dev_resource_id}.`);
71
+ }
72
+ catch (e) {
73
+ return toolError(`Failed to delete dev resource: ${formatApiError(e)}`);
74
+ }
75
+ });
76
+ },
77
+ });
78
+ //# sourceMappingURL=dev-resources.js.map