figmanage 0.1.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.
- package/LICENSE +21 -0
- package/README.md +338 -0
- package/dist/auth/client.d.ts +15 -0
- package/dist/auth/client.js +29 -0
- package/dist/auth/health.d.ts +5 -0
- package/dist/auth/health.js +93 -0
- package/dist/clients/internal-api.d.ts +4 -0
- package/dist/clients/internal-api.js +50 -0
- package/dist/clients/public-api.d.ts +4 -0
- package/dist/clients/public-api.js +47 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +100 -0
- package/dist/setup.d.ts +3 -0
- package/dist/setup.js +565 -0
- package/dist/tools/analytics.d.ts +2 -0
- package/dist/tools/analytics.js +58 -0
- package/dist/tools/branching.d.ts +2 -0
- package/dist/tools/branching.js +103 -0
- package/dist/tools/comments.d.ts +2 -0
- package/dist/tools/comments.js +147 -0
- package/dist/tools/components.d.ts +2 -0
- package/dist/tools/components.js +104 -0
- package/dist/tools/compound.d.ts +2 -0
- package/dist/tools/compound.js +334 -0
- package/dist/tools/export.d.ts +2 -0
- package/dist/tools/export.js +70 -0
- package/dist/tools/files.d.ts +2 -0
- package/dist/tools/files.js +241 -0
- package/dist/tools/libraries.d.ts +2 -0
- package/dist/tools/libraries.js +31 -0
- package/dist/tools/navigate.d.ts +2 -0
- package/dist/tools/navigate.js +436 -0
- package/dist/tools/org.d.ts +2 -0
- package/dist/tools/org.js +311 -0
- package/dist/tools/permissions.d.ts +2 -0
- package/dist/tools/permissions.js +246 -0
- package/dist/tools/projects.d.ts +2 -0
- package/dist/tools/projects.js +160 -0
- package/dist/tools/reading.d.ts +2 -0
- package/dist/tools/reading.js +60 -0
- package/dist/tools/register.d.ts +32 -0
- package/dist/tools/register.js +76 -0
- package/dist/tools/teams.d.ts +2 -0
- package/dist/tools/teams.js +81 -0
- package/dist/tools/variables.d.ts +2 -0
- package/dist/tools/variables.js +102 -0
- package/dist/tools/versions.d.ts +2 -0
- package/dist/tools/versions.js +69 -0
- package/dist/tools/webhooks.d.ts +2 -0
- package/dist/tools/webhooks.js +126 -0
- package/dist/types/figma.d.ts +58 -0
- package/dist/types/figma.js +2 -0
- package/package.json +55 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { internalClient } from '../clients/internal-api.js';
|
|
2
|
+
import { defineTool, toolResult, toolError, figmaId, requireOrgId } from './register.js';
|
|
3
|
+
// -- list_org_libraries --
|
|
4
|
+
defineTool({
|
|
5
|
+
toolset: 'libraries',
|
|
6
|
+
auth: 'cookie',
|
|
7
|
+
register(server, config) {
|
|
8
|
+
server.registerTool('list_org_libraries', {
|
|
9
|
+
description: 'List all design system libraries in the org with sharing group info.',
|
|
10
|
+
inputSchema: {
|
|
11
|
+
org_id: figmaId.optional().describe('Org ID override (defaults to current workspace)'),
|
|
12
|
+
},
|
|
13
|
+
}, async ({ org_id }) => {
|
|
14
|
+
try {
|
|
15
|
+
let orgId;
|
|
16
|
+
try {
|
|
17
|
+
orgId = requireOrgId(config, org_id);
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
return toolError(e.message);
|
|
21
|
+
}
|
|
22
|
+
const res = await internalClient(config).get('/api/design_systems/libraries', { params: { org_id: orgId, include_sharing_group_info: true } });
|
|
23
|
+
return toolResult(JSON.stringify(res.data, null, 2));
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
return toolError(`Failed to list org libraries: ${e.response?.status || e.message}`);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
//# sourceMappingURL=libraries.js.map
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { publicClient } from '../clients/public-api.js';
|
|
3
|
+
import { internalClient } from '../clients/internal-api.js';
|
|
4
|
+
import { hasCookie, hasPat } from '../auth/client.js';
|
|
5
|
+
import { defineTool, toolResult, toolError, figmaId, requireOrgId } from './register.js';
|
|
6
|
+
import { checkAuth, formatAuthStatus } from '../auth/health.js';
|
|
7
|
+
// -- check_auth --
|
|
8
|
+
defineTool({
|
|
9
|
+
toolset: 'navigate',
|
|
10
|
+
auth: 'either',
|
|
11
|
+
register(server, config) {
|
|
12
|
+
server.registerTool('check_auth', { description: 'Check authentication status for both PAT and session cookie' }, async () => {
|
|
13
|
+
try {
|
|
14
|
+
const status = await checkAuth(config);
|
|
15
|
+
return toolResult(formatAuthStatus(status, config));
|
|
16
|
+
}
|
|
17
|
+
catch (e) {
|
|
18
|
+
return toolError(`Auth check failed: ${e.message}`);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
// -- list_orgs --
|
|
24
|
+
defineTool({
|
|
25
|
+
toolset: 'navigate',
|
|
26
|
+
auth: 'cookie',
|
|
27
|
+
register(server, config) {
|
|
28
|
+
server.registerTool('list_orgs', {
|
|
29
|
+
description: 'List all Figma workspaces (orgs) you belong to. Shows which is currently active.',
|
|
30
|
+
}, async () => {
|
|
31
|
+
try {
|
|
32
|
+
const api = internalClient(config);
|
|
33
|
+
const res = await api.get('/api/user/state');
|
|
34
|
+
let orgs = (res.data?.meta?.orgs || [])
|
|
35
|
+
.filter((o) => o && o.id)
|
|
36
|
+
.map((o) => ({ id: String(o.id), name: o.name || String(o.id) }));
|
|
37
|
+
// Fallback: meta.orgs is empty for non-admin users.
|
|
38
|
+
// Extract unique org_ids from meta.roles instead.
|
|
39
|
+
if (orgs.length === 0) {
|
|
40
|
+
const roles = res.data?.meta?.roles || [];
|
|
41
|
+
const orgIds = [...new Set(roles
|
|
42
|
+
.map((r) => r.org_id)
|
|
43
|
+
.filter((id) => id != null)
|
|
44
|
+
.map(String))];
|
|
45
|
+
// Also include config.orgId if not already covered
|
|
46
|
+
if (config.orgId && !orgIds.includes(config.orgId)) {
|
|
47
|
+
orgIds.push(config.orgId);
|
|
48
|
+
}
|
|
49
|
+
// Resolve names via domain lookup
|
|
50
|
+
orgs = await Promise.all(orgIds.map(async (id) => {
|
|
51
|
+
let name = id;
|
|
52
|
+
try {
|
|
53
|
+
const domRes = await api.get(`/api/orgs/${id}/domains`);
|
|
54
|
+
const domains = domRes.data?.meta || [];
|
|
55
|
+
if (Array.isArray(domains) && domains.length > 0) {
|
|
56
|
+
name = domains[0].domain || id;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch { /* domain lookup optional */ }
|
|
60
|
+
return { id, name };
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
// Merge with any existing registry entries (preserves names from setup)
|
|
64
|
+
if (config.orgs && config.orgs.length > 0) {
|
|
65
|
+
for (const existing of config.orgs) {
|
|
66
|
+
if (!orgs.find(o => o.id === existing.id)) {
|
|
67
|
+
orgs.push(existing);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
// Prefer stored name over ID-only fallback
|
|
71
|
+
const entry = orgs.find(o => o.id === existing.id);
|
|
72
|
+
if (entry.name === entry.id && existing.name !== existing.id) {
|
|
73
|
+
entry.name = existing.name;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (orgs.length > 0)
|
|
79
|
+
config.orgs = orgs;
|
|
80
|
+
if (orgs.length === 0) {
|
|
81
|
+
return toolResult('No workspaces found. You may be on a free/starter plan.');
|
|
82
|
+
}
|
|
83
|
+
const result = orgs.map(o => ({
|
|
84
|
+
id: o.id,
|
|
85
|
+
name: o.name,
|
|
86
|
+
active: o.id === config.orgId,
|
|
87
|
+
}));
|
|
88
|
+
return toolResult(JSON.stringify(result, null, 2));
|
|
89
|
+
}
|
|
90
|
+
catch (e) {
|
|
91
|
+
return toolError(`Failed to list orgs: ${e.response?.status || e.message}`);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
// -- switch_org --
|
|
97
|
+
defineTool({
|
|
98
|
+
toolset: 'navigate',
|
|
99
|
+
auth: 'cookie',
|
|
100
|
+
register(server, config) {
|
|
101
|
+
server.registerTool('switch_org', {
|
|
102
|
+
description: 'Switch active workspace. Accepts org name (fuzzy match) or ID. Use list_orgs to see available workspaces.',
|
|
103
|
+
inputSchema: {
|
|
104
|
+
org: z.string().describe('Org name or ID to switch to'),
|
|
105
|
+
},
|
|
106
|
+
}, async ({ org }) => {
|
|
107
|
+
try {
|
|
108
|
+
// Ensure we have an org list
|
|
109
|
+
if (!config.orgs || config.orgs.length === 0) {
|
|
110
|
+
const res = await internalClient(config).get('/api/user/state');
|
|
111
|
+
const orgs = (res.data?.meta?.orgs || []).map((o) => ({
|
|
112
|
+
id: String(o.id),
|
|
113
|
+
name: o.name,
|
|
114
|
+
}));
|
|
115
|
+
if (orgs.length > 0)
|
|
116
|
+
config.orgs = orgs;
|
|
117
|
+
}
|
|
118
|
+
if (!config.orgs || config.orgs.length === 0) {
|
|
119
|
+
return toolError('No workspaces found. You may be on a free/starter plan.');
|
|
120
|
+
}
|
|
121
|
+
// Try exact ID match first
|
|
122
|
+
let match = config.orgs.find(o => o.id === org);
|
|
123
|
+
// Then case-insensitive substring match on name
|
|
124
|
+
if (!match) {
|
|
125
|
+
const lower = org.toLowerCase();
|
|
126
|
+
const matches = config.orgs.filter(o => o.name.toLowerCase().includes(lower));
|
|
127
|
+
if (matches.length === 1) {
|
|
128
|
+
match = matches[0];
|
|
129
|
+
}
|
|
130
|
+
else if (matches.length > 1) {
|
|
131
|
+
const names = matches.map(o => `${o.name} (${o.id})`).join(', ');
|
|
132
|
+
return toolError(`Ambiguous: "${org}" matches multiple workspaces: ${names}. Be more specific or use the org ID.`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (!match) {
|
|
136
|
+
const available = config.orgs.map(o => `${o.name} (${o.id})`).join(', ');
|
|
137
|
+
return toolError(`No workspace matching "${org}". Available: ${available}`);
|
|
138
|
+
}
|
|
139
|
+
const previousId = config.orgId;
|
|
140
|
+
const previousOrg = config.orgs.find(o => o.id === previousId);
|
|
141
|
+
const previousLabel = previousOrg ? `${previousOrg.name} (${previousId})` : (previousId || 'none');
|
|
142
|
+
config.orgId = match.id;
|
|
143
|
+
return toolResult(`Switched workspace: ${previousLabel} -> ${match.name} (${match.id})`);
|
|
144
|
+
}
|
|
145
|
+
catch (e) {
|
|
146
|
+
return toolError(`Failed to switch org: ${e.response?.status || e.message}`);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
// -- list_teams --
|
|
152
|
+
defineTool({
|
|
153
|
+
toolset: 'navigate',
|
|
154
|
+
auth: 'cookie',
|
|
155
|
+
register(server, config) {
|
|
156
|
+
server.registerTool('list_teams', {
|
|
157
|
+
description: 'List all teams you belong to. Returns team IDs and names.',
|
|
158
|
+
}, async () => {
|
|
159
|
+
try {
|
|
160
|
+
if (config.orgId) {
|
|
161
|
+
const res = await internalClient(config).get(`/api/orgs/${config.orgId}/teams`);
|
|
162
|
+
const data = res.data?.meta || res.data;
|
|
163
|
+
const teams = (data.teams || (Array.isArray(data) ? data : [])).map((t) => ({
|
|
164
|
+
id: String(t.id),
|
|
165
|
+
name: t.name,
|
|
166
|
+
}));
|
|
167
|
+
return toolResult(JSON.stringify(teams, null, 2));
|
|
168
|
+
}
|
|
169
|
+
// Fallback: get teams from user/state
|
|
170
|
+
const res = await internalClient(config).get('/api/user/state');
|
|
171
|
+
const teams = (res.data?.meta?.teams || []).map((t) => ({
|
|
172
|
+
id: String(t.id),
|
|
173
|
+
name: t.name,
|
|
174
|
+
}));
|
|
175
|
+
return toolResult(JSON.stringify(teams, null, 2));
|
|
176
|
+
}
|
|
177
|
+
catch (e) {
|
|
178
|
+
return toolError(`Failed to list teams: ${e.response?.status || e.message}`);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
// -- list_projects --
|
|
184
|
+
defineTool({
|
|
185
|
+
toolset: 'navigate',
|
|
186
|
+
auth: 'either',
|
|
187
|
+
register(server, config) {
|
|
188
|
+
server.registerTool('list_projects', {
|
|
189
|
+
description: 'List projects (folders) in a team. Returns project IDs, names.',
|
|
190
|
+
inputSchema: {
|
|
191
|
+
team_id: figmaId.describe('Team ID'),
|
|
192
|
+
},
|
|
193
|
+
}, async ({ team_id }) => {
|
|
194
|
+
try {
|
|
195
|
+
if (hasCookie(config)) {
|
|
196
|
+
const res = await internalClient(config).get(`/api/teams/${team_id}/folders`);
|
|
197
|
+
const rows = res.data?.meta?.folder_rows || res.data || [];
|
|
198
|
+
const projects = (Array.isArray(rows) ? rows : []).map((p) => ({
|
|
199
|
+
id: String(p.id),
|
|
200
|
+
name: p.name || p.path,
|
|
201
|
+
}));
|
|
202
|
+
return toolResult(JSON.stringify(projects, null, 2));
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
const res = await publicClient(config).get(`/v1/teams/${team_id}/projects`);
|
|
206
|
+
const projects = (res.data.projects || []).map((p) => ({
|
|
207
|
+
id: String(p.id),
|
|
208
|
+
name: p.name,
|
|
209
|
+
}));
|
|
210
|
+
return toolResult(JSON.stringify(projects, null, 2));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
catch (e) {
|
|
214
|
+
return toolError(`Failed to list projects: ${e.response?.status || e.message}`);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
// -- list_files --
|
|
220
|
+
defineTool({
|
|
221
|
+
toolset: 'navigate',
|
|
222
|
+
auth: 'either',
|
|
223
|
+
register(server, config) {
|
|
224
|
+
server.registerTool('list_files', {
|
|
225
|
+
description: 'List files in a project. Returns file keys, names, last modified, editor type. Supports pagination.',
|
|
226
|
+
inputSchema: {
|
|
227
|
+
project_id: figmaId.describe('Project (folder) ID'),
|
|
228
|
+
page_size: z.number().optional().describe('Results per page (default 25, max 100)'),
|
|
229
|
+
page_token: z.string().optional().describe('Pagination token from previous response'),
|
|
230
|
+
},
|
|
231
|
+
}, async ({ project_id, page_size, page_token }) => {
|
|
232
|
+
try {
|
|
233
|
+
// Prefer public API when PAT available -- returns keys compatible
|
|
234
|
+
// with all public endpoints (versions, comments, export).
|
|
235
|
+
if (hasPat(config)) {
|
|
236
|
+
const res = await publicClient(config).get(`/v1/projects/${project_id}/files`);
|
|
237
|
+
const files = (res.data.files || []).map((f) => ({
|
|
238
|
+
key: f.key,
|
|
239
|
+
name: f.name,
|
|
240
|
+
last_modified: f.last_modified,
|
|
241
|
+
thumbnail_url: f.thumbnail_url,
|
|
242
|
+
}));
|
|
243
|
+
return toolResult(JSON.stringify({ files }, null, 2));
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
const pageSize = Math.min(page_size || 25, 100);
|
|
247
|
+
const params = new URLSearchParams({
|
|
248
|
+
folderId: project_id,
|
|
249
|
+
sort_column: 'touched_at',
|
|
250
|
+
sort_order: 'desc',
|
|
251
|
+
page_size: String(pageSize),
|
|
252
|
+
file_type: '',
|
|
253
|
+
});
|
|
254
|
+
if (page_token)
|
|
255
|
+
params.set('before', page_token);
|
|
256
|
+
const res = await internalClient(config).get(`/api/folders/${project_id}/paginated_files?${params}`);
|
|
257
|
+
const meta = res.data?.meta || res.data;
|
|
258
|
+
const files = (meta.files || meta.results || []).map((f) => ({
|
|
259
|
+
key: f.key,
|
|
260
|
+
name: f.name,
|
|
261
|
+
last_modified: f.touched_at || f.updated_at || f.last_modified,
|
|
262
|
+
editor_type: f.editor_type,
|
|
263
|
+
}));
|
|
264
|
+
const pagination = res.data?.pagination;
|
|
265
|
+
const result = { files };
|
|
266
|
+
if (pagination?.next_page || files.length === pageSize) {
|
|
267
|
+
result.pagination = {
|
|
268
|
+
has_more: true,
|
|
269
|
+
hint: `To get the next page, call list_files again with page_token="${pagination?.next_page || files[files.length - 1]?.last_modified || ''}"`,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
return toolResult(JSON.stringify(result, null, 2));
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
catch (e) {
|
|
276
|
+
return toolError(`Failed to list files: ${e.response?.status || e.message}`);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
// -- list_recent_files --
|
|
282
|
+
defineTool({
|
|
283
|
+
toolset: 'navigate',
|
|
284
|
+
auth: 'cookie',
|
|
285
|
+
register(server, config) {
|
|
286
|
+
server.registerTool('list_recent_files', {
|
|
287
|
+
description: 'List your recently viewed/edited files across all teams.',
|
|
288
|
+
}, async () => {
|
|
289
|
+
try {
|
|
290
|
+
const res = await internalClient(config).get('/api/recent_files');
|
|
291
|
+
const files = (res.data?.meta?.recent_files || []).map((f) => ({
|
|
292
|
+
key: f.key,
|
|
293
|
+
name: f.name,
|
|
294
|
+
team_id: f.team_id,
|
|
295
|
+
folder_id: f.folder_id,
|
|
296
|
+
editor_type: f.editor_type,
|
|
297
|
+
last_touched: f.touched_at,
|
|
298
|
+
url: f.url || `https://www.figma.com/design/${f.key}`,
|
|
299
|
+
}));
|
|
300
|
+
return toolResult(JSON.stringify(files, null, 2));
|
|
301
|
+
}
|
|
302
|
+
catch (e) {
|
|
303
|
+
return toolError(`Failed to list recent files: ${e.response?.status || e.message}`);
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
// -- search --
|
|
309
|
+
defineTool({
|
|
310
|
+
toolset: 'navigate',
|
|
311
|
+
auth: 'cookie',
|
|
312
|
+
register(server, config) {
|
|
313
|
+
server.registerTool('search', {
|
|
314
|
+
description: 'Search for files across the workspace. Requires org context for results.',
|
|
315
|
+
inputSchema: {
|
|
316
|
+
query: z.string().describe('Search query'),
|
|
317
|
+
sort: z.enum(['relevancy', 'last_modified']).optional().describe('Sort order (default: relevancy)'),
|
|
318
|
+
org_id: figmaId.optional().describe('Org ID override (defaults to current workspace)'),
|
|
319
|
+
},
|
|
320
|
+
}, async ({ query, sort, org_id }) => {
|
|
321
|
+
try {
|
|
322
|
+
let orgId;
|
|
323
|
+
try {
|
|
324
|
+
orgId = requireOrgId(config, org_id);
|
|
325
|
+
}
|
|
326
|
+
catch (e) {
|
|
327
|
+
return toolError(e.message);
|
|
328
|
+
}
|
|
329
|
+
const params = {
|
|
330
|
+
query,
|
|
331
|
+
sort: sort || 'relevancy',
|
|
332
|
+
desc: 'true',
|
|
333
|
+
is_global: 'true',
|
|
334
|
+
org_id: orgId,
|
|
335
|
+
current_org_id: orgId,
|
|
336
|
+
plan_type: 'org',
|
|
337
|
+
};
|
|
338
|
+
const res = await internalClient(config).get('/api/search/file_browser_preview', { params });
|
|
339
|
+
const meta = res.data?.meta;
|
|
340
|
+
// Response is an array of category objects; files are in the first entry
|
|
341
|
+
const categories = Array.isArray(meta) ? meta : [];
|
|
342
|
+
const fileCategory = categories.find((c) => c.search_model_type === 'files') || categories[0];
|
|
343
|
+
const rawResults = fileCategory?.results || [];
|
|
344
|
+
const results = rawResults.map((r) => {
|
|
345
|
+
const m = r.model || r;
|
|
346
|
+
return {
|
|
347
|
+
key: m.key,
|
|
348
|
+
name: m.name,
|
|
349
|
+
editor_type: m.editor_type,
|
|
350
|
+
team_id: m.team_id ? String(m.team_id) : undefined,
|
|
351
|
+
folder_id: m.folder_id ? String(m.folder_id) : undefined,
|
|
352
|
+
last_modified: m.touched_at,
|
|
353
|
+
url: m.url || `https://www.figma.com/design/${m.key}`,
|
|
354
|
+
};
|
|
355
|
+
});
|
|
356
|
+
if (results.length === 0) {
|
|
357
|
+
return toolResult('No results. Try list_recent_files or browse via list_projects + list_files.');
|
|
358
|
+
}
|
|
359
|
+
return toolResult(JSON.stringify(results, null, 2));
|
|
360
|
+
}
|
|
361
|
+
catch (e) {
|
|
362
|
+
return toolError(`Search failed: ${e.response?.status || e.message}`);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
// -- get_file_info --
|
|
368
|
+
defineTool({
|
|
369
|
+
toolset: 'navigate',
|
|
370
|
+
auth: 'either',
|
|
371
|
+
register(server, config) {
|
|
372
|
+
server.registerTool('get_file_info', {
|
|
373
|
+
description: 'Get metadata for a file: name, last modified, editor type, project, team.',
|
|
374
|
+
inputSchema: {
|
|
375
|
+
file_key: figmaId.describe('Figma file key (from the URL)'),
|
|
376
|
+
},
|
|
377
|
+
}, async ({ file_key }) => {
|
|
378
|
+
try {
|
|
379
|
+
// Prefer public API for consistent key format.
|
|
380
|
+
if (hasPat(config)) {
|
|
381
|
+
const res = await publicClient(config).get(`/v1/files/${file_key}/meta`);
|
|
382
|
+
const f = res.data.file || res.data;
|
|
383
|
+
return toolResult(JSON.stringify({
|
|
384
|
+
key: f.key || file_key,
|
|
385
|
+
name: f.name,
|
|
386
|
+
last_modified: f.lastModified || f.last_modified,
|
|
387
|
+
editor_type: f.editorType || f.editor_type,
|
|
388
|
+
url: `https://www.figma.com/design/${file_key}`,
|
|
389
|
+
}, null, 2));
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
const res = await internalClient(config).get(`/api/files/${file_key}`);
|
|
393
|
+
const f = res.data?.meta || res.data;
|
|
394
|
+
return toolResult(JSON.stringify({
|
|
395
|
+
key: f.key || file_key,
|
|
396
|
+
name: f.name,
|
|
397
|
+
updated_at: f.updated_at,
|
|
398
|
+
editor_type: f.editor_type,
|
|
399
|
+
folder_id: f.folder_id ? String(f.folder_id) : undefined,
|
|
400
|
+
team_id: f.team_id ? String(f.team_id) : undefined,
|
|
401
|
+
link_access: f.link_access,
|
|
402
|
+
url: f.url || `https://www.figma.com/design/${file_key}`,
|
|
403
|
+
}, null, 2));
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
catch (e) {
|
|
407
|
+
return toolError(`Failed to get file info: ${e.response?.status || e.message}`);
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
},
|
|
411
|
+
});
|
|
412
|
+
// -- list_favorites --
|
|
413
|
+
defineTool({
|
|
414
|
+
toolset: 'navigate',
|
|
415
|
+
auth: 'cookie',
|
|
416
|
+
register(server, config) {
|
|
417
|
+
server.registerTool('list_favorites', {
|
|
418
|
+
description: 'List your starred/favorited files. Note: may not work on all account types.',
|
|
419
|
+
}, async () => {
|
|
420
|
+
try {
|
|
421
|
+
const res = await internalClient(config).get('/api/user/favorited_resources');
|
|
422
|
+
const data = res.data?.meta || res.data;
|
|
423
|
+
const favorites = (Array.isArray(data) ? data : data.favorites || data.resources || []).map((f) => ({
|
|
424
|
+
key: f.key || f.file_key || f.id,
|
|
425
|
+
name: f.name,
|
|
426
|
+
type: f.resource_type || f.type,
|
|
427
|
+
}));
|
|
428
|
+
return toolResult(JSON.stringify(favorites, null, 2));
|
|
429
|
+
}
|
|
430
|
+
catch (e) {
|
|
431
|
+
return toolError(`Failed to list favorites: ${e.response?.status || e.message}`);
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
},
|
|
435
|
+
});
|
|
436
|
+
//# sourceMappingURL=navigate.js.map
|