@sxl-studio/bridge 1.5.1 → 1.7.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/README.md +35 -2
- package/dist/agent-recipes.d.ts +268 -0
- package/dist/agent-recipes.js +637 -0
- package/dist/agent-recipes.js.map +1 -1
- package/dist/command-queue.js +16 -0
- package/dist/command-queue.js.map +1 -1
- package/dist/http-api.d.ts +1 -0
- package/dist/http-api.js +7 -2
- package/dist/http-api.js.map +1 -1
- package/dist/mcp-factory.js +11 -1
- package/dist/mcp-factory.js.map +1 -1
- package/dist/sxl-mcp-instructions.js +142 -2
- package/dist/sxl-mcp-instructions.js.map +1 -1
- package/dist/tools/audit.d.ts +49 -0
- package/dist/tools/audit.js +83 -0
- package/dist/tools/audit.js.map +1 -0
- package/dist/tools/catalogue-bootstrap.js +34 -0
- package/dist/tools/catalogue-bootstrap.js.map +1 -1
- package/dist/tools/compositions-orchestration.d.ts +91 -0
- package/dist/tools/compositions-orchestration.js +101 -0
- package/dist/tools/compositions-orchestration.js.map +1 -0
- package/dist/tools/mockup.d.ts +323 -0
- package/dist/tools/mockup.js +206 -0
- package/dist/tools/mockup.js.map +1 -0
- package/dist/tools/registry.d.ts +1 -1
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/resources.d.ts +1 -1
- package/dist/tools/resources.js +52 -2
- package/dist/tools/resources.js.map +1 -1
- package/dist/tools/styles-orchestration.d.ts +544 -0
- package/dist/tools/styles-orchestration.js +175 -0
- package/dist/tools/styles-orchestration.js.map +1 -0
- package/dist/tools/tokens.d.ts +60 -60
- package/dist/tools/variables-orchestration.d.ts +20 -0
- package/dist/tools/variables-orchestration.js +116 -0
- package/dist/tools/variables-orchestration.js.map +1 -0
- package/dist/workspace-blob-http.d.ts +9 -0
- package/dist/workspace-blob-http.js +154 -0
- package/dist/workspace-blob-http.js.map +1 -0
- package/dist/workspace-blob-store.d.ts +12 -0
- package/dist/workspace-blob-store.js +87 -0
- package/dist/workspace-blob-store.js.map +1 -0
- package/package.json +3 -2
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Variables orchestration — Bridge platform Phase B tools.
|
|
3
|
+
*
|
|
4
|
+
* "Thick" commands that wrap multi-step Figma Variables refactors:
|
|
5
|
+
* - `import_variable_spec` — declarative bulk create / update with aliases.
|
|
6
|
+
* - `analyze_variable_order` — read-only diff to recommend a new order.
|
|
7
|
+
* - `dedupe_variables` — find / merge duplicates (dry-run by default).
|
|
8
|
+
* - `rebind_variable_aliases` — bulk rewrite alias chains across modes.
|
|
9
|
+
* - `apply_coverage_suggestions` — convert audit_variable_coverage suggestions
|
|
10
|
+
* into safe `setBoundVariableFor*` writes.
|
|
11
|
+
*
|
|
12
|
+
* Token economy:
|
|
13
|
+
* - dry-run (default for dedupe / apply_coverage_suggestions / spec preview)
|
|
14
|
+
* returns the exact plan WITHOUT touching the canvas. Agents inspect the
|
|
15
|
+
* summary, surface it to the user, then re-call with `apply: true` /
|
|
16
|
+
* `dryRun: false` to commit.
|
|
17
|
+
*/
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
import { callPluginCommand } from "./shared.js";
|
|
20
|
+
const variableValueLiteralZod = z.union([
|
|
21
|
+
z.number(),
|
|
22
|
+
z.string(),
|
|
23
|
+
z.boolean(),
|
|
24
|
+
z.object({
|
|
25
|
+
r: z.number(),
|
|
26
|
+
g: z.number(),
|
|
27
|
+
b: z.number(),
|
|
28
|
+
a: z.number().optional(),
|
|
29
|
+
}),
|
|
30
|
+
]);
|
|
31
|
+
const aliasRefZod = z.union([
|
|
32
|
+
z.object({ variableId: z.string() }),
|
|
33
|
+
z.object({ variableName: z.string() }),
|
|
34
|
+
]);
|
|
35
|
+
const variableValueZod = z.union([
|
|
36
|
+
z.object({ value: variableValueLiteralZod }),
|
|
37
|
+
z.object({ alias: aliasRefZod }),
|
|
38
|
+
z.object({
|
|
39
|
+
rawAlias: z.union([
|
|
40
|
+
z.object({ type: z.literal("VARIABLE_ALIAS"), id: z.string() }),
|
|
41
|
+
z.object({ aliasOf: z.string() }),
|
|
42
|
+
]),
|
|
43
|
+
}),
|
|
44
|
+
]);
|
|
45
|
+
const modeZod = z.object({
|
|
46
|
+
key: z.string().describe("Stable mode key referenced from variable values"),
|
|
47
|
+
name: z.string().optional().describe("Display name (required when creating a new mode)"),
|
|
48
|
+
});
|
|
49
|
+
const variableTypeZod = z.enum(["COLOR", "FLOAT", "STRING", "BOOLEAN"]);
|
|
50
|
+
const variableEntryZod = z.object({
|
|
51
|
+
name: z.string(),
|
|
52
|
+
type: variableTypeZod,
|
|
53
|
+
valuesByMode: z.record(variableValueZod).optional(),
|
|
54
|
+
scopes: z.array(z.string()).optional(),
|
|
55
|
+
codeSyntax: z.record(z.string()).optional(),
|
|
56
|
+
description: z.string().optional(),
|
|
57
|
+
});
|
|
58
|
+
const collectionSpecZod = z
|
|
59
|
+
.object({
|
|
60
|
+
collectionId: z.string().optional(),
|
|
61
|
+
collectionName: z.string().optional(),
|
|
62
|
+
modes: z.array(modeZod).optional(),
|
|
63
|
+
variables: z.array(variableEntryZod).optional(),
|
|
64
|
+
})
|
|
65
|
+
.refine((c) => Boolean(c.collectionId ?? c.collectionName), {
|
|
66
|
+
message: "Collection requires collectionId or collectionName",
|
|
67
|
+
});
|
|
68
|
+
const variableSpecZod = z.object({
|
|
69
|
+
collections: z.array(collectionSpecZod).min(1),
|
|
70
|
+
resolveAliases: z.boolean().optional(),
|
|
71
|
+
dryRun: z.boolean().optional(),
|
|
72
|
+
});
|
|
73
|
+
const reorderStrategyZod = z.union([
|
|
74
|
+
z.object({ type: z.literal("alphabetical") }),
|
|
75
|
+
z.object({ type: z.literal("byPath"), separator: z.string().optional() }),
|
|
76
|
+
z.object({ type: z.literal("explicit"), order: z.array(z.string()).min(1) }),
|
|
77
|
+
]);
|
|
78
|
+
const dedupeStrategyZod = z.enum(["byName", "byDefaultModeValue"]);
|
|
79
|
+
export function registerVariablesOrchestrationTools(server, queue) {
|
|
80
|
+
server.tool("import_variable_spec", "Idempotent bulk-create / update of Variables from a single declarative spec. Creates collections + modes + variables, applies per-mode literal values, then resolves aliases in a second pass so cross-collection references work. Errors are collected per-item without aborting the run. Pass `dryRun: true` for a preview (returns the diff and skips Figma writes). CANVAS WRITE in apply mode — blocked in Dev Mode.", {
|
|
81
|
+
spec: variableSpecZod.describe("Top-level spec. `collections[]` is required; each collection identifies itself by `collectionId` (existing) or `collectionName` (resolves existing or creates new). Variable values support `{ value }` literals (hex strings auto-coerce for COLOR), `{ alias: { variableId | variableName } }`, or `{ rawAlias }` passthrough."),
|
|
82
|
+
}, async ({ spec }) => callPluginCommand(queue, "import_variable_spec", { spec }));
|
|
83
|
+
server.tool("analyze_variable_order", "READ-ONLY: produce a recommended order of variables inside a collection. Returns `moves[]` describing every position that would change. Strategies: `alphabetical`, `byPath` (with optional separator), `explicit` (caller-supplied list). Figma Plugin API does not expose a stable `move` primitive, so this command stays diff-only — use it to drive a manual reorder in the Variables panel.", {
|
|
84
|
+
collectionId: z.string().describe("Variable collection id (VariableCollectionId:...)"),
|
|
85
|
+
strategy: reorderStrategyZod,
|
|
86
|
+
}, async (args) => callPluginCommand(queue, "analyze_variable_order", args));
|
|
87
|
+
server.tool("dedupe_variables", "Find (and optionally merge) duplicate Variables. Strategies: `byName` (same name across collections) or `byDefaultModeValue` (same default-mode literal value within the same resolvedType). Default is dry-run — returns groups + suggested survivor + would-be rebindings. With `apply: true` the survivor inherits incoming aliases (via `rebind_variable_aliases`) and duplicates are removed. CANVAS WRITE only in apply mode.", {
|
|
88
|
+
strategy: dedupeStrategyZod.default("byName"),
|
|
89
|
+
apply: z.boolean().optional().describe("When true, perform the merge. Default: false (dry-run)."),
|
|
90
|
+
collectionId: z
|
|
91
|
+
.string()
|
|
92
|
+
.optional()
|
|
93
|
+
.describe("Restrict the scan to a single collection. Default: scan all local variables."),
|
|
94
|
+
}, async (args) => callPluginCommand(queue, "dedupe_variables", args));
|
|
95
|
+
server.tool("rebind_variable_aliases", "Bulk rewrite alias targets inside Variable collections. Each `{ fromVariableId, toVariableId }` pair redirects every `valuesByMode` entry that aliases the source so it points at the target. Read-only nodes / bindings are NOT touched (use `apply_coverage_suggestions` for that). Pass `dryRun: true` to count rewrites without writing.", {
|
|
96
|
+
mappings: z
|
|
97
|
+
.array(z.object({
|
|
98
|
+
fromVariableId: z.string(),
|
|
99
|
+
toVariableId: z.string(),
|
|
100
|
+
}))
|
|
101
|
+
.min(1),
|
|
102
|
+
dryRun: z.boolean().optional().describe("When true, count rewrites without applying. Default: false."),
|
|
103
|
+
}, async (args) => callPluginCommand(queue, "rebind_variable_aliases", args));
|
|
104
|
+
server.tool("apply_coverage_suggestions", "Apply the `suggestion` entries from `audit_variable_coverage` / `find_variable_coverage_misses`. Translates property paths like `fills[0].color`, `effects[2].color`, `cornerRadius` etc. into Figma `setBoundVariableForPaint` / `setBoundVariableForEffect` / `setBoundVariable` writes. Default mode is dry-run — agent receives a `preview[]` it can show the user. Set `dryRun: false` to commit. CANVAS WRITE only when `dryRun: false`.", {
|
|
105
|
+
suggestions: z
|
|
106
|
+
.array(z.object({
|
|
107
|
+
nodeId: z.string(),
|
|
108
|
+
property: z.string().describe("Property path as returned by find_variable_coverage_misses"),
|
|
109
|
+
variableId: z.string(),
|
|
110
|
+
variableName: z.string().optional(),
|
|
111
|
+
}))
|
|
112
|
+
.min(1),
|
|
113
|
+
dryRun: z.boolean().optional().describe("Default: true. Pass false to actually wire bindings."),
|
|
114
|
+
}, async (args) => callPluginCommand(queue, "apply_coverage_suggestions", args));
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=variables-orchestration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"variables-orchestration.js","sourceRoot":"","sources":["../../src/tools/variables-orchestration.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP handlers for /api/workspace-blob — disk-backed blob cache for plugin Git Local Storage.
|
|
3
|
+
* Does not require an active plugin WebSocket session (unlike /api/command).
|
|
4
|
+
*/
|
|
5
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
6
|
+
/**
|
|
7
|
+
* @returns true if this handler consumed the request.
|
|
8
|
+
*/
|
|
9
|
+
export declare function tryHandleWorkspaceBlobApi(req: IncomingMessage, res: ServerResponse, reqUrl: string): Promise<boolean>;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP handlers for /api/workspace-blob — disk-backed blob cache for plugin Git Local Storage.
|
|
3
|
+
* Does not require an active plugin WebSocket session (unlike /api/command).
|
|
4
|
+
*/
|
|
5
|
+
import { deleteWorkspaceBlob, getWorkspaceBlob, getWorkspaceBlobRootDir, isValidBlobKey, isValidWorkspaceId, putWorkspaceBlob, } from "./workspace-blob-store.js";
|
|
6
|
+
const MAX_JSON_BODY = 40 * 1024 * 1024;
|
|
7
|
+
function corsJson(res, status, data) {
|
|
8
|
+
res.writeHead(status, {
|
|
9
|
+
"Content-Type": "application/json",
|
|
10
|
+
"Access-Control-Allow-Origin": "*",
|
|
11
|
+
"Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
|
|
12
|
+
"Access-Control-Allow-Headers": "Content-Type, Accept, Authorization, Mcp-Session-Id, mcp-protocol-version, Idempotency-Key",
|
|
13
|
+
});
|
|
14
|
+
res.end(JSON.stringify(data, null, 2));
|
|
15
|
+
}
|
|
16
|
+
function readBody(req) {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
let body = "";
|
|
19
|
+
req.on("data", (chunk) => {
|
|
20
|
+
body += chunk.toString();
|
|
21
|
+
});
|
|
22
|
+
req.on("end", () => resolve(body));
|
|
23
|
+
req.on("error", reject);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function pathnameOnly(reqUrl) {
|
|
27
|
+
try {
|
|
28
|
+
return new URL(reqUrl || "/", "http://127.0.0.1").pathname;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return "/";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function searchParamsOf(reqUrl) {
|
|
35
|
+
try {
|
|
36
|
+
return new URL(reqUrl || "/", "http://127.0.0.1").searchParams;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return new URLSearchParams();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function parseContentLength(req) {
|
|
43
|
+
const raw = req.headers["content-length"];
|
|
44
|
+
if (typeof raw !== "string")
|
|
45
|
+
return null;
|
|
46
|
+
const n = parseInt(raw, 10);
|
|
47
|
+
return Number.isFinite(n) ? n : null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* @returns true if this handler consumed the request.
|
|
51
|
+
*/
|
|
52
|
+
export async function tryHandleWorkspaceBlobApi(req, res, reqUrl) {
|
|
53
|
+
const pathname = pathnameOnly(reqUrl);
|
|
54
|
+
if (pathname !== "/api/workspace-blob") {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const rootDir = getWorkspaceBlobRootDir();
|
|
58
|
+
try {
|
|
59
|
+
if (req.method === "GET") {
|
|
60
|
+
const sp = searchParamsOf(reqUrl);
|
|
61
|
+
const workspaceId = sp.get("workspaceId") ?? "";
|
|
62
|
+
const key = sp.get("key") ?? "";
|
|
63
|
+
if (!isValidWorkspaceId(workspaceId) || !isValidBlobKey(key)) {
|
|
64
|
+
corsJson(res, 400, { ok: false, code: "INVALID_PARAMS" });
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
const data = await getWorkspaceBlob(rootDir, workspaceId, key);
|
|
68
|
+
if (data === null) {
|
|
69
|
+
corsJson(res, 404, { ok: false, code: "NOT_FOUND" });
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
corsJson(res, 200, { ok: true, encoding: "utf8", data });
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
if (req.method === "POST") {
|
|
77
|
+
const cl = parseContentLength(req);
|
|
78
|
+
if (cl !== null && cl > MAX_JSON_BODY) {
|
|
79
|
+
corsJson(res, 413, { ok: false, code: "PAYLOAD_TOO_LARGE" });
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
const raw = await readBody(req);
|
|
83
|
+
if (raw.length > MAX_JSON_BODY) {
|
|
84
|
+
corsJson(res, 413, { ok: false, code: "PAYLOAD_TOO_LARGE" });
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
let parsed;
|
|
88
|
+
try {
|
|
89
|
+
parsed = JSON.parse(raw || "{}");
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
corsJson(res, 400, { ok: false, code: "INVALID_JSON" });
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
const workspaceId = typeof parsed.workspaceId === "string" ? parsed.workspaceId : "";
|
|
96
|
+
const key = typeof parsed.key === "string" ? parsed.key : "";
|
|
97
|
+
const data = typeof parsed.data === "string" ? parsed.data : null;
|
|
98
|
+
if (!isValidWorkspaceId(workspaceId) || !isValidBlobKey(key)) {
|
|
99
|
+
corsJson(res, 400, { ok: false, code: "INVALID_PARAMS" });
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
if (data === null) {
|
|
103
|
+
corsJson(res, 400, { ok: false, code: "MISSING_DATA" });
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
await putWorkspaceBlob(rootDir, workspaceId, key, data);
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
const msg = String(e);
|
|
111
|
+
if (msg.includes("INVALID_WORKSPACE_ID") || msg.includes("INVALID_BLOB_KEY")) {
|
|
112
|
+
corsJson(res, 400, { ok: false, code: "INVALID_PARAMS", message: msg });
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
if (msg.includes("PAYLOAD_TOO_LARGE")) {
|
|
116
|
+
corsJson(res, 413, { ok: false, code: "PAYLOAD_TOO_LARGE" });
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
throw e;
|
|
120
|
+
}
|
|
121
|
+
corsJson(res, 200, { ok: true });
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
if (req.method === "DELETE") {
|
|
125
|
+
const sp = searchParamsOf(reqUrl);
|
|
126
|
+
const workspaceId = sp.get("workspaceId") ?? "";
|
|
127
|
+
const key = sp.get("key") ?? "";
|
|
128
|
+
if (!isValidWorkspaceId(workspaceId) || !isValidBlobKey(key)) {
|
|
129
|
+
corsJson(res, 400, { ok: false, code: "INVALID_PARAMS" });
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
await deleteWorkspaceBlob(rootDir, workspaceId, key);
|
|
134
|
+
}
|
|
135
|
+
catch (e) {
|
|
136
|
+
const msg = String(e);
|
|
137
|
+
if (msg.includes("INVALID_WORKSPACE_ID") || msg.includes("INVALID_BLOB_KEY")) {
|
|
138
|
+
corsJson(res, 400, { ok: false, code: "INVALID_PARAMS", message: msg });
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
throw e;
|
|
142
|
+
}
|
|
143
|
+
corsJson(res, 200, { ok: true });
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
corsJson(res, 405, { ok: false, code: "METHOD_NOT_ALLOWED" });
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
corsJson(res, 500, { ok: false, code: "INTERNAL_ERROR", message: String(err) });
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=workspace-blob-http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-blob-http.js","sourceRoot":"","sources":["../src/workspace-blob-http.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persists large workspace blobs on local disk (Git Sync "Local Storage").
|
|
3
|
+
* Root defaults to OS cache dir via env-paths; override with SXL_BRIDGE_WORKSPACE_BLOB_ROOT for tests.
|
|
4
|
+
*/
|
|
5
|
+
export declare function isValidWorkspaceId(workspaceId: string): boolean;
|
|
6
|
+
export declare function isValidBlobKey(key: string): boolean;
|
|
7
|
+
export declare function getWorkspaceBlobRootDir(): string;
|
|
8
|
+
export declare function assertValidWorkspaceId(workspaceId: string): string;
|
|
9
|
+
export declare function assertValidBlobKey(key: string): string;
|
|
10
|
+
export declare function putWorkspaceBlob(rootDir: string, workspaceId: string, blobKey: string, bodyUtf8: string): Promise<void>;
|
|
11
|
+
export declare function getWorkspaceBlob(rootDir: string, workspaceId: string, blobKey: string): Promise<string | null>;
|
|
12
|
+
export declare function deleteWorkspaceBlob(rootDir: string, workspaceId: string, blobKey: string): Promise<boolean>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persists large workspace blobs on local disk (Git Sync "Local Storage").
|
|
3
|
+
* Root defaults to OS cache dir via env-paths; override with SXL_BRIDGE_WORKSPACE_BLOB_ROOT for tests.
|
|
4
|
+
*/
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import fs from "node:fs/promises";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import envPaths from "env-paths";
|
|
9
|
+
const CACHE_SUBDIR = "workspace-blobs";
|
|
10
|
+
/** workspaceId must be 64-char lowercase hex (SHA-256 of plugin workspace key). */
|
|
11
|
+
const WORKSPACE_ID_RE = /^[a-f0-9]{64}$/;
|
|
12
|
+
/** Blob keys: safe filename fragment (token/dataset storage prefixes). */
|
|
13
|
+
const BLOB_KEY_RE = /^[\w.\-]{1,240}$/;
|
|
14
|
+
export function isValidWorkspaceId(workspaceId) {
|
|
15
|
+
return WORKSPACE_ID_RE.test(workspaceId);
|
|
16
|
+
}
|
|
17
|
+
export function isValidBlobKey(key) {
|
|
18
|
+
return BLOB_KEY_RE.test(key);
|
|
19
|
+
}
|
|
20
|
+
const MAX_BODY_CHARS = 48 * 1024 * 1024; // ~48 MB UTF-16 escape hatch; enforce tighter at HTTP layer
|
|
21
|
+
function getRootFromEnv() {
|
|
22
|
+
const raw = process.env["SXL_BRIDGE_WORKSPACE_BLOB_ROOT"];
|
|
23
|
+
if (!raw || raw.trim().length === 0)
|
|
24
|
+
return null;
|
|
25
|
+
return path.resolve(raw.trim());
|
|
26
|
+
}
|
|
27
|
+
export function getWorkspaceBlobRootDir() {
|
|
28
|
+
const override = getRootFromEnv();
|
|
29
|
+
if (override)
|
|
30
|
+
return override;
|
|
31
|
+
const paths = envPaths("sxl-studio-bridge", { suffix: "" });
|
|
32
|
+
return path.join(paths.cache, CACHE_SUBDIR);
|
|
33
|
+
}
|
|
34
|
+
export function assertValidWorkspaceId(workspaceId) {
|
|
35
|
+
if (!isValidWorkspaceId(workspaceId)) {
|
|
36
|
+
throw new Error("INVALID_WORKSPACE_ID");
|
|
37
|
+
}
|
|
38
|
+
return workspaceId;
|
|
39
|
+
}
|
|
40
|
+
export function assertValidBlobKey(key) {
|
|
41
|
+
if (!isValidBlobKey(key)) {
|
|
42
|
+
throw new Error("INVALID_BLOB_KEY");
|
|
43
|
+
}
|
|
44
|
+
return key;
|
|
45
|
+
}
|
|
46
|
+
function blobFilePath(rootDir, workspaceId, blobKey) {
|
|
47
|
+
const wid = assertValidWorkspaceId(workspaceId);
|
|
48
|
+
const k = assertValidBlobKey(blobKey);
|
|
49
|
+
const digest = createHash("sha256").update(`${wid}|${k}`, "utf8").digest("hex").slice(0, 32);
|
|
50
|
+
return path.join(rootDir, wid, `${digest}.blob`);
|
|
51
|
+
}
|
|
52
|
+
export async function putWorkspaceBlob(rootDir, workspaceId, blobKey, bodyUtf8) {
|
|
53
|
+
if (bodyUtf8.length > MAX_BODY_CHARS) {
|
|
54
|
+
throw new Error("PAYLOAD_TOO_LARGE");
|
|
55
|
+
}
|
|
56
|
+
const target = blobFilePath(rootDir, workspaceId, blobKey);
|
|
57
|
+
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
58
|
+
const tmp = `${target}.${process.pid}.${Date.now()}.tmp`;
|
|
59
|
+
await fs.writeFile(tmp, bodyUtf8, "utf8");
|
|
60
|
+
await fs.rename(tmp, target);
|
|
61
|
+
}
|
|
62
|
+
export async function getWorkspaceBlob(rootDir, workspaceId, blobKey) {
|
|
63
|
+
const target = blobFilePath(rootDir, workspaceId, blobKey);
|
|
64
|
+
try {
|
|
65
|
+
return await fs.readFile(target, "utf8");
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
const err = e;
|
|
69
|
+
if (err.code === "ENOENT")
|
|
70
|
+
return null;
|
|
71
|
+
throw e;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export async function deleteWorkspaceBlob(rootDir, workspaceId, blobKey) {
|
|
75
|
+
const target = blobFilePath(rootDir, workspaceId, blobKey);
|
|
76
|
+
try {
|
|
77
|
+
await fs.unlink(target);
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
const err = e;
|
|
82
|
+
if (err.code === "ENOENT")
|
|
83
|
+
return false;
|
|
84
|
+
throw e;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=workspace-blob-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-blob-store.js","sourceRoot":"","sources":["../src/workspace-blob-store.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sxl-studio/bridge",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "MCP/HTTP bridge server for SXL Studio Figma plugin — enables remote control from IDE",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
28
|
+
"env-paths": "^3.0.0",
|
|
28
29
|
"ws": "^8.18.0",
|
|
29
30
|
"zod": "^3.24.0"
|
|
30
31
|
},
|
|
@@ -34,4 +35,4 @@
|
|
|
34
35
|
"typescript": "^5.7.0",
|
|
35
36
|
"vitest": "^3.0.0"
|
|
36
37
|
}
|
|
37
|
-
}
|
|
38
|
+
}
|