@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.
@@ -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
- }, 1e4);
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.1.4",
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", scale: scale || 1 });
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
  })();