@sxl-studio/bridge 1.7.2 → 1.7.3
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/README.md +342 -16
- package/dist/agent-recipes.d.ts +781 -11
- package/dist/agent-recipes.js +886 -13
- package/dist/agent-recipes.js.map +1 -1
- package/dist/agent-runbook.d.ts +50 -0
- package/dist/agent-runbook.js +243 -0
- package/dist/agent-runbook.js.map +1 -0
- package/dist/asset-upload.d.ts +63 -0
- package/dist/asset-upload.js +225 -0
- package/dist/asset-upload.js.map +1 -0
- package/dist/audit-store.d.ts +15 -0
- package/dist/audit-store.js +100 -0
- package/dist/audit-store.js.map +1 -0
- package/dist/audit.d.ts +4 -3
- package/dist/audit.js +37 -4
- package/dist/audit.js.map +1 -1
- package/dist/auth.d.ts +8 -1
- package/dist/auth.js +41 -1
- package/dist/auth.js.map +1 -1
- package/dist/bridge-agent-workflow-validation-cli.d.ts +2 -0
- package/dist/bridge-agent-workflow-validation-cli.js +68 -0
- package/dist/bridge-agent-workflow-validation-cli.js.map +1 -0
- package/dist/bridge-agent-workflow-validation.d.ts +42 -0
- package/dist/bridge-agent-workflow-validation.js +170 -0
- package/dist/bridge-agent-workflow-validation.js.map +1 -0
- package/dist/bridge-contract-audit.d.ts +45 -0
- package/dist/bridge-contract-audit.js +345 -0
- package/dist/bridge-contract-audit.js.map +1 -0
- package/dist/bridge-health-cli.d.ts +2 -0
- package/dist/bridge-health-cli.js +115 -0
- package/dist/bridge-health-cli.js.map +1 -0
- package/dist/bridge-health.d.ts +33 -0
- package/dist/bridge-health.js +594 -0
- package/dist/bridge-health.js.map +1 -0
- package/dist/bridge-live-validation-cli.d.ts +2 -0
- package/dist/bridge-live-validation-cli.js +114 -0
- package/dist/bridge-live-validation-cli.js.map +1 -0
- package/dist/bridge-live-validation.d.ts +39 -0
- package/dist/bridge-live-validation.js +1141 -0
- package/dist/bridge-live-validation.js.map +1 -0
- package/dist/bridge-performance-profile.d.ts +81 -0
- package/dist/bridge-performance-profile.js +227 -0
- package/dist/bridge-performance-profile.js.map +1 -0
- package/dist/bridge-readiness-cli.d.ts +30 -0
- package/dist/bridge-readiness-cli.js +242 -0
- package/dist/bridge-readiness-cli.js.map +1 -0
- package/dist/bridge-runtime-summary.d.ts +50 -0
- package/dist/bridge-runtime-summary.js +112 -0
- package/dist/bridge-runtime-summary.js.map +1 -0
- package/dist/bridge-workflow-smoke-cli.d.ts +2 -0
- package/dist/bridge-workflow-smoke-cli.js +126 -0
- package/dist/bridge-workflow-smoke-cli.js.map +1 -0
- package/dist/bridge-workflow-smoke.d.ts +39 -0
- package/dist/bridge-workflow-smoke.js +431 -0
- package/dist/bridge-workflow-smoke.js.map +1 -0
- package/dist/codeconnect-suggestions.d.ts +74 -0
- package/dist/codeconnect-suggestions.js +398 -0
- package/dist/codeconnect-suggestions.js.map +1 -0
- package/dist/codeconnect-template.d.ts +98 -0
- package/dist/codeconnect-template.js +280 -0
- package/dist/codeconnect-template.js.map +1 -0
- package/dist/command-queue.d.ts +11 -1
- package/dist/command-queue.js +200 -1
- package/dist/command-queue.js.map +1 -1
- package/dist/command-safety.d.ts +13 -0
- package/dist/command-safety.js +59 -0
- package/dist/command-safety.js.map +1 -0
- package/dist/enabled-library-search.d.ts +49 -0
- package/dist/enabled-library-search.js +151 -0
- package/dist/enabled-library-search.js.map +1 -0
- package/dist/figma-mcp-parity.d.ts +49 -0
- package/dist/figma-mcp-parity.js +368 -0
- package/dist/figma-mcp-parity.js.map +1 -0
- package/dist/figma-mcp-skills-parity.d.ts +61 -0
- package/dist/figma-mcp-skills-parity.js +434 -0
- package/dist/figma-mcp-skills-parity.js.map +1 -0
- package/dist/figma-rest-diagnostics.d.ts +50 -0
- package/dist/figma-rest-diagnostics.js +314 -0
- package/dist/figma-rest-diagnostics.js.map +1 -0
- package/dist/figma-rest.d.ts +27 -0
- package/dist/figma-rest.js +116 -0
- package/dist/figma-rest.js.map +1 -0
- package/dist/http-api.d.ts +14 -2
- package/dist/http-api.js +323 -17
- package/dist/http-api.js.map +1 -1
- package/dist/index.js +25 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-factory.d.ts +6 -1
- package/dist/mcp-factory.js +23 -4
- package/dist/mcp-factory.js.map +1 -1
- package/dist/mcp-runtime-probe.d.ts +22 -0
- package/dist/mcp-runtime-probe.js +777 -0
- package/dist/mcp-runtime-probe.js.map +1 -0
- package/dist/mcp-server.d.ts +2 -1
- package/dist/mcp-server.js +2 -2
- package/dist/mcp-server.js.map +1 -1
- package/dist/sxl-mcp-instructions.js +97 -25
- package/dist/sxl-mcp-instructions.js.map +1 -1
- package/dist/tools/audit.d.ts +22 -6
- package/dist/tools/audit.js +49 -7
- package/dist/tools/audit.js.map +1 -1
- package/dist/tools/capability-matrix.d.ts +22 -0
- package/dist/tools/capability-matrix.js +38 -0
- package/dist/tools/capability-matrix.js.map +1 -0
- package/dist/tools/catalogue-bootstrap.d.ts +1 -0
- package/dist/tools/catalogue-bootstrap.js +665 -30
- package/dist/tools/catalogue-bootstrap.js.map +1 -1
- package/dist/tools/code-connect-context.d.ts +3 -0
- package/dist/tools/code-connect-context.js +319 -0
- package/dist/tools/code-connect-context.js.map +1 -0
- package/dist/tools/code-connect-template.d.ts +3 -0
- package/dist/tools/code-connect-template.js +111 -0
- package/dist/tools/code-connect-template.js.map +1 -0
- package/dist/tools/composition.js +13 -28
- package/dist/tools/composition.js.map +1 -1
- package/dist/tools/compositions-orchestration.d.ts +14 -14
- package/dist/tools/compositions-orchestration.js +2 -2
- package/dist/tools/compositions-orchestration.js.map +1 -1
- package/dist/tools/data.js +839 -27
- package/dist/tools/data.js.map +1 -1
- package/dist/tools/design-context.d.ts +3 -0
- package/dist/tools/design-context.js +197 -0
- package/dist/tools/design-context.js.map +1 -0
- package/dist/tools/destructive-confirmation.d.ts +10 -0
- package/dist/tools/destructive-confirmation.js +22 -0
- package/dist/tools/destructive-confirmation.js.map +1 -0
- package/dist/tools/diagnostics.js +76 -51
- package/dist/tools/diagnostics.js.map +1 -1
- package/dist/tools/figma-mcp-design.d.ts +3 -0
- package/dist/tools/figma-mcp-design.js +377 -0
- package/dist/tools/figma-mcp-design.js.map +1 -0
- package/dist/tools/figma-nodes.js +57 -43
- package/dist/tools/figma-nodes.js.map +1 -1
- package/dist/tools/figma-rc-extended.js +23 -6
- package/dist/tools/figma-rc-extended.js.map +1 -1
- package/dist/tools/figma-rest.d.ts +39 -0
- package/dist/tools/figma-rest.js +279 -0
- package/dist/tools/figma-rest.js.map +1 -0
- package/dist/tools/git.js +11 -7
- package/dist/tools/git.js.map +1 -1
- package/dist/tools/large-data.d.ts +14 -0
- package/dist/tools/large-data.js +189 -0
- package/dist/tools/large-data.js.map +1 -0
- package/dist/tools/meta.d.ts +6 -1
- package/dist/tools/meta.js +89 -11
- package/dist/tools/meta.js.map +1 -1
- package/dist/tools/metadata.d.ts +3 -0
- package/dist/tools/metadata.js +140 -0
- package/dist/tools/metadata.js.map +1 -0
- package/dist/tools/mockup.d.ts +15 -156
- package/dist/tools/mockup.js +54 -121
- package/dist/tools/mockup.js.map +1 -1
- package/dist/tools/orchestration.js +73 -45
- package/dist/tools/orchestration.js.map +1 -1
- package/dist/tools/prompts.d.ts +3 -0
- package/dist/tools/prompts.js +219 -0
- package/dist/tools/prompts.js.map +1 -0
- package/dist/tools/registry.d.ts +19 -1
- package/dist/tools/registry.js +4 -4
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/resources.d.ts +19 -2
- package/dist/tools/resources.js +149 -5
- package/dist/tools/resources.js.map +1 -1
- package/dist/tools/schema-contracts.d.ts +4763 -0
- package/dist/tools/schema-contracts.js +814 -0
- package/dist/tools/schema-contracts.js.map +1 -0
- package/dist/tools/screenshot.d.ts +3 -0
- package/dist/tools/screenshot.js +144 -0
- package/dist/tools/screenshot.js.map +1 -0
- package/dist/tools/shared.d.ts +11 -1
- package/dist/tools/shared.js +55 -2
- package/dist/tools/shared.js.map +1 -1
- package/dist/tools/styles-orchestration.d.ts +2 -2
- package/dist/tools/styles-orchestration.js +13 -5
- package/dist/tools/styles-orchestration.js.map +1 -1
- package/dist/tools/styles.js +22 -8
- package/dist/tools/styles.js.map +1 -1
- package/dist/tools/tokens.d.ts +31 -692
- package/dist/tools/tokens.js +175 -135
- package/dist/tools/tokens.js.map +1 -1
- package/dist/tools/variable-defs.d.ts +3 -0
- package/dist/tools/variable-defs.js +338 -0
- package/dist/tools/variable-defs.js.map +1 -0
- package/dist/tools/variables-orchestration.js +13 -5
- package/dist/tools/variables-orchestration.js.map +1 -1
- package/dist/tools/variables.js +18 -15
- package/dist/tools/variables.js.map +1 -1
- package/dist/types.d.ts +53 -0
- package/dist/ultimate-readiness-audit.d.ts +37 -0
- package/dist/ultimate-readiness-audit.js +431 -0
- package/dist/ultimate-readiness-audit.js.map +1 -0
- package/dist/workflow-planner.d.ts +57 -0
- package/dist/workflow-planner.js +464 -0
- package/dist/workflow-planner.js.map +1 -0
- package/dist/ws-server.js +16 -3
- package/dist/ws-server.js.map +1 -1
- package/package.json +18 -2
package/dist/tools/data.js
CHANGED
|
@@ -2,55 +2,867 @@
|
|
|
2
2
|
* MCP tools for Data / Mapping operations.
|
|
3
3
|
*/
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import { prepareUploadAssets, uploadAssetReports, } from "../asset-upload.js";
|
|
6
|
+
import { buildCodeConnectSuggestions } from "../codeconnect-suggestions.js";
|
|
7
|
+
import { buildEnabledLibrarySearchResult } from "../enabled-library-search.js";
|
|
8
|
+
import { callPluginCommand, callPluginWriteCommand, err, executePluginCommand, executePluginWriteCommand, ok, } from "./shared.js";
|
|
9
|
+
import { destructiveConfirmationArgs, withDestructiveConfirmation } from "./destructive-confirmation.js";
|
|
10
|
+
import { largeDataResponseArgs, normalizeLargeDataResult } from "./large-data.js";
|
|
11
|
+
const datasetZod = z.object({ id: z.string() }).passthrough();
|
|
12
|
+
const assetZod = z.object({ id: z.string() }).passthrough();
|
|
13
|
+
const mappingZod = z.object({ id: z.string() }).passthrough();
|
|
14
|
+
const dryRunZod = z.boolean().optional().describe("Preview the operation without modifying plugin state.");
|
|
15
|
+
const applyScopeZod = z.enum(["selection", "page", "document"]);
|
|
16
|
+
const databaseAssetPayloadZod = z.union([
|
|
17
|
+
assetZod,
|
|
18
|
+
z.object({
|
|
19
|
+
asset: assetZod,
|
|
20
|
+
content: z.string().optional(),
|
|
21
|
+
data: z.string().optional(),
|
|
22
|
+
}).passthrough(),
|
|
23
|
+
]);
|
|
24
|
+
const databasePayloadZod = z.object({
|
|
25
|
+
kind: z.string().optional(),
|
|
26
|
+
version: z.number().optional(),
|
|
27
|
+
exportedAt: z.string().optional(),
|
|
28
|
+
datasets: z.array(datasetZod).optional(),
|
|
29
|
+
assets: z.array(databaseAssetPayloadZod).optional(),
|
|
30
|
+
mappings: z.array(mappingZod).optional(),
|
|
31
|
+
}).passthrough();
|
|
32
|
+
const imageScaleModeZod = z.enum(["FILL", "FIT", "CROP", "TILE"]);
|
|
33
|
+
const codeConnectFrameworkZod = z.enum(["react", "angular", "html", "swift", "kotlin", "vue3", "compose"]);
|
|
34
|
+
const enabledLibraryAssetKindZod = z.enum(["component", "style", "variable", "variableCollection"]);
|
|
35
|
+
const variableResolvedTypeZod = z.enum(["BOOLEAN", "COLOR", "FLOAT", "STRING"]);
|
|
36
|
+
const codeConnectTargetZod = z.object({
|
|
37
|
+
nodeId: z.string().optional(),
|
|
38
|
+
nodeName: z.string().describe("Figma node/component name to match against local code files."),
|
|
39
|
+
nodeType: z.string().optional(),
|
|
40
|
+
});
|
|
41
|
+
const uploadAssetZod = z.object({
|
|
42
|
+
url: z.string().url().optional().describe("HTTP(S) image URL. Supported: PNG, JPG/JPEG, GIF, WebP."),
|
|
43
|
+
dataBase64: z.string().optional().describe("Optional base64 image body or data:image/*;base64,... URI."),
|
|
44
|
+
contentType: z.string().optional().describe("Optional MIME type override, e.g. image/png."),
|
|
45
|
+
name: z.string().optional(),
|
|
46
|
+
nodeId: z.string().optional().describe("Existing node to receive this image as fill. If omitted Bridge creates a rectangle."),
|
|
47
|
+
parentId: z.string().optional(),
|
|
48
|
+
x: z.number().optional(),
|
|
49
|
+
y: z.number().optional(),
|
|
50
|
+
width: z.number().positive().optional(),
|
|
51
|
+
height: z.number().positive().optional(),
|
|
52
|
+
cornerRadius: z.number().min(0).optional(),
|
|
53
|
+
scaleMode: imageScaleModeZod.optional(),
|
|
54
|
+
}).refine((value) => Boolean(value.url || value.dataBase64), {
|
|
55
|
+
message: "Each asset requires url or dataBase64.",
|
|
56
|
+
});
|
|
57
|
+
function dryRunPreview(action, id, extra = {}) {
|
|
58
|
+
return ok({
|
|
59
|
+
ok: true,
|
|
60
|
+
dryRun: true,
|
|
61
|
+
action,
|
|
62
|
+
id,
|
|
63
|
+
willCallPlugin: false,
|
|
64
|
+
...extra,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
function isRecord(value) {
|
|
68
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
69
|
+
}
|
|
70
|
+
function readArrayResult(result, key) {
|
|
71
|
+
if (!isRecord(result))
|
|
72
|
+
return [];
|
|
73
|
+
const value = result[key];
|
|
74
|
+
if (!Array.isArray(value))
|
|
75
|
+
return [];
|
|
76
|
+
return value.filter(isRecord);
|
|
77
|
+
}
|
|
78
|
+
function readSingleResult(result, key) {
|
|
79
|
+
if (!isRecord(result))
|
|
80
|
+
return null;
|
|
81
|
+
const value = result[key];
|
|
82
|
+
return isRecord(value) ? value : null;
|
|
83
|
+
}
|
|
84
|
+
function readIdValue(item) {
|
|
85
|
+
return typeof item.id === "string" && item.id.length > 0 ? item.id : null;
|
|
86
|
+
}
|
|
87
|
+
function filterByIds(items, ids) {
|
|
88
|
+
if (!Array.isArray(ids) || ids.length === 0)
|
|
89
|
+
return items;
|
|
90
|
+
const wanted = new Set(ids);
|
|
91
|
+
return items.filter((item) => {
|
|
92
|
+
const id = readIdValue(item);
|
|
93
|
+
return id ? wanted.has(id) : false;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function idSet(items) {
|
|
97
|
+
return new Set(items.map(readIdValue).filter((id) => Boolean(id)));
|
|
98
|
+
}
|
|
99
|
+
function isExistingId(id, existing) {
|
|
100
|
+
return typeof id === "string" && existing.has(id);
|
|
101
|
+
}
|
|
102
|
+
function normalizeDatabaseAssetEntry(entry, warnings, index) {
|
|
103
|
+
if (!isRecord(entry)) {
|
|
104
|
+
warnings.push(`assets[${index}] ignored: entry must be an object`);
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
if (isRecord(entry.asset)) {
|
|
108
|
+
const id = readIdValue(entry.asset);
|
|
109
|
+
if (!id) {
|
|
110
|
+
warnings.push(`assets[${index}] ignored: asset.id is required`);
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
const content = typeof entry.content === "string"
|
|
114
|
+
? entry.content
|
|
115
|
+
: typeof entry.data === "string"
|
|
116
|
+
? entry.data
|
|
117
|
+
: undefined;
|
|
118
|
+
return { asset: entry.asset, content };
|
|
119
|
+
}
|
|
120
|
+
const id = readIdValue(entry);
|
|
121
|
+
if (!id) {
|
|
122
|
+
warnings.push(`assets[${index}] ignored: id is required`);
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
const content = typeof entry.data === "string" ? entry.data : undefined;
|
|
126
|
+
return { asset: entry, content };
|
|
127
|
+
}
|
|
128
|
+
function normalizeDatabasePayload(payload) {
|
|
129
|
+
const warnings = [];
|
|
130
|
+
if (!isRecord(payload)) {
|
|
131
|
+
return { datasets: [], assets: [], mappings: [], warnings: ["payload must be an object"] };
|
|
132
|
+
}
|
|
133
|
+
const datasets = Array.isArray(payload.datasets)
|
|
134
|
+
? payload.datasets.filter((dataset, index) => {
|
|
135
|
+
const valid = isRecord(dataset) && Boolean(readIdValue(dataset));
|
|
136
|
+
if (!valid)
|
|
137
|
+
warnings.push(`datasets[${index}] ignored: id is required`);
|
|
138
|
+
return valid;
|
|
139
|
+
})
|
|
140
|
+
: [];
|
|
141
|
+
const assets = Array.isArray(payload.assets)
|
|
142
|
+
? payload.assets
|
|
143
|
+
.map((asset, index) => normalizeDatabaseAssetEntry(asset, warnings, index))
|
|
144
|
+
.filter((asset) => Boolean(asset))
|
|
145
|
+
: [];
|
|
146
|
+
const mappings = Array.isArray(payload.mappings)
|
|
147
|
+
? payload.mappings.filter((mapping, index) => {
|
|
148
|
+
const valid = isRecord(mapping) && Boolean(readIdValue(mapping));
|
|
149
|
+
if (!valid)
|
|
150
|
+
warnings.push(`mappings[${index}] ignored: id is required`);
|
|
151
|
+
return valid;
|
|
152
|
+
})
|
|
153
|
+
: [];
|
|
154
|
+
return { datasets, assets, mappings, warnings };
|
|
155
|
+
}
|
|
156
|
+
function databaseCounts(payload) {
|
|
157
|
+
return {
|
|
158
|
+
datasets: payload.datasets.length,
|
|
159
|
+
assets: payload.assets.length,
|
|
160
|
+
assetContents: payload.assets.filter((asset) => typeof asset.content === "string").length,
|
|
161
|
+
mappings: payload.mappings.length,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function readCountResult(result) {
|
|
165
|
+
if (!isRecord(result))
|
|
166
|
+
return null;
|
|
167
|
+
return typeof result.count === "number" && Number.isFinite(result.count) ? result.count : null;
|
|
168
|
+
}
|
|
169
|
+
function mappingFields(mapping) {
|
|
170
|
+
if (!mapping || !Array.isArray(mapping.fields))
|
|
171
|
+
return [];
|
|
172
|
+
return mapping.fields.filter(isRecord);
|
|
173
|
+
}
|
|
174
|
+
function mappingTargetLayerNames(mapping) {
|
|
175
|
+
return [...new Set(mappingFields(mapping)
|
|
176
|
+
.map((field) => field.layerName)
|
|
177
|
+
.filter((name) => typeof name === "string" && name.length > 0))];
|
|
178
|
+
}
|
|
179
|
+
function mappingDatasetIds(mapping) {
|
|
180
|
+
if (!mapping)
|
|
181
|
+
return [];
|
|
182
|
+
const ids = new Set();
|
|
183
|
+
if (typeof mapping.datasetId === "string" && mapping.datasetId.length > 0)
|
|
184
|
+
ids.add(mapping.datasetId);
|
|
185
|
+
for (const field of mappingFields(mapping)) {
|
|
186
|
+
if (typeof field.datasetId === "string" && field.datasetId.length > 0)
|
|
187
|
+
ids.add(field.datasetId);
|
|
188
|
+
}
|
|
189
|
+
return [...ids];
|
|
190
|
+
}
|
|
191
|
+
function rowCountFromDataset(dataset) {
|
|
192
|
+
if (!dataset)
|
|
193
|
+
return null;
|
|
194
|
+
if (Array.isArray(dataset.data))
|
|
195
|
+
return dataset.data.length;
|
|
196
|
+
if (dataset.data !== undefined && dataset.data !== null)
|
|
197
|
+
return 1;
|
|
198
|
+
if (typeof dataset.lines === "number" && Number.isFinite(dataset.lines))
|
|
199
|
+
return dataset.lines;
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
function resolveRequestedRowCount(datasetRows, requested) {
|
|
203
|
+
if (typeof requested === "number" && Number.isFinite(requested)) {
|
|
204
|
+
return datasetRows === null ? Math.max(0, Math.floor(requested)) : Math.min(datasetRows, Math.max(0, Math.floor(requested)));
|
|
205
|
+
}
|
|
206
|
+
return datasetRows;
|
|
207
|
+
}
|
|
208
|
+
function summarizeMapping(mapping) {
|
|
209
|
+
if (!mapping)
|
|
210
|
+
return null;
|
|
211
|
+
return {
|
|
212
|
+
id: typeof mapping.id === "string" ? mapping.id : null,
|
|
213
|
+
name: typeof mapping.name === "string" ? mapping.name : null,
|
|
214
|
+
fieldCount: mappingFields(mapping).length,
|
|
215
|
+
datasetIds: mappingDatasetIds(mapping),
|
|
216
|
+
targetLayerNames: mappingTargetLayerNames(mapping),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
async function getMappingById(queue, mappingId) {
|
|
220
|
+
const result = await executePluginCommand(queue, "get_mapping", { mappingId });
|
|
221
|
+
return readSingleResult(result, "mapping");
|
|
222
|
+
}
|
|
223
|
+
function readTargetNodeIds(args) {
|
|
224
|
+
const direct = Array.isArray(args.targetNodeIds)
|
|
225
|
+
? args.targetNodeIds.filter((id) => typeof id === "string" && id.length > 0)
|
|
226
|
+
: [];
|
|
227
|
+
const legacy = Array.isArray(args.targetNodes)
|
|
228
|
+
? args.targetNodes
|
|
229
|
+
.map((node) => isRecord(node) ? node.id : undefined)
|
|
230
|
+
.filter((id) => typeof id === "string" && id.length > 0)
|
|
231
|
+
: [];
|
|
232
|
+
return [...new Set([...direct, ...legacy])];
|
|
233
|
+
}
|
|
234
|
+
async function countApplyTargetsSafely(queue, scope, targetNodeIds = []) {
|
|
235
|
+
try {
|
|
236
|
+
const result = await executePluginCommand(queue, "count_apply_targets", {
|
|
237
|
+
scope,
|
|
238
|
+
...(targetNodeIds.length > 0 ? { targetNodeIds } : {}),
|
|
239
|
+
});
|
|
240
|
+
return { count: readCountResult(result) };
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
return { count: null, warning: `count_apply_targets failed: ${error instanceof Error ? error.message : String(error)}` };
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
async function datasetSummariesForMapping(queue, mapping) {
|
|
247
|
+
const warnings = [];
|
|
248
|
+
const datasets = [];
|
|
249
|
+
for (const datasetId of mappingDatasetIds(mapping)) {
|
|
250
|
+
try {
|
|
251
|
+
const dataset = readSingleResult(await executePluginCommand(queue, "get_dataset", { datasetId }), "dataset");
|
|
252
|
+
datasets.push({
|
|
253
|
+
id: datasetId,
|
|
254
|
+
exists: Boolean(dataset),
|
|
255
|
+
name: dataset && typeof dataset.name === "string" ? dataset.name : null,
|
|
256
|
+
rowCount: rowCountFromDataset(dataset),
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
warnings.push(`Dataset ${datasetId} could not be read: ${error instanceof Error ? error.message : String(error)}`);
|
|
261
|
+
datasets.push({ id: datasetId, exists: false, rowCount: null });
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return { datasets, warnings };
|
|
265
|
+
}
|
|
266
|
+
async function readExistingDatabaseIds(queue) {
|
|
267
|
+
const [datasetsResult, assetsResult, mappingsResult] = await Promise.all([
|
|
268
|
+
executePluginCommand(queue, "list_datasets"),
|
|
269
|
+
executePluginCommand(queue, "list_assets"),
|
|
270
|
+
executePluginCommand(queue, "list_mappings"),
|
|
271
|
+
]);
|
|
272
|
+
return {
|
|
273
|
+
datasets: idSet(readArrayResult(datasetsResult, "datasets")),
|
|
274
|
+
assets: idSet(readArrayResult(assetsResult, "assets")),
|
|
275
|
+
mappings: idSet(readArrayResult(mappingsResult, "mappings")),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function findConflicts(payload, existing) {
|
|
279
|
+
const datasets = payload.datasets.map(readIdValue).filter((id) => isExistingId(id, existing.datasets));
|
|
280
|
+
const assets = payload.assets
|
|
281
|
+
.map((entry) => readIdValue(entry.asset))
|
|
282
|
+
.filter((id) => isExistingId(id, existing.assets));
|
|
283
|
+
const mappings = payload.mappings.map(readIdValue).filter((id) => isExistingId(id, existing.mappings));
|
|
284
|
+
return {
|
|
285
|
+
datasets,
|
|
286
|
+
assets,
|
|
287
|
+
mappings,
|
|
288
|
+
total: datasets.length + assets.length + mappings.length,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
async function exportDatabasePayload(queue, args) {
|
|
292
|
+
const includeDatasets = args.includeDatasets !== false;
|
|
293
|
+
const includeAssets = args.includeAssets !== false;
|
|
294
|
+
const includeMappings = args.includeMappings !== false;
|
|
295
|
+
const includeAssetContent = args.includeAssetContent === true;
|
|
296
|
+
const datasetIds = Array.isArray(args.datasetIds) ? args.datasetIds.filter((id) => typeof id === "string") : undefined;
|
|
297
|
+
const assetIds = Array.isArray(args.assetIds) ? args.assetIds.filter((id) => typeof id === "string") : undefined;
|
|
298
|
+
const mappingIds = Array.isArray(args.mappingIds) ? args.mappingIds.filter((id) => typeof id === "string") : undefined;
|
|
299
|
+
const warnings = [];
|
|
300
|
+
let datasets = [];
|
|
301
|
+
if (includeDatasets) {
|
|
302
|
+
const listResult = await executePluginCommand(queue, "list_datasets");
|
|
303
|
+
const listed = filterByIds(readArrayResult(listResult, "datasets"), datasetIds);
|
|
304
|
+
datasets = await Promise.all(listed.map(async (dataset) => {
|
|
305
|
+
const id = readIdValue(dataset);
|
|
306
|
+
if (!id)
|
|
307
|
+
return dataset;
|
|
308
|
+
try {
|
|
309
|
+
return readSingleResult(await executePluginCommand(queue, "get_dataset", { datasetId: id }), "dataset") ?? dataset;
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
warnings.push(`Dataset ${id} could not be hydrated: ${error instanceof Error ? error.message : String(error)}`);
|
|
313
|
+
return dataset;
|
|
314
|
+
}
|
|
315
|
+
}));
|
|
316
|
+
}
|
|
317
|
+
let assets = [];
|
|
318
|
+
if (includeAssets) {
|
|
319
|
+
const listResult = await executePluginCommand(queue, "list_assets");
|
|
320
|
+
const listed = filterByIds(readArrayResult(listResult, "assets"), assetIds);
|
|
321
|
+
assets = await Promise.all(listed.map(async (asset) => {
|
|
322
|
+
const id = readIdValue(asset);
|
|
323
|
+
if (!id)
|
|
324
|
+
return { asset };
|
|
325
|
+
try {
|
|
326
|
+
const result = await executePluginCommand(queue, "get_asset", { assetId: id, includeContent: includeAssetContent });
|
|
327
|
+
const hydratedAsset = readSingleResult(result, "asset") ?? asset;
|
|
328
|
+
const content = includeAssetContent && isRecord(result) && typeof result.content === "string"
|
|
329
|
+
? result.content
|
|
330
|
+
: typeof hydratedAsset.data === "string"
|
|
331
|
+
? hydratedAsset.data
|
|
332
|
+
: undefined;
|
|
333
|
+
return { asset: hydratedAsset, ...(includeAssetContent && content ? { content } : {}) };
|
|
334
|
+
}
|
|
335
|
+
catch (error) {
|
|
336
|
+
warnings.push(`Asset ${id} could not be hydrated: ${error instanceof Error ? error.message : String(error)}`);
|
|
337
|
+
return { asset };
|
|
338
|
+
}
|
|
339
|
+
}));
|
|
340
|
+
}
|
|
341
|
+
let mappings = [];
|
|
342
|
+
if (includeMappings) {
|
|
343
|
+
const listResult = await executePluginCommand(queue, "list_mappings");
|
|
344
|
+
const listed = filterByIds(readArrayResult(listResult, "mappings"), mappingIds);
|
|
345
|
+
mappings = await Promise.all(listed.map(async (mapping) => {
|
|
346
|
+
const id = readIdValue(mapping);
|
|
347
|
+
if (!id)
|
|
348
|
+
return mapping;
|
|
349
|
+
try {
|
|
350
|
+
return readSingleResult(await executePluginCommand(queue, "get_mapping", { mappingId: id }), "mapping") ?? mapping;
|
|
351
|
+
}
|
|
352
|
+
catch (error) {
|
|
353
|
+
warnings.push(`Mapping ${id} could not be hydrated: ${error instanceof Error ? error.message : String(error)}`);
|
|
354
|
+
return mapping;
|
|
355
|
+
}
|
|
356
|
+
}));
|
|
357
|
+
}
|
|
358
|
+
const normalized = normalizeDatabasePayload({ datasets, assets, mappings });
|
|
359
|
+
return {
|
|
360
|
+
ok: true,
|
|
361
|
+
kind: "sxl-database-payload",
|
|
362
|
+
version: 1,
|
|
363
|
+
exportedAt: new Date().toISOString(),
|
|
364
|
+
counts: databaseCounts(normalized),
|
|
365
|
+
datasets: normalized.datasets,
|
|
366
|
+
assets: normalized.assets,
|
|
367
|
+
mappings: normalized.mappings,
|
|
368
|
+
warnings: [...warnings, ...normalized.warnings],
|
|
369
|
+
};
|
|
370
|
+
}
|
|
5
371
|
export function registerDataTools(server, queue) {
|
|
6
|
-
server.tool("
|
|
372
|
+
server.tool("list_datasets", "List SXL database datasets in the plugin workspace. Large responses are summarized automatically for MCP unless bridgeResponseMode='full'.", { ...largeDataResponseArgs }, async (args) => callPluginCommand(queue, "list_datasets", args, { largeData: true }));
|
|
373
|
+
server.tool("get_dataset", "Get one SXL dataset by id. Large responses are summarized automatically for MCP unless bridgeResponseMode='full'.", { datasetId: z.string(), ...largeDataResponseArgs }, async ({ datasetId, ...controls }) => callPluginCommand(queue, "get_dataset", { datasetId, ...controls }, { largeData: true }));
|
|
374
|
+
server.tool("save_dataset", "Create or update one SXL dataset. Set dryRun=true to preview without writing.", { dataset: datasetZod, dryRun: dryRunZod }, async ({ dataset, dryRun }) => dryRun
|
|
375
|
+
? dryRunPreview("save_dataset", dataset.id)
|
|
376
|
+
: callPluginWriteCommand(queue, "save_dataset", { dataset }, {
|
|
377
|
+
okFalseErrorCode: "SXL_DATABASE_WRITE_FAILED",
|
|
378
|
+
}));
|
|
379
|
+
server.tool("delete_dataset", "Delete one SXL dataset by id. Set dryRun=true to preview without deleting.", { datasetId: z.string(), dryRun: dryRunZod, ...destructiveConfirmationArgs }, async ({ datasetId, dryRun, confirmDestructive, destructiveReason }) => dryRun
|
|
380
|
+
? dryRunPreview("delete_dataset", datasetId, { destructive: true })
|
|
381
|
+
: callPluginWriteCommand(queue, "delete_dataset", withDestructiveConfirmation({ datasetId }, { confirmDestructive, destructiveReason }), { okFalseErrorCode: "SXL_DATABASE_WRITE_FAILED" }));
|
|
382
|
+
server.tool("list_assets", "List SXL database local assets in the plugin workspace. Large responses are summarized automatically for MCP unless bridgeResponseMode='full'.", { ...largeDataResponseArgs }, async (args) => callPluginCommand(queue, "list_assets", args, { largeData: true }));
|
|
383
|
+
server.tool("get_asset", "Get one SXL asset by id. Set includeContent=true to include stored base64 content. Large responses are summarized automatically for MCP unless bridgeResponseMode='full'.", { assetId: z.string(), includeContent: z.boolean().optional(), ...largeDataResponseArgs }, async ({ assetId, includeContent, ...controls }) => callPluginCommand(queue, "get_asset", { assetId, includeContent, ...controls }, { largeData: true }));
|
|
384
|
+
server.tool("save_asset", "Create or update one SXL asset. Optional data is base64 content. Set dryRun=true to preview without writing.", { asset: assetZod, data: z.string().optional(), dryRun: dryRunZod }, async ({ asset, data, dryRun }) => dryRun
|
|
385
|
+
? dryRunPreview("save_asset", asset.id, { hasData: typeof data === "string" })
|
|
386
|
+
: callPluginWriteCommand(queue, "save_asset", { asset, data }, {
|
|
387
|
+
okFalseErrorCode: "SXL_DATABASE_WRITE_FAILED",
|
|
388
|
+
}));
|
|
389
|
+
server.tool("delete_asset", "Delete one SXL asset by id. Set dryRun=true to preview without deleting.", { assetId: z.string(), dryRun: dryRunZod, ...destructiveConfirmationArgs }, async ({ assetId, dryRun, confirmDestructive, destructiveReason }) => dryRun
|
|
390
|
+
? dryRunPreview("delete_asset", assetId, { destructive: true })
|
|
391
|
+
: callPluginWriteCommand(queue, "delete_asset", withDestructiveConfirmation({ assetId }, { confirmDestructive, destructiveReason }), { okFalseErrorCode: "SXL_DATABASE_WRITE_FAILED" }));
|
|
392
|
+
server.tool("list_mappings", "List SXL database mappings in the plugin workspace. Large responses are summarized automatically for MCP unless bridgeResponseMode='full'.", { ...largeDataResponseArgs }, async (args) => callPluginCommand(queue, "list_mappings", args, { largeData: true }));
|
|
393
|
+
server.tool("get_mapping", "Get one SXL mapping by id. Large responses are summarized automatically for MCP unless bridgeResponseMode='full'.", { mappingId: z.string(), ...largeDataResponseArgs }, async ({ mappingId, ...controls }) => callPluginCommand(queue, "get_mapping", { mappingId, ...controls }, { largeData: true }));
|
|
394
|
+
server.tool("save_mapping", "Create or update one SXL mapping. Set dryRun=true to preview without writing.", { mapping: mappingZod, dryRun: dryRunZod }, async ({ mapping, dryRun }) => dryRun
|
|
395
|
+
? dryRunPreview("save_mapping", mapping.id)
|
|
396
|
+
: callPluginWriteCommand(queue, "save_mapping", { mapping }, {
|
|
397
|
+
okFalseErrorCode: "SXL_DATABASE_WRITE_FAILED",
|
|
398
|
+
}));
|
|
399
|
+
server.tool("delete_mapping", "Delete one SXL mapping by id. Set dryRun=true to preview without deleting.", { mappingId: z.string(), dryRun: dryRunZod, ...destructiveConfirmationArgs }, async ({ mappingId, dryRun, confirmDestructive, destructiveReason }) => dryRun
|
|
400
|
+
? dryRunPreview("delete_mapping", mappingId, { destructive: true })
|
|
401
|
+
: callPluginWriteCommand(queue, "delete_mapping", withDestructiveConfirmation({ mappingId }, { confirmDestructive, destructiveReason }), { okFalseErrorCode: "SXL_DATABASE_WRITE_FAILED" }));
|
|
402
|
+
server.tool("export_database_payload", "Export a portable SXL database payload (datasets, local assets, mappings) through existing plugin CRUD commands. Asset content is excluded by default; set includeAssetContent=true for backups that need embedded image data.", {
|
|
403
|
+
includeDatasets: z.boolean().optional(),
|
|
404
|
+
includeAssets: z.boolean().optional(),
|
|
405
|
+
includeMappings: z.boolean().optional(),
|
|
406
|
+
includeAssetContent: z.boolean().optional().describe("Include stored base64/data URL asset content. Default false to keep MCP responses small."),
|
|
407
|
+
datasetIds: z.array(z.string()).optional(),
|
|
408
|
+
assetIds: z.array(z.string()).optional(),
|
|
409
|
+
mappingIds: z.array(z.string()).optional(),
|
|
410
|
+
...largeDataResponseArgs,
|
|
411
|
+
}, async (args) => {
|
|
412
|
+
try {
|
|
413
|
+
const payload = await exportDatabasePayload(queue, args);
|
|
414
|
+
return ok(normalizeLargeDataResult("export_database_payload", args, payload));
|
|
415
|
+
}
|
|
416
|
+
catch (error) {
|
|
417
|
+
return err(error instanceof Error ? error.message : String(error), {
|
|
418
|
+
code: "EXPORT_DATABASE_PAYLOAD_ERROR",
|
|
419
|
+
retryable: /No plugin connected|Session disconnected/i.test(error instanceof Error ? error.message : String(error)),
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
server.tool("import_database_payload", "Import a portable SXL database payload by upserting datasets, local assets, and mappings through existing plugin CRUD commands. Defaults to dryRun=true. Existing IDs require confirmDestructive=true when writing.", {
|
|
424
|
+
payload: databasePayloadZod,
|
|
425
|
+
dryRun: dryRunZod,
|
|
426
|
+
confirmDestructive: destructiveConfirmationArgs.confirmDestructive,
|
|
427
|
+
destructiveReason: destructiveConfirmationArgs.destructiveReason,
|
|
428
|
+
...largeDataResponseArgs,
|
|
429
|
+
}, async ({ payload, dryRun, confirmDestructive, destructiveReason, ...controls }) => {
|
|
430
|
+
try {
|
|
431
|
+
const normalized = normalizeDatabasePayload(payload);
|
|
432
|
+
const existing = await readExistingDatabaseIds(queue);
|
|
433
|
+
const conflicts = findConflicts(normalized, existing);
|
|
434
|
+
const effectiveDryRun = dryRun !== false;
|
|
435
|
+
const preview = {
|
|
436
|
+
ok: true,
|
|
437
|
+
dryRun: effectiveDryRun,
|
|
438
|
+
action: "import_database_payload",
|
|
439
|
+
counts: databaseCounts(normalized),
|
|
440
|
+
conflicts,
|
|
441
|
+
warnings: normalized.warnings,
|
|
442
|
+
willCallPlugin: !effectiveDryRun,
|
|
443
|
+
};
|
|
444
|
+
if (effectiveDryRun) {
|
|
445
|
+
return ok(normalizeLargeDataResult("import_database_payload", controls, preview));
|
|
446
|
+
}
|
|
447
|
+
if (conflicts.total > 0 && confirmDestructive !== true) {
|
|
448
|
+
return err("import_database_payload would overwrite existing datasets/assets/mappings. Re-run with confirmDestructive=true and destructiveReason.", {
|
|
449
|
+
code: "DATABASE_IMPORT_CONFIRMATION_REQUIRED",
|
|
450
|
+
retryable: false,
|
|
451
|
+
conflicts,
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
if (conflicts.total > 0 && (typeof destructiveReason !== "string" || destructiveReason.trim().length === 0)) {
|
|
455
|
+
return err("destructiveReason is required when import_database_payload overwrites existing IDs.", {
|
|
456
|
+
code: "DATABASE_IMPORT_REASON_REQUIRED",
|
|
457
|
+
retryable: false,
|
|
458
|
+
conflicts,
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
const results = [];
|
|
462
|
+
for (const dataset of normalized.datasets) {
|
|
463
|
+
const id = readIdValue(dataset);
|
|
464
|
+
await executePluginWriteCommand(queue, "save_dataset", { dataset });
|
|
465
|
+
results.push({ type: "dataset", id, status: "saved" });
|
|
466
|
+
}
|
|
467
|
+
for (const entry of normalized.assets) {
|
|
468
|
+
const id = readIdValue(entry.asset);
|
|
469
|
+
await executePluginWriteCommand(queue, "save_asset", { asset: entry.asset, data: entry.content });
|
|
470
|
+
results.push({ type: "asset", id, status: "saved", hasContent: typeof entry.content === "string" });
|
|
471
|
+
}
|
|
472
|
+
for (const mapping of normalized.mappings) {
|
|
473
|
+
const id = readIdValue(mapping);
|
|
474
|
+
await executePluginWriteCommand(queue, "save_mapping", { mapping });
|
|
475
|
+
results.push({ type: "mapping", id, status: "saved" });
|
|
476
|
+
}
|
|
477
|
+
return ok(normalizeLargeDataResult("import_database_payload", controls, {
|
|
478
|
+
...preview,
|
|
479
|
+
ok: true,
|
|
480
|
+
dryRun: false,
|
|
481
|
+
applied: true,
|
|
482
|
+
results,
|
|
483
|
+
}));
|
|
484
|
+
}
|
|
485
|
+
catch (error) {
|
|
486
|
+
return err(error instanceof Error ? error.message : String(error), {
|
|
487
|
+
code: "IMPORT_DATABASE_PAYLOAD_ERROR",
|
|
488
|
+
retryable: /No plugin connected|Session disconnected/i.test(error instanceof Error ? error.message : String(error)),
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
server.tool("apply_mapping", "Apply a data mapping to explicit targetNodeIds or the current Figma selection. Use dryRun=true first to inspect mapping fields, dataset ids, and target count without writing.", {
|
|
7
493
|
mappingId: z.string().describe("ID of the mapping to apply"),
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
494
|
+
targetNodeIds: z.array(z.string()).optional().describe("Optional explicit scene node ids to use as selection roots for this apply."),
|
|
495
|
+
targetNodes: z.array(z.unknown()).optional().describe("Legacy target node objects with id fields. Prefer targetNodeIds."),
|
|
496
|
+
dryRun: dryRunZod,
|
|
497
|
+
}, async ({ mappingId, targetNodeIds, targetNodes, dryRun }) => {
|
|
498
|
+
const resolvedTargetNodeIds = readTargetNodeIds({ targetNodeIds, targetNodes });
|
|
499
|
+
if (dryRun) {
|
|
500
|
+
try {
|
|
501
|
+
const mapping = await getMappingById(queue, mappingId);
|
|
502
|
+
const targetCount = await countApplyTargetsSafely(queue, "selection", resolvedTargetNodeIds);
|
|
503
|
+
const datasets = await datasetSummariesForMapping(queue, mapping);
|
|
504
|
+
const warnings = [
|
|
505
|
+
...datasets.warnings,
|
|
506
|
+
...(targetCount.warning ? [targetCount.warning] : []),
|
|
507
|
+
...(Array.isArray(targetNodes) && targetNodes.length > 0
|
|
508
|
+
? ["targetNodes is supported as a legacy id carrier; prefer targetNodeIds for new clients."]
|
|
509
|
+
: []),
|
|
510
|
+
];
|
|
511
|
+
return ok({
|
|
512
|
+
ok: Boolean(mapping),
|
|
513
|
+
dryRun: true,
|
|
514
|
+
action: "apply_mapping",
|
|
515
|
+
mappingId,
|
|
516
|
+
mappingExists: Boolean(mapping),
|
|
517
|
+
mapping: summarizeMapping(mapping),
|
|
518
|
+
scope: "selection",
|
|
519
|
+
targetMode: resolvedTargetNodeIds.length > 0 ? "explicit-targets" : "current-selection",
|
|
520
|
+
selectionTargetCount: targetCount.count,
|
|
521
|
+
targetNodeIds: resolvedTargetNodeIds,
|
|
522
|
+
providedTargetNodes: resolvedTargetNodeIds.length,
|
|
523
|
+
datasets: datasets.datasets,
|
|
524
|
+
warnings,
|
|
525
|
+
willCallPlugin: false,
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
catch (error) {
|
|
529
|
+
return err(error instanceof Error ? error.message : String(error), {
|
|
530
|
+
code: "APPLY_MAPPING_PREVIEW_ERROR",
|
|
531
|
+
retryable: /No plugin connected|Session disconnected/i.test(error instanceof Error ? error.message : String(error)),
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return callPluginWriteCommand(queue, "apply_mapping", {
|
|
536
|
+
mappingId,
|
|
537
|
+
...(resolvedTargetNodeIds.length > 0 ? { targetNodeIds: resolvedTargetNodeIds } : {}),
|
|
538
|
+
...(targetNodes ? { targetNodes } : {}),
|
|
539
|
+
}, { okFalseErrorCode: "SXL_DATA_WRITE_FAILED" });
|
|
12
540
|
});
|
|
13
|
-
server.tool("apply_all_mappings", "Apply all mappings within a scope (selection, page, or document).
|
|
14
|
-
|
|
15
|
-
|
|
541
|
+
server.tool("apply_all_mappings", "Apply all mappings within a scope (selection, page, or document). Use dryRun=true first to count mappings and matching targets without writing.", {
|
|
542
|
+
scope: applyScopeZod.optional().describe("Scope: selection, page, or document. Default: selection."),
|
|
543
|
+
targetNodeIds: z.array(z.string()).optional().describe("Optional explicit scene node ids; when provided Bridge applies/counts these as selection roots."),
|
|
544
|
+
targetNodes: z.array(z.unknown()).optional().describe("Legacy target node objects with id fields. Prefer targetNodeIds."),
|
|
545
|
+
dryRun: dryRunZod,
|
|
546
|
+
}, async ({ scope, targetNodeIds, targetNodes, dryRun }) => {
|
|
547
|
+
const effectiveScope = scope ?? "selection";
|
|
548
|
+
const resolvedTargetNodeIds = readTargetNodeIds({ targetNodeIds, targetNodes });
|
|
549
|
+
if (dryRun) {
|
|
550
|
+
try {
|
|
551
|
+
const mappingsResult = await executePluginCommand(queue, "list_mappings");
|
|
552
|
+
const mappings = readArrayResult(mappingsResult, "mappings");
|
|
553
|
+
const targetCount = await countApplyTargetsSafely(queue, effectiveScope, resolvedTargetNodeIds);
|
|
554
|
+
return ok({
|
|
555
|
+
ok: true,
|
|
556
|
+
dryRun: true,
|
|
557
|
+
action: "apply_all_mappings",
|
|
558
|
+
scope: effectiveScope,
|
|
559
|
+
targetMode: resolvedTargetNodeIds.length > 0 ? "explicit-targets" : "scope",
|
|
560
|
+
targetNodeIds: resolvedTargetNodeIds,
|
|
561
|
+
mappingCount: mappings.length,
|
|
562
|
+
mappings: mappings.map((mapping) => summarizeMapping(mapping)),
|
|
563
|
+
targetCount: targetCount.count,
|
|
564
|
+
warnings: targetCount.warning ? [targetCount.warning] : [],
|
|
565
|
+
willCallPlugin: false,
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
catch (error) {
|
|
569
|
+
return err(error instanceof Error ? error.message : String(error), {
|
|
570
|
+
code: "APPLY_ALL_MAPPINGS_PREVIEW_ERROR",
|
|
571
|
+
retryable: /No plugin connected|Session disconnected/i.test(error instanceof Error ? error.message : String(error)),
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
return callPluginWriteCommand(queue, "apply_all_mappings", {
|
|
576
|
+
scope: effectiveScope,
|
|
577
|
+
...(resolvedTargetNodeIds.length > 0 ? { targetNodeIds: resolvedTargetNodeIds } : {}),
|
|
578
|
+
...(targetNodes ? { targetNodes } : {}),
|
|
579
|
+
}, { okFalseErrorCode: "SXL_DATA_WRITE_FAILED" });
|
|
16
580
|
});
|
|
17
|
-
server.tool("count_apply_targets", "Count how many nodes match a mapping scope.", {
|
|
18
|
-
|
|
19
|
-
|
|
581
|
+
server.tool("count_apply_targets", "Count how many nodes match a mapping scope.", {
|
|
582
|
+
scope: applyScopeZod.describe("Scope: selection, page, or document"),
|
|
583
|
+
targetNodeIds: z.array(z.string()).optional().describe("Optional explicit scene node ids to count as selection roots."),
|
|
584
|
+
targetNodes: z.array(z.unknown()).optional().describe("Legacy target node objects with id fields. Prefer targetNodeIds."),
|
|
585
|
+
}, async ({ scope, targetNodeIds, targetNodes }) => {
|
|
586
|
+
const resolvedTargetNodeIds = readTargetNodeIds({ targetNodeIds, targetNodes });
|
|
587
|
+
return callPluginCommand(queue, "count_apply_targets", {
|
|
588
|
+
scope,
|
|
589
|
+
...(resolvedTargetNodeIds.length > 0 ? { targetNodeIds: resolvedTargetNodeIds } : {}),
|
|
590
|
+
...(targetNodes ? { targetNodes } : {}),
|
|
591
|
+
});
|
|
20
592
|
});
|
|
21
|
-
server.tool("generate_instances", "Generate component instances from mapping data.", {
|
|
593
|
+
server.tool("generate_instances", "Generate component instances from mapping data into explicit targetNodeIds or the current Figma selection. Use dryRun=true first to estimate rows and validate mapping/dataset references without writing.", {
|
|
22
594
|
mappingId: z.string().describe("Mapping ID to generate from"),
|
|
23
595
|
rowCount: z.number().nullable().optional().describe("Number of rows to generate (null for all)"),
|
|
24
|
-
|
|
25
|
-
|
|
596
|
+
targetNodeIds: z.array(z.string()).optional().describe("Optional explicit scene node ids to use as the template/container source."),
|
|
597
|
+
targetNodes: z.array(z.unknown()).optional().describe("Legacy target node objects with id fields. Prefer targetNodeIds."),
|
|
598
|
+
dryRun: dryRunZod,
|
|
599
|
+
}, async ({ mappingId, rowCount, targetNodeIds, targetNodes, dryRun }) => {
|
|
600
|
+
const resolvedTargetNodeIds = readTargetNodeIds({ targetNodeIds, targetNodes });
|
|
601
|
+
if (dryRun) {
|
|
602
|
+
try {
|
|
603
|
+
const mapping = await getMappingById(queue, mappingId);
|
|
604
|
+
const datasets = await datasetSummariesForMapping(queue, mapping);
|
|
605
|
+
const primaryRows = datasets.datasets.find((dataset) => typeof dataset.rowCount === "number")?.rowCount;
|
|
606
|
+
const warnings = [
|
|
607
|
+
...datasets.warnings,
|
|
608
|
+
...(Array.isArray(targetNodes) && targetNodes.length > 0
|
|
609
|
+
? ["targetNodes is supported as a legacy id carrier; prefer targetNodeIds for new clients."]
|
|
610
|
+
: []),
|
|
611
|
+
...(resolvedTargetNodeIds.length === 0
|
|
612
|
+
? ["No targetNodeIds provided; committed generate_instances will use the current Figma selection as the template/container source."]
|
|
613
|
+
: []),
|
|
614
|
+
];
|
|
615
|
+
return ok({
|
|
616
|
+
ok: Boolean(mapping),
|
|
617
|
+
dryRun: true,
|
|
618
|
+
action: "generate_instances",
|
|
619
|
+
mappingId,
|
|
620
|
+
mappingExists: Boolean(mapping),
|
|
621
|
+
mapping: summarizeMapping(mapping),
|
|
622
|
+
scope: "selection",
|
|
623
|
+
targetMode: resolvedTargetNodeIds.length > 0 ? "explicit-targets" : "current-selection",
|
|
624
|
+
targetNodeIds: resolvedTargetNodeIds,
|
|
625
|
+
providedTargetNodes: resolvedTargetNodeIds.length,
|
|
626
|
+
requestedRowCount: rowCount ?? null,
|
|
627
|
+
estimatedRows: resolveRequestedRowCount(primaryRows ?? null, rowCount ?? null),
|
|
628
|
+
datasets: datasets.datasets,
|
|
629
|
+
warnings,
|
|
630
|
+
willCallPlugin: false,
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
catch (error) {
|
|
634
|
+
return err(error instanceof Error ? error.message : String(error), {
|
|
635
|
+
code: "GENERATE_INSTANCES_PREVIEW_ERROR",
|
|
636
|
+
retryable: /No plugin connected|Session disconnected/i.test(error instanceof Error ? error.message : String(error)),
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
return callPluginWriteCommand(queue, "generate_instances", {
|
|
26
641
|
mappingId,
|
|
27
642
|
rowCount: rowCount ?? null,
|
|
28
643
|
scope: "selection",
|
|
29
|
-
|
|
30
|
-
|
|
644
|
+
...(resolvedTargetNodeIds.length > 0 ? { targetNodeIds: resolvedTargetNodeIds } : {}),
|
|
645
|
+
...(targetNodes ? { targetNodes } : {}),
|
|
646
|
+
}, { okFalseErrorCode: "SXL_DATA_WRITE_FAILED" });
|
|
31
647
|
});
|
|
32
648
|
server.tool("apply_image", "Apply an image from URL to the current selection.", {
|
|
33
649
|
url: z.string().describe("Image URL"),
|
|
34
650
|
name: z.string().describe("Image name"),
|
|
35
|
-
}, async ({ url, name }) => {
|
|
36
|
-
|
|
37
|
-
|
|
651
|
+
}, async ({ url, name }) => callPluginWriteCommand(queue, "apply_image", { url, name }, {
|
|
652
|
+
okFalseErrorCode: "SXL_CANVAS_WRITE_FAILED",
|
|
653
|
+
}));
|
|
654
|
+
server.tool("upload_assets", "Bridge asset uploader for Figma Design: fetch/validate PNG, JPG/JPEG, GIF, or WebP assets up to 10MB each and apply them as image fills. If nodeId is supplied the asset fills that node; otherwise Bridge creates image rectangles. Use dryRun=true first.", {
|
|
655
|
+
assets: z.array(uploadAssetZod).min(1).max(10),
|
|
656
|
+
dryRun: dryRunZod,
|
|
657
|
+
fetchInDryRun: z.boolean().optional().describe("If true, dryRun also fetches assets to verify MIME/size."),
|
|
658
|
+
maxBytes: z.number().int().positive().max(10 * 1024 * 1024).optional().describe("Per-asset byte limit. Default/max: 10MB."),
|
|
659
|
+
parentId: z.string().optional(),
|
|
660
|
+
targetNodeId: z.string().optional().describe("Default nodeId for assets that omit nodeId."),
|
|
661
|
+
x: z.number().optional(),
|
|
662
|
+
y: z.number().optional(),
|
|
663
|
+
width: z.number().positive().optional(),
|
|
664
|
+
height: z.number().positive().optional(),
|
|
665
|
+
gap: z.number().min(0).optional(),
|
|
666
|
+
scaleMode: imageScaleModeZod.optional(),
|
|
667
|
+
}, async (args) => {
|
|
668
|
+
try {
|
|
669
|
+
const prepared = await prepareUploadAssets(args);
|
|
670
|
+
if (prepared.dryRun) {
|
|
671
|
+
return ok({
|
|
672
|
+
ok: true,
|
|
673
|
+
dryRun: true,
|
|
674
|
+
count: prepared.count,
|
|
675
|
+
maxBytes: prepared.maxBytes,
|
|
676
|
+
assets: uploadAssetReports(prepared),
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
const results = [];
|
|
680
|
+
for (const asset of prepared.assets) {
|
|
681
|
+
if (!asset.payload) {
|
|
682
|
+
results.push({ ...asset.report, status: "failed", error: "Missing prepared plugin payload" });
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
try {
|
|
686
|
+
const pluginResult = await executePluginCommand(queue, "set_image_fill", asset.payload);
|
|
687
|
+
if (isRecord(pluginResult) && pluginResult.ok === false) {
|
|
688
|
+
results.push({
|
|
689
|
+
...asset.report,
|
|
690
|
+
status: "failed",
|
|
691
|
+
error: typeof pluginResult.error === "string"
|
|
692
|
+
? pluginResult.error
|
|
693
|
+
: typeof pluginResult.message === "string"
|
|
694
|
+
? pluginResult.message
|
|
695
|
+
: "set_image_fill returned ok:false",
|
|
696
|
+
result: pluginResult,
|
|
697
|
+
});
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
results.push({ ...asset.report, status: "completed", result: pluginResult });
|
|
701
|
+
}
|
|
702
|
+
catch (error) {
|
|
703
|
+
results.push({
|
|
704
|
+
...asset.report,
|
|
705
|
+
status: "failed",
|
|
706
|
+
error: error instanceof Error ? error.message : String(error),
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
const failures = results.filter((result) => result.status !== "completed");
|
|
711
|
+
return ok({
|
|
712
|
+
ok: failures.length === 0,
|
|
713
|
+
dryRun: false,
|
|
714
|
+
count: prepared.count,
|
|
715
|
+
completed: results.length - failures.length,
|
|
716
|
+
failed: failures.length,
|
|
717
|
+
assets: results,
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
catch (error) {
|
|
721
|
+
return err(error instanceof Error ? error.message : String(error), {
|
|
722
|
+
code: "UPLOAD_ASSETS_ERROR",
|
|
723
|
+
retryable: false,
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
server.tool("get_code_connect_suggestions", "Bridge-local Code Connect suggestion engine. Scans a local codebase for likely component files, ranks candidate CodeConnectBinding payloads by selected Figma node/component names, and returns reviewable bindings for codeconnect_save_binding. If targets/componentNames are omitted Bridge reads the current Figma selection via get_selection_summary.", {
|
|
728
|
+
targets: z.array(codeConnectTargetZod).optional(),
|
|
729
|
+
componentNames: z.array(z.string()).optional(),
|
|
730
|
+
rootDir: z.string().optional().describe("Local codebase root to scan. Defaults to SXL_CODECONNECT_ROOT or the Bridge process cwd."),
|
|
731
|
+
repositoryUrl: z.string().optional().describe("Repository web URL copied into suggested binding.gitlabUrl."),
|
|
732
|
+
gitlabUrl: z.string().optional().describe("Explicit CodeConnectBinding.gitlabUrl override."),
|
|
733
|
+
documentationUrl: z.string().optional(),
|
|
734
|
+
documentationDocsQuerySuffix: z.string().optional(),
|
|
735
|
+
documentationStoryPathTemplate: z.string().optional(),
|
|
736
|
+
frameworks: z.array(codeConnectFrameworkZod).optional(),
|
|
737
|
+
maxSuggestionsPerTarget: z.number().int().positive().max(20).optional(),
|
|
738
|
+
maxFiles: z.number().int().positive().max(10_000).optional(),
|
|
739
|
+
maxFileBytes: z.number().int().positive().max(1024 * 1024).optional(),
|
|
740
|
+
includeLowConfidence: z.boolean().optional(),
|
|
741
|
+
...largeDataResponseArgs,
|
|
742
|
+
}, async (args) => {
|
|
743
|
+
let hasDirectTargets = false;
|
|
744
|
+
try {
|
|
745
|
+
let selectionSummary;
|
|
746
|
+
hasDirectTargets =
|
|
747
|
+
(Array.isArray(args.targets) && args.targets.length > 0) ||
|
|
748
|
+
(Array.isArray(args.componentNames) && args.componentNames.length > 0);
|
|
749
|
+
if (!hasDirectTargets) {
|
|
750
|
+
selectionSummary = await executePluginCommand(queue, "get_selection_summary");
|
|
751
|
+
}
|
|
752
|
+
const result = buildCodeConnectSuggestions(args, selectionSummary);
|
|
753
|
+
if (result.targets.length === 0) {
|
|
754
|
+
return err("No Code Connect targets found. Provide targets/componentNames or select component nodes in Figma with Remote Connect active.", {
|
|
755
|
+
code: "CODECONNECT_SUGGESTIONS_NO_TARGETS",
|
|
756
|
+
retryable: true,
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
return ok(normalizeLargeDataResult("get_code_connect_suggestions", args, result));
|
|
760
|
+
}
|
|
761
|
+
catch (error) {
|
|
762
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
763
|
+
const retryable = !hasDirectTargets && /No plugin connected|Session disconnected/i.test(message);
|
|
764
|
+
return err(message, {
|
|
765
|
+
code: "CODECONNECT_SUGGESTIONS_ERROR",
|
|
766
|
+
retryable,
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
server.tool("inspect_enabled_libraries", "Read enabled team-library descriptors for the current Figma file through the plugin teamLibrary API. Returns enabled variable collections and, when available in the Figma runtime, component/style descriptors. This cannot enable libraries or enumerate arbitrary organization/community libraries.", {
|
|
771
|
+
query: z.string().optional().describe("Optional case-insensitive filter by library/name."),
|
|
772
|
+
limitPerKind: z.number().int().positive().max(5_000).optional(),
|
|
773
|
+
includeVariableCollections: z.boolean().optional(),
|
|
774
|
+
includeStyles: z.boolean().optional(),
|
|
775
|
+
includeComponents: z.boolean().optional(),
|
|
776
|
+
...largeDataResponseArgs,
|
|
777
|
+
}, async (args) => callPluginCommand(queue, "inspect_enabled_libraries", args, { largeData: true }));
|
|
778
|
+
server.tool("list_enabled_library_variables", "Read variables from enabled team-library variable collections. Provide collectionKey, collectionKeys, collectionName, libraryName, or includeAllCollections=true. Large responses are summarized automatically for MCP unless bridgeResponseMode='full'.", {
|
|
779
|
+
collectionKey: z.string().optional(),
|
|
780
|
+
collectionKeys: z.array(z.string()).optional(),
|
|
781
|
+
collectionName: z.string().optional(),
|
|
782
|
+
libraryName: z.string().optional(),
|
|
783
|
+
includeAllCollections: z.boolean().optional(),
|
|
784
|
+
resolvedTypes: z.array(variableResolvedTypeZod).optional(),
|
|
785
|
+
query: z.string().optional(),
|
|
786
|
+
limit: z.number().int().positive().max(10_000).optional(),
|
|
787
|
+
...largeDataResponseArgs,
|
|
788
|
+
}, async (args) => callPluginCommand(queue, "list_enabled_library_variables", args, { largeData: true }));
|
|
789
|
+
server.tool("search_enabled_library_assets", "Bridge orchestration search over libraries already enabled for the current Figma file. Searches component/style/variable collection descriptors plus enabled library variables with bounded scans. This cannot enable libraries or discover arbitrary organization/community libraries.", {
|
|
790
|
+
query: z.string().min(1),
|
|
791
|
+
kinds: z.array(enabledLibraryAssetKindZod).optional(),
|
|
792
|
+
libraryName: z.string().optional(),
|
|
793
|
+
collectionKey: z.string().optional(),
|
|
794
|
+
collectionName: z.string().optional(),
|
|
795
|
+
resolvedTypes: z.array(variableResolvedTypeZod).optional(),
|
|
796
|
+
maxMatches: z.number().int().positive().max(500).optional(),
|
|
797
|
+
maxVariables: z.number().int().positive().max(10_000).optional(),
|
|
798
|
+
includeVariables: z.boolean().optional().describe("Default true. Set false for a cheaper descriptor-only search."),
|
|
799
|
+
...largeDataResponseArgs,
|
|
800
|
+
}, async (args) => {
|
|
801
|
+
try {
|
|
802
|
+
const kinds = Array.isArray(args.kinds) && args.kinds.length > 0
|
|
803
|
+
? args.kinds
|
|
804
|
+
: ["component", "style", "variable", "variableCollection"];
|
|
805
|
+
const includeVariables = args.includeVariables !== false && kinds.includes("variable");
|
|
806
|
+
const inspectResult = await executePluginCommand(queue, "inspect_enabled_libraries", {
|
|
807
|
+
query: args.query,
|
|
808
|
+
limitPerKind: args.maxMatches ?? 100,
|
|
809
|
+
includeComponents: kinds.includes("component"),
|
|
810
|
+
includeStyles: kinds.includes("style"),
|
|
811
|
+
includeVariableCollections: true,
|
|
812
|
+
});
|
|
813
|
+
let variableResult;
|
|
814
|
+
const warnings = [];
|
|
815
|
+
if (includeVariables) {
|
|
816
|
+
try {
|
|
817
|
+
variableResult = await executePluginCommand(queue, "list_enabled_library_variables", {
|
|
818
|
+
query: args.query,
|
|
819
|
+
collectionKey: args.collectionKey,
|
|
820
|
+
collectionName: args.collectionName,
|
|
821
|
+
libraryName: args.libraryName,
|
|
822
|
+
includeAllCollections: !args.collectionKey && !args.collectionName && !args.libraryName,
|
|
823
|
+
resolvedTypes: args.resolvedTypes,
|
|
824
|
+
limit: args.maxVariables ?? 1_000,
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
catch (error) {
|
|
828
|
+
warnings.push(`Variable scan failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
const result = buildEnabledLibrarySearchResult({
|
|
832
|
+
args: {
|
|
833
|
+
query: args.query,
|
|
834
|
+
kinds,
|
|
835
|
+
libraryName: args.libraryName,
|
|
836
|
+
collectionKey: args.collectionKey,
|
|
837
|
+
collectionName: args.collectionName,
|
|
838
|
+
maxMatches: args.maxMatches,
|
|
839
|
+
},
|
|
840
|
+
inspectResult,
|
|
841
|
+
variableResult,
|
|
842
|
+
warnings,
|
|
843
|
+
});
|
|
844
|
+
return ok(normalizeLargeDataResult("search_enabled_library_assets", args, result));
|
|
845
|
+
}
|
|
846
|
+
catch (error) {
|
|
847
|
+
return err(error instanceof Error ? error.message : String(error), {
|
|
848
|
+
code: "SEARCH_ENABLED_LIBRARY_ASSETS_ERROR",
|
|
849
|
+
retryable: /No plugin connected|Session disconnected/i.test(error instanceof Error ? error.message : String(error)),
|
|
850
|
+
});
|
|
851
|
+
}
|
|
38
852
|
});
|
|
39
853
|
server.tool("set_node_text", "Set text content on a text layer in the currently selected nodes (or a specific node by ID). If layerName is provided, finds text nodes with that name inside the selection. Otherwise sets text on all text nodes in selection.", {
|
|
40
854
|
text: z.string().describe("Text content to set"),
|
|
41
855
|
layerName: z.string().optional().describe("Name of the text layer to target (e.g. 'label', 'title'). Omit to set all text nodes."),
|
|
42
856
|
nodeId: z.string().optional().describe("Specific Figma node ID. Omit to use current selection."),
|
|
43
|
-
}, async ({ text, layerName, nodeId }) => {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
});
|
|
857
|
+
}, async ({ text, layerName, nodeId }) => callPluginWriteCommand(queue, "set_node_text", { text, layerName, nodeId }, {
|
|
858
|
+
okFalseErrorCode: "SXL_CANVAS_WRITE_FAILED",
|
|
859
|
+
}));
|
|
47
860
|
server.tool("set_node_property", "Set a component property on a selected instance or component. For instances, sets instance property. For components, sets default value.", {
|
|
48
861
|
propertyName: z.string().describe("Property name (e.g. 'label', 'isIcon', 'size')"),
|
|
49
862
|
propertyValue: z.union([z.string(), z.boolean(), z.number()]).describe("Property value"),
|
|
50
863
|
nodeId: z.string().optional().describe("Specific Figma node ID. Omit to use current selection."),
|
|
51
|
-
}, async ({ propertyName, propertyValue, nodeId }) => {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
});
|
|
864
|
+
}, async ({ propertyName, propertyValue, nodeId }) => callPluginWriteCommand(queue, "set_node_property", { propertyName, propertyValue, nodeId }, {
|
|
865
|
+
okFalseErrorCode: "SXL_CANVAS_WRITE_FAILED",
|
|
866
|
+
}));
|
|
55
867
|
}
|
|
56
868
|
//# sourceMappingURL=data.js.map
|