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.
Files changed (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +338 -0
  3. package/dist/auth/client.d.ts +15 -0
  4. package/dist/auth/client.js +29 -0
  5. package/dist/auth/health.d.ts +5 -0
  6. package/dist/auth/health.js +93 -0
  7. package/dist/clients/internal-api.d.ts +4 -0
  8. package/dist/clients/internal-api.js +50 -0
  9. package/dist/clients/public-api.d.ts +4 -0
  10. package/dist/clients/public-api.js +47 -0
  11. package/dist/index.d.ts +19 -0
  12. package/dist/index.js +100 -0
  13. package/dist/setup.d.ts +3 -0
  14. package/dist/setup.js +565 -0
  15. package/dist/tools/analytics.d.ts +2 -0
  16. package/dist/tools/analytics.js +58 -0
  17. package/dist/tools/branching.d.ts +2 -0
  18. package/dist/tools/branching.js +103 -0
  19. package/dist/tools/comments.d.ts +2 -0
  20. package/dist/tools/comments.js +147 -0
  21. package/dist/tools/components.d.ts +2 -0
  22. package/dist/tools/components.js +104 -0
  23. package/dist/tools/compound.d.ts +2 -0
  24. package/dist/tools/compound.js +334 -0
  25. package/dist/tools/export.d.ts +2 -0
  26. package/dist/tools/export.js +70 -0
  27. package/dist/tools/files.d.ts +2 -0
  28. package/dist/tools/files.js +241 -0
  29. package/dist/tools/libraries.d.ts +2 -0
  30. package/dist/tools/libraries.js +31 -0
  31. package/dist/tools/navigate.d.ts +2 -0
  32. package/dist/tools/navigate.js +436 -0
  33. package/dist/tools/org.d.ts +2 -0
  34. package/dist/tools/org.js +311 -0
  35. package/dist/tools/permissions.d.ts +2 -0
  36. package/dist/tools/permissions.js +246 -0
  37. package/dist/tools/projects.d.ts +2 -0
  38. package/dist/tools/projects.js +160 -0
  39. package/dist/tools/reading.d.ts +2 -0
  40. package/dist/tools/reading.js +60 -0
  41. package/dist/tools/register.d.ts +32 -0
  42. package/dist/tools/register.js +76 -0
  43. package/dist/tools/teams.d.ts +2 -0
  44. package/dist/tools/teams.js +81 -0
  45. package/dist/tools/variables.d.ts +2 -0
  46. package/dist/tools/variables.js +102 -0
  47. package/dist/tools/versions.d.ts +2 -0
  48. package/dist/tools/versions.js +69 -0
  49. package/dist/tools/webhooks.d.ts +2 -0
  50. package/dist/tools/webhooks.js +126 -0
  51. package/dist/types/figma.d.ts +58 -0
  52. package/dist/types/figma.js +2 -0
  53. package/package.json +55 -0
@@ -0,0 +1,58 @@
1
+ import { z } from 'zod';
2
+ import { internalClient } from '../clients/internal-api.js';
3
+ import { defineTool, toolResult, toolError, figmaId, requireOrgId } from './register.js';
4
+ // -- library_usage --
5
+ defineTool({
6
+ toolset: 'analytics',
7
+ auth: 'cookie',
8
+ register(server, config) {
9
+ server.registerTool('library_usage', {
10
+ description: 'Team-level library adoption metrics. Shows how a published library is used across teams.',
11
+ inputSchema: {
12
+ library_file_key: figmaId.describe('File key of the library'),
13
+ days: z.number().optional().describe('Lookback period in days (default 30). Suggest 30, 60, 90, or 365.'),
14
+ },
15
+ }, async ({ library_file_key, days }) => {
16
+ try {
17
+ const lookback = days ?? 30;
18
+ const end_ts = Math.floor(Date.now() / 1000);
19
+ const start_ts = end_ts - (lookback * 86400);
20
+ const res = await internalClient(config).get(`/api/dsa/library/${library_file_key}/team_usage`, { params: { start_ts, end_ts } });
21
+ return toolResult(JSON.stringify(res.data, null, 2));
22
+ }
23
+ catch (e) {
24
+ return toolError(`Failed to fetch library usage: ${e.response?.status || e.message}`);
25
+ }
26
+ });
27
+ },
28
+ });
29
+ // -- component_usage --
30
+ defineTool({
31
+ toolset: 'analytics',
32
+ auth: 'cookie',
33
+ register(server, config) {
34
+ server.registerTool('component_usage', {
35
+ description: 'Per-file component usage analytics. Shows which files use a specific component.',
36
+ inputSchema: {
37
+ component_key: figmaId.describe('Component key'),
38
+ org_id: figmaId.optional().describe('Org ID override (defaults to current workspace)'),
39
+ },
40
+ }, async ({ component_key, org_id }) => {
41
+ try {
42
+ let orgId;
43
+ try {
44
+ orgId = requireOrgId(config, org_id);
45
+ }
46
+ catch (e) {
47
+ return toolError(e.message);
48
+ }
49
+ const res = await internalClient(config).get(`/api/design_systems/component/${component_key}/file_usage`, { params: { org_id: orgId, fv: 4 } });
50
+ return toolResult(JSON.stringify(res.data, null, 2));
51
+ }
52
+ catch (e) {
53
+ return toolError(`Failed to fetch component usage: ${e.response?.status || e.message}`);
54
+ }
55
+ });
56
+ },
57
+ });
58
+ //# sourceMappingURL=analytics.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=branching.d.ts.map
@@ -0,0 +1,103 @@
1
+ import { z } from 'zod';
2
+ import { hasCookie } from '../auth/client.js';
3
+ import { publicClient } from '../clients/public-api.js';
4
+ import { internalClient } from '../clients/internal-api.js';
5
+ import { defineTool, toolResult, toolError, figmaId } from './register.js';
6
+ // -- list_branches --
7
+ defineTool({
8
+ toolset: 'branching',
9
+ auth: 'either',
10
+ register(server, config) {
11
+ server.registerTool('list_branches', {
12
+ description: 'List branches of a file.',
13
+ inputSchema: {
14
+ file_key: figmaId.describe('File key of the main file'),
15
+ },
16
+ }, async ({ file_key }) => {
17
+ try {
18
+ let branches;
19
+ if (hasCookie(config)) {
20
+ const res = await internalClient(config).get(`/api/files/${file_key}`);
21
+ const f = res.data?.meta || res.data;
22
+ branches = f.branches || [];
23
+ }
24
+ else {
25
+ const res = await publicClient(config).get(`/v1/files/${file_key}`, {
26
+ params: { branch_data: 'true', depth: '0' },
27
+ });
28
+ branches = res.data?.branches || [];
29
+ }
30
+ if (branches.length === 0)
31
+ return toolResult('No branches found.');
32
+ const mapped = branches.map((b) => ({
33
+ key: b.key,
34
+ name: b.name,
35
+ thumbnail_url: b.thumbnail_url || null,
36
+ last_modified: b.last_modified || null,
37
+ link_access: b.link_access || null,
38
+ }));
39
+ return toolResult(JSON.stringify(mapped, null, 2));
40
+ }
41
+ catch (e) {
42
+ return toolError(`Failed to list branches: ${e.response?.status || e.message}`);
43
+ }
44
+ });
45
+ },
46
+ });
47
+ // -- create_branch --
48
+ defineTool({
49
+ toolset: 'branching',
50
+ auth: 'cookie',
51
+ mutates: true,
52
+ register(server, config) {
53
+ server.registerTool('create_branch', {
54
+ description: 'Create a branch from a file.',
55
+ inputSchema: {
56
+ file_key: figmaId.describe('File key to branch from'),
57
+ name: z.string().describe('Branch name'),
58
+ },
59
+ }, async ({ file_key, name }) => {
60
+ try {
61
+ const res = await internalClient(config).post(`/api/multiplayer/${file_key}/branch_create?name=${encodeURIComponent(name)}`);
62
+ const meta = res.data?.meta || res.data || {};
63
+ const file = meta.file || meta;
64
+ const branchKey = file.key || file.file_key || null;
65
+ return toolResult(JSON.stringify({
66
+ key: branchKey,
67
+ name,
68
+ main_file_key: file_key,
69
+ }, null, 2));
70
+ }
71
+ catch (e) {
72
+ return toolError(`Failed to create branch: ${e.response?.status || e.message}`);
73
+ }
74
+ });
75
+ },
76
+ });
77
+ // -- delete_branch --
78
+ // Archiving a branch is the same as trashing the branch file.
79
+ defineTool({
80
+ toolset: 'branching',
81
+ auth: 'cookie',
82
+ mutates: true,
83
+ destructive: true,
84
+ register(server, config) {
85
+ server.registerTool('delete_branch', {
86
+ description: 'Archive (delete) a branch. Uses the branch file key from list_branches.',
87
+ inputSchema: {
88
+ branch_key: figmaId.describe('Branch file key (from list_branches)'),
89
+ },
90
+ }, async ({ branch_key }) => {
91
+ try {
92
+ await internalClient(config).delete('/api/files_batch', {
93
+ data: { files: [{ key: branch_key }], trashed: true },
94
+ });
95
+ return toolResult(`Archived branch ${branch_key}`);
96
+ }
97
+ catch (e) {
98
+ return toolError(`Failed to delete branch: ${e.response?.status || e.message}`);
99
+ }
100
+ });
101
+ },
102
+ });
103
+ //# sourceMappingURL=branching.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=comments.d.ts.map
@@ -0,0 +1,147 @@
1
+ import { z } from 'zod';
2
+ import { publicClient } from '../clients/public-api.js';
3
+ import { defineTool, toolResult, toolError, figmaId } from './register.js';
4
+ // -- list_comments --
5
+ defineTool({
6
+ toolset: 'comments',
7
+ auth: 'pat',
8
+ register(server, config) {
9
+ server.registerTool('list_comments', {
10
+ description: 'List comments on a file. Returns comment text, author, timestamps, and thread structure.',
11
+ inputSchema: {
12
+ file_key: figmaId.describe('File key'),
13
+ as_md: z.boolean().optional().describe('Format as markdown thread (default: false)'),
14
+ },
15
+ }, async ({ file_key, as_md }) => {
16
+ try {
17
+ const res = await publicClient(config).get(`/v1/files/${file_key}/comments`);
18
+ const comments = res.data?.comments || [];
19
+ if (as_md) {
20
+ const threads = new Map();
21
+ for (const c of comments) {
22
+ const parentId = c.parent_id || c.id;
23
+ if (!threads.has(parentId))
24
+ threads.set(parentId, []);
25
+ threads.get(parentId).push(c);
26
+ }
27
+ const lines = [];
28
+ for (const [, thread] of threads) {
29
+ thread.sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
30
+ for (const c of thread) {
31
+ const indent = c.parent_id ? ' ' : '';
32
+ const resolved = c.resolved_at ? ' [resolved]' : '';
33
+ lines.push(`${indent}- **${c.user.handle}** (${c.created_at})${resolved}: ${c.message}`);
34
+ }
35
+ lines.push('');
36
+ }
37
+ return toolResult(lines.join('\n'));
38
+ }
39
+ const mapped = comments.map((c) => ({
40
+ id: c.id,
41
+ parent_id: c.parent_id || null,
42
+ message: c.message,
43
+ author: c.user?.handle,
44
+ author_id: c.user?.id,
45
+ created_at: c.created_at,
46
+ resolved_at: c.resolved_at || null,
47
+ node_id: c.client_meta?.node_id || c.client_meta?.node_offset?.node_id || null,
48
+ order_id: c.order_id,
49
+ }));
50
+ return toolResult(JSON.stringify(mapped, null, 2));
51
+ }
52
+ catch (e) {
53
+ return toolError(`Failed to list comments: ${e.response?.status || e.message}`);
54
+ }
55
+ });
56
+ },
57
+ });
58
+ // -- post_comment --
59
+ defineTool({
60
+ toolset: 'comments',
61
+ auth: 'pat',
62
+ mutates: true,
63
+ register(server, config) {
64
+ server.registerTool('post_comment', {
65
+ description: 'Post a comment on a file. Optionally pin to a specific node or reply to an existing comment.',
66
+ inputSchema: {
67
+ file_key: figmaId.describe('File key'),
68
+ message: z.string().describe('Comment text'),
69
+ comment_id: figmaId.optional().describe('Parent comment ID to reply to'),
70
+ node_id: figmaId.optional().describe('Node ID to pin the comment to'),
71
+ },
72
+ }, async ({ file_key, message, comment_id, node_id }) => {
73
+ try {
74
+ const body = { message };
75
+ if (comment_id)
76
+ body.comment_id = comment_id;
77
+ if (node_id)
78
+ body.client_meta = { node_id, node_offset: { x: 0, y: 0 } };
79
+ const res = await publicClient(config).post(`/v1/files/${file_key}/comments`, body);
80
+ const c = res.data;
81
+ return toolResult(JSON.stringify({
82
+ id: c.id,
83
+ message: c.message,
84
+ author: c.user?.handle,
85
+ created_at: c.created_at,
86
+ parent_id: c.parent_id || null,
87
+ }, null, 2));
88
+ }
89
+ catch (e) {
90
+ return toolError(`Failed to post comment: ${e.response?.status || e.message}`);
91
+ }
92
+ });
93
+ },
94
+ });
95
+ // -- delete_comment --
96
+ defineTool({
97
+ toolset: 'comments',
98
+ auth: 'pat',
99
+ mutates: true,
100
+ destructive: true,
101
+ register(server, config) {
102
+ server.registerTool('delete_comment', {
103
+ description: 'Permanently delete a comment. For top-level comments, the entire thread is removed.',
104
+ inputSchema: {
105
+ file_key: figmaId.describe('File key'),
106
+ comment_id: figmaId.describe('Comment ID to delete'),
107
+ },
108
+ }, async ({ file_key, comment_id }) => {
109
+ try {
110
+ await publicClient(config).delete(`/v1/files/${file_key}/comments/${comment_id}`);
111
+ return toolResult(`Deleted comment ${comment_id}`);
112
+ }
113
+ catch (e) {
114
+ return toolError(`Failed to delete comment: ${e.response?.status || e.message}`);
115
+ }
116
+ });
117
+ },
118
+ });
119
+ // -- list_comment_reactions --
120
+ defineTool({
121
+ toolset: 'comments',
122
+ auth: 'pat',
123
+ register(server, config) {
124
+ server.registerTool('list_comment_reactions', {
125
+ description: 'List emoji reactions on a comment.',
126
+ inputSchema: {
127
+ file_key: figmaId.describe('File key'),
128
+ comment_id: figmaId.describe('Comment ID'),
129
+ },
130
+ }, async ({ file_key, comment_id }) => {
131
+ try {
132
+ const res = await publicClient(config).get(`/v1/files/${file_key}/comments/${comment_id}/reactions`);
133
+ const reactions = res.data?.reactions || [];
134
+ const mapped = reactions.map((r) => ({
135
+ emoji: r.emoji,
136
+ user: r.user?.handle,
137
+ created_at: r.created_at,
138
+ }));
139
+ return toolResult(JSON.stringify(mapped, null, 2));
140
+ }
141
+ catch (e) {
142
+ return toolError(`Failed to list reactions: ${e.response?.status || e.message}`);
143
+ }
144
+ });
145
+ },
146
+ });
147
+ //# sourceMappingURL=comments.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=components.d.ts.map
@@ -0,0 +1,104 @@
1
+ import { z } from 'zod';
2
+ import { publicClient } from '../clients/public-api.js';
3
+ import { defineTool, toolResult, toolError, figmaId } from './register.js';
4
+ // -- list_file_components --
5
+ defineTool({
6
+ toolset: 'components',
7
+ auth: 'pat',
8
+ register(server, config) {
9
+ server.registerTool('list_file_components', {
10
+ description: 'List components published from a specific file.',
11
+ inputSchema: {
12
+ file_key: figmaId.describe('File key'),
13
+ },
14
+ }, async ({ file_key }) => {
15
+ try {
16
+ const res = await publicClient(config).get(`/v1/files/${file_key}/components`);
17
+ const components = res.data?.meta?.components || [];
18
+ return toolResult(JSON.stringify(components, null, 2));
19
+ }
20
+ catch (e) {
21
+ return toolError(`Failed to list file components: ${e.response?.status || e.message}`);
22
+ }
23
+ });
24
+ },
25
+ });
26
+ // -- list_file_styles --
27
+ defineTool({
28
+ toolset: 'components',
29
+ auth: 'pat',
30
+ register(server, config) {
31
+ server.registerTool('list_file_styles', {
32
+ description: 'List styles in a specific file.',
33
+ inputSchema: {
34
+ file_key: figmaId.describe('File key'),
35
+ },
36
+ }, async ({ file_key }) => {
37
+ try {
38
+ const res = await publicClient(config).get(`/v1/files/${file_key}/styles`);
39
+ const styles = res.data?.meta?.styles || [];
40
+ return toolResult(JSON.stringify(styles, null, 2));
41
+ }
42
+ catch (e) {
43
+ return toolError(`Failed to list file styles: ${e.response?.status || e.message}`);
44
+ }
45
+ });
46
+ },
47
+ });
48
+ // -- list_team_components --
49
+ defineTool({
50
+ toolset: 'components',
51
+ auth: 'pat',
52
+ register(server, config) {
53
+ server.registerTool('list_team_components', {
54
+ description: 'List published components across a team. Supports pagination.',
55
+ inputSchema: {
56
+ team_id: figmaId.describe('Team ID'),
57
+ page_size: z.number().optional().default(30).describe('Max items per page (default: 30)'),
58
+ cursor: z.string().optional().describe('Pagination cursor from previous response'),
59
+ },
60
+ }, async ({ team_id, page_size, cursor }) => {
61
+ try {
62
+ const params = { page_size };
63
+ if (cursor)
64
+ params.after = cursor;
65
+ const res = await publicClient(config).get(`/v1/teams/${team_id}/components`, { params });
66
+ const components = res.data?.meta?.components || [];
67
+ const pagination = res.data?.pagination || null;
68
+ return toolResult(JSON.stringify({ components, pagination }, null, 2));
69
+ }
70
+ catch (e) {
71
+ return toolError(`Failed to list team components: ${e.response?.status || e.message}`);
72
+ }
73
+ });
74
+ },
75
+ });
76
+ // -- list_team_styles --
77
+ defineTool({
78
+ toolset: 'components',
79
+ auth: 'pat',
80
+ register(server, config) {
81
+ server.registerTool('list_team_styles', {
82
+ description: 'List published styles across a team. Supports pagination.',
83
+ inputSchema: {
84
+ team_id: figmaId.describe('Team ID'),
85
+ page_size: z.number().optional().default(30).describe('Max items per page (default: 30)'),
86
+ cursor: z.string().optional().describe('Pagination cursor from previous response'),
87
+ },
88
+ }, async ({ team_id, page_size, cursor }) => {
89
+ try {
90
+ const params = { page_size };
91
+ if (cursor)
92
+ params.after = cursor;
93
+ const res = await publicClient(config).get(`/v1/teams/${team_id}/styles`, { params });
94
+ const styles = res.data?.meta?.styles || [];
95
+ const pagination = res.data?.pagination || null;
96
+ return toolResult(JSON.stringify({ styles, pagination }, null, 2));
97
+ }
98
+ catch (e) {
99
+ return toolError(`Failed to list team styles: ${e.response?.status || e.message}`);
100
+ }
101
+ });
102
+ },
103
+ });
104
+ //# sourceMappingURL=components.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=compound.d.ts.map