@browxai/plugin-figma 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 +41 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +220 -0
- package/package.json +61 -0
- package/schema.d.ts +48 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kalebtec
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# @browxai/plugin-figma
|
|
2
|
+
|
|
3
|
+
First-party browxai canvas-app adapter for Figma. Exposes five small,
|
|
4
|
+
useful tools (`figma.get_selection`, `figma.get_viewport`,
|
|
5
|
+
`figma.select_node`, `figma.move_node`, `figma.create_rectangle`) over the
|
|
6
|
+
page-side `figma.*` global that Figma's plugin context exposes. Each tool
|
|
7
|
+
is a thin wrapper around an `eval_js` round-trip: the plugin builds the
|
|
8
|
+
appropriate `figma.viewport` / `figma.currentPage.selection` /
|
|
9
|
+
`figma.createRectangle()` expression, dispatches through `eval_js`, and
|
|
10
|
+
parses the value back. When `figma` isn't defined on the page (no editor
|
|
11
|
+
loaded), every tool returns the structured `code:"figma-not-loaded"`
|
|
12
|
+
envelope rather than crashing.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
$ browxai plugin install @browxai/plugin-figma
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The host must have the `eval` and `canvas` capabilities enabled — the
|
|
21
|
+
plugin declares both at the manifest level and the runtime gates the
|
|
22
|
+
whole plugin against the operator's active capability set.
|
|
23
|
+
|
|
24
|
+
After install, restart the browxai server (plugin lifecycle is
|
|
25
|
+
resolved-once-at-server-start). The tools surface as
|
|
26
|
+
`figma.get_selection` (etc.) on MCP `tools/list`, and on the SDK as
|
|
27
|
+
`client.plugins.figma.get_selection(...)`.
|
|
28
|
+
|
|
29
|
+
## Targeted Figma API surface
|
|
30
|
+
|
|
31
|
+
This plugin pokes the long-stable parts of the Figma plugin API as of
|
|
32
|
+
2026-06: `figma.viewport.{center,zoom}`, `figma.currentPage.selection`,
|
|
33
|
+
`figma.getNodeById()`, `figma.createRectangle()`, plus mutable `x` / `y`
|
|
34
|
+
/ `fills` properties on scene nodes. Future Figma versions may add
|
|
35
|
+
fields; the targeted surface should remain compatible.
|
|
36
|
+
|
|
37
|
+
## Full reference
|
|
38
|
+
|
|
39
|
+
The per-tool reference for this adapter — every op with args, return
|
|
40
|
+
shape, and error codes, plus a usage walkthrough — lives at
|
|
41
|
+
<https://browxai.com/plugins/first-party/>.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
interface ToolResponse {
|
|
2
|
+
readonly content: ReadonlyArray<{
|
|
3
|
+
type: "text";
|
|
4
|
+
text: string;
|
|
5
|
+
} | {
|
|
6
|
+
type: "image";
|
|
7
|
+
data: string;
|
|
8
|
+
mimeType: string;
|
|
9
|
+
}>;
|
|
10
|
+
}
|
|
11
|
+
interface PluginApi {
|
|
12
|
+
readonly namespace: string;
|
|
13
|
+
readonly declaredCapabilities: ReadonlyArray<string>;
|
|
14
|
+
registerTool(name: string, def: {
|
|
15
|
+
description: string;
|
|
16
|
+
inputSchema?: Record<string, any> | undefined;
|
|
17
|
+
}, handler: (args: unknown) => Promise<ToolResponse>): void;
|
|
18
|
+
callTool(name: string, args?: Record<string, unknown>): Promise<ToolResponse>;
|
|
19
|
+
log: {
|
|
20
|
+
info(msg: string, meta?: Record<string, unknown>): void;
|
|
21
|
+
warn(msg: string, meta?: Record<string, unknown>): void;
|
|
22
|
+
error(msg: string, meta?: Record<string, unknown>): void;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export declare const handlers: {
|
|
26
|
+
/** `figma.get_selection()` → `{ok, nodes:[{id,name,type,x,y,width,height}]}`. */
|
|
27
|
+
get_selection(api: PluginApi, _args: unknown): Promise<ToolResponse>;
|
|
28
|
+
/** `figma.get_viewport()` → `{ok, center:{x,y}, zoom}`. */
|
|
29
|
+
get_viewport(api: PluginApi, _args: unknown): Promise<ToolResponse>;
|
|
30
|
+
/** `figma.select_node({nodeId})` — sets `figma.currentPage.selection`. */
|
|
31
|
+
select_node(api: PluginApi, args: unknown): Promise<ToolResponse>;
|
|
32
|
+
/** `figma.move_node({nodeId, dx, dy})` — mutates `node.x`/`node.y` in place. */
|
|
33
|
+
move_node(api: PluginApi, args: unknown): Promise<ToolResponse>;
|
|
34
|
+
/** `figma.create_rectangle({x,y,width,height, fillColor?})` → `{ok, nodeId}`. */
|
|
35
|
+
create_rectangle(api: PluginApi, args: unknown): Promise<ToolResponse>;
|
|
36
|
+
};
|
|
37
|
+
export declare function register(api: PluginApi): void;
|
|
38
|
+
export default register;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
// @browxai/plugin-figma — Figma canvas-app adapter.
|
|
2
|
+
//
|
|
3
|
+
// Surfaces a small, useful first-party tool surface (selection, viewport,
|
|
4
|
+
// node mutate, rectangle create) over the page-side `figma.*` global that
|
|
5
|
+
// Figma exposes in its plugin-iframe context (and, for agent-driven
|
|
6
|
+
// sessions, reachable through `eval_js` when the file is open in the
|
|
7
|
+
// editor with the plugin context loaded).
|
|
8
|
+
//
|
|
9
|
+
// Design contract:
|
|
10
|
+
// - All five tools route through `api.callTool("eval_js", {expr})`.
|
|
11
|
+
// - The plugin declares the `eval` + `canvas` capabilities at the
|
|
12
|
+
// manifest level — the host gates the whole plugin against those.
|
|
13
|
+
// - Handlers are resilient to the app not being loaded: a guard
|
|
14
|
+
// `typeof figma === "undefined"` returns the structured
|
|
15
|
+
// `figma-not-loaded` error.
|
|
16
|
+
// - The canonical canvas-app adapter pattern — keep `register(api)`
|
|
17
|
+
// small, push the heavy lifting into the eval-expression strings,
|
|
18
|
+
// parse the result back in the plugin.
|
|
19
|
+
//
|
|
20
|
+
// Targeted API surface (as of 2026-06): figma.viewport.{center,zoom},
|
|
21
|
+
// figma.currentPage.selection, figma.createRectangle(),
|
|
22
|
+
// figma.getNodeById(). These are the stable parts of Figma's plugin API
|
|
23
|
+
// and have been present for years; later versions may add fields but
|
|
24
|
+
// shouldn't break this set.
|
|
25
|
+
const json = (obj) => ({
|
|
26
|
+
content: [{ type: "text", text: JSON.stringify(obj, null, 2) }],
|
|
27
|
+
});
|
|
28
|
+
const NOT_LOADED = {
|
|
29
|
+
ok: false,
|
|
30
|
+
error: "Figma not loaded — open the app first OR the surface is not exposed on this version of the app",
|
|
31
|
+
code: "figma-not-loaded",
|
|
32
|
+
};
|
|
33
|
+
const badArg = (which) => ({
|
|
34
|
+
ok: false,
|
|
35
|
+
error: `bad-arg: missing or invalid \`${which}\``,
|
|
36
|
+
code: "bad-arg",
|
|
37
|
+
});
|
|
38
|
+
/** Parse the first text item of an eval_js MCP envelope as JSON. */
|
|
39
|
+
function parseEvalEnvelope(res) {
|
|
40
|
+
const first = res.content[0];
|
|
41
|
+
if (!first || first.type !== "text") {
|
|
42
|
+
return { ok: false, error: "eval_js returned no text content" };
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
return JSON.parse(first.text);
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
return { ok: false, error: `eval_js envelope parse failure: ${e.message}` };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Run an eval_js expression and unwrap its envelope to the page-side value.
|
|
53
|
+
*
|
|
54
|
+
* Returns `{ok:true, value}` on a successful page-side eval, or a structured
|
|
55
|
+
* error envelope (passed straight through to the caller) otherwise.
|
|
56
|
+
*/
|
|
57
|
+
async function runEval(api, expr) {
|
|
58
|
+
const res = await api.callTool("eval_js", { expr });
|
|
59
|
+
const env = parseEvalEnvelope(res);
|
|
60
|
+
if (!env.ok)
|
|
61
|
+
return { ok: false, error: env.error ?? "eval_js failed" };
|
|
62
|
+
return { ok: true, value: env.value };
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Probe `typeof figma` in the page; if undefined, the editor isn't
|
|
66
|
+
* loaded (or the surface isn't exposed for this build) and the caller
|
|
67
|
+
* should return the canonical `figma-not-loaded` envelope.
|
|
68
|
+
*/
|
|
69
|
+
async function figmaLoaded(api) {
|
|
70
|
+
const r = await runEval(api, `(typeof figma !== "undefined")`);
|
|
71
|
+
return r.ok && r.value === true;
|
|
72
|
+
}
|
|
73
|
+
export const handlers = {
|
|
74
|
+
/** `figma.get_selection()` → `{ok, nodes:[{id,name,type,x,y,width,height}]}`. */
|
|
75
|
+
async get_selection(api, _args) {
|
|
76
|
+
if (!(await figmaLoaded(api)))
|
|
77
|
+
return json(NOT_LOADED);
|
|
78
|
+
const expr = `(() => {
|
|
79
|
+
const sel = figma.currentPage.selection || [];
|
|
80
|
+
return {
|
|
81
|
+
nodes: sel.map(n => ({
|
|
82
|
+
id: n.id,
|
|
83
|
+
name: n.name,
|
|
84
|
+
type: n.type,
|
|
85
|
+
x: n.x,
|
|
86
|
+
y: n.y,
|
|
87
|
+
width: n.width,
|
|
88
|
+
height: n.height,
|
|
89
|
+
})),
|
|
90
|
+
};
|
|
91
|
+
})()`;
|
|
92
|
+
const r = await runEval(api, expr);
|
|
93
|
+
if (!r.ok)
|
|
94
|
+
return json({ ok: false, error: r.error, code: "eval-failed" });
|
|
95
|
+
return json({ ok: true, ...r.value });
|
|
96
|
+
},
|
|
97
|
+
/** `figma.get_viewport()` → `{ok, center:{x,y}, zoom}`. */
|
|
98
|
+
async get_viewport(api, _args) {
|
|
99
|
+
if (!(await figmaLoaded(api)))
|
|
100
|
+
return json(NOT_LOADED);
|
|
101
|
+
const expr = `(() => {
|
|
102
|
+
const v = figma.viewport;
|
|
103
|
+
return { center: { x: v.center.x, y: v.center.y }, zoom: v.zoom };
|
|
104
|
+
})()`;
|
|
105
|
+
const r = await runEval(api, expr);
|
|
106
|
+
if (!r.ok)
|
|
107
|
+
return json({ ok: false, error: r.error, code: "eval-failed" });
|
|
108
|
+
return json({ ok: true, ...r.value });
|
|
109
|
+
},
|
|
110
|
+
/** `figma.select_node({nodeId})` — sets `figma.currentPage.selection`. */
|
|
111
|
+
async select_node(api, args) {
|
|
112
|
+
const a = (args ?? {});
|
|
113
|
+
if (typeof a.nodeId !== "string" || a.nodeId.length === 0)
|
|
114
|
+
return json(badArg("nodeId"));
|
|
115
|
+
if (!(await figmaLoaded(api)))
|
|
116
|
+
return json(NOT_LOADED);
|
|
117
|
+
const id = JSON.stringify(a.nodeId);
|
|
118
|
+
const expr = `(() => {
|
|
119
|
+
const n = figma.getNodeById(${id});
|
|
120
|
+
if (!n) return { found: false };
|
|
121
|
+
figma.currentPage.selection = [n];
|
|
122
|
+
return { found: true, nodeId: n.id };
|
|
123
|
+
})()`;
|
|
124
|
+
const r = await runEval(api, expr);
|
|
125
|
+
if (!r.ok)
|
|
126
|
+
return json({ ok: false, error: r.error, code: "eval-failed" });
|
|
127
|
+
const v = r.value;
|
|
128
|
+
if (!v.found)
|
|
129
|
+
return json({ ok: false, error: `node not found: ${a.nodeId}`, code: "node-not-found" });
|
|
130
|
+
return json({ ok: true, nodeId: v.nodeId });
|
|
131
|
+
},
|
|
132
|
+
/** `figma.move_node({nodeId, dx, dy})` — mutates `node.x`/`node.y` in place. */
|
|
133
|
+
async move_node(api, args) {
|
|
134
|
+
const a = (args ?? {});
|
|
135
|
+
if (typeof a.nodeId !== "string" || a.nodeId.length === 0)
|
|
136
|
+
return json(badArg("nodeId"));
|
|
137
|
+
if (typeof a.dx !== "number")
|
|
138
|
+
return json(badArg("dx"));
|
|
139
|
+
if (typeof a.dy !== "number")
|
|
140
|
+
return json(badArg("dy"));
|
|
141
|
+
if (!(await figmaLoaded(api)))
|
|
142
|
+
return json(NOT_LOADED);
|
|
143
|
+
const id = JSON.stringify(a.nodeId);
|
|
144
|
+
const expr = `(() => {
|
|
145
|
+
const n = figma.getNodeById(${id});
|
|
146
|
+
if (!n) return { found: false };
|
|
147
|
+
n.x = n.x + (${a.dx});
|
|
148
|
+
n.y = n.y + (${a.dy});
|
|
149
|
+
return { found: true, nodeId: n.id, x: n.x, y: n.y };
|
|
150
|
+
})()`;
|
|
151
|
+
const r = await runEval(api, expr);
|
|
152
|
+
if (!r.ok)
|
|
153
|
+
return json({ ok: false, error: r.error, code: "eval-failed" });
|
|
154
|
+
const v = r.value;
|
|
155
|
+
if (!v.found)
|
|
156
|
+
return json({ ok: false, error: `node not found: ${a.nodeId}`, code: "node-not-found" });
|
|
157
|
+
return json({ ok: true, nodeId: v.nodeId, x: v.x, y: v.y });
|
|
158
|
+
},
|
|
159
|
+
/** `figma.create_rectangle({x,y,width,height, fillColor?})` → `{ok, nodeId}`. */
|
|
160
|
+
async create_rectangle(api, args) {
|
|
161
|
+
const a = (args ?? {});
|
|
162
|
+
if (typeof a.x !== "number")
|
|
163
|
+
return json(badArg("x"));
|
|
164
|
+
if (typeof a.y !== "number")
|
|
165
|
+
return json(badArg("y"));
|
|
166
|
+
if (typeof a.width !== "number" || a.width <= 0)
|
|
167
|
+
return json(badArg("width"));
|
|
168
|
+
if (typeof a.height !== "number" || a.height <= 0)
|
|
169
|
+
return json(badArg("height"));
|
|
170
|
+
const fill = a.fillColor && typeof a.fillColor === "object"
|
|
171
|
+
? a.fillColor
|
|
172
|
+
: undefined;
|
|
173
|
+
if (fill &&
|
|
174
|
+
(typeof fill.r !== "number" || typeof fill.g !== "number" || typeof fill.b !== "number")) {
|
|
175
|
+
return json(badArg("fillColor"));
|
|
176
|
+
}
|
|
177
|
+
if (!(await figmaLoaded(api)))
|
|
178
|
+
return json(NOT_LOADED);
|
|
179
|
+
const fillExpr = fill
|
|
180
|
+
? `rect.fills = [{ type: "SOLID", color: { r: ${fill.r}, g: ${fill.g}, b: ${fill.b} } }];`
|
|
181
|
+
: "";
|
|
182
|
+
const expr = `(() => {
|
|
183
|
+
const rect = figma.createRectangle();
|
|
184
|
+
rect.x = ${a.x};
|
|
185
|
+
rect.y = ${a.y};
|
|
186
|
+
rect.resize(${a.width}, ${a.height});
|
|
187
|
+
${fillExpr}
|
|
188
|
+
return { nodeId: rect.id };
|
|
189
|
+
})()`;
|
|
190
|
+
const r = await runEval(api, expr);
|
|
191
|
+
if (!r.ok)
|
|
192
|
+
return json({ ok: false, error: r.error, code: "eval-failed" });
|
|
193
|
+
const v = r.value;
|
|
194
|
+
return json({ ok: true, nodeId: v.nodeId });
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
export function register(api) {
|
|
198
|
+
api.log.info("figma plugin: registering tools", { namespace: api.namespace });
|
|
199
|
+
api.registerTool(`${api.namespace}.get_selection`, {
|
|
200
|
+
description: "Read the current `figma.currentPage.selection` — returns `{ok, nodes:[{id,name,type,x,y,width,height}]}`. App-not-loaded surfaces `code:'figma-not-loaded'`.",
|
|
201
|
+
inputSchema: {},
|
|
202
|
+
}, (args) => handlers.get_selection(api, args));
|
|
203
|
+
api.registerTool(`${api.namespace}.get_viewport`, {
|
|
204
|
+
description: "Read `figma.viewport.center` + `figma.viewport.zoom` — returns `{ok, center:{x,y}, zoom}`. App-not-loaded surfaces `code:'figma-not-loaded'`.",
|
|
205
|
+
inputSchema: {},
|
|
206
|
+
}, (args) => handlers.get_viewport(api, args));
|
|
207
|
+
api.registerTool(`${api.namespace}.select_node`, {
|
|
208
|
+
description: "Set `figma.currentPage.selection` to the node addressed by `nodeId`. Returns `{ok, nodeId}` on success, `{ok:false, code:'node-not-found'|'bad-arg'|'figma-not-loaded'}` otherwise.",
|
|
209
|
+
inputSchema: {},
|
|
210
|
+
}, (args) => handlers.select_node(api, args));
|
|
211
|
+
api.registerTool(`${api.namespace}.move_node`, {
|
|
212
|
+
description: "Translate a node by `(dx, dy)` — mutates `node.x` and `node.y` in place. Returns `{ok, nodeId, x, y}` with the post-move position. App-not-loaded / missing-arg / unknown-id surface structured errors.",
|
|
213
|
+
inputSchema: {},
|
|
214
|
+
}, (args) => handlers.move_node(api, args));
|
|
215
|
+
api.registerTool(`${api.namespace}.create_rectangle`, {
|
|
216
|
+
description: "Create a rectangle via `figma.createRectangle()` at `(x, y)` with `(width, height)`. Optional `fillColor:{r,g,b}` (0–1 floats — Figma's color convention) sets a solid fill. Returns `{ok, nodeId}`.",
|
|
217
|
+
inputSchema: {},
|
|
218
|
+
}, (args) => handlers.create_rectangle(api, args));
|
|
219
|
+
}
|
|
220
|
+
export default register;
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@browxai/plugin-figma",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Figma canvas-app adapter plugin for browxai — surfaces a small first-party tool surface (selection, viewport, node mutate, rectangle create) over the `figma.*` page-side global.",
|
|
5
|
+
"author": "Kalebtec",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"browxai",
|
|
10
|
+
"browxai-plugin",
|
|
11
|
+
"figma",
|
|
12
|
+
"canvas",
|
|
13
|
+
"mcp",
|
|
14
|
+
"browser-automation",
|
|
15
|
+
"ai-agent"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://browxai.com/plugins/first-party/",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/kalebteccom/browxai.git",
|
|
21
|
+
"directory": "packages/plugins/figma"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/kalebteccom/browxai/issues"
|
|
25
|
+
},
|
|
26
|
+
"main": "dist/index.js",
|
|
27
|
+
"types": "dist/index.d.ts",
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"schema.d.ts",
|
|
31
|
+
"LICENSE"
|
|
32
|
+
],
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public",
|
|
35
|
+
"provenance": true
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsc -p tsconfig.json",
|
|
39
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
40
|
+
"test": "vitest run"
|
|
41
|
+
},
|
|
42
|
+
"browxai": {
|
|
43
|
+
"apiVersion": "1.0.0",
|
|
44
|
+
"browxaiVersion": "^0.7.0",
|
|
45
|
+
"namespace": "figma",
|
|
46
|
+
"register": "dist/index.js",
|
|
47
|
+
"capabilities": [
|
|
48
|
+
"eval",
|
|
49
|
+
"canvas"
|
|
50
|
+
],
|
|
51
|
+
"trust": "kalebtec",
|
|
52
|
+
"dependsOn": []
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=20"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"typescript": "^5.5.0",
|
|
59
|
+
"vitest": "^2.0.0"
|
|
60
|
+
}
|
|
61
|
+
}
|
package/schema.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Typed SDK overlay for `@browxai/plugin-figma` consumers.
|
|
2
|
+
//
|
|
3
|
+
// Compose this schema into the host's `BrowxaiClientWithPlugins` helper to
|
|
4
|
+
// get autocomplete on every figma.* tool:
|
|
5
|
+
//
|
|
6
|
+
// import type { FigmaPluginSchema } from "@browxai/plugin-figma/schema";
|
|
7
|
+
// import type { BrowxaiClientWithPlugins } from "browxai";
|
|
8
|
+
//
|
|
9
|
+
// const client = (await createBrowxai({...})) as BrowxaiClientWithPlugins<FigmaPluginSchema>;
|
|
10
|
+
// await client.plugins.figma.get_selection({});
|
|
11
|
+
|
|
12
|
+
interface FigmaBrowxaiResult {
|
|
13
|
+
readonly content: ReadonlyArray<
|
|
14
|
+
{ type: "text"; text: string } | { type: "image"; data: string; mimeType: string }
|
|
15
|
+
>;
|
|
16
|
+
readonly data?: Record<string, unknown>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface FigmaSceneNode {
|
|
20
|
+
readonly id: string;
|
|
21
|
+
readonly name: string;
|
|
22
|
+
readonly type: string;
|
|
23
|
+
readonly x: number;
|
|
24
|
+
readonly y: number;
|
|
25
|
+
readonly width: number;
|
|
26
|
+
readonly height: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface FigmaPluginSchema {
|
|
30
|
+
readonly figma: {
|
|
31
|
+
/** Read the current `figma.currentPage.selection` shape. */
|
|
32
|
+
get_selection(args?: Record<string, never>): Promise<FigmaBrowxaiResult>;
|
|
33
|
+
/** Read `figma.viewport.center` + `figma.viewport.zoom`. */
|
|
34
|
+
get_viewport(args?: Record<string, never>): Promise<FigmaBrowxaiResult>;
|
|
35
|
+
/** Set `figma.currentPage.selection` to the node with `nodeId`. */
|
|
36
|
+
select_node(args: { nodeId: string }): Promise<FigmaBrowxaiResult>;
|
|
37
|
+
/** Mutate `node.x += dx; node.y += dy` on the addressed node. */
|
|
38
|
+
move_node(args: { nodeId: string; dx: number; dy: number }): Promise<FigmaBrowxaiResult>;
|
|
39
|
+
/** Create a rectangle via `figma.createRectangle()` — returns `{nodeId}`. */
|
|
40
|
+
create_rectangle(args: {
|
|
41
|
+
x: number;
|
|
42
|
+
y: number;
|
|
43
|
+
width: number;
|
|
44
|
+
height: number;
|
|
45
|
+
fillColor?: { r: number; g: number; b: number };
|
|
46
|
+
}): Promise<FigmaBrowxaiResult>;
|
|
47
|
+
};
|
|
48
|
+
}
|