@dannote/figma-use 0.1.4 → 0.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.
- package/CHANGELOG.md +123 -0
- package/README.md +195 -140
- package/SKILL.md +173 -0
- package/dist/cli/index.js +4570 -2414
- package/dist/proxy/index.js +13 -2
- package/package.json +4 -2
- package/packages/plugin/dist/main.js +141 -4
package/dist/proxy/index.js
CHANGED
|
@@ -19398,6 +19398,14 @@ var consola = createConsola2();
|
|
|
19398
19398
|
|
|
19399
19399
|
// packages/proxy/src/index.ts
|
|
19400
19400
|
var PORT = Number(process.env.PORT) || 38451;
|
|
19401
|
+
var TIMEOUT_LIGHT = 1e4;
|
|
19402
|
+
var TIMEOUT_HEAVY = 120000;
|
|
19403
|
+
var HEAVY_COMMANDS = new Set([
|
|
19404
|
+
"export-node",
|
|
19405
|
+
"screenshot",
|
|
19406
|
+
"export-selection",
|
|
19407
|
+
"eval"
|
|
19408
|
+
]);
|
|
19401
19409
|
var pendingRequests = new Map;
|
|
19402
19410
|
var sendToPlugin = null;
|
|
19403
19411
|
new Elysia().ws("/plugin", {
|
|
@@ -19428,15 +19436,18 @@ new Elysia().ws("/plugin", {
|
|
|
19428
19436
|
if (!sendToPlugin) {
|
|
19429
19437
|
return { error: "Plugin not connected" };
|
|
19430
19438
|
}
|
|
19431
|
-
const { command, args } = body;
|
|
19439
|
+
const { command, args, timeout: customTimeout } = body;
|
|
19432
19440
|
const id = crypto.randomUUID();
|
|
19433
19441
|
consola.info(`${command}`, args || "");
|
|
19434
19442
|
try {
|
|
19443
|
+
const defaultTimeout = HEAVY_COMMANDS.has(command) ? TIMEOUT_HEAVY : TIMEOUT_LIGHT;
|
|
19444
|
+
const timeoutMs = customTimeout || defaultTimeout;
|
|
19445
|
+
consola.info(`Timeout: ${timeoutMs}ms (custom: ${customTimeout}, default: ${defaultTimeout})`);
|
|
19435
19446
|
const result = await new Promise((resolve, reject) => {
|
|
19436
19447
|
const timeout = setTimeout(() => {
|
|
19437
19448
|
pendingRequests.delete(id);
|
|
19438
19449
|
reject(new Error("Request timeout"));
|
|
19439
|
-
},
|
|
19450
|
+
}, timeoutMs);
|
|
19440
19451
|
pendingRequests.set(id, { resolve, reject, timeout });
|
|
19441
19452
|
sendToPlugin(JSON.stringify({ id, command, args }));
|
|
19442
19453
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dannote/figma-use",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Control Figma from the command line. Full read/write access for AI agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
"bin",
|
|
20
20
|
"dist",
|
|
21
21
|
"packages/plugin/dist",
|
|
22
|
-
"README.md"
|
|
22
|
+
"README.md",
|
|
23
|
+
"CHANGELOG.md",
|
|
24
|
+
"SKILL.md"
|
|
23
25
|
],
|
|
24
26
|
"keywords": [
|
|
25
27
|
"figma",
|
|
@@ -64,6 +64,21 @@
|
|
|
64
64
|
const node = yield figma.getNodeByIdAsync(id);
|
|
65
65
|
return node ? serializeNode(node) : null;
|
|
66
66
|
}
|
|
67
|
+
case "get-current-page":
|
|
68
|
+
return { id: figma.currentPage.id, name: figma.currentPage.name };
|
|
69
|
+
case "get-node-tree": {
|
|
70
|
+
const { id } = args;
|
|
71
|
+
const node = yield figma.getNodeByIdAsync(id);
|
|
72
|
+
if (!node) throw new Error("Node not found");
|
|
73
|
+
const serializeTree = (n) => {
|
|
74
|
+
const base = serializeNode(n);
|
|
75
|
+
if ("children" in n && n.children) {
|
|
76
|
+
base.children = n.children.map(serializeTree);
|
|
77
|
+
}
|
|
78
|
+
return base;
|
|
79
|
+
};
|
|
80
|
+
return serializeTree(node);
|
|
81
|
+
}
|
|
67
82
|
case "get-all-components": {
|
|
68
83
|
const components = [];
|
|
69
84
|
figma.root.findAll((node) => {
|
|
@@ -120,7 +135,7 @@
|
|
|
120
135
|
result.effectStyles = styles.map((s) => ({
|
|
121
136
|
id: s.id,
|
|
122
137
|
name: s.name,
|
|
123
|
-
effects: s.effects.map((e) => ({ type: e.type, radius: e.radius }))
|
|
138
|
+
effects: s.effects.map((e) => ({ type: e.type, radius: "radius" in e ? e.radius : void 0 }))
|
|
124
139
|
}));
|
|
125
140
|
}
|
|
126
141
|
}
|
|
@@ -337,6 +352,7 @@
|
|
|
337
352
|
} else if (type === "BLUR" || type === "BACKGROUND_BLUR") {
|
|
338
353
|
style.effects = [{
|
|
339
354
|
type,
|
|
355
|
+
blurType: "NORMAL",
|
|
340
356
|
radius: radius || 10,
|
|
341
357
|
visible: true
|
|
342
358
|
}];
|
|
@@ -368,10 +384,12 @@
|
|
|
368
384
|
return serializeNode(node);
|
|
369
385
|
}
|
|
370
386
|
case "set-stroke-color": {
|
|
371
|
-
const { id, color } = args;
|
|
387
|
+
const { id, color, weight, align } = args;
|
|
372
388
|
const node = yield figma.getNodeByIdAsync(id);
|
|
373
389
|
if (!node || !("strokes" in node)) throw new Error("Node not found");
|
|
374
390
|
node.strokes = [{ type: "SOLID", color: hexToRgb(color) }];
|
|
391
|
+
if (weight !== void 0 && "strokeWeight" in node) node.strokeWeight = weight;
|
|
392
|
+
if (align && "strokeAlign" in node) node.strokeAlign = align;
|
|
375
393
|
return serializeNode(node);
|
|
376
394
|
}
|
|
377
395
|
case "set-corner-radius": {
|
|
@@ -444,6 +462,7 @@
|
|
|
444
462
|
} else if (type === "BLUR") {
|
|
445
463
|
node.effects = [{
|
|
446
464
|
type: "LAYER_BLUR",
|
|
465
|
+
blurType: "NORMAL",
|
|
447
466
|
radius: radius != null ? radius : 8,
|
|
448
467
|
visible: true
|
|
449
468
|
}];
|
|
@@ -603,7 +622,7 @@
|
|
|
603
622
|
const { id, angle } = args;
|
|
604
623
|
const node = yield figma.getNodeByIdAsync(id);
|
|
605
624
|
if (!node) throw new Error("Node not found");
|
|
606
|
-
node.rotation = angle;
|
|
625
|
+
if ("rotation" in node) node.rotation = angle;
|
|
607
626
|
return serializeNode(node);
|
|
608
627
|
}
|
|
609
628
|
case "set-stroke-align": {
|
|
@@ -825,7 +844,7 @@
|
|
|
825
844
|
}
|
|
826
845
|
}
|
|
827
846
|
}
|
|
828
|
-
const bytes = yield frame.exportAsync({ format: "PNG",
|
|
847
|
+
const bytes = yield frame.exportAsync({ format: "PNG", constraint: { type: "SCALE", value: scale || 1 } });
|
|
829
848
|
frame.remove();
|
|
830
849
|
return { data: figma.base64Encode(bytes) };
|
|
831
850
|
}
|
|
@@ -888,6 +907,79 @@
|
|
|
888
907
|
const fn = new AsyncFunction("figma", wrappedCode);
|
|
889
908
|
return yield fn(figma);
|
|
890
909
|
}
|
|
910
|
+
// ==================== VARIABLES ====================
|
|
911
|
+
case "get-variables": {
|
|
912
|
+
const { type } = args;
|
|
913
|
+
const variables = yield figma.variables.getLocalVariablesAsync(type);
|
|
914
|
+
return variables.map((v) => serializeVariable(v));
|
|
915
|
+
}
|
|
916
|
+
case "get-variable": {
|
|
917
|
+
const { id } = args;
|
|
918
|
+
const variable = yield figma.variables.getVariableByIdAsync(id);
|
|
919
|
+
if (!variable) throw new Error("Variable not found");
|
|
920
|
+
return serializeVariable(variable);
|
|
921
|
+
}
|
|
922
|
+
case "create-variable": {
|
|
923
|
+
const { name, collectionId, type, value } = args;
|
|
924
|
+
const collection = yield figma.variables.getVariableCollectionByIdAsync(collectionId);
|
|
925
|
+
if (!collection) throw new Error("Collection not found");
|
|
926
|
+
const variable = figma.variables.createVariable(name, collection, type);
|
|
927
|
+
if (value !== void 0 && collection.modes.length > 0) {
|
|
928
|
+
const modeId = collection.modes[0].modeId;
|
|
929
|
+
variable.setValueForMode(modeId, parseVariableValue(value, type));
|
|
930
|
+
}
|
|
931
|
+
return serializeVariable(variable);
|
|
932
|
+
}
|
|
933
|
+
case "set-variable-value": {
|
|
934
|
+
const { id, modeId, value } = args;
|
|
935
|
+
const variable = yield figma.variables.getVariableByIdAsync(id);
|
|
936
|
+
if (!variable) throw new Error("Variable not found");
|
|
937
|
+
variable.setValueForMode(modeId, parseVariableValue(value, variable.resolvedType));
|
|
938
|
+
return serializeVariable(variable);
|
|
939
|
+
}
|
|
940
|
+
case "delete-variable": {
|
|
941
|
+
const { id } = args;
|
|
942
|
+
const variable = yield figma.variables.getVariableByIdAsync(id);
|
|
943
|
+
if (!variable) throw new Error("Variable not found");
|
|
944
|
+
variable.remove();
|
|
945
|
+
return { deleted: true };
|
|
946
|
+
}
|
|
947
|
+
case "bind-variable": {
|
|
948
|
+
const { nodeId, field, variableId } = args;
|
|
949
|
+
const node = yield figma.getNodeByIdAsync(nodeId);
|
|
950
|
+
if (!node) throw new Error("Node not found");
|
|
951
|
+
const variable = yield figma.variables.getVariableByIdAsync(variableId);
|
|
952
|
+
if (!variable) throw new Error("Variable not found");
|
|
953
|
+
if ("setBoundVariable" in node) {
|
|
954
|
+
node.setBoundVariable(field, variable);
|
|
955
|
+
} else {
|
|
956
|
+
throw new Error("Node does not support variable binding");
|
|
957
|
+
}
|
|
958
|
+
return serializeNode(node);
|
|
959
|
+
}
|
|
960
|
+
// ==================== VARIABLE COLLECTIONS ====================
|
|
961
|
+
case "get-variable-collections": {
|
|
962
|
+
const collections = yield figma.variables.getLocalVariableCollectionsAsync();
|
|
963
|
+
return collections.map((c) => serializeCollection(c));
|
|
964
|
+
}
|
|
965
|
+
case "get-variable-collection": {
|
|
966
|
+
const { id } = args;
|
|
967
|
+
const collection = yield figma.variables.getVariableCollectionByIdAsync(id);
|
|
968
|
+
if (!collection) throw new Error("Collection not found");
|
|
969
|
+
return serializeCollection(collection);
|
|
970
|
+
}
|
|
971
|
+
case "create-variable-collection": {
|
|
972
|
+
const { name } = args;
|
|
973
|
+
const collection = figma.variables.createVariableCollection(name);
|
|
974
|
+
return serializeCollection(collection);
|
|
975
|
+
}
|
|
976
|
+
case "delete-variable-collection": {
|
|
977
|
+
const { id } = args;
|
|
978
|
+
const collection = yield figma.variables.getVariableCollectionByIdAsync(id);
|
|
979
|
+
if (!collection) throw new Error("Collection not found");
|
|
980
|
+
collection.remove();
|
|
981
|
+
return { deleted: true };
|
|
982
|
+
}
|
|
891
983
|
default:
|
|
892
984
|
throw new Error(`Unknown command: ${command}`);
|
|
893
985
|
}
|
|
@@ -1004,4 +1096,49 @@
|
|
|
1004
1096
|
a: hasAlpha ? parseInt(clean.slice(6, 8), 16) / 255 : 1
|
|
1005
1097
|
};
|
|
1006
1098
|
}
|
|
1099
|
+
function serializeVariable(v) {
|
|
1100
|
+
return {
|
|
1101
|
+
id: v.id,
|
|
1102
|
+
name: v.name,
|
|
1103
|
+
type: v.resolvedType,
|
|
1104
|
+
collectionId: v.variableCollectionId,
|
|
1105
|
+
description: v.description || void 0,
|
|
1106
|
+
valuesByMode: Object.fromEntries(
|
|
1107
|
+
Object.entries(v.valuesByMode).map(([modeId, value]) => [
|
|
1108
|
+
modeId,
|
|
1109
|
+
serializeVariableValue(value, v.resolvedType)
|
|
1110
|
+
])
|
|
1111
|
+
)
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
function serializeCollection(c) {
|
|
1115
|
+
return {
|
|
1116
|
+
id: c.id,
|
|
1117
|
+
name: c.name,
|
|
1118
|
+
modes: c.modes.map((m) => ({ modeId: m.modeId, name: m.name })),
|
|
1119
|
+
variableIds: c.variableIds
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
function serializeVariableValue(value, type) {
|
|
1123
|
+
if (type === "COLOR" && typeof value === "object" && "r" in value) {
|
|
1124
|
+
return rgbToHex(value);
|
|
1125
|
+
}
|
|
1126
|
+
if (typeof value === "object" && "type" in value && value.type === "VARIABLE_ALIAS") {
|
|
1127
|
+
return { alias: value.id };
|
|
1128
|
+
}
|
|
1129
|
+
return value;
|
|
1130
|
+
}
|
|
1131
|
+
function parseVariableValue(value, type) {
|
|
1132
|
+
switch (type) {
|
|
1133
|
+
case "COLOR":
|
|
1134
|
+
return hexToRgb(value);
|
|
1135
|
+
case "FLOAT":
|
|
1136
|
+
return parseFloat(value);
|
|
1137
|
+
case "BOOLEAN":
|
|
1138
|
+
return value === "true";
|
|
1139
|
+
case "STRING":
|
|
1140
|
+
default:
|
|
1141
|
+
return value;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1007
1144
|
})();
|