figmanage 1.0.1 → 1.2.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 (98) hide show
  1. package/README.md +74 -59
  2. package/dist/cli/analytics.d.ts +3 -0
  3. package/dist/cli/analytics.js +48 -0
  4. package/dist/cli/branching.d.ts +3 -0
  5. package/dist/cli/branching.js +56 -0
  6. package/dist/cli/comments.d.ts +3 -0
  7. package/dist/cli/comments.js +86 -0
  8. package/dist/cli/completion.d.ts +7 -0
  9. package/dist/cli/completion.js +160 -0
  10. package/dist/cli/components.d.ts +3 -0
  11. package/dist/cli/components.js +82 -0
  12. package/dist/cli/compound-commands.d.ts +14 -0
  13. package/dist/cli/compound-commands.js +291 -0
  14. package/dist/cli/export.d.ts +3 -0
  15. package/dist/cli/export.js +51 -0
  16. package/dist/cli/files.d.ts +3 -0
  17. package/dist/cli/files.js +156 -0
  18. package/dist/cli/format.js +147 -2
  19. package/dist/cli/helpers.d.ts +7 -0
  20. package/dist/cli/helpers.js +43 -0
  21. package/dist/cli/index.js +68 -89
  22. package/dist/cli/libraries.d.ts +3 -0
  23. package/dist/cli/libraries.js +26 -0
  24. package/dist/cli/navigate.d.ts +3 -0
  25. package/dist/cli/navigate.js +192 -0
  26. package/dist/cli/org.d.ts +3 -0
  27. package/dist/cli/org.js +227 -0
  28. package/dist/cli/permissions.d.ts +3 -0
  29. package/dist/cli/permissions.js +133 -0
  30. package/dist/cli/projects.d.ts +3 -0
  31. package/dist/cli/projects.js +110 -0
  32. package/dist/cli/reading.d.ts +3 -0
  33. package/dist/cli/reading.js +51 -0
  34. package/dist/cli/teams.d.ts +3 -0
  35. package/dist/cli/teams.js +56 -0
  36. package/dist/cli/variables.d.ts +3 -0
  37. package/dist/cli/variables.js +80 -0
  38. package/dist/cli/versions.d.ts +3 -0
  39. package/dist/cli/versions.js +46 -0
  40. package/dist/cli/webhooks.d.ts +3 -0
  41. package/dist/cli/webhooks.js +100 -0
  42. package/dist/operations/analytics.d.ts +10 -0
  43. package/dist/operations/analytics.js +15 -0
  44. package/dist/operations/branching.d.ts +24 -0
  45. package/dist/operations/branching.js +41 -0
  46. package/dist/operations/comments.d.ts +43 -0
  47. package/dist/operations/comments.js +65 -0
  48. package/dist/operations/components.d.ts +24 -0
  49. package/dist/operations/components.js +30 -0
  50. package/dist/operations/compound-manager.d.ts +101 -0
  51. package/dist/operations/compound-manager.js +629 -0
  52. package/dist/operations/compound.d.ts +102 -0
  53. package/dist/operations/compound.js +595 -0
  54. package/dist/operations/export.d.ts +19 -0
  55. package/dist/operations/export.js +27 -0
  56. package/dist/operations/files.d.ts +55 -0
  57. package/dist/operations/files.js +89 -0
  58. package/dist/operations/libraries.d.ts +5 -0
  59. package/dist/operations/libraries.js +10 -0
  60. package/dist/operations/navigate.d.ts +99 -0
  61. package/dist/operations/navigate.js +266 -0
  62. package/dist/operations/org.d.ts +95 -0
  63. package/dist/operations/org.js +205 -0
  64. package/dist/operations/permissions.d.ts +59 -0
  65. package/dist/operations/permissions.js +112 -0
  66. package/dist/operations/projects.d.ts +29 -0
  67. package/dist/operations/projects.js +40 -0
  68. package/dist/operations/reading.d.ts +12 -0
  69. package/dist/operations/reading.js +20 -0
  70. package/dist/operations/teams.d.ts +17 -0
  71. package/dist/operations/teams.js +17 -0
  72. package/dist/operations/variables.d.ts +17 -0
  73. package/dist/operations/variables.js +39 -0
  74. package/dist/operations/versions.d.ts +23 -0
  75. package/dist/operations/versions.js +27 -0
  76. package/dist/operations/webhooks.d.ts +25 -0
  77. package/dist/operations/webhooks.js +38 -0
  78. package/dist/tools/analytics.js +6 -16
  79. package/dist/tools/branching.js +7 -36
  80. package/dist/tools/comments.js +9 -56
  81. package/dist/tools/components.js +7 -19
  82. package/dist/tools/compound-manager.js +21 -644
  83. package/dist/tools/compound.js +32 -566
  84. package/dist/tools/export.js +4 -23
  85. package/dist/tools/files.js +21 -68
  86. package/dist/tools/libraries.js +4 -11
  87. package/dist/tools/navigate.js +23 -246
  88. package/dist/tools/org.js +29 -245
  89. package/dist/tools/permissions.js +18 -97
  90. package/dist/tools/projects.js +8 -27
  91. package/dist/tools/reading.js +5 -15
  92. package/dist/tools/teams.js +8 -16
  93. package/dist/tools/variables.js +13 -30
  94. package/dist/tools/versions.js +6 -24
  95. package/dist/tools/webhooks.js +7 -24
  96. package/package.json +1 -1
  97. package/dist/cli/commands.d.ts +0 -47
  98. package/dist/cli/commands.js +0 -1204
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
- import { publicClient } from '../clients/public-api.js';
3
2
  import { defineTool, toolResult, toolError, figmaId } from './register.js';
3
+ import { exportNodes, getImageFills } from '../operations/export.js';
4
4
  // -- export_nodes --
5
5
  defineTool({
6
6
  toolset: 'export',
@@ -16,22 +16,8 @@ defineTool({
16
16
  },
17
17
  }, async ({ file_key, node_ids, format, scale }) => {
18
18
  try {
19
- const params = {
20
- ids: node_ids.join(','),
21
- format: format || 'png',
22
- };
23
- if (scale)
24
- params.scale = String(Math.min(Math.max(scale, 0.01), 4));
25
- const res = await publicClient(config).get(`/v1/images/${file_key}`, { params });
26
- const images = res.data?.images || {};
27
- const err = res.data?.err;
28
- if (err)
29
- return toolError(`Export error: ${err}`);
30
- const results = Object.entries(images).map(([nodeId, url]) => ({
31
- node_id: nodeId,
32
- url,
33
- }));
34
- return toolResult(JSON.stringify(results, null, 2));
19
+ const result = await exportNodes(config, { file_key, node_ids, format, scale });
20
+ return toolResult(JSON.stringify(result, null, 2));
35
21
  }
36
22
  catch (e) {
37
23
  return toolError(`Failed to export nodes: ${e.response?.status || e.message}`);
@@ -51,12 +37,7 @@ defineTool({
51
37
  },
52
38
  }, async ({ file_key }) => {
53
39
  try {
54
- const res = await publicClient(config).get(`/v1/files/${file_key}/images`);
55
- const images = res.data?.meta?.images || res.data?.images || {};
56
- const results = Object.entries(images).map(([ref, url]) => ({
57
- image_ref: ref,
58
- url,
59
- }));
40
+ const results = await getImageFills(config, { file_key });
60
41
  if (results.length === 0)
61
42
  return toolResult('No image fills in this file.');
62
43
  return toolResult(JSON.stringify(results, null, 2));
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
- import { internalClient } from '../clients/internal-api.js';
3
- import { defineTool, toolResult, toolError, figmaId, resolveOrgId } from './register.js';
2
+ import { defineTool, toolResult, toolError, figmaId } from './register.js';
3
+ import { createFile, renameFile, moveFiles, duplicateFile, trashFiles, restoreFiles, favoriteFile, setLinkAccess, } from '../operations/files.js';
4
4
  // -- create_file --
5
5
  defineTool({
6
6
  toolset: 'files',
@@ -16,18 +16,8 @@ defineTool({
16
16
  },
17
17
  }, async ({ project_id, editor_type, org_id }) => {
18
18
  try {
19
- const res = await internalClient(config).post('/api/files/create', {
20
- folder_id: project_id,
21
- org_id: resolveOrgId(config, org_id),
22
- editor_type: editor_type || 'design',
23
- });
24
- const f = res.data?.meta?.fig_file || res.data?.meta || res.data;
25
- return toolResult(JSON.stringify({
26
- key: f.key,
27
- name: f.name,
28
- editor_type: f.editor_type,
29
- url: `https://www.figma.com/design/${f.key}`,
30
- }, null, 2));
19
+ const result = await createFile(config, { project_id, editor_type, org_id });
20
+ return toolResult(JSON.stringify(result, null, 2));
31
21
  }
32
22
  catch (e) {
33
23
  return toolError(`Failed to create file: ${e.response?.status || e.message}`);
@@ -49,10 +39,7 @@ defineTool({
49
39
  },
50
40
  }, async ({ file_key, name }) => {
51
41
  try {
52
- await internalClient(config).put(`/api/files/${file_key}`, {
53
- key: file_key,
54
- name,
55
- });
42
+ await renameFile(config, { file_key, name });
56
43
  return toolResult(`Renamed to "${name}"`);
57
44
  }
58
45
  catch (e) {
@@ -75,19 +62,10 @@ defineTool({
75
62
  },
76
63
  }, async ({ file_keys, destination_project_id }) => {
77
64
  try {
78
- const files = file_keys.map(key => ({
79
- key,
80
- folder_id: destination_project_id,
81
- is_multi_move: file_keys.length > 1,
82
- restore_files: false,
83
- }));
84
- const res = await internalClient(config).put('/api/files_batch', { files });
85
- const data = res.data?.meta || res.data;
86
- const succeeded = Object.keys(data.success || {}).length;
87
- const failed = Object.keys(data.errors || {}).length;
88
- let msg = `Moved ${succeeded} file(s) to project ${destination_project_id}`;
89
- if (failed > 0)
90
- msg += `. ${failed} failed: ${JSON.stringify(data.errors)}`;
65
+ const result = await moveFiles(config, { file_keys, destination_project_id });
66
+ let msg = `Moved ${result.succeeded} file(s) to project ${destination_project_id}`;
67
+ if (result.failed > 0)
68
+ msg += `. ${result.failed} failed: ${JSON.stringify(result.errors)}`;
91
69
  return toolResult(msg);
92
70
  }
93
71
  catch (e) {
@@ -110,16 +88,8 @@ defineTool({
110
88
  },
111
89
  }, async ({ file_key, project_id }) => {
112
90
  try {
113
- const res = await internalClient(config).post(`/api/multiplayer/${file_key}/copy`, null, {
114
- headers: { 'Content-Length': '0' },
115
- params: project_id ? { folder_id: project_id } : undefined,
116
- });
117
- const f = res.data?.meta?.fig_file || res.data?.meta || res.data;
118
- return toolResult(JSON.stringify({
119
- key: f.key,
120
- name: f.name,
121
- url: `https://www.figma.com/design/${f.key}`,
122
- }, null, 2));
91
+ const result = await duplicateFile(config, { file_key, project_id });
92
+ return toolResult(JSON.stringify(result, null, 2));
123
93
  }
124
94
  catch (e) {
125
95
  return toolError(`Failed to duplicate file: ${e.response?.status || e.message}`);
@@ -141,13 +111,8 @@ defineTool({
141
111
  },
142
112
  }, async ({ file_keys }) => {
143
113
  try {
144
- const files = file_keys.map(key => ({ key }));
145
- const res = await internalClient(config).delete('/api/files_batch', {
146
- data: { files, trashed: true },
147
- });
148
- const data = res.data?.meta || res.data;
149
- const succeeded = Object.keys(data.success || {}).length;
150
- return toolResult(`Trashed ${succeeded} file(s)`);
114
+ const result = await trashFiles(config, { file_keys });
115
+ return toolResult(`Trashed ${result.succeeded} file(s)`);
151
116
  }
152
117
  catch (e) {
153
118
  return toolError(`Failed to trash files: ${e.response?.status || e.message}`);
@@ -168,14 +133,10 @@ defineTool({
168
133
  },
169
134
  }, async ({ file_keys }) => {
170
135
  try {
171
- const files = file_keys.map(key => ({ key }));
172
- const res = await internalClient(config).post('/api/files_batch/restore', { files });
173
- const data = res.data?.meta || res.data;
174
- const succeeded = Object.keys(data?.success || {}).length || file_keys.length;
175
- const failed = Object.keys(data?.errors || {}).length;
176
- let msg = `Restored ${succeeded} file(s)`;
177
- if (failed > 0)
178
- msg += `. ${failed} failed: ${JSON.stringify(data.errors)}`;
136
+ const result = await restoreFiles(config, { file_keys });
137
+ let msg = `Restored ${result.succeeded} file(s)`;
138
+ if (result.failed > 0)
139
+ msg += `. ${result.failed} failed: ${JSON.stringify(result.errors)}`;
179
140
  return toolResult(msg);
180
141
  }
181
142
  catch (e) {
@@ -198,13 +159,8 @@ defineTool({
198
159
  },
199
160
  }, async ({ file_key, favorited }) => {
200
161
  try {
201
- const isFavorited = favorited !== false;
202
- await internalClient(config).put('/api/favorited_resources', {
203
- resource_type: 'file',
204
- resource_id_or_key: file_key,
205
- is_favorited: isFavorited,
206
- });
207
- return toolResult(`${isFavorited ? 'Favorited' : 'Unfavorited'} file ${file_key}`);
162
+ const result = await favoriteFile(config, { file_key, favorited });
163
+ return toolResult(`${result.favorited ? 'Favorited' : 'Unfavorited'} file ${file_key}`);
208
164
  }
209
165
  catch (e) {
210
166
  return toolError(`Failed to toggle favorite: ${e.response?.status || e.message}`);
@@ -226,11 +182,8 @@ defineTool({
226
182
  },
227
183
  }, async ({ file_key, link_access }) => {
228
184
  try {
229
- const res = await internalClient(config).put(`/api/files/${file_key}`, {
230
- link_access: link_access || 'inherit',
231
- });
232
- const newAccess = res.data?.meta?.link_access || link_access || 'inherit';
233
- return toolResult(`Set link access to "${newAccess}" on file ${file_key}`);
185
+ const result = await setLinkAccess(config, { file_key, link_access });
186
+ return toolResult(`Set link access to "${result.link_access}" on file ${file_key}`);
234
187
  }
235
188
  catch (e) {
236
189
  return toolError(`Failed to set link access: ${e.response?.status || e.message}`);
@@ -1,5 +1,5 @@
1
- import { internalClient } from '../clients/internal-api.js';
2
- import { defineTool, toolResult, toolError, figmaId, requireOrgId } from './register.js';
1
+ import { defineTool, toolResult, toolError, figmaId } from './register.js';
2
+ import { listOrgLibraries } from '../operations/libraries.js';
3
3
  // -- list_org_libraries --
4
4
  defineTool({
5
5
  toolset: 'libraries',
@@ -12,15 +12,8 @@ defineTool({
12
12
  },
13
13
  }, async ({ org_id }) => {
14
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));
15
+ const result = await listOrgLibraries(config, { org_id });
16
+ return toolResult(JSON.stringify(result, null, 2));
24
17
  }
25
18
  catch (e) {
26
19
  return toolError(`Failed to list org libraries: ${e.response?.status || e.message}`);
@@ -1,9 +1,6 @@
1
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';
2
+ import { defineTool, toolResult, toolError, figmaId } from './register.js';
3
+ import { checkAuthStatus, listOrgs, switchOrg, listTeams, listProjects, listFiles, listRecentFiles, search, getFileInfo, listFavorites, } from '../operations/navigate.js';
7
4
  // -- check_auth --
8
5
  defineTool({
9
6
  toolset: 'navigate',
@@ -11,8 +8,8 @@ defineTool({
11
8
  register(server, config) {
12
9
  server.registerTool('check_auth', { description: 'Check authentication status for both PAT and session cookie' }, async () => {
13
10
  try {
14
- const status = await checkAuth(config);
15
- return toolResult(formatAuthStatus(status, config));
11
+ const result = await checkAuthStatus(config);
12
+ return toolResult(result.formatted);
16
13
  }
17
14
  catch (e) {
18
15
  return toolError(`Auth check failed: ${e.message}`);
@@ -29,63 +26,15 @@ defineTool({
29
26
  description: 'List all Figma workspaces (orgs) you belong to. Shows which is currently active.',
30
27
  }, async () => {
31
28
  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
- }
29
+ const orgs = await listOrgs(config);
30
+ // Side effect: update config org registry
31
+ if (orgs.length > 0) {
32
+ config.orgs = orgs.map(o => ({ id: o.id, name: o.name }));
77
33
  }
78
- if (orgs.length > 0)
79
- config.orgs = orgs;
80
34
  if (orgs.length === 0) {
81
35
  return toolResult('No workspaces found. You may be on a free/starter plan.');
82
36
  }
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));
37
+ return toolResult(JSON.stringify(orgs, null, 2));
89
38
  }
90
39
  catch (e) {
91
40
  return toolError(`Failed to list orgs: ${e.response?.status || e.message}`);
@@ -105,42 +54,10 @@ defineTool({
105
54
  },
106
55
  }, async ({ org }) => {
107
56
  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})`);
57
+ const result = await switchOrg(config, { org });
58
+ // Side effect: update config org ID
59
+ config.orgId = result.current.id;
60
+ return toolResult(`Switched workspace: ${result.previous} -> ${result.current.name} (${result.current.id})`);
144
61
  }
145
62
  catch (e) {
146
63
  return toolError(`Failed to switch org: ${e.response?.status || e.message}`);
@@ -157,21 +74,7 @@ defineTool({
157
74
  description: 'List all teams you belong to. Returns team IDs and names.',
158
75
  }, async () => {
159
76
  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
- }));
77
+ const teams = await listTeams(config);
175
78
  return toolResult(JSON.stringify(teams, null, 2));
176
79
  }
177
80
  catch (e) {
@@ -192,23 +95,8 @@ defineTool({
192
95
  },
193
96
  }, async ({ team_id }) => {
194
97
  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
- }
98
+ const projects = await listProjects(config, { team_id });
99
+ return toolResult(JSON.stringify(projects, null, 2));
212
100
  }
213
101
  catch (e) {
214
102
  return toolError(`Failed to list projects: ${e.response?.status || e.message}`);
@@ -230,47 +118,8 @@ defineTool({
230
118
  },
231
119
  }, async ({ project_id, page_size, page_token }) => {
232
120
  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
- }
121
+ const result = await listFiles(config, { project_id, page_size, page_token });
122
+ return toolResult(JSON.stringify(result, null, 2));
274
123
  }
275
124
  catch (e) {
276
125
  return toolError(`Failed to list files: ${e.response?.status || e.message}`);
@@ -287,16 +136,7 @@ defineTool({
287
136
  description: 'List your recently viewed/edited files across all teams.',
288
137
  }, async () => {
289
138
  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
- }));
139
+ const files = await listRecentFiles(config);
300
140
  return toolResult(JSON.stringify(files, null, 2));
301
141
  }
302
142
  catch (e) {
@@ -319,40 +159,7 @@ defineTool({
319
159
  },
320
160
  }, async ({ query, sort, org_id }) => {
321
161
  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
- });
162
+ const results = await search(config, { query, sort, org_id });
356
163
  if (results.length === 0) {
357
164
  return toolResult('No results. Try list_recent_files or browse via list_projects + list_files.');
358
165
  }
@@ -376,32 +183,8 @@ defineTool({
376
183
  },
377
184
  }, async ({ file_key }) => {
378
185
  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
- }
186
+ const info = await getFileInfo(config, { file_key });
187
+ return toolResult(JSON.stringify(info, null, 2));
405
188
  }
406
189
  catch (e) {
407
190
  return toolError(`Failed to get file info: ${e.response?.status || e.message}`);
@@ -418,13 +201,7 @@ defineTool({
418
201
  description: 'List your starred/favorited files. Note: may not work on all account types.',
419
202
  }, async () => {
420
203
  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
- }));
204
+ const favorites = await listFavorites(config);
428
205
  return toolResult(JSON.stringify(favorites, null, 2));
429
206
  }
430
207
  catch (e) {