webstudio 0.287.0 → 0.289.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/lib/cli.js +1716 -725
- package/lib/content-runtime.js +88 -88
- package/package.json +27 -21
- package/templates/cloudflare/package.json +1 -1
- package/templates/defaults/package.json +16 -9
- package/templates/react-router/package.json +16 -9
- package/templates/react-router-cloudflare/package.json +1 -1
- package/templates/ssg/package.json +14 -7
package/lib/cli.js
CHANGED
|
@@ -2,6 +2,7 @@ import process$1, { exit as exit$2, cwd as cwd$1, stdin, stdout, chdir, stderr,
|
|
|
2
2
|
import { hideBin } from "yargs/helpers";
|
|
3
3
|
import { simple } from "acorn-walk";
|
|
4
4
|
import path$2, { join as join$1, dirname as dirname$1, resolve, sep, normalize as normalize$4, basename as basename$2, relative, isAbsolute, win32, delimiter, parse as parse$X, extname as extname$1 } from "node:path";
|
|
5
|
+
import { Transform } from "node:stream";
|
|
5
6
|
import hash from "@emotion/hash";
|
|
6
7
|
import deepEqual$1 from "fast-deep-equal";
|
|
7
8
|
import { z as z$4, lazy, array, union, strictObject, discriminatedUnion, string as string$5, literal as literal$2, enum as _enum, record, object, number as number$3, boolean as boolean$2, json as json$1 } from "zod";
|
|
@@ -32,10 +33,13 @@ import pLimit from "p-limit";
|
|
|
32
33
|
import { isDeepStrictEqual, promisify } from "node:util";
|
|
33
34
|
import { fileURLToPath } from "node:url";
|
|
34
35
|
import reservedIdentifiers from "reserved-identifiers";
|
|
35
|
-
import { release, tmpdir } from "node:os";
|
|
36
|
+
import { release, tmpdir, homedir } from "node:os";
|
|
36
37
|
import { execFile, spawn } from "node:child_process";
|
|
37
|
-
import
|
|
38
|
+
import detectPort from "detect-port";
|
|
39
|
+
import getPort from "get-port";
|
|
40
|
+
import pathKey from "path-key";
|
|
38
41
|
import { Launcher } from "chrome-launcher";
|
|
42
|
+
import which from "which";
|
|
39
43
|
import { Buffer as Buffer$1 } from "node:buffer";
|
|
40
44
|
import { gzip } from "node:zlib";
|
|
41
45
|
import { createRequire } from "node:module";
|
|
@@ -7358,6 +7362,17 @@ const createQuerySourceCodec = (capabilities) => {
|
|
|
7358
7362
|
value2[control.key] = expression2;
|
|
7359
7363
|
continue;
|
|
7360
7364
|
}
|
|
7365
|
+
if (control.type === "select") {
|
|
7366
|
+
const selected = parseJsonExpression(expression2);
|
|
7367
|
+
if (typeof selected !== "string" || control.options.some(({ value: value22 }) => value22 === selected) === false) {
|
|
7368
|
+
return {
|
|
7369
|
+
success: false,
|
|
7370
|
+
message: `Select a valid ${control.label.toLowerCase()}.`
|
|
7371
|
+
};
|
|
7372
|
+
}
|
|
7373
|
+
value2[control.key] = selected;
|
|
7374
|
+
continue;
|
|
7375
|
+
}
|
|
7361
7376
|
if (control.type === "filter") {
|
|
7362
7377
|
const where = parseWhere({
|
|
7363
7378
|
expression: expression2,
|
|
@@ -7409,6 +7424,13 @@ const createQuerySourceCodec = (capabilities) => {
|
|
|
7409
7424
|
fields.set(control.key, value2);
|
|
7410
7425
|
continue;
|
|
7411
7426
|
}
|
|
7427
|
+
if (control.type === "select") {
|
|
7428
|
+
if (typeof value2 !== "string" || control.options.some(({ value: option2 }) => option2 === value2) === false) {
|
|
7429
|
+
throw new Error(`Query ${control.key} is invalid`);
|
|
7430
|
+
}
|
|
7431
|
+
fields.set(control.key, generateJsonExpression(value2));
|
|
7432
|
+
continue;
|
|
7433
|
+
}
|
|
7412
7434
|
if (control.type === "filter") {
|
|
7413
7435
|
const where = value2;
|
|
7414
7436
|
const metrics = getQueryWhereMetrics(where);
|
|
@@ -8321,11 +8343,13 @@ const assetQuerySort = strictObject({
|
|
|
8321
8343
|
field: assetQueryFieldPath,
|
|
8322
8344
|
direction: _enum(["asc", "desc"])
|
|
8323
8345
|
});
|
|
8346
|
+
const assetQueryResultMode = _enum(["many", "one", "first", "last"]);
|
|
8324
8347
|
const hasAssetQueryOutput = ({
|
|
8325
8348
|
output,
|
|
8326
8349
|
content: content2
|
|
8327
8350
|
}) => output.includeMetadata || output.mode === "all" || output.mode === "fields" && output.fields.length > 0 || content2.mode !== "none";
|
|
8328
8351
|
const assetQuery = strictObject({
|
|
8352
|
+
result: assetQueryResultMode.default("many"),
|
|
8329
8353
|
where: assetQueryWhere.default({ all: [] }),
|
|
8330
8354
|
sort: array(assetQuerySort).max(contentEngineLimits.sortCount).default([]),
|
|
8331
8355
|
limit: number$3().int().nonnegative().max(contentEngineLimits.resultCount).default(contentEngineLimits.defaultResultCount),
|
|
@@ -8334,8 +8358,20 @@ const assetQuery = strictObject({
|
|
|
8334
8358
|
defaultAssetResourceOutputSelection
|
|
8335
8359
|
),
|
|
8336
8360
|
content: assetResourceContentOptions.default({ mode: "none" })
|
|
8337
|
-
}).
|
|
8338
|
-
|
|
8361
|
+
}).superRefine((query, context) => {
|
|
8362
|
+
if (hasAssetQueryOutput(query) === false) {
|
|
8363
|
+
context.addIssue({
|
|
8364
|
+
code: "custom",
|
|
8365
|
+
message: "Select at least one asset query output"
|
|
8366
|
+
});
|
|
8367
|
+
}
|
|
8368
|
+
if ((query.result === "first" || query.result === "last") && query.sort.length === 0) {
|
|
8369
|
+
context.addIssue({
|
|
8370
|
+
code: "custom",
|
|
8371
|
+
path: ["sort"],
|
|
8372
|
+
message: "Add sorting to define which item is first."
|
|
8373
|
+
});
|
|
8374
|
+
}
|
|
8339
8375
|
});
|
|
8340
8376
|
strictObject({
|
|
8341
8377
|
query: assetQuery,
|
|
@@ -8391,11 +8427,19 @@ const contentDatabaseCapacityStats = contentDatabaseStats.pick({
|
|
|
8391
8427
|
omissionReason: true,
|
|
8392
8428
|
truncated: true
|
|
8393
8429
|
});
|
|
8394
|
-
const
|
|
8430
|
+
const assetQueryCollectionResult = strictObject({
|
|
8395
8431
|
items: array(assetQueryItem),
|
|
8396
8432
|
totalCount: number$3().int().nonnegative(),
|
|
8397
8433
|
hasMore: boolean$2()
|
|
8398
8434
|
});
|
|
8435
|
+
const assetQuerySingleResult = strictObject({
|
|
8436
|
+
item: assetQueryItem.nullable(),
|
|
8437
|
+
totalCount: number$3().int().nonnegative()
|
|
8438
|
+
});
|
|
8439
|
+
const assetQueryResult = union([
|
|
8440
|
+
assetQueryCollectionResult,
|
|
8441
|
+
assetQuerySingleResult
|
|
8442
|
+
]);
|
|
8399
8443
|
const assetQueryPreviewDiagnostics = strictObject({
|
|
8400
8444
|
scope: literal$2("query-preview"),
|
|
8401
8445
|
query: contentDatabaseCapacityStats,
|
|
@@ -8420,6 +8464,7 @@ const assetResourceErrorCode = _enum([
|
|
|
8420
8464
|
"CONTENT_NOT_TEXT",
|
|
8421
8465
|
"CONTENT_DECODING_FAILED",
|
|
8422
8466
|
"CONTENT_LIMIT_EXCEEDED",
|
|
8467
|
+
"MULTIPLE_RESULTS",
|
|
8423
8468
|
"INTERNAL_ERROR"
|
|
8424
8469
|
]);
|
|
8425
8470
|
object({
|
|
@@ -13983,6 +14028,15 @@ class AssetQueryExecutionError extends Error {
|
|
|
13983
14028
|
this.name = "AssetQueryExecutionError";
|
|
13984
14029
|
}
|
|
13985
14030
|
}
|
|
14031
|
+
class AssetQueryMultipleResultsError extends AssetQueryExecutionError {
|
|
14032
|
+
code = "MULTIPLE_RESULTS";
|
|
14033
|
+
matchedCount;
|
|
14034
|
+
constructor(matchedCount) {
|
|
14035
|
+
super(`Expected at most one asset, but the query matched ${matchedCount}.`);
|
|
14036
|
+
this.matchedCount = matchedCount;
|
|
14037
|
+
this.name = "AssetQueryMultipleResultsError";
|
|
14038
|
+
}
|
|
14039
|
+
}
|
|
13986
14040
|
const getCatalogPath = (path2) => {
|
|
13987
14041
|
if (path2[0] !== "properties") {
|
|
13988
14042
|
return path2[0];
|
|
@@ -14341,10 +14395,11 @@ const finalizeAssetQueries = async ({
|
|
|
14341
14395
|
const match = state.matches.get(document2);
|
|
14342
14396
|
return match === void 0 ? [] : [match];
|
|
14343
14397
|
});
|
|
14344
|
-
const
|
|
14345
|
-
|
|
14346
|
-
|
|
14347
|
-
|
|
14398
|
+
const resultMode = query.result ?? "many";
|
|
14399
|
+
if (resultMode === "one" && matched.length > 1) {
|
|
14400
|
+
throw new AssetQueryMultipleResultsError(matched.length);
|
|
14401
|
+
}
|
|
14402
|
+
const selected = resultMode === "many" ? matched.slice(query.offset, query.offset + query.limit) : resultMode === "last" ? matched.slice(-1) : matched.slice(0, 1);
|
|
14348
14403
|
const selectedDocuments = selected.map(({ document: document2 }) => document2);
|
|
14349
14404
|
let items = selected.map(({ item }) => item);
|
|
14350
14405
|
if (query.content.mode !== "none") {
|
|
@@ -14379,10 +14434,13 @@ const finalizeAssetQueries = async ({
|
|
|
14379
14434
|
};
|
|
14380
14435
|
});
|
|
14381
14436
|
}
|
|
14382
|
-
const result2 = {
|
|
14437
|
+
const result2 = resultMode === "many" ? {
|
|
14383
14438
|
items,
|
|
14384
14439
|
totalCount: matched.length,
|
|
14385
14440
|
hasMore: query.offset + selected.length < matched.length
|
|
14441
|
+
} : {
|
|
14442
|
+
item: items[0] ?? null,
|
|
14443
|
+
totalCount: matched.length
|
|
14386
14444
|
};
|
|
14387
14445
|
assertAssetQueryResultSize(result2);
|
|
14388
14446
|
results[state.index] = { status: "fulfilled", value: result2 };
|
|
@@ -14450,10 +14508,10 @@ const executeAssetQueries = async ({
|
|
|
14450
14508
|
});
|
|
14451
14509
|
return requireSettledAssetQueryResults(results);
|
|
14452
14510
|
};
|
|
14453
|
-
|
|
14511
|
+
async function executeAssetQuery({
|
|
14454
14512
|
query,
|
|
14455
14513
|
...input2
|
|
14456
|
-
})
|
|
14514
|
+
}) {
|
|
14457
14515
|
const [result2] = await executeAssetQueries({
|
|
14458
14516
|
...input2,
|
|
14459
14517
|
queries: [query]
|
|
@@ -14462,7 +14520,7 @@ const executeAssetQuery = async ({
|
|
|
14462
14520
|
throw result2.reason;
|
|
14463
14521
|
}
|
|
14464
14522
|
return result2.value;
|
|
14465
|
-
}
|
|
14523
|
+
}
|
|
14466
14524
|
const isJsonObject$1 = (value2) => typeof value2 === "object" && value2 !== null && Array.isArray(value2) === false;
|
|
14467
14525
|
const getJsonReferenceMarkerValue = (value2) => {
|
|
14468
14526
|
if (isJsonObject$1(value2) === false) {
|
|
@@ -21836,7 +21894,7 @@ const hasOverlappingFields = (fields) => fields.some(
|
|
|
21836
21894
|
)
|
|
21837
21895
|
);
|
|
21838
21896
|
const getMaterializedFields = (query) => {
|
|
21839
|
-
if (query.content.mode !== "none" || query.output.mode !== "fields" || query.output.includeMetadata) {
|
|
21897
|
+
if ((query.result ?? "many") !== "many" || query.content.mode !== "none" || query.output.mode !== "fields" || query.output.includeMetadata) {
|
|
21840
21898
|
return;
|
|
21841
21899
|
}
|
|
21842
21900
|
const fields = [
|
|
@@ -21907,6 +21965,9 @@ const materializeQuery = async ({
|
|
|
21907
21965
|
}
|
|
21908
21966
|
throw error;
|
|
21909
21967
|
}
|
|
21968
|
+
if ("items" in result2 === false) {
|
|
21969
|
+
return;
|
|
21970
|
+
}
|
|
21910
21971
|
const rows = result2.items.map(
|
|
21911
21972
|
(item) => fields.map((field) => getItemFieldValue(item, field))
|
|
21912
21973
|
);
|
|
@@ -21944,6 +22005,12 @@ const materializeContentCompilationQueries = async ({
|
|
|
21944
22005
|
}
|
|
21945
22006
|
return { values, materializedQueries };
|
|
21946
22007
|
};
|
|
22008
|
+
const assetQueryResultOptions = [
|
|
22009
|
+
{ value: "many", label: "Many" },
|
|
22010
|
+
{ value: "one", label: "Exactly one" },
|
|
22011
|
+
{ value: "first", label: "First" },
|
|
22012
|
+
{ value: "last", label: "Last" }
|
|
22013
|
+
];
|
|
21947
22014
|
const getDefaultFilterValue = (operator) => operator === "in" ? "[]" : operator === "exists" || operator === "isEmpty" ? "true" : '""';
|
|
21948
22015
|
const fieldLabels = {
|
|
21949
22016
|
id: "id",
|
|
@@ -22019,6 +22086,14 @@ const assetQuerySourceDefinition = {
|
|
|
22019
22086
|
io: "input"
|
|
22020
22087
|
})
|
|
22021
22088
|
},
|
|
22089
|
+
{
|
|
22090
|
+
type: "select",
|
|
22091
|
+
key: "result",
|
|
22092
|
+
label: "Result",
|
|
22093
|
+
sectionLabel: "Result",
|
|
22094
|
+
defaultValue: "many",
|
|
22095
|
+
options: assetQueryResultOptions
|
|
22096
|
+
},
|
|
22022
22097
|
{
|
|
22023
22098
|
type: "filter",
|
|
22024
22099
|
key: "where",
|
|
@@ -22106,6 +22181,7 @@ const assetQueryOffsetExpression = z$4.union([
|
|
|
22106
22181
|
)
|
|
22107
22182
|
]);
|
|
22108
22183
|
const assetQueryResourceConfigurationInput = z$4.strictObject({
|
|
22184
|
+
result: assetQueryResultMode.default("many"),
|
|
22109
22185
|
where: assetQueryWhereExpression.describe(
|
|
22110
22186
|
"A boolean filter tree. Use { all: [...] } for AND and { any: [...] } for OR; leaves contain field, operator, and value."
|
|
22111
22187
|
).default({ all: [] }),
|
|
@@ -22122,6 +22198,7 @@ const assetQueryResourceConfigurationInput = z$4.strictObject({
|
|
|
22122
22198
|
content: assetResourceContentOptions.default({ mode: "none" })
|
|
22123
22199
|
});
|
|
22124
22200
|
const assetQueryResourceConfigurationPatchInput = z$4.strictObject({
|
|
22201
|
+
result: assetQueryResourceConfigurationInput.shape.result.removeDefault().optional(),
|
|
22125
22202
|
where: assetQueryResourceConfigurationInput.shape.where.removeDefault().optional(),
|
|
22126
22203
|
sort: assetQueryResourceConfigurationInput.shape.sort.removeDefault().optional(),
|
|
22127
22204
|
limit: assetQueryResourceConfigurationInput.shape.limit.removeDefault().optional(),
|
|
@@ -25347,7 +25424,7 @@ function clone$1(color2) {
|
|
|
25347
25424
|
alpha: color2.alpha
|
|
25348
25425
|
};
|
|
25349
25426
|
}
|
|
25350
|
-
function distance(color1, color2, space = "lab") {
|
|
25427
|
+
function distance$1(color1, color2, space = "lab") {
|
|
25351
25428
|
space = ColorSpace.get(space);
|
|
25352
25429
|
let coords1 = space.from(color1);
|
|
25353
25430
|
let coords2 = space.from(color2);
|
|
@@ -25362,7 +25439,7 @@ function distance(color1, color2, space = "lab") {
|
|
|
25362
25439
|
);
|
|
25363
25440
|
}
|
|
25364
25441
|
function deltaE76(color2, sample) {
|
|
25365
|
-
return distance(color2, sample, "lab");
|
|
25442
|
+
return distance$1(color2, sample, "lab");
|
|
25366
25443
|
}
|
|
25367
25444
|
const π = Math.PI;
|
|
25368
25445
|
const d2r = π / 180;
|
|
@@ -28483,7 +28560,7 @@ const colorjs = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
|
|
|
28483
28560
|
deltaEOK2,
|
|
28484
28561
|
deltas,
|
|
28485
28562
|
display: display$1,
|
|
28486
|
-
distance,
|
|
28563
|
+
distance: distance$1,
|
|
28487
28564
|
equals,
|
|
28488
28565
|
get,
|
|
28489
28566
|
getAll,
|
|
@@ -29487,6 +29564,7 @@ const createAssetContentCompilationQuery = ({
|
|
|
29487
29564
|
configuration
|
|
29488
29565
|
}) => ({
|
|
29489
29566
|
id: resourceId2,
|
|
29567
|
+
result: configuration.result,
|
|
29490
29568
|
where: toContentCompilationWhere(configuration.where),
|
|
29491
29569
|
sort: configuration.sort,
|
|
29492
29570
|
limit: toContentCompilationInteger(configuration.limit),
|
|
@@ -29550,6 +29628,7 @@ const parseStructuredAssetQueryResourceBody = (body2) => {
|
|
|
29550
29628
|
return parsed.value;
|
|
29551
29629
|
};
|
|
29552
29630
|
const createStructuredAssetQueryResourceBody = ({
|
|
29631
|
+
result: result2 = "many",
|
|
29553
29632
|
where,
|
|
29554
29633
|
sort,
|
|
29555
29634
|
limit: limit2,
|
|
@@ -29561,6 +29640,7 @@ const createStructuredAssetQueryResourceBody = ({
|
|
|
29561
29640
|
throw new Error("Select at least one asset query output");
|
|
29562
29641
|
}
|
|
29563
29642
|
const query = assetQuerySourceCodec.format({
|
|
29643
|
+
result: result2,
|
|
29564
29644
|
where,
|
|
29565
29645
|
sort,
|
|
29566
29646
|
limit: limit2,
|
|
@@ -29568,7 +29648,17 @@ const createStructuredAssetQueryResourceBody = ({
|
|
|
29568
29648
|
output,
|
|
29569
29649
|
content: assetResourceContentOptions.parse(content2)
|
|
29570
29650
|
});
|
|
29571
|
-
|
|
29651
|
+
const fields = parseExpressionObject(query);
|
|
29652
|
+
if (fields === void 0) {
|
|
29653
|
+
throw new Error("Assets query could not be serialized");
|
|
29654
|
+
}
|
|
29655
|
+
if (result2 !== "many") {
|
|
29656
|
+
fields.delete("limit");
|
|
29657
|
+
fields.delete("offset");
|
|
29658
|
+
}
|
|
29659
|
+
return generateObjectExpression(
|
|
29660
|
+
/* @__PURE__ */ new Map([["query", `(${generateObjectExpression(fields)})`]])
|
|
29661
|
+
);
|
|
29572
29662
|
};
|
|
29573
29663
|
const AccordionIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16" width="100%" height="100%" style="display: block;"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M13.056 8H14.5V4.101a1.3 1.3 0 0 0-1.3-1.299H2.8a1.3 1.3 0 0 0-1.3 1.3V8H13.056ZM13.056 13.198h.145a1.3 1.3 0 0 0 1.299-1.3V8h-13v3.899a1.3 1.3 0 0 0 1.3 1.299h10.256Z"/><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="m10.026 4.913.975.976.976-.976M10.026 10.111l.975.976.976-.976"/></svg>`;
|
|
29574
29664
|
const AddTemplateInstanceIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16" width="100%" height="100%" style="display: block;"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M4.5 2H3.333A1.333 1.333 0 0 0 2 3.333V4.5M14 12.667c0 .021 0 .042-.002.063M11.5 14h1.167a1.333 1.333 0 0 0 1.331-1.27m0 0V11.5M2 11.5v1.167A1.333 1.333 0 0 0 3.333 14H4.5M7 14h2M2 7v2M8.461 4.77H14M11.23 2v5.538"/></svg>`;
|
|
@@ -31550,6 +31640,131 @@ const getInputJsonSchemaMetadata = (inputSchema) => {
|
|
|
31550
31640
|
)
|
|
31551
31641
|
};
|
|
31552
31642
|
};
|
|
31643
|
+
const peq = new Uint32Array(65536);
|
|
31644
|
+
const myers_32 = (a2, b2) => {
|
|
31645
|
+
const n2 = a2.length;
|
|
31646
|
+
const m3 = b2.length;
|
|
31647
|
+
const lst = 1 << n2 - 1;
|
|
31648
|
+
let pv = -1;
|
|
31649
|
+
let mv = 0;
|
|
31650
|
+
let sc = n2;
|
|
31651
|
+
let i = n2;
|
|
31652
|
+
while (i--) {
|
|
31653
|
+
peq[a2.charCodeAt(i)] |= 1 << i;
|
|
31654
|
+
}
|
|
31655
|
+
for (i = 0; i < m3; i++) {
|
|
31656
|
+
let eq = peq[b2.charCodeAt(i)];
|
|
31657
|
+
const xv = eq | mv;
|
|
31658
|
+
eq |= (eq & pv) + pv ^ pv;
|
|
31659
|
+
mv |= ~(eq | pv);
|
|
31660
|
+
pv &= eq;
|
|
31661
|
+
if (mv & lst) {
|
|
31662
|
+
sc++;
|
|
31663
|
+
}
|
|
31664
|
+
if (pv & lst) {
|
|
31665
|
+
sc--;
|
|
31666
|
+
}
|
|
31667
|
+
mv = mv << 1 | 1;
|
|
31668
|
+
pv = pv << 1 | ~(xv | mv);
|
|
31669
|
+
mv &= xv;
|
|
31670
|
+
}
|
|
31671
|
+
i = n2;
|
|
31672
|
+
while (i--) {
|
|
31673
|
+
peq[a2.charCodeAt(i)] = 0;
|
|
31674
|
+
}
|
|
31675
|
+
return sc;
|
|
31676
|
+
};
|
|
31677
|
+
const myers_x = (b2, a2) => {
|
|
31678
|
+
const n2 = a2.length;
|
|
31679
|
+
const m3 = b2.length;
|
|
31680
|
+
const mhc = [];
|
|
31681
|
+
const phc = [];
|
|
31682
|
+
const hsize = Math.ceil(n2 / 32);
|
|
31683
|
+
const vsize = Math.ceil(m3 / 32);
|
|
31684
|
+
for (let i = 0; i < hsize; i++) {
|
|
31685
|
+
phc[i] = -1;
|
|
31686
|
+
mhc[i] = 0;
|
|
31687
|
+
}
|
|
31688
|
+
let j2 = 0;
|
|
31689
|
+
for (; j2 < vsize - 1; j2++) {
|
|
31690
|
+
let mv2 = 0;
|
|
31691
|
+
let pv2 = -1;
|
|
31692
|
+
const start2 = j2 * 32;
|
|
31693
|
+
const vlen2 = Math.min(32, m3) + start2;
|
|
31694
|
+
for (let k2 = start2; k2 < vlen2; k2++) {
|
|
31695
|
+
peq[b2.charCodeAt(k2)] |= 1 << k2;
|
|
31696
|
+
}
|
|
31697
|
+
for (let i = 0; i < n2; i++) {
|
|
31698
|
+
const eq = peq[a2.charCodeAt(i)];
|
|
31699
|
+
const pb = phc[i / 32 | 0] >>> i & 1;
|
|
31700
|
+
const mb = mhc[i / 32 | 0] >>> i & 1;
|
|
31701
|
+
const xv = eq | mv2;
|
|
31702
|
+
const xh = ((eq | mb) & pv2) + pv2 ^ pv2 | eq | mb;
|
|
31703
|
+
let ph = mv2 | ~(xh | pv2);
|
|
31704
|
+
let mh = pv2 & xh;
|
|
31705
|
+
if (ph >>> 31 ^ pb) {
|
|
31706
|
+
phc[i / 32 | 0] ^= 1 << i;
|
|
31707
|
+
}
|
|
31708
|
+
if (mh >>> 31 ^ mb) {
|
|
31709
|
+
mhc[i / 32 | 0] ^= 1 << i;
|
|
31710
|
+
}
|
|
31711
|
+
ph = ph << 1 | pb;
|
|
31712
|
+
mh = mh << 1 | mb;
|
|
31713
|
+
pv2 = mh | ~(xv | ph);
|
|
31714
|
+
mv2 = ph & xv;
|
|
31715
|
+
}
|
|
31716
|
+
for (let k2 = start2; k2 < vlen2; k2++) {
|
|
31717
|
+
peq[b2.charCodeAt(k2)] = 0;
|
|
31718
|
+
}
|
|
31719
|
+
}
|
|
31720
|
+
let mv = 0;
|
|
31721
|
+
let pv = -1;
|
|
31722
|
+
const start = j2 * 32;
|
|
31723
|
+
const vlen = Math.min(32, m3 - start) + start;
|
|
31724
|
+
for (let k2 = start; k2 < vlen; k2++) {
|
|
31725
|
+
peq[b2.charCodeAt(k2)] |= 1 << k2;
|
|
31726
|
+
}
|
|
31727
|
+
let score = m3;
|
|
31728
|
+
for (let i = 0; i < n2; i++) {
|
|
31729
|
+
const eq = peq[a2.charCodeAt(i)];
|
|
31730
|
+
const pb = phc[i / 32 | 0] >>> i & 1;
|
|
31731
|
+
const mb = mhc[i / 32 | 0] >>> i & 1;
|
|
31732
|
+
const xv = eq | mv;
|
|
31733
|
+
const xh = ((eq | mb) & pv) + pv ^ pv | eq | mb;
|
|
31734
|
+
let ph = mv | ~(xh | pv);
|
|
31735
|
+
let mh = pv & xh;
|
|
31736
|
+
score += ph >>> m3 - 1 & 1;
|
|
31737
|
+
score -= mh >>> m3 - 1 & 1;
|
|
31738
|
+
if (ph >>> 31 ^ pb) {
|
|
31739
|
+
phc[i / 32 | 0] ^= 1 << i;
|
|
31740
|
+
}
|
|
31741
|
+
if (mh >>> 31 ^ mb) {
|
|
31742
|
+
mhc[i / 32 | 0] ^= 1 << i;
|
|
31743
|
+
}
|
|
31744
|
+
ph = ph << 1 | pb;
|
|
31745
|
+
mh = mh << 1 | mb;
|
|
31746
|
+
pv = mh | ~(xv | ph);
|
|
31747
|
+
mv = ph & xv;
|
|
31748
|
+
}
|
|
31749
|
+
for (let k2 = start; k2 < vlen; k2++) {
|
|
31750
|
+
peq[b2.charCodeAt(k2)] = 0;
|
|
31751
|
+
}
|
|
31752
|
+
return score;
|
|
31753
|
+
};
|
|
31754
|
+
const distance = (a2, b2) => {
|
|
31755
|
+
if (a2.length < b2.length) {
|
|
31756
|
+
const tmp = b2;
|
|
31757
|
+
b2 = a2;
|
|
31758
|
+
a2 = tmp;
|
|
31759
|
+
}
|
|
31760
|
+
if (b2.length === 0) {
|
|
31761
|
+
return a2.length;
|
|
31762
|
+
}
|
|
31763
|
+
if (a2.length <= 32) {
|
|
31764
|
+
return myers_32(a2, b2);
|
|
31765
|
+
}
|
|
31766
|
+
return myers_x(a2, b2);
|
|
31767
|
+
};
|
|
31553
31768
|
const runtimeOperationContractData = [
|
|
31554
31769
|
{
|
|
31555
31770
|
id: "pages.list",
|
|
@@ -92891,7 +93106,9 @@ const runtimeOperationContractData = [
|
|
|
92891
93106
|
type: "string"
|
|
92892
93107
|
},
|
|
92893
93108
|
state: {
|
|
92894
|
-
type: "string"
|
|
93109
|
+
type: "string",
|
|
93110
|
+
minLength: 1,
|
|
93111
|
+
description: "Exact state selector to delete. An invalid selector is accepted only when it identifies an existing declaration with the same style source, breakpoint, property, and state."
|
|
92895
93112
|
}
|
|
92896
93113
|
},
|
|
92897
93114
|
required: ["instanceId", "property"]
|
|
@@ -93029,7 +93246,9 @@ const runtimeOperationContractData = [
|
|
|
93029
93246
|
type: "string"
|
|
93030
93247
|
},
|
|
93031
93248
|
state: {
|
|
93032
|
-
type: "string"
|
|
93249
|
+
type: "string",
|
|
93250
|
+
minLength: 1,
|
|
93251
|
+
description: "Exact state selector to delete. An invalid selector is accepted only when it identifies an existing declaration with the same style source, breakpoint, property, and state."
|
|
93033
93252
|
},
|
|
93034
93253
|
styleSourceId: {
|
|
93035
93254
|
type: "string"
|
|
@@ -93844,7 +94063,9 @@ const runtimeOperationContractData = [
|
|
|
93844
94063
|
type: "string"
|
|
93845
94064
|
},
|
|
93846
94065
|
state: {
|
|
93847
|
-
type: "string"
|
|
94066
|
+
type: "string",
|
|
94067
|
+
minLength: 1,
|
|
94068
|
+
description: "Exact state selector to delete. An invalid selector is accepted only when it identifies an existing declaration with the same style source, breakpoint, property, and state."
|
|
93848
94069
|
}
|
|
93849
94070
|
},
|
|
93850
94071
|
required: ["property"]
|
|
@@ -97870,6 +98091,10 @@ const runtimeOperationContractData = [
|
|
|
97870
98091
|
query: {
|
|
97871
98092
|
type: "object",
|
|
97872
98093
|
properties: {
|
|
98094
|
+
result: {
|
|
98095
|
+
type: "string",
|
|
98096
|
+
enum: ["many", "one", "first", "last"]
|
|
98097
|
+
},
|
|
97873
98098
|
where: {
|
|
97874
98099
|
$ref: "#/$defs/__schema1"
|
|
97875
98100
|
},
|
|
@@ -98034,7 +98259,14 @@ const runtimeOperationContractData = [
|
|
|
98034
98259
|
]
|
|
98035
98260
|
}
|
|
98036
98261
|
},
|
|
98037
|
-
required: [
|
|
98262
|
+
required: [
|
|
98263
|
+
"result",
|
|
98264
|
+
"where",
|
|
98265
|
+
"sort",
|
|
98266
|
+
"limit",
|
|
98267
|
+
"offset",
|
|
98268
|
+
"content"
|
|
98269
|
+
],
|
|
98038
98270
|
additionalProperties: {}
|
|
98039
98271
|
},
|
|
98040
98272
|
configurationError: {
|
|
@@ -98254,6 +98486,10 @@ const runtimeOperationContractData = [
|
|
|
98254
98486
|
query: {
|
|
98255
98487
|
type: "object",
|
|
98256
98488
|
properties: {
|
|
98489
|
+
result: {
|
|
98490
|
+
type: "string",
|
|
98491
|
+
enum: ["many", "one", "first", "last"]
|
|
98492
|
+
},
|
|
98257
98493
|
where: {
|
|
98258
98494
|
$ref: "#/$defs/__schema1"
|
|
98259
98495
|
},
|
|
@@ -98418,7 +98654,14 @@ const runtimeOperationContractData = [
|
|
|
98418
98654
|
]
|
|
98419
98655
|
}
|
|
98420
98656
|
},
|
|
98421
|
-
required: [
|
|
98657
|
+
required: [
|
|
98658
|
+
"result",
|
|
98659
|
+
"where",
|
|
98660
|
+
"sort",
|
|
98661
|
+
"limit",
|
|
98662
|
+
"offset",
|
|
98663
|
+
"content"
|
|
98664
|
+
],
|
|
98422
98665
|
additionalProperties: {}
|
|
98423
98666
|
},
|
|
98424
98667
|
configurationError: {
|
|
@@ -98573,6 +98816,11 @@ const runtimeOperationContractData = [
|
|
|
98573
98816
|
query: {
|
|
98574
98817
|
type: "object",
|
|
98575
98818
|
properties: {
|
|
98819
|
+
result: {
|
|
98820
|
+
default: "many",
|
|
98821
|
+
type: "string",
|
|
98822
|
+
enum: ["many", "one", "first", "last"]
|
|
98823
|
+
},
|
|
98576
98824
|
where: {
|
|
98577
98825
|
default: {
|
|
98578
98826
|
all: []
|
|
@@ -99137,6 +99385,10 @@ const runtimeOperationContractData = [
|
|
|
99137
99385
|
{
|
|
99138
99386
|
type: "object",
|
|
99139
99387
|
properties: {
|
|
99388
|
+
result: {
|
|
99389
|
+
type: "string",
|
|
99390
|
+
enum: ["many", "one", "first", "last"]
|
|
99391
|
+
},
|
|
99140
99392
|
where: {
|
|
99141
99393
|
description: "A boolean filter tree. Use { all: [...] } for AND and { any: [...] } for OR; leaves contain field, operator, and value.",
|
|
99142
99394
|
anyOf: [
|
|
@@ -104318,6 +104570,18 @@ const deleteAssets$1 = (state, input2) => {
|
|
|
104318
104570
|
const replaceTextValue = (value2, input2) => input2.match === "exact" ? value2 === input2.find ? input2.replace : value2 : value2.replaceAll(input2.find, input2.replace);
|
|
104319
104571
|
const getExpressionErrorMessages = (options) => lintExpression(options).filter((diagnostic) => diagnostic.severity === "error").map((diagnostic) => diagnostic.message);
|
|
104320
104572
|
const getExpressionErrors = (expression2) => getExpressionErrorMessages({ expression: expression2 });
|
|
104573
|
+
const addExpressionValidationIssues = (context, errors, path2 = []) => {
|
|
104574
|
+
for (const detail of errors) {
|
|
104575
|
+
addZodValidationIssue(context, {
|
|
104576
|
+
code: "invalid_expression",
|
|
104577
|
+
path: path2.map(String),
|
|
104578
|
+
message: "Invalid Webstudio expression",
|
|
104579
|
+
constraint: "valid_webstudio_expression",
|
|
104580
|
+
example: "item.title",
|
|
104581
|
+
detail
|
|
104582
|
+
});
|
|
104583
|
+
}
|
|
104584
|
+
};
|
|
104321
104585
|
const expressionWarningSchema = z$4.object({
|
|
104322
104586
|
severity: z$4.literal("warning"),
|
|
104323
104587
|
code: z$4.string(),
|
|
@@ -133049,18 +133313,6 @@ const getPropValueErrors = ({
|
|
|
133049
133313
|
}
|
|
133050
133314
|
return getExpressionErrors(String(value2));
|
|
133051
133315
|
};
|
|
133052
|
-
const addExpressionIssues$1 = (context, errors, path2 = []) => {
|
|
133053
|
-
for (const message of errors) {
|
|
133054
|
-
addZodValidationIssue(context, {
|
|
133055
|
-
code: "invalid_expression",
|
|
133056
|
-
path: path2.map(String),
|
|
133057
|
-
message: "Invalid Webstudio expression",
|
|
133058
|
-
constraint: "valid_webstudio_expression",
|
|
133059
|
-
example: "item.title",
|
|
133060
|
-
detail: message
|
|
133061
|
-
});
|
|
133062
|
-
}
|
|
133063
|
-
};
|
|
133064
133316
|
const propValueBaseInput = {
|
|
133065
133317
|
propId: runtimeGeneratedIdInput,
|
|
133066
133318
|
instanceId: z$4.string().describe("Instance id that owns the prop."),
|
|
@@ -133149,7 +133401,9 @@ const propValueInputVariants = [
|
|
|
133149
133401
|
const propValueInput = z$4.discriminatedUnion("type", propValueInputVariants).describe(
|
|
133150
133402
|
'Direct prop value update. Match value to type: use type "string" for fixed text attributes such as placeholder, aria-label, alt, id, class, href, and title; use bind-props for dynamic expressions/resources/actions.'
|
|
133151
133403
|
).superRefine((value2, context) => {
|
|
133152
|
-
|
|
133404
|
+
addExpressionValidationIssues(context, getPropValueErrors(value2), [
|
|
133405
|
+
"value"
|
|
133406
|
+
]);
|
|
133153
133407
|
});
|
|
133154
133408
|
const dataPropBindingInput = z$4.discriminatedUnion("type", [
|
|
133155
133409
|
z$4.object({
|
|
@@ -133180,7 +133434,7 @@ const propBindingInput = z$4.object({
|
|
|
133180
133434
|
actionPropBindingInput
|
|
133181
133435
|
])
|
|
133182
133436
|
}).superRefine((value2, context) => {
|
|
133183
|
-
|
|
133437
|
+
addExpressionValidationIssues(
|
|
133184
133438
|
context,
|
|
133185
133439
|
getPropValueErrors({
|
|
133186
133440
|
type: value2.binding.type,
|
|
@@ -134521,31 +134775,31 @@ const createDataVariableCreatePayload = ({
|
|
|
134521
134775
|
errors
|
|
134522
134776
|
};
|
|
134523
134777
|
};
|
|
134524
|
-
const
|
|
134778
|
+
const createDataSourceUpsertPayload = ({
|
|
134525
134779
|
pages: pages2,
|
|
134526
134780
|
instances,
|
|
134527
134781
|
props: props2,
|
|
134528
134782
|
dataSources,
|
|
134529
134783
|
resources,
|
|
134530
|
-
|
|
134784
|
+
dataSource: dataSource2
|
|
134531
134785
|
}) => {
|
|
134532
134786
|
if (instances === void 0 || props2 === void 0 || dataSources === void 0 || resources === void 0) {
|
|
134533
134787
|
return [];
|
|
134534
134788
|
}
|
|
134535
|
-
if (
|
|
134789
|
+
if (dataSource2.scopeInstanceId === void 0) {
|
|
134536
134790
|
return [];
|
|
134537
134791
|
}
|
|
134538
|
-
const scopeInstanceId =
|
|
134792
|
+
const scopeInstanceId = dataSource2.scopeInstanceId;
|
|
134539
134793
|
return produceWebstudioDataMutation(
|
|
134540
134794
|
{ pages: pages2, instances, props: props2, dataSources, resources },
|
|
134541
134795
|
(draft) => {
|
|
134542
|
-
if (
|
|
134543
|
-
const previous2 = draft.dataSources.get(
|
|
134796
|
+
if (dataSource2.type === "variable") {
|
|
134797
|
+
const previous2 = draft.dataSources.get(dataSource2.id);
|
|
134544
134798
|
if (previous2?.type === "resource") {
|
|
134545
134799
|
draft.resources.delete(previous2.resourceId);
|
|
134546
134800
|
}
|
|
134547
134801
|
}
|
|
134548
|
-
draft.dataSources.set(
|
|
134802
|
+
draft.dataSources.set(dataSource2.id, dataSource2);
|
|
134549
134803
|
rebindTreeVariablesMutable({
|
|
134550
134804
|
startingInstanceId: scopeInstanceId,
|
|
134551
134805
|
...draft
|
|
@@ -134607,9 +134861,9 @@ const createDataVariable = (state, input2, context) => {
|
|
|
134607
134861
|
value: input2.value
|
|
134608
134862
|
});
|
|
134609
134863
|
return createRuntimeMutation({
|
|
134610
|
-
payload:
|
|
134864
|
+
payload: createDataSourceUpsertPayload({
|
|
134611
134865
|
...state,
|
|
134612
|
-
|
|
134866
|
+
dataSource: variable2
|
|
134613
134867
|
}) ?? payload,
|
|
134614
134868
|
result: { dataSourceId: dataSourceId2 },
|
|
134615
134869
|
invalidatesNamespaces: [
|
|
@@ -134627,12 +134881,6 @@ const updateDataVariable = (state, input2) => {
|
|
|
134627
134881
|
if (dataSource2 === void 0) {
|
|
134628
134882
|
return throwBuilderRuntimeError("NOT_FOUND", "Variable not found");
|
|
134629
134883
|
}
|
|
134630
|
-
if (dataSource2.type !== "variable" && input2.values.value === void 0) {
|
|
134631
|
-
return throwBuilderRuntimeError(
|
|
134632
|
-
"BAD_REQUEST",
|
|
134633
|
-
"Variable value is required"
|
|
134634
|
-
);
|
|
134635
|
-
}
|
|
134636
134884
|
const scopeInstanceId = input2.values.scopeInstanceId ?? dataSource2.scopeInstanceId;
|
|
134637
134885
|
if (scopeInstanceId === void 0) {
|
|
134638
134886
|
return throwBuilderRuntimeError(
|
|
@@ -134640,27 +134888,41 @@ const updateDataVariable = (state, input2) => {
|
|
|
134640
134888
|
"Variable scope instance is required"
|
|
134641
134889
|
);
|
|
134642
134890
|
}
|
|
134643
|
-
const
|
|
134644
|
-
|
|
134645
|
-
scopeInstanceId,
|
|
134646
|
-
name: dataSource2.name,
|
|
134647
|
-
value: input2.values.value ?? { type: "string", value: "" }
|
|
134648
|
-
});
|
|
134649
|
-
const { error } = createDataVariableUpdatePayload({
|
|
134650
|
-
variable: variable2,
|
|
134891
|
+
const { error, payload } = createDataVariableUpdatePayload({
|
|
134892
|
+
variable: dataSource2,
|
|
134651
134893
|
values: input2.values,
|
|
134652
134894
|
dataSources: dataSources.values()
|
|
134653
134895
|
});
|
|
134654
134896
|
if (error) {
|
|
134655
134897
|
return throwBuilderRuntimeError("BAD_REQUEST", error.message);
|
|
134656
134898
|
}
|
|
134657
|
-
const
|
|
134899
|
+
const { value: value2, ...metadataValues } = input2.values;
|
|
134900
|
+
if (dataSource2.type === "resource" && value2 === void 0 && (metadataValues.scopeInstanceId === void 0 || metadataValues.scopeInstanceId === dataSource2.scopeInstanceId)) {
|
|
134901
|
+
return createRuntimeMutation({
|
|
134902
|
+
payload,
|
|
134903
|
+
result: { dataSourceId: dataSource2.id },
|
|
134904
|
+
invalidatesNamespaces: ["dataSources"]
|
|
134905
|
+
});
|
|
134906
|
+
}
|
|
134907
|
+
const nextDataSource = dataSource2.type === "resource" && value2 !== void 0 ? {
|
|
134908
|
+
...createDataVariableValue({
|
|
134909
|
+
dataSourceId: dataSource2.id,
|
|
134910
|
+
scopeInstanceId,
|
|
134911
|
+
name: dataSource2.name,
|
|
134912
|
+
value: value2
|
|
134913
|
+
}),
|
|
134914
|
+
...metadataValues
|
|
134915
|
+
} : {
|
|
134916
|
+
...dataSource2,
|
|
134917
|
+
...metadataValues,
|
|
134918
|
+
...value2 === void 0 ? {} : { value: value2 }
|
|
134919
|
+
};
|
|
134658
134920
|
return createRuntimeMutation({
|
|
134659
|
-
payload:
|
|
134921
|
+
payload: createDataSourceUpsertPayload({
|
|
134660
134922
|
...state,
|
|
134661
|
-
|
|
134923
|
+
dataSource: nextDataSource
|
|
134662
134924
|
}),
|
|
134663
|
-
result: { dataSourceId:
|
|
134925
|
+
result: { dataSourceId: nextDataSource.id },
|
|
134664
134926
|
invalidatesNamespaces: [
|
|
134665
134927
|
"pages",
|
|
134666
134928
|
"instances",
|
|
@@ -135670,6 +135932,7 @@ const normalizeWhere = (where) => mapQueryWhere(where, (condition) => ({
|
|
|
135670
135932
|
value: normalizeResourceExpressionInput(condition.value)
|
|
135671
135933
|
}));
|
|
135672
135934
|
const createAssetResourceBody = (configuration) => createStructuredAssetQueryResourceBody({
|
|
135935
|
+
result: configuration.result,
|
|
135673
135936
|
where: normalizeWhere(configuration.where),
|
|
135674
135937
|
sort: configuration.sort,
|
|
135675
135938
|
limit: normalizeResourceExpressionInput(configuration.limit),
|
|
@@ -135706,6 +135969,7 @@ const serializeAssetResource = ({
|
|
|
135706
135969
|
configurationError: "Stored Assets query configuration could not be decoded."
|
|
135707
135970
|
} : {
|
|
135708
135971
|
query: {
|
|
135972
|
+
result: configuration.result,
|
|
135709
135973
|
where: serializeWhere(configuration.where, unsetNameById),
|
|
135710
135974
|
sort: configuration.sort,
|
|
135711
135975
|
limit: unsetExpressionVariables({
|
|
@@ -136332,6 +136596,42 @@ const computeAllowedCategories = ({
|
|
|
136332
136596
|
}
|
|
136333
136597
|
return allowedCategories;
|
|
136334
136598
|
};
|
|
136599
|
+
const findHtmlConstraintInstance = ({
|
|
136600
|
+
instances,
|
|
136601
|
+
props: props2,
|
|
136602
|
+
metas,
|
|
136603
|
+
instanceSelector,
|
|
136604
|
+
htmlTagsByInstanceId,
|
|
136605
|
+
tag: tag2,
|
|
136606
|
+
component
|
|
136607
|
+
}) => {
|
|
136608
|
+
let allowedCategories;
|
|
136609
|
+
let wasSatisfying = true;
|
|
136610
|
+
let constraintInstance;
|
|
136611
|
+
for (const instanceId2 of instanceSelector.slice(1).reverse()) {
|
|
136612
|
+
const ancestor = instances.get(instanceId2);
|
|
136613
|
+
if (ancestor === void 0) {
|
|
136614
|
+
continue;
|
|
136615
|
+
}
|
|
136616
|
+
const ancestorTag = getTag({
|
|
136617
|
+
instance: ancestor,
|
|
136618
|
+
metas,
|
|
136619
|
+
props: props2,
|
|
136620
|
+
htmlTagsByInstanceId
|
|
136621
|
+
});
|
|
136622
|
+
allowedCategories = getElementChildren$1(ancestorTag, allowedCategories);
|
|
136623
|
+
const isSatisfying = isTagSatisfyingContentModel({
|
|
136624
|
+
tag: tag2,
|
|
136625
|
+
component,
|
|
136626
|
+
allowedCategories
|
|
136627
|
+
});
|
|
136628
|
+
if (wasSatisfying && isSatisfying === false) {
|
|
136629
|
+
constraintInstance = ancestor;
|
|
136630
|
+
}
|
|
136631
|
+
wasSatisfying = isSatisfying;
|
|
136632
|
+
}
|
|
136633
|
+
return constraintInstance;
|
|
136634
|
+
};
|
|
136335
136635
|
const defaultComponentContentModel = {
|
|
136336
136636
|
category: "instance",
|
|
136337
136637
|
children: ["rich-text", "instance"]
|
|
@@ -136345,9 +136645,10 @@ const isTextContentCapableInstance = ({
|
|
|
136345
136645
|
}) => {
|
|
136346
136646
|
const tag2 = getTag({ instance: instance2, metas, props: props2, htmlTagsByInstanceId });
|
|
136347
136647
|
const elementContentModel = getElementContentModel(tag2);
|
|
136348
|
-
|
|
136349
|
-
|
|
136350
|
-
);
|
|
136648
|
+
const componentChildren = getComponentContentModel(
|
|
136649
|
+
metas.get(instance2.component)
|
|
136650
|
+
).children;
|
|
136651
|
+
return (elementContentModel === void 0 || elementContentModel.children.length > 0) && (componentChildren.includes("rich-text") || componentChildren.includes("text"));
|
|
136351
136652
|
};
|
|
136352
136653
|
const isComponentSatisfyingContentModel = ({
|
|
136353
136654
|
metas,
|
|
@@ -136438,19 +136739,27 @@ const isTreeSatisfyingContentModel = ({
|
|
|
136438
136739
|
allowedCategories
|
|
136439
136740
|
});
|
|
136440
136741
|
if (isTagSatisfying === false) {
|
|
136441
|
-
const
|
|
136442
|
-
|
|
136443
|
-
|
|
136444
|
-
|
|
136445
|
-
|
|
136742
|
+
const constraintInstance = findHtmlConstraintInstance({
|
|
136743
|
+
instances,
|
|
136744
|
+
props: props2,
|
|
136745
|
+
metas,
|
|
136746
|
+
instanceSelector,
|
|
136747
|
+
htmlTagsByInstanceId,
|
|
136748
|
+
tag: tag2,
|
|
136749
|
+
component: instance2.component
|
|
136750
|
+
});
|
|
136751
|
+
let constraintTag;
|
|
136752
|
+
if (constraintInstance) {
|
|
136753
|
+
constraintTag = getTag({
|
|
136754
|
+
instance: constraintInstance,
|
|
136446
136755
|
metas,
|
|
136447
136756
|
props: props2,
|
|
136448
136757
|
htmlTagsByInstanceId
|
|
136449
136758
|
});
|
|
136450
136759
|
}
|
|
136451
|
-
if (
|
|
136760
|
+
if (constraintTag) {
|
|
136452
136761
|
onError?.(
|
|
136453
|
-
`Placing <${tag2}> element inside a <${
|
|
136762
|
+
`Placing <${tag2}> element inside a <${constraintTag}> violates HTML spec.`,
|
|
136454
136763
|
instanceSelector
|
|
136455
136764
|
);
|
|
136456
136765
|
} else {
|
|
@@ -136487,6 +136796,19 @@ const isTreeSatisfyingContentModel = ({
|
|
|
136487
136796
|
}
|
|
136488
136797
|
}
|
|
136489
136798
|
let isSatisfying = isTagSatisfying && isComponentSatisfying;
|
|
136799
|
+
if (instance2.children.some((child) => child.type !== "id") && isTextContentCapableInstance({
|
|
136800
|
+
instance: instance2,
|
|
136801
|
+
props: props2,
|
|
136802
|
+
metas,
|
|
136803
|
+
htmlTagsByInstanceId
|
|
136804
|
+
}) === false) {
|
|
136805
|
+
const [, name2] = parseComponentName(instance2.component);
|
|
136806
|
+
onError?.(
|
|
136807
|
+
`"${name2}" does not accept text content. Insert an element child instead.`,
|
|
136808
|
+
instanceSelector
|
|
136809
|
+
);
|
|
136810
|
+
isSatisfying = false;
|
|
136811
|
+
}
|
|
136490
136812
|
const contentModel2 = getComponentContentModel(metas.get(instance2.component));
|
|
136491
136813
|
allowedCategories = getElementChildren$1(tag2, allowedCategories);
|
|
136492
136814
|
allowedParentCategories = contentModel2.children;
|
|
@@ -139110,7 +139432,7 @@ const metaCollapsibleTrigger = {
|
|
|
139110
139432
|
icon: TriggerIcon,
|
|
139111
139433
|
contentModel: {
|
|
139112
139434
|
category: "none",
|
|
139113
|
-
children: ["instance"
|
|
139435
|
+
children: ["instance"]
|
|
139114
139436
|
},
|
|
139115
139437
|
states: [
|
|
139116
139438
|
{ label: "Open", selector: '[data-state="open"]' },
|
|
@@ -140976,6 +141298,18 @@ const listFragmentExpressions = (fragment) => [
|
|
|
140976
141298
|
}))
|
|
140977
141299
|
)
|
|
140978
141300
|
];
|
|
141301
|
+
const webstudioFragmentMutationInput = webstudioFragment.superRefine(
|
|
141302
|
+
(fragment, context) => {
|
|
141303
|
+
for (const entry2 of listFragmentExpressions(fragment)) {
|
|
141304
|
+
const errors = getExpressionErrorMessages({
|
|
141305
|
+
expression: entry2.expression,
|
|
141306
|
+
allowAssignment: entry2.allowAssignment,
|
|
141307
|
+
availableVariables: new Set(entry2.variables)
|
|
141308
|
+
});
|
|
141309
|
+
addExpressionValidationIssues(context, errors, entry2.path);
|
|
141310
|
+
}
|
|
141311
|
+
}
|
|
141312
|
+
);
|
|
140979
141313
|
const setDifference = (current4, other) => {
|
|
140980
141314
|
const result2 = new Set(current4);
|
|
140981
141315
|
for (const item of other) {
|
|
@@ -151411,7 +151745,7 @@ const generateJsxChildren = ({
|
|
|
151411
151745
|
usedDataSources,
|
|
151412
151746
|
scope: scope2
|
|
151413
151747
|
});
|
|
151414
|
-
generatedChildren
|
|
151748
|
+
generatedChildren += `{renderText(${expression2})}
|
|
151415
151749
|
`;
|
|
151416
151750
|
continue;
|
|
151417
151751
|
}
|
|
@@ -151607,6 +151941,11 @@ class ResourceValue {
|
|
|
151607
151941
|
name;
|
|
151608
151942
|
config;
|
|
151609
151943
|
constructor(name2, config) {
|
|
151944
|
+
if (config === void 0) {
|
|
151945
|
+
throw new Error(
|
|
151946
|
+
"Invalid JSX prop: ResourceValue requires a resource definition. Existing resource ids are not supported in JSX; insert the component first, then bind its resource prop with update-props."
|
|
151947
|
+
);
|
|
151948
|
+
}
|
|
151610
151949
|
this.name = name2;
|
|
151611
151950
|
this.config = config;
|
|
151612
151951
|
}
|
|
@@ -183900,8 +184239,8 @@ function requireReactDom_development() {
|
|
|
183900
184239
|
case "compositionend":
|
|
183901
184240
|
return getDataFromCustomEvent(nativeEvent);
|
|
183902
184241
|
case "keypress":
|
|
183903
|
-
var
|
|
183904
|
-
if (
|
|
184242
|
+
var which2 = nativeEvent.which;
|
|
184243
|
+
if (which2 !== SPACEBAR_CODE) {
|
|
183905
184244
|
return null;
|
|
183906
184245
|
}
|
|
183907
184246
|
hasSpaceKeypress = true;
|
|
@@ -227601,7 +227940,7 @@ const insertCollectionInput = z$4.object({
|
|
|
227601
227940
|
data: collectionDataInput.describe(
|
|
227602
227941
|
"Complete iterable for the Collection. Do not pass one indexed item."
|
|
227603
227942
|
),
|
|
227604
|
-
itemFragment:
|
|
227943
|
+
itemFragment: webstudioFragmentMutationInput.describe(
|
|
227605
227944
|
"One structured repeated-item fragment. Descendant expressions may reference collectionItem and collectionItemKey."
|
|
227606
227945
|
),
|
|
227607
227946
|
mode: instanceInsertModeInput.optional(),
|
|
@@ -228018,7 +228357,7 @@ const insertComponentInput = z$4.object({
|
|
|
228018
228357
|
});
|
|
228019
228358
|
const insertFragmentInput = z$4.object({
|
|
228020
228359
|
parentInstanceId: z$4.string().optional(),
|
|
228021
|
-
fragment:
|
|
228360
|
+
fragment: webstudioFragmentMutationInput.describe(
|
|
228022
228361
|
"Structured Webstudio fragment produced by Webstudio JSX/template helpers. Runtime remaps fragment ids to generated project ids."
|
|
228023
228362
|
),
|
|
228024
228363
|
conflictResolution: conflictResolutionInput.optional(),
|
|
@@ -236897,6 +237236,9 @@ const styleStateInput = z$4.string().superRefine((state, context) => {
|
|
|
236897
237236
|
});
|
|
236898
237237
|
}
|
|
236899
237238
|
});
|
|
237239
|
+
const existingStyleStateDeletionInput = z$4.string().min(1).describe(
|
|
237240
|
+
"Exact state selector to delete. An invalid selector is accepted only when it identifies an existing declaration with the same style source, breakpoint, property, and state."
|
|
237241
|
+
);
|
|
236900
237242
|
const styleValueExample = { type: "keyword", value: "red" };
|
|
236901
237243
|
const typedStyleValueInput = z$4.object({ type: z$4.string() }).passthrough().meta({
|
|
236902
237244
|
description: "Typed CSS StyleValue object.",
|
|
@@ -236925,7 +237267,7 @@ const styleDeleteInput = z$4.object({
|
|
|
236925
237267
|
instanceId: z$4.string(),
|
|
236926
237268
|
property: z$4.string(),
|
|
236927
237269
|
breakpoint: z$4.string().optional(),
|
|
236928
|
-
state:
|
|
237270
|
+
state: existingStyleStateDeletionInput.optional()
|
|
236929
237271
|
});
|
|
236930
237272
|
const jsonArrayStringInput = (value2) => {
|
|
236931
237273
|
if (typeof value2 !== "string") {
|
|
@@ -237496,19 +237838,18 @@ const validateStyleSourceName = ({
|
|
|
237496
237838
|
}
|
|
237497
237839
|
}
|
|
237498
237840
|
};
|
|
237499
|
-
const createDesignTokenStyleInputs = (input2) =>
|
|
237500
|
-
|
|
237501
|
-
property: property2,
|
|
237502
|
-
value: typeof value2 === "string" ? parseCssValue$1(hyphenateProperty(property2), value2) : styleValue$1.parse(value2)
|
|
237503
|
-
})),
|
|
237504
|
-
...(input2.declarations ?? []).map((declaration2) => ({
|
|
237841
|
+
const createDesignTokenStyleInputs = (input2) => {
|
|
237842
|
+
const parseInput = ({ value: value2, ...declaration2 }) => ({
|
|
237505
237843
|
...declaration2,
|
|
237506
|
-
value: typeof
|
|
237507
|
-
|
|
237508
|
-
|
|
237509
|
-
|
|
237510
|
-
|
|
237511
|
-
|
|
237844
|
+
value: typeof value2 === "string" ? parseCssValue$1(hyphenateProperty(declaration2.property), value2) : styleValue$1.parse(value2)
|
|
237845
|
+
});
|
|
237846
|
+
return [
|
|
237847
|
+
...Object.entries(input2.styles ?? {}).map(
|
|
237848
|
+
([property2, value2]) => parseInput({ property: property2, value: value2 })
|
|
237849
|
+
),
|
|
237850
|
+
...(input2.declarations ?? []).map(parseInput)
|
|
237851
|
+
];
|
|
237852
|
+
};
|
|
237512
237853
|
const getLocalStyleSourceId = ({
|
|
237513
237854
|
styleSources,
|
|
237514
237855
|
styleSourceSelection: styleSourceSelection2
|
|
@@ -237803,7 +238144,10 @@ const createStyleDeclsFromInput = ({
|
|
|
237803
238144
|
`.styles{${cssProperty}:${toValue(parsedValue.data)}}`,
|
|
237804
238145
|
/* @__PURE__ */ new Map()
|
|
237805
238146
|
);
|
|
237806
|
-
|
|
238147
|
+
const unresolvedVariableWasExpanded = parsed.styles.every(
|
|
238148
|
+
({ value: parsedStyleValue }) => parsedStyleValue.type === "var"
|
|
238149
|
+
);
|
|
238150
|
+
if (parsed.errors.length > 0 && unresolvedVariableWasExpanded === false || parsed.styles.length === 0 || parsed.styles.some(
|
|
237807
238151
|
({ value: parsedStyleValue }) => ["invalid", "guaranteedInvalid"].includes(parsedStyleValue.type)
|
|
237808
238152
|
)) {
|
|
237809
238153
|
return throwBuilderRuntimeError(
|
|
@@ -237826,6 +238170,30 @@ const getStyleDeclKeyFromInput = ({
|
|
|
237826
238170
|
state,
|
|
237827
238171
|
property: normalizeStyleProperty(property2)
|
|
237828
238172
|
});
|
|
238173
|
+
const getExistingStyleDeletionKeys = ({
|
|
238174
|
+
deletions,
|
|
238175
|
+
styles,
|
|
238176
|
+
getStyleKey
|
|
238177
|
+
}) => {
|
|
238178
|
+
const existingStyleKeys = new Set(
|
|
238179
|
+
Array.from(styles, (styleDecl2) => getStyleDeclKey(styleDecl2))
|
|
238180
|
+
);
|
|
238181
|
+
const matchedStyleKeys = /* @__PURE__ */ new Set();
|
|
238182
|
+
for (const deletion of deletions) {
|
|
238183
|
+
const styleKey = getStyleKey(deletion);
|
|
238184
|
+
const exists = styleKey !== void 0 && existingStyleKeys.has(styleKey) === true;
|
|
238185
|
+
if (deletion.state !== void 0 && validateSelector(deletion.state).success === false && exists === false) {
|
|
238186
|
+
return throwBuilderRuntimeError(
|
|
238187
|
+
"BAD_REQUEST",
|
|
238188
|
+
"An invalid state selector can only delete an existing declaration. Use the exact target, state selector, breakpoint, and property reported by the style audit."
|
|
238189
|
+
);
|
|
238190
|
+
}
|
|
238191
|
+
if (exists) {
|
|
238192
|
+
matchedStyleKeys.add(styleKey);
|
|
238193
|
+
}
|
|
238194
|
+
}
|
|
238195
|
+
return Array.from(matchedStyleKeys);
|
|
238196
|
+
};
|
|
237829
238197
|
const updateStyleDecl = (declaration2, values) => styleDecl.parse({
|
|
237830
238198
|
...declaration2,
|
|
237831
238199
|
...values,
|
|
@@ -238278,17 +238646,15 @@ const createDesignTokenStyleDeletePayload = ({
|
|
|
238278
238646
|
deletions,
|
|
238279
238647
|
styles
|
|
238280
238648
|
}) => {
|
|
238281
|
-
const
|
|
238282
|
-
|
|
238283
|
-
|
|
238284
|
-
|
|
238285
|
-
const key2 = getStyleDeclKeyFromInput({
|
|
238649
|
+
const styleKeys = getExistingStyleDeletionKeys({
|
|
238650
|
+
deletions,
|
|
238651
|
+
styles,
|
|
238652
|
+
getStyleKey: (deletion) => getStyleDeclKeyFromInput({
|
|
238286
238653
|
styleSourceId: designTokenId,
|
|
238287
238654
|
breakpoint: deletion.breakpoint,
|
|
238288
238655
|
state: deletion.state,
|
|
238289
238656
|
property: deletion.property
|
|
238290
|
-
})
|
|
238291
|
-
return existingStyleKeys.has(key2) ? [key2] : [];
|
|
238657
|
+
})
|
|
238292
238658
|
});
|
|
238293
238659
|
return {
|
|
238294
238660
|
payload: createStyleRemovePayload(styleKeys),
|
|
@@ -238454,39 +238820,35 @@ const createStyleDeclarationDeletePayload = ({
|
|
|
238454
238820
|
styleSourceSelections,
|
|
238455
238821
|
styles
|
|
238456
238822
|
}) => {
|
|
238457
|
-
const styleKeys = /* @__PURE__ */ new Set();
|
|
238458
238823
|
const styleSourceSelectionByInstanceId = new Map(
|
|
238459
238824
|
Array.from(styleSourceSelections, (selection) => [
|
|
238460
238825
|
selection.instanceId,
|
|
238461
238826
|
selection
|
|
238462
238827
|
])
|
|
238463
238828
|
);
|
|
238464
|
-
const
|
|
238465
|
-
|
|
238466
|
-
|
|
238467
|
-
|
|
238468
|
-
|
|
238469
|
-
|
|
238470
|
-
|
|
238471
|
-
|
|
238472
|
-
|
|
238473
|
-
|
|
238474
|
-
|
|
238475
|
-
|
|
238476
|
-
|
|
238477
|
-
|
|
238478
|
-
|
|
238479
|
-
|
|
238480
|
-
|
|
238481
|
-
|
|
238482
|
-
|
|
238483
|
-
|
|
238484
|
-
|
|
238485
|
-
if (existingStyleKeys.has(key2)) {
|
|
238486
|
-
styleKeys.add(key2);
|
|
238829
|
+
const removedStyleKeys = getExistingStyleDeletionKeys({
|
|
238830
|
+
deletions,
|
|
238831
|
+
styles,
|
|
238832
|
+
getStyleKey: (deletion) => {
|
|
238833
|
+
const styleSourceId2 = getLocalStyleSourceIdWithCreated({
|
|
238834
|
+
createdLocalSourceIds: /* @__PURE__ */ new Map(),
|
|
238835
|
+
instanceId: deletion.instanceId,
|
|
238836
|
+
styleSources,
|
|
238837
|
+
styleSourceSelection: styleSourceSelectionByInstanceId.get(
|
|
238838
|
+
deletion.instanceId
|
|
238839
|
+
)
|
|
238840
|
+
});
|
|
238841
|
+
if (styleSourceId2 === void 0) {
|
|
238842
|
+
return;
|
|
238843
|
+
}
|
|
238844
|
+
return getStyleDeclKeyFromInput({
|
|
238845
|
+
styleSourceId: styleSourceId2,
|
|
238846
|
+
breakpoint: deletion.breakpoint,
|
|
238847
|
+
state: deletion.state,
|
|
238848
|
+
property: deletion.property
|
|
238849
|
+
});
|
|
238487
238850
|
}
|
|
238488
|
-
}
|
|
238489
|
-
const removedStyleKeys = Array.from(styleKeys);
|
|
238851
|
+
});
|
|
238490
238852
|
return {
|
|
238491
238853
|
payload: createStyleRemovePayload(removedStyleKeys),
|
|
238492
238854
|
styleKeys: removedStyleKeys
|
|
@@ -238592,17 +238954,15 @@ const createSelectedStyleDeclarationDeletePayload = ({
|
|
|
238592
238954
|
deletions,
|
|
238593
238955
|
styles
|
|
238594
238956
|
}) => {
|
|
238595
|
-
const
|
|
238596
|
-
|
|
238597
|
-
|
|
238598
|
-
|
|
238599
|
-
const styleKey = getStyleDeclKeyFromInput({
|
|
238957
|
+
const styleKeys = getExistingStyleDeletionKeys({
|
|
238958
|
+
deletions,
|
|
238959
|
+
styles,
|
|
238960
|
+
getStyleKey: (deletion) => getStyleDeclKeyFromInput({
|
|
238600
238961
|
styleSourceId: deletion.styleSourceId,
|
|
238601
238962
|
breakpoint: deletion.breakpoint,
|
|
238602
238963
|
state: deletion.state,
|
|
238603
238964
|
property: deletion.property
|
|
238604
|
-
})
|
|
238605
|
-
return existingStyleKeys.has(styleKey) ? [styleKey] : [];
|
|
238965
|
+
})
|
|
238606
238966
|
});
|
|
238607
238967
|
return {
|
|
238608
238968
|
payload: createStyleRemovePayload(styleKeys),
|
|
@@ -239057,7 +239417,7 @@ const updateDesignTokenStyles$1 = (state, input2) => {
|
|
|
239057
239417
|
getDesignTokenOrThrow(styleState.styleSources.values(), input2.designTokenId);
|
|
239058
239418
|
const { payload, styleKeys } = createDesignTokenStyleUpdatePayload({
|
|
239059
239419
|
designTokenId: input2.designTokenId,
|
|
239060
|
-
updates: input2.updates.map(
|
|
239420
|
+
updates: createDesignTokenStyleInputs({ declarations: input2.updates }).map(
|
|
239061
239421
|
(update) => withValidatedBreakpoint(update, state.breakpoints)
|
|
239062
239422
|
),
|
|
239063
239423
|
styles: styleState.styles.values()
|
|
@@ -239071,11 +239431,12 @@ const updateDesignTokenStyles$1 = (state, input2) => {
|
|
|
239071
239431
|
const deleteDesignTokenStyles$1 = (state, input2) => {
|
|
239072
239432
|
const styleState = getRequiredStyleState(state);
|
|
239073
239433
|
getDesignTokenOrThrow(styleState.styleSources.values(), input2.designTokenId);
|
|
239434
|
+
const deletions = input2.deletions.map(
|
|
239435
|
+
(deletion) => withValidatedBreakpoint(deletion, state.breakpoints)
|
|
239436
|
+
);
|
|
239074
239437
|
const { payload, styleKeys } = createDesignTokenStyleDeletePayload({
|
|
239075
239438
|
designTokenId: input2.designTokenId,
|
|
239076
|
-
deletions
|
|
239077
|
-
(deletion) => withValidatedBreakpoint(deletion, state.breakpoints)
|
|
239078
|
-
),
|
|
239439
|
+
deletions,
|
|
239079
239440
|
styles: styleState.styles.values()
|
|
239080
239441
|
});
|
|
239081
239442
|
return createRuntimeMutation({
|
|
@@ -239265,6 +239626,7 @@ const createParentIdsByInstance = (instances) => {
|
|
|
239265
239626
|
}
|
|
239266
239627
|
return parentIdsByInstance;
|
|
239267
239628
|
};
|
|
239629
|
+
const getLabelTargetId = (props2) => props2?.get("for") ?? props2?.get("htmlFor");
|
|
239268
239630
|
const hasAssociatedFormLabel = ({
|
|
239269
239631
|
instanceId: instanceId2,
|
|
239270
239632
|
instances,
|
|
@@ -239277,7 +239639,7 @@ const hasAssociatedFormLabel = ({
|
|
|
239277
239639
|
if (isLabelInstance(label2) === false || relatedInstanceIds !== void 0 && relatedInstanceIds.has(label2.id) === false) {
|
|
239278
239640
|
continue;
|
|
239279
239641
|
}
|
|
239280
|
-
if (id2 !== void 0 && propsByInstance.get(label2.id)
|
|
239642
|
+
if (id2 !== void 0 && getLabelTargetId(propsByInstance.get(label2.id)) === id2 && hasAccessibleName({
|
|
239281
239643
|
instanceId: label2.id,
|
|
239282
239644
|
instances,
|
|
239283
239645
|
propsByInstance
|
|
@@ -239722,7 +240084,8 @@ const analyzeProject = (state, input2) => {
|
|
|
239722
240084
|
if (isInteractiveInstance({ ...instance2, props: props2 }) && hasDynamicProp(propTypes, "aria-label", "aria-labelledby", "title") === false && hasAccessibleName({
|
|
239723
240085
|
instanceId: instance2.id,
|
|
239724
240086
|
instances: state.instances,
|
|
239725
|
-
propsByInstance
|
|
240087
|
+
propsByInstance,
|
|
240088
|
+
propTypesByInstance
|
|
239726
240089
|
}) === false) {
|
|
239727
240090
|
matches2.push({
|
|
239728
240091
|
kind: "accessibility",
|
|
@@ -239740,7 +240103,8 @@ const analyzeProject = (state, input2) => {
|
|
|
239740
240103
|
}) && hasDynamicProp(propTypes, "aria-label", "aria-labelledby", "title") === false && hasAccessibleName({
|
|
239741
240104
|
instanceId: instance2.id,
|
|
239742
240105
|
instances: state.instances,
|
|
239743
|
-
propsByInstance
|
|
240106
|
+
propsByInstance,
|
|
240107
|
+
propTypesByInstance
|
|
239744
240108
|
}) === false && hasAssociatedFormLabel({
|
|
239745
240109
|
instanceId: instance2.id,
|
|
239746
240110
|
instances: state.instances,
|
|
@@ -239892,7 +240256,7 @@ const analyzeProject = (state, input2) => {
|
|
|
239892
240256
|
if (isInScope(instance2.id) === false || isLabelInstance(instance2) === false) {
|
|
239893
240257
|
continue;
|
|
239894
240258
|
}
|
|
239895
|
-
const htmlFor = propsByInstance.get(instance2.id)
|
|
240259
|
+
const htmlFor = getLabelTargetId(propsByInstance.get(instance2.id));
|
|
239896
240260
|
if (typeof htmlFor !== "string" || htmlFor.trim().length === 0) {
|
|
239897
240261
|
continue;
|
|
239898
240262
|
}
|
|
@@ -243151,6 +243515,7 @@ const resource = looseObject({
|
|
|
243151
243515
|
dataSourceId: id.optional()
|
|
243152
243516
|
});
|
|
243153
243517
|
const assetResourceConfiguration = looseObject({
|
|
243518
|
+
result: assetQueryResultMode,
|
|
243154
243519
|
where: assetQueryWhereExpression,
|
|
243155
243520
|
sort: z$4.array(assetQuerySort),
|
|
243156
243521
|
limit: z$4.string(),
|
|
@@ -249956,7 +250321,7 @@ const getZodMcpInputSchema = (schema2) => getHandshakeInputSchema(getZodObjectSc
|
|
|
249956
250321
|
const insertCollectionMcpInputSchema = getOperationInputSchema({
|
|
249957
250322
|
inputSchema: getInputSchemaMetadata(insertCollectionMcpInput).inputJsonSchema
|
|
249958
250323
|
});
|
|
249959
|
-
const assetsResourceResultDescription = "Pass query as structured tool input
|
|
250324
|
+
const assetsResourceResultDescription = "Pass query as structured tool input with Webstudio JavaScript expressions. Keep one final resource per rendered query and remove obsolete duplicates. Use result many for listings and result one for unique details; first and last require sorting. Keep static filters, limits, and offsets literal; single modes omit pagination. Select only rendered fields, keep includeMetadata false, and use content mode none unless file content is rendered. For Markdown details, use markdown-body-ref; compilation keeps a document reference and fetches the body at runtime. Bind <dataSourceName>.data, including id and content.text. Many returns an ID map plus totalCount and hasMore at <dataSourceName>.meta; single modes return an item or null plus totalCount.";
|
|
249960
250325
|
const mcpOperationOverrides = /* @__PURE__ */ new Map([
|
|
249961
250326
|
[
|
|
249962
250327
|
"insert-fragment",
|
|
@@ -250312,7 +250677,7 @@ const screenshotInputSchema = {
|
|
|
250312
250677
|
timeout: {
|
|
250313
250678
|
type: "number",
|
|
250314
250679
|
default: defaultScreenshotTimeout,
|
|
250315
|
-
description: "Maximum milliseconds
|
|
250680
|
+
description: "Maximum milliseconds for browser capture after preview is ready."
|
|
250316
250681
|
},
|
|
250317
250682
|
source: {
|
|
250318
250683
|
type: "string",
|
|
@@ -251365,19 +251730,26 @@ const refreshDataSchema = {
|
|
|
251365
251730
|
required: ["refreshedNamespaces"],
|
|
251366
251731
|
additionalProperties: false
|
|
251367
251732
|
};
|
|
251368
|
-
const
|
|
251733
|
+
const previewStatusDataSchema = {
|
|
251369
251734
|
type: "object",
|
|
251370
251735
|
properties: {
|
|
251371
251736
|
url: { type: "string" },
|
|
251372
251737
|
pid: { type: "integer" },
|
|
251373
251738
|
running: { type: "boolean" },
|
|
251374
|
-
mode: {
|
|
251739
|
+
mode: {
|
|
251740
|
+
type: "string",
|
|
251741
|
+
enum: projectSessionPreviewModes
|
|
251742
|
+
},
|
|
251375
251743
|
stale: { type: "boolean" },
|
|
251376
251744
|
renderedProjectVersion: { type: "integer" }
|
|
251377
251745
|
},
|
|
251378
|
-
required: ["
|
|
251746
|
+
required: ["running"],
|
|
251379
251747
|
additionalProperties: false
|
|
251380
251748
|
};
|
|
251749
|
+
const previewDataSchema = {
|
|
251750
|
+
...previewStatusDataSchema,
|
|
251751
|
+
required: ["url", "running", "mode"]
|
|
251752
|
+
};
|
|
251381
251753
|
const restorePointSummaryDataSchema = getZodObjectSchema(
|
|
251382
251754
|
projectSessionRestorePointSummarySchema
|
|
251383
251755
|
);
|
|
@@ -252146,7 +252518,7 @@ const previewTools = [
|
|
|
252146
252518
|
name: "preview.status",
|
|
252147
252519
|
description: "Return the active generated-site preview server URL and process state for screenshot-based verification.",
|
|
252148
252520
|
inputSchema: emptyInputSchema,
|
|
252149
|
-
outputSchema: getMcpOutputSchema(
|
|
252521
|
+
outputSchema: getMcpOutputSchema(previewStatusDataSchema),
|
|
252150
252522
|
mcpExamples: getMcpExamples("preview.status"),
|
|
252151
252523
|
annotations: {
|
|
252152
252524
|
command: "preview.status",
|
|
@@ -252165,7 +252537,7 @@ const previewTools = [
|
|
|
252165
252537
|
name: "preview.stop",
|
|
252166
252538
|
description: "Stop the active generated-site preview server owned by this MCP session.",
|
|
252167
252539
|
inputSchema: emptyInputSchema,
|
|
252168
|
-
outputSchema: getMcpOutputSchema(
|
|
252540
|
+
outputSchema: getMcpOutputSchema(previewStatusDataSchema),
|
|
252169
252541
|
mcpExamples: getMcpExamples("preview.stop"),
|
|
252170
252542
|
annotations: {
|
|
252171
252543
|
command: "preview.stop",
|
|
@@ -253850,21 +254222,17 @@ const metaGoalGuides = [
|
|
|
253850
254222
|
"preview-asset-query",
|
|
253851
254223
|
"create-page",
|
|
253852
254224
|
"create-assets-resource",
|
|
253853
|
-
"insert-fragment-verified",
|
|
253854
254225
|
"insert-collection",
|
|
253855
254226
|
"verify-page-responsive"
|
|
253856
254227
|
],
|
|
253857
254228
|
workflow: [
|
|
253858
|
-
'Call meta.get-more-tools with {"tools":["create-assets-resource"]}
|
|
253859
|
-
|
|
253860
|
-
'
|
|
253861
|
-
"
|
|
253862
|
-
'
|
|
253863
|
-
|
|
253864
|
-
|
|
253865
|
-
'Call insert-collection exactly once with the entire recipe.overviewCollection object and exactly once with the entire recipe.detailCollection object, changing only parentInstanceId to the returned root id. Do not reshape or stringify any field: data must remain the recipe object {"type":"expression","value":"posts.data"} or {"type":"expression","value":"post.data"}. Do not improvise another fragment or call meta.get-more-tools again.',
|
|
253866
|
-
"Validate both queries and preview the detail query with one concrete slug before saving dynamic expressions. Query-preview diagnostics report this query separately from the merged published database; use the merged database measurement when checking the deployment limit. The merged database must contain every source document without truncation, no embedded Markdown contents, and only one materialized overview query.",
|
|
253867
|
-
"Verify only after both Collections succeed and confirm that both pages load their content from Assets. Call verify-page-responsive once for /blog and once for one concrete detail route, including empty/not-found behavior, before finishing. If any call fails, stop and report it without retrying."
|
|
254229
|
+
'Call meta.get-more-tools once with {"tools":["create-assets-resource"]}; the recipe below supplies the remaining inputs.',
|
|
254230
|
+
'Create one asset folder named exactly "Blog", then call upload-assets exactly once with all Markdown files and assetsDir ".webstudio/assets". Put slug, title, author, publishedAt, excerpt, and draft in frontmatter. Each asset uses {"name":"<filename>.md","type":"file","format":"md","folderId":"<blog-folder-id>","meta":{}}; do not create companion files.',
|
|
254231
|
+
'Create exactly two pages once: "/blog" and "/blog/:slug". Do not dry-run page creation, create one page per post, or copy Markdown into static page content.',
|
|
254232
|
+
"Substitute the returned folder id in both recipe queries. Validate both, then preview the detail query with one concrete slug. Keep overview values literal and keep system.params.slug as the detail query's only dynamic value.",
|
|
254233
|
+
'Create exactly one scoped Assets resource per page by copying recipe.overviewResource and recipe.detailResource unchanged except for id placeholders. Keep the detail query result as "one"; Collection accepts this single-result object. Do not add query defaults or create placeholder resources.',
|
|
254234
|
+
"Insert recipe.overviewCollection under the overview root and recipe.detailCollection under the detail root. Use each object unchanged except for parentInstanceId. Both bindings must remain editable Collections; do not replace the detail Collection with a static fragment.",
|
|
254235
|
+
'After both insertions succeed, call verify-page-responsive once for "/blog" and once for one concrete detail path with desktop and mobile viewports. Confirm Assets-backed content and empty/not-found behavior. Stop on an error instead of retrying.'
|
|
253868
254236
|
],
|
|
253869
254237
|
recipe: {
|
|
253870
254238
|
overviewResource: {
|
|
@@ -253872,6 +254240,7 @@ const metaGoalGuides = [
|
|
|
253872
254240
|
scopeInstanceId: "<overview-root-id>",
|
|
253873
254241
|
dataSourceName: "posts",
|
|
253874
254242
|
query: {
|
|
254243
|
+
result: "many",
|
|
253875
254244
|
where: {
|
|
253876
254245
|
all: [
|
|
253877
254246
|
{
|
|
@@ -253916,6 +254285,7 @@ const metaGoalGuides = [
|
|
|
253916
254285
|
scopeInstanceId: "<detail-root-id>",
|
|
253917
254286
|
dataSourceName: "post",
|
|
253918
254287
|
query: {
|
|
254288
|
+
result: "one",
|
|
253919
254289
|
where: {
|
|
253920
254290
|
all: [
|
|
253921
254291
|
{
|
|
@@ -253935,8 +254305,6 @@ const metaGoalGuides = [
|
|
|
253935
254305
|
}
|
|
253936
254306
|
]
|
|
253937
254307
|
},
|
|
253938
|
-
limit: { type: "literal", value: 1 },
|
|
253939
|
-
offset: { type: "literal", value: 0 },
|
|
253940
254308
|
output: {
|
|
253941
254309
|
mode: "fields",
|
|
253942
254310
|
includeMetadata: false,
|
|
@@ -253955,8 +254323,11 @@ const metaGoalGuides = [
|
|
|
253955
254323
|
},
|
|
253956
254324
|
detailCollection: {
|
|
253957
254325
|
parentInstanceId: "<detail-root-id>",
|
|
253958
|
-
data: {
|
|
253959
|
-
|
|
254326
|
+
data: {
|
|
254327
|
+
type: "expression",
|
|
254328
|
+
value: "post.data == null ? [] : [post.data]"
|
|
254329
|
+
},
|
|
254330
|
+
itemFragment: '<ws.element ws:tag="article"><ws.element ws:tag="h1">{expression`collectionItem.properties.title ?? "Untitled"`}</ws.element><ws.element ws:tag="p">By {expression`collectionItem.properties.author.name ?? ""`}</ws.element><$.MarkdownEmbed code={expression`collectionItem.content.text ?? ""`} /></ws.element>'
|
|
253960
254331
|
}
|
|
253961
254332
|
}
|
|
253962
254333
|
},
|
|
@@ -254150,24 +254521,24 @@ const getMetaGuide = (brief, tools, guidance) => {
|
|
|
254150
254521
|
const canDiffScreenshots = tools.some(
|
|
254151
254522
|
(tool) => tool.name === "screenshot.diff"
|
|
254152
254523
|
);
|
|
254524
|
+
const generalWorkflow = [
|
|
254525
|
+
"Use the fewest discovery calls needed for the immediate action.",
|
|
254526
|
+
"Call permissions or status only when the task depends on capabilities or local session freshness.",
|
|
254527
|
+
matches2.some((tool) => tool.annotations.localCapable) && matches2.some((tool) => tool.name === "verify-font-assets") === false ? "Call refresh if cached namespaces may be stale." : void 0,
|
|
254528
|
+
"Use focused read tools to collect ids and current values.",
|
|
254529
|
+
"Use the smallest semantic mutation tool that matches the requested change.",
|
|
254530
|
+
valuesVsBindingsRule,
|
|
254531
|
+
"Use apply-patch only when no semantic mutation tool fits.",
|
|
254532
|
+
canVerifyVisually && guidance !== void 0 ? guidance.getVisionWorkflowSummary({ includeDiff: canDiffScreenshots }) : void 0
|
|
254533
|
+
].filter(Boolean);
|
|
254153
254534
|
return {
|
|
254154
254535
|
delegatedAgentRule: "Do not spend the whole phase on discovery. If you are delegated/non-streaming and the parent asks for status within 30 seconds, run exactly one shortcut command such as webstudio meta.index or one explicit webstudio mcp single-op-call command, report its command/result, and wait before the next MCP command.",
|
|
254155
|
-
workflow:
|
|
254156
|
-
...goalGuide?.workflow ?? [],
|
|
254157
|
-
"Use the fewest discovery calls needed for the immediate action.",
|
|
254158
|
-
"Call permissions or status only when the task depends on capabilities or local session freshness.",
|
|
254159
|
-
matches2.some((tool) => tool.annotations.localCapable) && matches2.some((tool) => tool.name === "verify-font-assets") === false ? "Call refresh if cached namespaces may be stale." : void 0,
|
|
254160
|
-
"Use focused read tools to collect ids and current values.",
|
|
254161
|
-
"Use the smallest semantic mutation tool that matches the requested change.",
|
|
254162
|
-
valuesVsBindingsRule,
|
|
254163
|
-
"Use apply-patch only when no semantic mutation tool fits.",
|
|
254164
|
-
canVerifyVisually && guidance !== void 0 ? guidance.getVisionWorkflowSummary({ includeDiff: canDiffScreenshots }) : void 0
|
|
254165
|
-
].filter(Boolean),
|
|
254536
|
+
workflow: goalGuide?.workflow ?? generalWorkflow,
|
|
254166
254537
|
tools: matches2.map(
|
|
254167
254538
|
(tool) => serializeMetaGuideTool(tool, goalGuide === void 0)
|
|
254168
254539
|
),
|
|
254169
254540
|
...goalGuide !== void 0 && "recipe" in goalGuide ? { recipe: goalGuide.recipe } : {},
|
|
254170
|
-
more: goalGuide === void 0 ? "The MCP handshake provides top-level argument contracts and required fields, while this guide includes exact examples plus complete schemas for selected complex tools. Call meta.get-more-tools once with all needed tool names only when a nested input shape is not covered here or when you need server/local behavior that the guide does not cover." : "
|
|
254541
|
+
more: goalGuide === void 0 ? "The MCP handshake provides top-level argument contracts and required fields, while this guide includes exact examples plus complete schemas for selected complex tools. Call meta.get-more-tools once with all needed tool names only when a nested input shape is not covered here or when you need server/local behavior that the guide does not cover." : "Follow this workflow and recipe without additional discovery unless the workflow explicitly requests it."
|
|
254171
254542
|
};
|
|
254172
254543
|
};
|
|
254173
254544
|
const getWorkflowInput = (input2) => {
|
|
@@ -254304,22 +254675,22 @@ const getWorkflowNext = (input2) => {
|
|
|
254304
254675
|
allPhases: workflowPhaseNames
|
|
254305
254676
|
};
|
|
254306
254677
|
};
|
|
254678
|
+
const toUnderscoredToolName = (name2) => name2.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
254307
254679
|
const getExactToolSelection = (toolNames, tools) => {
|
|
254308
|
-
const
|
|
254680
|
+
const toolNameIndex = createToolNameIndex(tools);
|
|
254309
254681
|
const selectedTools = [];
|
|
254310
254682
|
const missingTools = [];
|
|
254311
254683
|
const includedToolNames = /* @__PURE__ */ new Set();
|
|
254312
254684
|
for (const requestedName of toolNames) {
|
|
254313
|
-
const
|
|
254314
|
-
const tool = toolByName.get(resolvedName);
|
|
254685
|
+
const tool = toolNameIndex.get(requestedName);
|
|
254315
254686
|
if (tool === void 0) {
|
|
254316
254687
|
missingTools.push(requestedName);
|
|
254317
254688
|
continue;
|
|
254318
254689
|
}
|
|
254319
|
-
if (includedToolNames.has(
|
|
254690
|
+
if (includedToolNames.has(tool.name)) {
|
|
254320
254691
|
continue;
|
|
254321
254692
|
}
|
|
254322
|
-
includedToolNames.add(
|
|
254693
|
+
includedToolNames.add(tool.name);
|
|
254323
254694
|
selectedTools.push(tool);
|
|
254324
254695
|
}
|
|
254325
254696
|
return { tools: selectedTools, missingTools };
|
|
@@ -254421,13 +254792,58 @@ const toolAliases = /* @__PURE__ */ new Map([
|
|
|
254421
254792
|
["get-component-coverage-plan", "components.coverage-plan"],
|
|
254422
254793
|
["meta.get_more_tools", "meta.get-more-tools"]
|
|
254423
254794
|
]);
|
|
254424
|
-
const
|
|
254795
|
+
const createToolNameIndex = (tools) => {
|
|
254796
|
+
const toolByAcceptedName = new Map(
|
|
254797
|
+
tools.map((tool) => [tool.name, tool])
|
|
254798
|
+
);
|
|
254799
|
+
const canonicalNamesByUnderscoredName = /* @__PURE__ */ new Map();
|
|
254800
|
+
for (const tool of tools) {
|
|
254801
|
+
const underscoredName = toUnderscoredToolName(tool.name);
|
|
254802
|
+
const canonicalNames = canonicalNamesByUnderscoredName.get(underscoredName) ?? [];
|
|
254803
|
+
canonicalNames.push(tool.name);
|
|
254804
|
+
canonicalNamesByUnderscoredName.set(underscoredName, canonicalNames);
|
|
254805
|
+
}
|
|
254806
|
+
for (const [
|
|
254807
|
+
underscoredName,
|
|
254808
|
+
canonicalNames
|
|
254809
|
+
] of canonicalNamesByUnderscoredName) {
|
|
254810
|
+
if (canonicalNames.length === 1 && toolByAcceptedName.has(underscoredName) === false) {
|
|
254811
|
+
const tool = toolByAcceptedName.get(canonicalNames[0] ?? "");
|
|
254812
|
+
if (tool !== void 0) {
|
|
254813
|
+
toolByAcceptedName.set(underscoredName, tool);
|
|
254814
|
+
}
|
|
254815
|
+
}
|
|
254816
|
+
}
|
|
254817
|
+
const getAcceptedName = (name2) => toolAliases.get(name2) ?? name2;
|
|
254818
|
+
const get2 = (name2) => toolByAcceptedName.get(getAcceptedName(name2));
|
|
254819
|
+
return {
|
|
254820
|
+
canonicalNamesByUnderscoredName,
|
|
254821
|
+
get: get2,
|
|
254822
|
+
resolve: (name2) => get2(name2)?.name ?? getAcceptedName(name2)
|
|
254823
|
+
};
|
|
254824
|
+
};
|
|
254825
|
+
const getUnknownToolMessage = (requestedName, tools) => {
|
|
254826
|
+
const normalizedRequestedName = toUnderscoredToolName(
|
|
254827
|
+
requestedName.toLowerCase()
|
|
254828
|
+
);
|
|
254829
|
+
const suggestions = tools.map((tool) => ({
|
|
254830
|
+
name: tool.name,
|
|
254831
|
+
distance: distance(
|
|
254832
|
+
normalizedRequestedName,
|
|
254833
|
+
toUnderscoredToolName(tool.name.toLowerCase())
|
|
254834
|
+
)
|
|
254835
|
+
})).filter(
|
|
254836
|
+
({ distance: distance2 }) => distance2 <= Math.max(2, Math.floor(normalizedRequestedName.length / 3))
|
|
254837
|
+
).sort(
|
|
254838
|
+
(left, right) => left.distance - right.distance || left.name.localeCompare(right.name)
|
|
254839
|
+
).slice(0, 3).map(({ name: name2 }) => name2);
|
|
254840
|
+
const suggestion = suggestions.length === 0 ? "" : ` Did you mean ${suggestions.map((name2) => `"${name2}"`).join(", ")}?`;
|
|
254841
|
+
return `Unknown MCP tool "${requestedName}".${suggestion} Use meta.index to list available tools.`;
|
|
254842
|
+
};
|
|
254425
254843
|
const isReadOnlyProjectSessionMcpTool = (tool) => tool.annotations.method === "query" || tool.annotations.method === "session" && readOnlySessionTools.has(tool.name);
|
|
254426
254844
|
const isReadOnlyProjectSessionMcpToolCall = (name2, tools) => {
|
|
254427
|
-
const
|
|
254428
|
-
return
|
|
254429
|
-
(tool) => tool.name === resolvedName && isReadOnlyProjectSessionMcpTool(tool)
|
|
254430
|
-
);
|
|
254845
|
+
const tool = createToolNameIndex(tools).get(name2);
|
|
254846
|
+
return tool !== void 0 && isReadOnlyProjectSessionMcpTool(tool);
|
|
254431
254847
|
};
|
|
254432
254848
|
const sdkScalarSchemaKeys = /* @__PURE__ */ new Set([
|
|
254433
254849
|
"type",
|
|
@@ -254506,11 +254922,14 @@ const getSdkInputSchema = (schema2, includeOptionalProperties) => {
|
|
|
254506
254922
|
)
|
|
254507
254923
|
};
|
|
254508
254924
|
};
|
|
254509
|
-
const
|
|
254925
|
+
const sdkDetailedInputToolNames = /* @__PURE__ */ new Set([
|
|
254510
254926
|
"meta.index",
|
|
254511
254927
|
"meta.guide",
|
|
254512
254928
|
"meta.get-more-tools",
|
|
254513
|
-
"workflow.next"
|
|
254929
|
+
"workflow.next"
|
|
254930
|
+
]);
|
|
254931
|
+
const sdkDescribedToolNames = /* @__PURE__ */ new Set([
|
|
254932
|
+
...sdkDetailedInputToolNames,
|
|
254514
254933
|
...metaGoalGuides.flatMap(({ tools }) => tools)
|
|
254515
254934
|
]);
|
|
254516
254935
|
const getSdkToolAnnotations = (tool) => {
|
|
@@ -254529,7 +254948,7 @@ const toSdkTool = (tool) => {
|
|
|
254529
254948
|
...sdkDescribedToolNames.has(tool.name) ? { description: tool.description } : {},
|
|
254530
254949
|
inputSchema: getSdkInputSchema(
|
|
254531
254950
|
tool.inputSchema,
|
|
254532
|
-
|
|
254951
|
+
sdkDetailedInputToolNames.has(tool.name)
|
|
254533
254952
|
),
|
|
254534
254953
|
...annotations === void 0 ? {} : { annotations }
|
|
254535
254954
|
};
|
|
@@ -255055,7 +255474,7 @@ const createProjectSessionMcpCore = ({
|
|
|
255055
255474
|
const operationByCommand = new Map(
|
|
255056
255475
|
operations.map((operation) => [operation.command, operation])
|
|
255057
255476
|
);
|
|
255058
|
-
const
|
|
255477
|
+
const tools = listProjectSessionMcpTools(operations, {
|
|
255059
255478
|
includeImport: importProject2 !== void 0,
|
|
255060
255479
|
includeDownloadAsset: downloadAsset !== void 0,
|
|
255061
255480
|
includeScreenshot: captureScreenshot2 !== void 0,
|
|
@@ -255065,6 +255484,8 @@ const createProjectSessionMcpCore = ({
|
|
|
255065
255484
|
includePreview: startPreview !== void 0 && getPreviewStatus !== void 0,
|
|
255066
255485
|
includeRestorePoints: restorePoints !== void 0
|
|
255067
255486
|
});
|
|
255487
|
+
const listTools = () => [...tools];
|
|
255488
|
+
const toolNameIndex = createToolNameIndex(tools);
|
|
255068
255489
|
const getSession = () => {
|
|
255069
255490
|
session ??= createProjectSession2();
|
|
255070
255491
|
return session;
|
|
@@ -255142,8 +255563,8 @@ const createProjectSessionMcpCore = ({
|
|
|
255142
255563
|
"webstudio://project/tools"
|
|
255143
255564
|
);
|
|
255144
255565
|
if (toolsInput !== void 0) {
|
|
255145
|
-
const
|
|
255146
|
-
const serializedTools = toolsInput.verbose ?
|
|
255566
|
+
const tools2 = listTools();
|
|
255567
|
+
const serializedTools = toolsInput.verbose ? tools2.map(serializeToolDetails) : tools2.map(serializeCompactTool);
|
|
255147
255568
|
const { page: page2, ...pagination } = paginateDiscoveryResource(
|
|
255148
255569
|
serializedTools,
|
|
255149
255570
|
toolsInput
|
|
@@ -255258,7 +255679,8 @@ const createProjectSessionMcpCore = ({
|
|
|
255258
255679
|
dryRun = false,
|
|
255259
255680
|
signal
|
|
255260
255681
|
}) {
|
|
255261
|
-
|
|
255682
|
+
const requestedName = name2;
|
|
255683
|
+
name2 = toolNameIndex.resolve(name2);
|
|
255262
255684
|
if (name2 === "checkpoint.ack") {
|
|
255263
255685
|
if (isRecord(input2) === false || input2.reported !== true || input2.continueAfterReport !== true || typeof input2.summary !== "string" || input2.summary.trim().length === 0) {
|
|
255264
255686
|
throw new Error(
|
|
@@ -255365,11 +255787,15 @@ const createProjectSessionMcpCore = ({
|
|
|
255365
255787
|
return toCheckpointedMetaResult(name2, getWorkflowNext(input2));
|
|
255366
255788
|
}
|
|
255367
255789
|
if (name2 === "meta.get-more-tools") {
|
|
255790
|
+
const normalizedInput2 = parseStringifiedJsonInputFields(
|
|
255791
|
+
input2,
|
|
255792
|
+
toolDetailsInputSchema
|
|
255793
|
+
);
|
|
255368
255794
|
return toMetaResult(
|
|
255369
255795
|
getMoreTools(
|
|
255370
|
-
getBrief(
|
|
255371
|
-
getToolNamesInput(
|
|
255372
|
-
|
|
255796
|
+
getBrief(normalizedInput2, "meta.get-more-tools"),
|
|
255797
|
+
getToolNamesInput(normalizedInput2),
|
|
255798
|
+
tools
|
|
255373
255799
|
)
|
|
255374
255800
|
);
|
|
255375
255801
|
}
|
|
@@ -255690,7 +256116,7 @@ const createProjectSessionMcpCore = ({
|
|
|
255690
256116
|
}
|
|
255691
256117
|
const operation = operationByCommand.get(name2);
|
|
255692
256118
|
if (operation === void 0) {
|
|
255693
|
-
throw new Error(
|
|
256119
|
+
throw new Error(getUnknownToolMessage(requestedName, tools));
|
|
255694
256120
|
}
|
|
255695
256121
|
const transportInput = getToolCallInput(input2, operation.requiresConfirm);
|
|
255696
256122
|
const toolInput = transportInput.input;
|
|
@@ -255934,23 +256360,24 @@ const createProjectSessionMcpServer = async ({
|
|
|
255934
256360
|
sendLog("info", message);
|
|
255935
256361
|
}
|
|
255936
256362
|
});
|
|
255937
|
-
const
|
|
256363
|
+
const tools = core2.listTools();
|
|
256364
|
+
const toolNameIndex = createToolNameIndex(tools);
|
|
256365
|
+
const exposedTools = tools.map((tool) => ({
|
|
255938
256366
|
...tool,
|
|
255939
|
-
name: toolNameFormat === "underscores" ? tool.name
|
|
256367
|
+
name: toolNameFormat === "underscores" ? toUnderscoredToolName(tool.name) : tool.name
|
|
255940
256368
|
}));
|
|
255941
|
-
|
|
255942
|
-
|
|
255943
|
-
|
|
255944
|
-
|
|
255945
|
-
|
|
255946
|
-
|
|
255947
|
-
|
|
255948
|
-
|
|
256369
|
+
if (toolNameFormat === "underscores") {
|
|
256370
|
+
for (const [
|
|
256371
|
+
exposedName,
|
|
256372
|
+
canonicalNames
|
|
256373
|
+
] of toolNameIndex.canonicalNamesByUnderscoredName) {
|
|
256374
|
+
if (canonicalNames.length < 2) {
|
|
256375
|
+
continue;
|
|
256376
|
+
}
|
|
255949
256377
|
throw new Error(
|
|
255950
|
-
`MCP tool names ${
|
|
256378
|
+
`MCP tool names ${canonicalNames.join(" and ")} both map to ${exposedName}.`
|
|
255951
256379
|
);
|
|
255952
256380
|
}
|
|
255953
|
-
canonicalToolNameByExposedName.set(exposedName, tool.name);
|
|
255954
256381
|
}
|
|
255955
256382
|
server.oninitialized = () => {
|
|
255956
256383
|
onInitialized?.(server.getClientVersion()?.name);
|
|
@@ -255996,7 +256423,7 @@ const createProjectSessionMcpServer = async ({
|
|
|
255996
256423
|
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
255997
256424
|
const params = getRequestParams(request);
|
|
255998
256425
|
const exposedName = typeof params.name === "string" ? params.name : "";
|
|
255999
|
-
const name2 =
|
|
256426
|
+
const name2 = toolNameIndex.resolve(exposedName);
|
|
256000
256427
|
const { input: input2, dryRun } = getToolCallInput(params.arguments ?? {});
|
|
256001
256428
|
const startedAt = Date.now();
|
|
256002
256429
|
sendLog("info", `tool ${name2} started${dryRun ? " (dry run)" : ""}`);
|
|
@@ -256043,9 +256470,66 @@ const connectProjectSessionMcpServer = async ({
|
|
|
256043
256470
|
};
|
|
256044
256471
|
const createMcpStdioTransport = async ({
|
|
256045
256472
|
stdin: stdin2,
|
|
256046
|
-
stdout: stdout2
|
|
256473
|
+
stdout: stdout2,
|
|
256474
|
+
partialFrameTimeoutMs = 5e3
|
|
256047
256475
|
}) => {
|
|
256048
|
-
|
|
256476
|
+
let partialFrame;
|
|
256477
|
+
let discardTimer;
|
|
256478
|
+
const clearDiscardTimer = () => {
|
|
256479
|
+
if (discardTimer !== void 0) {
|
|
256480
|
+
clearTimeout(discardTimer);
|
|
256481
|
+
discardTimer = void 0;
|
|
256482
|
+
}
|
|
256483
|
+
};
|
|
256484
|
+
const scheduleDiscard = () => {
|
|
256485
|
+
clearDiscardTimer();
|
|
256486
|
+
if (partialFrame === void 0 || partialFrameTimeoutMs <= 0) {
|
|
256487
|
+
return;
|
|
256488
|
+
}
|
|
256489
|
+
discardTimer = setTimeout(() => {
|
|
256490
|
+
partialFrame = void 0;
|
|
256491
|
+
discardTimer = void 0;
|
|
256492
|
+
}, partialFrameTimeoutMs);
|
|
256493
|
+
discardTimer.unref?.();
|
|
256494
|
+
};
|
|
256495
|
+
const recoveringInput = new Transform({
|
|
256496
|
+
transform(chunk, encoding, callback) {
|
|
256497
|
+
const nextChunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, encoding);
|
|
256498
|
+
const buffered = partialFrame === void 0 ? nextChunk : Buffer.concat([partialFrame, nextChunk]);
|
|
256499
|
+
const lastNewline = buffered.lastIndexOf(10);
|
|
256500
|
+
if (lastNewline === -1) {
|
|
256501
|
+
partialFrame = buffered;
|
|
256502
|
+
scheduleDiscard();
|
|
256503
|
+
callback();
|
|
256504
|
+
return;
|
|
256505
|
+
}
|
|
256506
|
+
clearDiscardTimer();
|
|
256507
|
+
this.push(buffered.subarray(0, lastNewline + 1));
|
|
256508
|
+
const remainder = buffered.subarray(lastNewline + 1);
|
|
256509
|
+
partialFrame = remainder.length === 0 ? void 0 : remainder;
|
|
256510
|
+
scheduleDiscard();
|
|
256511
|
+
callback();
|
|
256512
|
+
},
|
|
256513
|
+
flush(callback) {
|
|
256514
|
+
clearDiscardTimer();
|
|
256515
|
+
partialFrame = void 0;
|
|
256516
|
+
callback();
|
|
256517
|
+
},
|
|
256518
|
+
destroy(error, callback) {
|
|
256519
|
+
clearDiscardTimer();
|
|
256520
|
+
partialFrame = void 0;
|
|
256521
|
+
callback(error);
|
|
256522
|
+
}
|
|
256523
|
+
});
|
|
256524
|
+
stdin2.pipe(recoveringInput);
|
|
256525
|
+
const transport = new StdioServerTransport(recoveringInput, stdout2);
|
|
256526
|
+
const closeTransport = transport.close.bind(transport);
|
|
256527
|
+
transport.close = async () => {
|
|
256528
|
+
await closeTransport();
|
|
256529
|
+
stdin2.unpipe(recoveringInput);
|
|
256530
|
+
recoveringInput.destroy();
|
|
256531
|
+
};
|
|
256532
|
+
return transport;
|
|
256049
256533
|
};
|
|
256050
256534
|
const basicAuthInput = z$4.union([
|
|
256051
256535
|
z$4.object({
|
|
@@ -256866,6 +257350,11 @@ const serverOnlyRouterOperationMetadata = {
|
|
|
256866
257350
|
query: {
|
|
256867
257351
|
type: "object",
|
|
256868
257352
|
properties: {
|
|
257353
|
+
result: {
|
|
257354
|
+
default: "many",
|
|
257355
|
+
type: "string",
|
|
257356
|
+
enum: ["many", "one", "first", "last"]
|
|
257357
|
+
},
|
|
256869
257358
|
where: {
|
|
256870
257359
|
default: {
|
|
256871
257360
|
all: []
|
|
@@ -257247,6 +257736,11 @@ const serverOnlyRouterOperationMetadata = {
|
|
|
257247
257736
|
query: {
|
|
257248
257737
|
type: "object",
|
|
257249
257738
|
properties: {
|
|
257739
|
+
result: {
|
|
257740
|
+
default: "many",
|
|
257741
|
+
type: "string",
|
|
257742
|
+
enum: ["many", "one", "first", "last"]
|
|
257743
|
+
},
|
|
257250
257744
|
where: {
|
|
257251
257745
|
default: {
|
|
257252
257746
|
all: []
|
|
@@ -258519,7 +259013,7 @@ const curatedPublicApiOperationDocumentation = [
|
|
|
258519
259013
|
},
|
|
258520
259014
|
{
|
|
258521
259015
|
command: "create-assets-resource",
|
|
258522
|
-
description: 'Create a scoped Assets resource. Omit query to use the minimal default query. For an explicit query, minimize the content database by selecting only fields the page renders, keeping includeMetadata false, and using content mode none. Use markdown-body-ref when querying a Markdown body directly; it requires storage-backed document resolution and never embeds the body in the content database. A structured document may instead select a field such as { "$ref": "./article.md#body" }. Preview concrete queries and inspect size diagnostics before saving.',
|
|
259016
|
+
description: 'Create a scoped Assets resource. Omit query to use the minimal default query. Use result many for collections and result one for unique detail routes; first and last require sorting. For an explicit query, minimize the content database by selecting only fields the page renders, keeping includeMetadata false, and using content mode none. Use markdown-body-ref when querying a Markdown body directly; it requires storage-backed document resolution and never embeds the body in the content database. A structured document may instead select a field such as { "$ref": "./article.md#body" }. Preview concrete queries and inspect size diagnostics before saving.',
|
|
258523
259017
|
requiredOptions: ["input", "json"],
|
|
258524
259018
|
examples: [
|
|
258525
259019
|
"webstudio create-assets-resource --input assets-resource.json --json"
|
|
@@ -258527,7 +259021,7 @@ const curatedPublicApiOperationDocumentation = [
|
|
|
258527
259021
|
},
|
|
258528
259022
|
{
|
|
258529
259023
|
command: "update-assets-resource",
|
|
258530
|
-
description: 'Update an Assets resource. Set query to null to restore the minimal default query. Keep explicit queries storage-efficient by selecting only rendered fields, keeping includeMetadata false, and using content mode none. Use markdown-body-ref for a directly queried Markdown body; it requires storage-backed document resolution and never embeds the body in the content database. A structured document may instead select a field such as { "$ref": "./article.md#body" }.',
|
|
259024
|
+
description: 'Update an Assets resource. Set query to null to restore the minimal default query. Use result many for collections and result one for unique detail routes; first and last require sorting. Keep explicit queries storage-efficient by selecting only rendered fields, keeping includeMetadata false, and using content mode none. Use markdown-body-ref for a directly queried Markdown body; it requires storage-backed document resolution and never embeds the body in the content database. A structured document may instead select a field such as { "$ref": "./article.md#body" }.',
|
|
258531
259025
|
requiredOptions: ["input", "json"],
|
|
258532
259026
|
examples: [
|
|
258533
259027
|
"webstudio update-assets-resource --input assets-resource-update.json --json"
|
|
@@ -260453,18 +260947,18 @@ class HandledCliError extends Error {
|
|
|
260453
260947
|
}
|
|
260454
260948
|
const isHandledCliError = (error) => error instanceof HandledCliError;
|
|
260455
260949
|
const name = "webstudio";
|
|
260456
|
-
const version$1 = "0.
|
|
260950
|
+
const version$1 = "0.289.0";
|
|
260457
260951
|
const description = "Webstudio CLI";
|
|
260458
260952
|
const author = "Webstudio <github@webstudio.is>";
|
|
260459
260953
|
const homepage = "https://webstudio.is";
|
|
260460
260954
|
const type = "module";
|
|
260461
260955
|
const bin = { "webstudio-cli": "./bin.js", "webstudio": "./bin.js" };
|
|
260462
260956
|
const files = ["lib/*", "templates/*", "bin.js", "!*.{test,stories}.*"];
|
|
260463
|
-
const scripts = { "typecheck": "tsc --noEmit", "generate-docs": "tsx scripts/generate-docs.ts && oxfmt src/docs.generated.ts", "build:content-runtime": "esbuild @webstudio-is/content-engine/runtime --bundle --format=esm --platform=browser --target=es2022 --conditions=webstudio --minify --outfile=lib/content-runtime.js", "build": "pnpm generate-docs && rm -rf lib && vite build && pnpm build:content-runtime", "pretest": "mkdir -p lib && pnpm build:content-runtime", "evaluations": "tsx --import ../../scripts/register-react-global.ts --conditions=webstudio evaluations/high-impact/run-local-agent.ts", "test": "vitest run", "test:release-smoke": "pnpm build && tsx --conditions=webstudio scripts/release-smoke.ts", "test:release-smoke:model": "WEBSTUDIO_RELEASE_SMOKE_MODEL_EVAL_REQUIRED=1 pnpm test:release-smoke", "test:release-smoke:registry": "tsx --conditions=webstudio scripts/release-smoke.ts" };
|
|
260957
|
+
const scripts = { "typecheck": "tsc --noEmit", "generate-docs": "tsx scripts/generate-docs.ts && oxfmt src/docs.generated.ts", "build:content-runtime": "esbuild @webstudio-is/content-engine/runtime --bundle --format=esm --platform=browser --target=es2022 --conditions=webstudio --minify --outfile=lib/content-runtime.js", "fixtures:build": "pnpm build:content-runtime", "build": "pnpm generate-docs && rm -rf lib && vite build && pnpm build:content-runtime", "pretest": "mkdir -p lib && pnpm build:content-runtime", "evaluations": "tsx --import ../../scripts/register-react-global.ts --conditions=webstudio evaluations/high-impact/run-local-agent.ts", "test": "vitest run", "test:release-smoke": "pnpm build && tsx --conditions=webstudio scripts/release-smoke.ts", "test:release-smoke:model": "WEBSTUDIO_RELEASE_SMOKE_MODEL_EVAL_REQUIRED=1 pnpm test:release-smoke", "test:release-smoke:registry": "tsx --conditions=webstudio scripts/release-smoke.ts" };
|
|
260464
260958
|
const license = "AGPL-3.0-or-later";
|
|
260465
|
-
const engines = { "node": ">=22" };
|
|
260466
|
-
const dependencies = { "@clack/prompts": "^0.10.0", "@emotion/hash": "^0.9.2", "@trpc/client": "^10.45.2", "@webstudio-is/http-client": "workspace:*", "@webstudio-is/project-migrations": "workspace:*", "@webstudio-is/protocol": "workspace:*", "@webstudio-is/sync-client": "workspace:*", "acorn": "^8.14.1", "acorn-jsx": "^5.3.2", "acorn-walk": "^8.3.4", "change-case": "^5.4.4", "chrome-launcher": "^1.2.1", "deepmerge": "^4.3.1", "decode-named-character-reference": "^1.0.2", "env-paths": "^3.0.0", "esbuild": "^0.25.3", "fast-deep-equal": "^3.1.3", "immer": "^10.1.1", "nanoid": "^5.1.5", "p-limit": "^6.2.0", "parse5": "7.3.0", "picocolors": "^1.1.1", "reserved-identifiers": "^1.0.0", "tinyexec": "^0.3.2", "tus-js-client": "^4.3.1", "warn-once": "^0.1.1", "yargs": "^17.7.2", "zod": "^4.4.3" };
|
|
260467
|
-
const devDependencies = { "@cloudflare/vite-plugin": "^1.1.0", "@netlify/vite-plugin-react-router": "^1.0.1", "@react-router/dev": "^7.5.3", "@react-router/fs-routes": "^7.5.3", "@react-router/node": "^7.5.3", "@react-router/serve": "^7.5.3", "@remix-run/cloudflare": "^2.16.5", "@remix-run/cloudflare-pages": "^2.16.5", "@remix-run/dev": "^2.16.5", "@remix-run/node": "^2.16.5", "@remix-run/react": "^2.16.5", "@remix-run/server-runtime": "^2.16.5", "@shikijs/langs": "4.4.1", "@shikijs/themes": "4.4.1", "@types/mdast": "^4.0.4", "@types/react": "^18.2.70", "@types/react-dom": "^18.2.25", "@types/yargs": "^17.0.33", "@vercel/react-router": "^1.1.0", "@vitejs/plugin-react": "^4.4.1", "@webstudio-is/css-engine": "workspace:*", "@webstudio-is/content-engine": "workspace:*", "@webstudio-is/expression": "workspace:*", "@webstudio-is/fonts": "workspace:*", "@webstudio-is/image": "workspace:*", "@webstudio-is/project-build": "workspace:*", "@webstudio-is/query-builder": "workspace:*", "@webstudio-is/react-sdk": "workspace:*", "@webstudio-is/sdk": "workspace:*", "@webstudio-is/sdk-components-animation": "workspace:*", "@webstudio-is/sdk-components-react": "workspace:*", "@webstudio-is/sdk-components-react-radix": "workspace:*", "@webstudio-is/sdk-components-react-remix": "workspace:*", "@webstudio-is/sdk-components-react-router": "workspace:*", "@webstudio-is/sdk-components-registry": "workspace:*", "@webstudio-is/tsconfig": "workspace:*", "@webstudio-is/wsauth": "workspace:*", "h3": "^1.15.1", "ipx": "^3.0.3", "isbot": "^5.1.25", "mdast-util-directive": "^3.1.0", "mdast-util-from-markdown": "^2.0.3", "mdast-util-to-string": "^4.0.0", "micromark-extension-directive": "^4.0.0", "oxfmt": "0.58.0", "react": "18.3.0-canary-14898b6a9-20240318", "react-dom": "18.3.0-canary-14898b6a9-20240318", "react-router": "^7.5.3", "shiki": "4.4.1", "ts-expect": "^1.3.0", "typescript": "7.0.2", "vike": "^0.4.229", "vite": "^6.3.4", "vitest": "^3.1.2", "wrangler": "^3.63.2" };
|
|
260959
|
+
const engines = { "node": ">=22.12.0" };
|
|
260960
|
+
const dependencies = { "@clack/prompts": "^0.10.0", "@emotion/hash": "^0.9.2", "@trpc/client": "^10.45.2", "@webstudio-is/http-client": "workspace:*", "@webstudio-is/project-migrations": "workspace:*", "@webstudio-is/protocol": "workspace:*", "@webstudio-is/sync-client": "workspace:*", "acorn": "^8.14.1", "acorn-jsx": "^5.3.2", "acorn-walk": "^8.3.4", "change-case": "^5.4.4", "chrome-launcher": "^1.2.1", "deepmerge": "^4.3.1", "decode-named-character-reference": "^1.0.2", "detect-port": "^2.1.0", "env-paths": "^3.0.0", "esbuild": "^0.25.3", "fast-deep-equal": "^3.1.3", "get-port": "^7.2.0", "immer": "^10.1.1", "nanoid": "^5.1.5", "p-limit": "^6.2.0", "parse5": "7.3.0", "path-key": "^4.0.0", "picocolors": "^1.1.1", "reserved-identifiers": "^1.0.0", "tinyexec": "^0.3.2", "tus-js-client": "^4.3.1", "warn-once": "^0.1.1", "which": "^5.0.0", "yargs": "^17.7.2", "zod": "^4.4.3" };
|
|
260961
|
+
const devDependencies = { "@cloudflare/vite-plugin": "^1.1.0", "@netlify/vite-plugin-react-router": "^1.0.1", "@react-router/dev": "^7.5.3", "@react-router/fs-routes": "^7.5.3", "@react-router/node": "^7.5.3", "@react-router/serve": "^7.5.3", "@remix-run/cloudflare": "^2.16.5", "@remix-run/cloudflare-pages": "^2.16.5", "@remix-run/dev": "^2.16.5", "@remix-run/node": "^2.16.5", "@remix-run/react": "^2.16.5", "@remix-run/server-runtime": "^2.16.5", "@shikijs/langs": "4.4.1", "@shikijs/themes": "4.4.1", "@types/mdast": "^4.0.4", "@types/react": "^18.2.70", "@types/react-dom": "^18.2.25", "@types/which": "^3.0.4", "@types/yargs": "^17.0.33", "@vercel/react-router": "^1.1.0", "@vitejs/plugin-react": "^4.4.1", "@webstudio-is/css-engine": "workspace:*", "@webstudio-is/content-engine": "workspace:*", "@webstudio-is/expression": "workspace:*", "@webstudio-is/fonts": "workspace:*", "@webstudio-is/image": "workspace:*", "@webstudio-is/project-build": "workspace:*", "@webstudio-is/query-builder": "workspace:*", "@webstudio-is/react-sdk": "workspace:*", "@webstudio-is/sdk": "workspace:*", "@webstudio-is/sdk-components-animation": "workspace:*", "@webstudio-is/sdk-components-react": "workspace:*", "@webstudio-is/sdk-components-react-radix": "workspace:*", "@webstudio-is/sdk-components-react-remix": "workspace:*", "@webstudio-is/sdk-components-react-router": "workspace:*", "@webstudio-is/sdk-components-registry": "workspace:*", "@webstudio-is/tsconfig": "workspace:*", "@webstudio-is/wsauth": "workspace:*", "h3": "^1.15.1", "ipx": "^3.0.3", "isbot": "^5.1.25", "mdast-util-directive": "^3.1.0", "mdast-util-from-markdown": "^2.0.3", "mdast-util-to-string": "^4.0.0", "micromark-extension-directive": "^4.0.0", "oxfmt": "0.58.0", "react": "18.3.0-canary-14898b6a9-20240318", "react-dom": "18.3.0-canary-14898b6a9-20240318", "react-router": "^7.5.3", "shiki": "4.4.1", "ts-expect": "^1.3.0", "typescript": "7.0.2", "vike": "^0.4.229", "vite": "^6.3.4", "vitest": "^3.1.2", "wrangler": "^3.63.2" };
|
|
260468
260962
|
const packageJson = {
|
|
260469
260963
|
name,
|
|
260470
260964
|
version: version$1,
|
|
@@ -269585,28 +270079,31 @@ const remixComponents = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.def
|
|
|
269585
270079
|
RichTextLink: Link$2
|
|
269586
270080
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
269587
270081
|
const routeTemplatesDirectory = join$1("app", "route-templates");
|
|
270082
|
+
const getFrameworkTemplatesDirectory = (options = {}) => options.templatesDirectory ?? routeTemplatesDirectory;
|
|
269588
270083
|
const cleanupFrameworkTemplates = async ({
|
|
269589
|
-
preserveTemplates = false
|
|
270084
|
+
preserveTemplates = false,
|
|
270085
|
+
templatesDirectory = routeTemplatesDirectory
|
|
269590
270086
|
} = {}) => {
|
|
269591
270087
|
if (preserveTemplates === false) {
|
|
269592
|
-
await rm(
|
|
270088
|
+
await rm(templatesDirectory, { recursive: true, force: true });
|
|
269593
270089
|
}
|
|
269594
270090
|
};
|
|
269595
270091
|
const createFramework$2 = async (options = {}) => {
|
|
270092
|
+
const templatesDirectory = getFrameworkTemplatesDirectory(options);
|
|
269596
270093
|
const htmlTemplate = await readFile(
|
|
269597
|
-
join$1(
|
|
270094
|
+
join$1(templatesDirectory, "html.tsx"),
|
|
269598
270095
|
"utf8"
|
|
269599
270096
|
);
|
|
269600
270097
|
const xmlTemplate = await readFile(
|
|
269601
|
-
join$1(
|
|
270098
|
+
join$1(templatesDirectory, "xml.tsx"),
|
|
269602
270099
|
"utf8"
|
|
269603
270100
|
);
|
|
269604
270101
|
const textTemplate = await readFile(
|
|
269605
|
-
join$1(
|
|
270102
|
+
join$1(templatesDirectory, "text.tsx"),
|
|
269606
270103
|
"utf8"
|
|
269607
270104
|
);
|
|
269608
270105
|
const defaultSitemapTemplate = await readFile(
|
|
269609
|
-
join$1(
|
|
270106
|
+
join$1(templatesDirectory, "default-sitemap.tsx"),
|
|
269610
270107
|
"utf8"
|
|
269611
270108
|
);
|
|
269612
270109
|
await cleanupFrameworkTemplates(options);
|
|
@@ -271970,20 +272467,21 @@ const reactRouterComponents = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Obje
|
|
|
271970
272467
|
RichTextLink: Link
|
|
271971
272468
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
271972
272469
|
const createFramework$1 = async (options = {}) => {
|
|
272470
|
+
const templatesDirectory = getFrameworkTemplatesDirectory(options);
|
|
271973
272471
|
const htmlTemplate = await readFile(
|
|
271974
|
-
join$1(
|
|
272472
|
+
join$1(templatesDirectory, "html.tsx"),
|
|
271975
272473
|
"utf8"
|
|
271976
272474
|
);
|
|
271977
272475
|
const xmlTemplate = await readFile(
|
|
271978
|
-
join$1(
|
|
272476
|
+
join$1(templatesDirectory, "xml.tsx"),
|
|
271979
272477
|
"utf8"
|
|
271980
272478
|
);
|
|
271981
272479
|
const textTemplate = await readFile(
|
|
271982
|
-
join$1(
|
|
272480
|
+
join$1(templatesDirectory, "text.tsx"),
|
|
271983
272481
|
"utf8"
|
|
271984
272482
|
);
|
|
271985
272483
|
const defaultSitemapTemplate = await readFile(
|
|
271986
|
-
join$1(
|
|
272484
|
+
join$1(templatesDirectory, "default-sitemap.tsx"),
|
|
271987
272485
|
"utf8"
|
|
271988
272486
|
);
|
|
271989
272487
|
await cleanupFrameworkTemplates(options);
|
|
@@ -272050,16 +272548,17 @@ const generateVikeRoute = (pagePath2) => {
|
|
|
272050
272548
|
return route;
|
|
272051
272549
|
};
|
|
272052
272550
|
const createFramework = async (options = {}) => {
|
|
272551
|
+
const templatesDirectory = getFrameworkTemplatesDirectory(options);
|
|
272053
272552
|
const htmlPageTemplate = await readFile(
|
|
272054
|
-
join$1(
|
|
272553
|
+
join$1(templatesDirectory, "html", "+Page.tsx"),
|
|
272055
272554
|
"utf8"
|
|
272056
272555
|
);
|
|
272057
272556
|
const htmlHeadTemplate = await readFile(
|
|
272058
|
-
join$1(
|
|
272557
|
+
join$1(templatesDirectory, "html", "+Head.tsx"),
|
|
272059
272558
|
"utf8"
|
|
272060
272559
|
);
|
|
272061
272560
|
const htmlDataTemplate = await readFile(
|
|
272062
|
-
join$1(
|
|
272561
|
+
join$1(templatesDirectory, "html", "+data.ts"),
|
|
272063
272562
|
"utf8"
|
|
272064
272563
|
);
|
|
272065
272564
|
await cleanupFrameworkTemplates(options);
|
|
@@ -272568,6 +273067,7 @@ const importFrom = (importee, importer) => {
|
|
|
272568
273067
|
return relative(dirname$1(importer), importee).replaceAll("\\", "/");
|
|
272569
273068
|
};
|
|
272570
273069
|
const npmrc = `force=true
|
|
273070
|
+
engine-strict=true
|
|
272571
273071
|
loglevel=error
|
|
272572
273072
|
audit=false
|
|
272573
273073
|
fund=false
|
|
@@ -272661,19 +273161,17 @@ Please check webstudio --help for more details`
|
|
|
272661
273161
|
}
|
|
272662
273162
|
}
|
|
272663
273163
|
const preserveRouteTemplates = options.incremental === true || options.preserveRouteTemplates === true;
|
|
273164
|
+
const frameworkOptions = {
|
|
273165
|
+
preserveTemplates: preserveRouteTemplates,
|
|
273166
|
+
templatesDirectory: join$1(buildRoot, routeTemplatesDirectory)
|
|
273167
|
+
};
|
|
272664
273168
|
let framework;
|
|
272665
273169
|
if (options.template.includes("ssg")) {
|
|
272666
|
-
framework = await createFramework(
|
|
272667
|
-
preserveTemplates: preserveRouteTemplates
|
|
272668
|
-
});
|
|
273170
|
+
framework = await createFramework(frameworkOptions);
|
|
272669
273171
|
} else if (options.template.includes("react-router")) {
|
|
272670
|
-
framework = await createFramework$1(
|
|
272671
|
-
preserveTemplates: preserveRouteTemplates
|
|
272672
|
-
});
|
|
273172
|
+
framework = await createFramework$1(frameworkOptions);
|
|
272673
273173
|
} else {
|
|
272674
|
-
framework = await createFramework$2(
|
|
272675
|
-
preserveTemplates: preserveRouteTemplates
|
|
272676
|
-
});
|
|
273174
|
+
framework = await createFramework$2(frameworkOptions);
|
|
272677
273175
|
}
|
|
272678
273176
|
const assetBaseUrl = await readAssetBaseUrl(join$1(cwd$1(), "app/constants.mjs"));
|
|
272679
273177
|
const loadedSiteData = await loadJSONFile(LOCAL_DATA_FILE);
|
|
@@ -273160,9 +273658,10 @@ Please check webstudio --help for more details`
|
|
|
273160
273658
|
join$1(generatedDir, "$resources.redirects.ts"),
|
|
273161
273659
|
generateRedirectsModule(pages2.redirects)
|
|
273162
273660
|
);
|
|
273163
|
-
|
|
273661
|
+
const redirectFallbackPath = join$1(routesDir, "$.tsx");
|
|
273662
|
+
if (pages2.redirects !== void 0 && pages2.redirects.length > 0 && generatedFiles.has(normalize$4(redirectFallbackPath)) === false) {
|
|
273164
273663
|
await writeGeneratedFile(
|
|
273165
|
-
|
|
273664
|
+
redirectFallbackPath,
|
|
273166
273665
|
generateRedirectFallbackRoute(
|
|
273167
273666
|
options.template.includes("react-router") ? "react-router" : "remix"
|
|
273168
273667
|
)
|
|
@@ -273233,12 +273732,12 @@ const build = async (options) => {
|
|
|
273233
273732
|
await prebuild(options);
|
|
273234
273733
|
};
|
|
273235
273734
|
const cliDocs = {
|
|
273236
|
-
"api-use-cases": '# CLI API Use Cases\n\n## Link/configure one project\n\nCommands:\n\n- webstudio init --link <api-share-link> --json\n\nNotes:\n\n- Writes local project id and global origin/token config.\n\n## Import synced project bundle into another project\n\nCommands:\n\n- webstudio sync\n- webstudio import --to <destination-share-link>\n- MCP tool: import {"to":"<destination-share-link>"}\n\nNotes:\n\n- Imports local `.webstudio/data.json` into the destination project.\n- Destination share link must allow build/import access.\n- Use `--skip-assets` only when asset rows and files should not be imported.\n\n## Identify current token\n\nCommands:\n\n- MCP tool: whoami {}\n\n## Check token permissions\n\nCommands:\n\n- webstudio permissions --json\n\n## Inspect project/build/version\n\nCommands:\n\n- MCP tool: inspect {}\n- MCP tool: snapshot {"include":["pages","instances","styles"]}\n\n## Discover CLI/API capabilities\n\nCommands:\n\n- webstudio schema api\n- webstudio schema mcp\n- webstudio man --json\n- webstudio man llm --json\n- MCP tool: meta.index {}\n- MCP tool: meta.guide {"brief":"Create a pricing page"}\n- MCP tool: meta.get-more-tools {"brief":"update-styles"}\n- webstudio mcp list-resources\n- webstudio mcp read-resource webstudio://project/guide\n- webstudio mcp read-resource webstudio://project/expressions\n\nNotes:\n\n- Use `webstudio schema mcp` for a compact machine-readable MCP tool overview. Add `--verbose` or use focused `meta.get-more-tools` calls only when exact input schemas are needed.\n- Use focused MCP tools for discovery first: `meta.index`, `meta.guide`, `meta.get-more-tools`, `components.list`, `components.summary`, `components.search`, `components.get`, `templates.list`, and `templates.get`. Protocol clients can use `resources/list` and `resources/read`; shell agents can use `webstudio mcp list-resources` and `webstudio mcp read-resource <uri>`. Read longer resources such as `webstudio://project/tools` and `webstudio://project/components` only when focused tools are insufficient.\n- `components.summary` returns counts by default; request `{"detail":"components","limit":20}` for paginated entries. Registry list tools return compact metadata, while `components.get` and `templates.get` return focused full details.\n- Read `webstudio://project/expressions` before authoring unfamiliar computed text, prop bindings, resource expressions, actions, or Collection item bindings.\n- From a shell, call one MCP tool with the shortcut form `webstudio <tool> \'<json>\'`, for example `webstudio components.summary`. The explicit equivalent is `webstudio mcp single-op-call <tool> \'<json>\'`. Use `--input-file` for large payloads.\n\n## Inspect external shadcn registry items\n\nCommands:\n\n- webstudio registry inspect --source https://example.com/r/registry.json --item button --json\n- webstudio registry inspect --source ./registry.json --item dialog --json\n- webstudio registry inspect --source https://example.com/r/button.json --json\n\nNotes:\n\n- Reads a local or remote registry item without installing files or changing the configured Webstudio project.\n- Returns the item name, description, package and registry dependencies, file paths/targets, available docs, and a read-only compatibility report.\n- The report explicitly says whether installation or editable-component conversion is supported, lists declared requirements and manual steps, and says when arbitrary source code was not analyzed.\n- This is an inspection step only. It does not install files or change the configured project.\n\n## Understand what MCP can do\n\nCommands:\n\n- webstudio man mcp\n- MCP tool: meta.index {}\n- MCP tool: meta.guide {"brief":"What can Webstudio MCP do?"}\n\nMCP lets agents work on one configured Webstudio project. Agents can:\n\n- Inspect the linked project, token permissions, and latest editable build.\n- Read selected project data for audits, migrations, and repair.\n- Search labels, text, props, resource URLs, asset metadata, and styles.\n- Audit accessibility, security, SEO, performance settings, unused assets, ineffective Collection styles, and unused or duplicate style data.\n- Create and edit pages, folders, redirects, breakpoints, and page templates.\n- Create pages from reusable templates.\n- Update page metadata, SEO fields, auth settings, and marketplace metadata.\n- Insert components and styled JSX sections.\n- Create data-driven lists, grids, cards, and similar repeated UI from array or object data in one Collection operation.\n- Move, copy, wrap, unwrap, convert, rename, retag, and delete elements.\n- Update text, rich text, props, bindings, and actions.\n- Create and update local styles, design tokens, style sources, and CSS variables.\n- Create static data variables and JSON variables.\n- Create HTTP, GraphQL, and system resources.\n- Use system resources for sitemap, current date, and assets.\n- Bind resources to rendered data or form/action props.\n- Manage nested asset folders and upload, inspect, move, duplicate, download, replace, delete, and inspect usage for assets.\n- Publish, unpublish, inspect publish jobs, and manage custom domains.\n- Start preview, capture screenshots, compare screenshot diffs, and use OCR when installed.\n\n## Inspect and refresh MCP session cache\n\nCommands:\n\n- MCP tool: status {}\n- MCP tool: status {"verbose":true}\n- MCP tool: refresh {"namespaces":["pages","instances","styles"]}\n- MCP tool: reset-session {}\n\nNotes:\n\n- Use status before a task to understand the cached ProjectSession state.\n- Use status with `{"verbose":true}` only when debugging full namespaces, freshness, compatibility, or diagnostics.\n- Use refresh when project data may have changed outside the current MCP session.\n- Use reset-session when local cached state is corrupt or incompatible.\n\n## Visually verify rendered work with AI vision\n\nCommands:\n\n- MCP tool: preview.start {"host":"127.0.0.1","port":5173}\n- MCP tool: preview.status {}\n- MCP tool: preview.stop {}\n- MCP tool: screenshot {"path":"/","output":".webstudio/screenshots/home-current.png","viewport":{"width":1440,"height":900},"waitUntil":"load","waitForTimeout":250}\n- MCP tool: screenshot {"path":"/pricing","output":".webstudio/screenshots/pricing-current.png","viewport":{"width":1440,"height":900},"waitUntil":"load","waitForTimeout":250}\n- MCP tool: screenshot {"baseUrl":"http://127.0.0.1:5177","path":"/pricing","output":".webstudio/screenshots/pricing-current.png","viewport":{"width":1440,"height":900},"waitUntil":"load","waitForTimeout":250}\n- MCP tool: screenshot.diff {"baselinePath":".webstudio/screenshots/home-before.png","currentPath":".webstudio/screenshots/home-current.png","outputDir":".webstudio/screenshots/diff"}\n- MCP tool: screenshot.diff {"baselinePath":".webstudio/screenshots/home-before.png","currentPath":".webstudio/screenshots/home-current.png","outputDir":".webstudio/screenshots/diff","expectedText":["Pricing","Start free"]}\n- MCP tool: screenshot.diff {"baselinePath":".webstudio/screenshots/home-before.png","currentPath":".webstudio/screenshots/home-current.png","outputDir":".webstudio/screenshots/diff","expectedVisual":{"maxMismatchPercentage":2,"maxChangedRegions":3,"dominantColorChange":{"channel":"luminance","direction":"increase","minMagnitude":10}}}\n- MCP tool: screenshot.diff {"baselinePath":".webstudio/screenshots/pricing-before.png","currentPath":".webstudio/screenshots/pricing-current.png","outputDir":".webstudio/screenshots/diff"}\n- MCP tool: vision.install-ocr {"confirm":true}\n\nNotes:\n\n- `preview.status` reports whether generated output is `stale`. When present, `renderedProjectVersion` is the last project version materialized into the preview.\n- A managed `screenshot` or another `preview.start` refreshes stale generated output before capture.\n\n- Use this after page/content/style mutations so a vision-capable AI can see the generated site from the current MCP session. Use `path`; never pass a Webstudio Builder/share URL or capture Builder chrome.\n- For multi-page work, capture every changed page by `path` through the same preview server; no click navigation is required.\n- Iterative mode is the default: after MCP mutations, path screenshots ensure generated project files are current, wait for the exact session version, and perform an ordinary page reload without Vite HMR. The preview server and browser stay alive. Use `{"mode":"production"}` only for release-like verification; rendered audit does this automatically.\n- Do not call `preview.start` through one-shot `webstudio mcp single-op-call`: it is long-lived. From a shell, use `webstudio mcp run` with preview.start, screenshot, and preview.stop in one shared process, or use a real long-running MCP client.\n- From one-shot shell calls or another process, pass `baseUrl` with `path` to capture an already-running preview/site without generating, building, starting, or restarting preview.\n- Use preview.stop only in the same long-running MCP server or `webstudio mcp run` process that started preview. A separate one-shot `single-op-call` process does not own another process\'s preview controller.\n- Use waitForSelector when the rendered app has a reliable ready marker, waitUntil:"networkidle" for network-heavy pages, and waitForTimeout only for final visual settling.\n- Preview installs generated app dependencies under `.webstudio/preview` and reuses them across regenerations.\n- Do not add generated-preview dependencies to the repository root `package.json` or `pnpm-lock.yaml`.\n- If dependency installation fails, check npm and network configuration, then reinstall or update the Webstudio CLI if the problem persists.\n- When a baseline exists, use screenshot.diff once per baseline/current page or viewport pair to get changed regions, OCR textAnalysis, and diff artifact paths before deciding whether the result matches. Pass expectedText for explicit pass/fail current-screen text assertions with found and missing text. Pass expectedVisual for pass/fail limits on pixel mismatch percentage, changed-region count, or the overall dominant color/brightness direction.\n- If screenshot.diff reports OCR unavailable and the user agrees to install it, call vision.install-ocr {"confirm":true}; otherwise continue with pixel diff and visual inspection.\n- Compare the PNG, OCR text evidence, and diff artifacts against the user\'s intent for layout, typography, colors, spacing, imagery, and responsive framing; then iterate with focused mutations.\n- Root CLI equivalent: `webstudio screenshot --path /pricing --output pricing.png` generates a temporary production preview, captures that route, and stops the server. For repeated captures, keep `webstudio preview` running and pass its absolute URL to `webstudio screenshot`.\n\n## List pages\n\nCommands:\n\n- MCP tool: list-pages {}\n- MCP tool: list-folders {}\n\n## Read page by id\n\nCommands:\n\n- MCP tool: get-page {"pageId":"<pageId>"}\n\n## Read page by path\n\nCommands:\n\n- MCP tool: get-page-by-path {"path":"/pricing"}\n\n## Create page\n\nCommands:\n\n- MCP tool: create-page {"name":"Pricing","path":"/pricing"}\n- MCP tool: create-page {"name":"Pricing","path":"/pricing","title":"Pricing","meta":{"description":"Plans for teams"}}\n\nNotes:\n\n- `name`, `path`, page `title`, and metadata text fields accept plain fixed values.\n- For computed page titles or metadata, send JavaScript expression code such as `pageTitle ?? "Pricing"`.\n\n## Update page settings/metadata\n\nCommands:\n\n- MCP tool: update-page {"pageId":"<pageId>","values":{"title":"Pricing","meta":{"description":"Plans","status":200}}}\n- MCP tool: update-page {"pageId":"<pageId>","values":{"meta":{"auth":{"method":"basic","login":"<login>","password":"<password>"}}}}\n\nNotes:\n\n- Page `title` and metadata text fields accept plain fixed values.\n- For computed page titles or metadata, send JavaScript expression code such as `pageTitle ?? "Pricing"`.\n- Page `status` accepts a fixed HTTP status code as a number from 200 through 599 or a JavaScript expression string for a dynamic status.\n\n## Read project settings\n\nCommands:\n\n- MCP tool: get-project-settings {}\n\nNotes:\n\n- Read `meta.agentInstructions` before making project changes. It contains the project\'s own guidance for AI agents.\n- Agent instructions are shared project guidance. Do not store credentials or other secrets there.\n\n## Update project settings\n\nCommands:\n\n- MCP tool: update-project-settings {"meta":{"siteName":"Acme"}}\n- MCP tool: update-project-settings {"meta":{"agentInstructions":"Use existing design tokens and keep product copy concise."}}\n\n## Read marketplace product\n\nCommands:\n\n- MCP tool: get-marketplace-product {}\n\n## Update marketplace product\n\nCommands:\n\n- MCP tool: update-marketplace-product {"category":"pageTemplates","name":"Acme Template","thumbnailAssetId":"asset-id","author":"Acme Studio","email":"hello@example.com","website":"https://example.com","issues":"","description":"Reusable template project for Acme landing pages."}\n\n## Submit marketplace product\n\nCommands:\n\n- MCP tool: upload-asset {"asset":{"name":"marketplace-thumbnail.png","type":"image","format":"png","meta":{"width":1200,"height":630}},"assetsDir":".webstudio/assets"}\n- MCP tool: update-marketplace-product {"category":"pageTemplates","name":"Acme Template","thumbnailAssetId":"<uploadedAssetId>","author":"Acme Studio","email":"hello@example.com","website":"https://example.com","issues":"","description":"Reusable template project for Acme landing pages."}\n- MCP tool: publish {"target":"production"}\n- MCP tool: submit-marketplace-product {"acknowledgePublicSubmission":true}\n\nNotes:\n\n- Wait for the production publish to complete before submitting the product for review.\n- Submission requires complete, valid marketplace metadata.\n\n## List redirects\n\nCommands:\n\n- MCP tool: list-redirects {}\n\n## Create redirect\n\nCommands:\n\n- MCP tool: create-redirect {"old":"/old","new":"/new","status":301}\n\n## Update redirect\n\nCommands:\n\n- MCP tool: update-redirect {"old":"/old","values":{"new":"/newer","status":302}}\n- MCP tool: update-redirect {"old":"/old","values":{"status":null}}\n\n## Delete redirect\n\nCommands:\n\n- MCP tool: delete-redirect {"old":"/old"}\n\n## Set redirects\n\nCommands:\n\n- MCP tool: set-redirects {"redirects":[{"old":"/old","new":"/new","status":"301"}]}\n\n## List breakpoints\n\nCommands:\n\n- MCP tool: list-breakpoints {}\n\n## Create breakpoint\n\nCommands:\n\n- MCP tool: create-breakpoint {"label":"Tablet","maxWidth":991}\n\n## Update breakpoint\n\nCommands:\n\n- MCP tool: update-breakpoint {"breakpointId":"tablet","values":{"label":"Tablet","maxWidth":1023}}\n- MCP tool: update-breakpoint {"breakpointId":"tablet","values":{"condition":null,"minWidth":768}}\n- MCP tool: update-breakpoint {"breakpointId":"tablet","values":{"minWidth":null,"maxWidth":null,"condition":"(hover: hover)"}}\n\n## Delete breakpoint\n\nCommands:\n\n- MCP tool: delete-breakpoint {"breakpointId":"tablet"}\n\n## Duplicate page\n\nCommands:\n\n- MCP tool: duplicate-page {"pageId":"<pageId>","name":"Pricing Copy","path":"/pricing-copy"}\n- MCP tool: duplicate-page {"pageId":"<pageId>","name":"Paris","path":"/paris","substitutions":{"text":{"London":"Paris"},"variables":{"city":{"type":"string","value":"Paris"}}}}\n- webstudio duplicate-page --page <pageId> --name Paris --path /paris --substitutions \'{"text":{"London":"Paris"},"variables":{"city":{"type":"string","value":"Paris"}}}\' --json\n\nNotes:\n\n- Text substitutions replace exact fixed text only in the duplicated page\'s text children, string props, title, and metadata.\n- Variable substitutions are keyed by copied source-variable name and use typed variable values. The operation rejects missing or ambiguous names without committing a partial duplicate.\n- Existing expressions and cloned variable/resource references keep their remapped ids.\n\n## List page templates\n\nCommands:\n\n- MCP tool: list-page-templates {}\n\n## Create page template\n\nCommands:\n\n- MCP tool: create-page-template {"name":"Landing Template","title":"Landing"}\n\n## Update page template\n\nCommands:\n\n- MCP tool: update-page-template {"templateId":"<templateId>","values":{"name":"Article Template","meta":{"description":"Reusable article layout"}}}\n\n## Delete page template\n\nCommands:\n\n- MCP tool: delete-page-template {"templateId":"<templateId>"}\n\n## Duplicate page template\n\nCommands:\n\n- MCP tool: duplicate-page-template {"templateId":"<templateId>"}\n\n## Reorder page template\n\nCommands:\n\n- MCP tool: reorder-page-template {"sourceTemplateId":"<sourceTemplateId>","targetTemplateId":"<targetTemplateId>","position":"before"}\n\n## Create page from template\n\nCommands:\n\n- MCP tool: create-page-from-template {"templateId":"<templateId>","name":"Landing","path":"/landing"}\n\n## Delete page\n\nCommands:\n\n- MCP tool: delete-page {"pageId":"<pageId>"}\n\n## List folders\n\nCommands:\n\n- MCP tool: list-folders {}\n- MCP tool: list-pages {}\n\n## Create folder\n\nCommands:\n\n- MCP tool: create-folder {"name":"Blog","slug":"blog"}\n\n## Update folder\n\nCommands:\n\n- MCP tool: update-folder {"folderId":"<folderId>","values":{"name":"Blog","slug":"blog"}}\n\n## Delete folder\n\nCommands:\n\n- MCP tool: delete-folder {"folderId":"<folderId>"}\n\n## List element instances\n\nCommands:\n\n- MCP tool: list-instances {"pagePath":"/","maxDepth":3}\n\n## Inspect one element instance\n\nCommands:\n\n- MCP tool: inspect-instance {"instanceId":"<instanceId>","include":["props","styles","children"]}\n\n## Insert authored JSX or one component template\n\nCommands:\n\n- MCP tool: insert-fragment {"parentInstanceId":"<instanceId>","fragment":"<ws.element ws:tag=\'section\' ws:style={css`padding: 32px;`}><ws.element ws:tag=\'h2\'>Product OS</ws.element><radix.Switch><radix.SwitchThumb /></radix.Switch></ws.element>"}\n- MCP tool: insert-component {"parentInstanceId":"<instanceId>","component":"@webstudio-is/sdk-components-react-radix:Switch"}\n- MCP tool: insert-component {"parentInstanceId":"<instanceId>","component":"Form"}\n\nNotes:\n\n- Use MCP `insert-fragment` as the default way to author styled component trees. It converts JSX to a structured fragment before mutation.\n- Use only exact component ids returned by `components.search`, `components.get`, or `templates.get`. Never derive or guess component ids.\n- The `ws:` namespace contains specific Webstudio core components; it is not HTML-tag shorthand. Use `<ws.element ws:tag="div">` for a native `div` and `<ws.element ws:tag="form">` for a native form, never `<ws.div>` or `<ws.form>`.\n- For Webstudio\'s complete form structure, discover the Form component and insert its automatic template with `insert-component` using component `"Form"`.\n- MCP receives JSX as a JSON string because MCP arguments are JSON. The CLI converts it locally before the runtime mutation, so the project session receives structured Webstudio data, not JSX source.\n- In `insert-fragment` JSX, use ``ws:style={css`...`}`` for Webstudio-native CSS, or use React-style object syntax such as `style={{ padding: 24 }}` when that is simpler. Both forms create editable Webstudio style data.\n- Do not access host globals or dynamic code APIs in JSX fragments, including `process`, `globalThis`, `eval`, `Function`, or `constructor`.\n- Use Webstudio prop names such as `class` and `for`; do not use React aliases `className` or `htmlFor`.\n- Use Webstudio actions for event/action props, for example `onClick={new ActionValue(["event"], expression\\`console.log(event)\\`)}`. Do not pass JavaScript functions such as `onClick={() => ...}`.\n- Plain prop values must be JSON-compatible: `null`, strings, booleans, finite numbers, arrays, and plain objects. Do not pass `undefined`, `Symbol`, `BigInt`, `NaN`, `Infinity`, `Date`, `Map`, `Set`, class instances, or circular objects; omit the prop, use plain data, or use `expression`/`ActionValue` when the value is dynamic.\n- Template-backed components used in JSX must include required child/part components explicitly under the same parent structure as the template, for example `<radix.Switch><radix.SwitchThumb /></radix.Switch>`.\n- Webstudio applies a registered template automatically when using `insert-component`, so composed components such as Switch include required child parts and styles.\n- Use `components.list`, `components.summary`, `components.search`, `components.get`, `templates.list`, and `templates.get` to discover known registry items, component ids, props, templates, insertability, and content model. Read `webstudio://project/components` only when those focused tools are insufficient.\n- Component/template registry items use a shadcn-compatible top-level shape plus Webstudio-specific superset metadata in `meta`. They are for Builder/MCP discovery, not a published shadcn install registry yet.\n- Known components with `contentModel.category: "none"` are not standalone-insertable; insert their root component template instead so required providers/parents are included.\n- Unknown custom component ids are a low-level extension mechanism, not a discovery fallback. Agents must not synthesize them.\n\n## Make a region editable in Content mode\n\nCommands:\n\n- MCP tool: insert-component {"parentInstanceId":"<instanceId>","component":"ws:block"}\n- MCP tool: inspect-instance {"instanceId":"<instanceId>","include":["children"]}\n\nNotes:\n\n- When a page will be handed to a Content-mode editor, wrap every region they should be able to edit in a Content Block (`ws:block`). Content-mode editors can edit text and supported props only in Content Block descendants. Content outside those blocks remains read-only, even when it looks like ordinary editable text.\n- Put reusable insertable options inside the Content Block\'s `ws:block-template` child. A template is source material, not editor content: editors cannot edit or delete it directly. When an editor inserts a template, its copy becomes a direct child of the Content Block and is editable.\n- Before handing off a page, verify with `inspect-instance` that the intended text, images, and links are inside a Content Block, and that templates include all required styling because Content-mode editors cannot use the Style panel.\n\n## Move elements\n\nCommands:\n\n- MCP tool: move-instance {"moves":"moves.json contents"}\n\nNotes:\n\n- Use `position: "end"` to append an instance. Repeating this for A and then B preserves the final order A, B.\n- A numeric `insertIndex` addresses the target parent\'s children before the moved instance is removed. Use it for exact placement; do not calculate the last index to append.\n- Moves in one `moves` array are applied sequentially in array order.\n\n## Clone element subtree\n\nCommands:\n\n- MCP tool: clone-instance {"sourceInstanceId":"<instanceId>","targetParentInstanceId":"<targetParentId>"}\n\n## Delete element subtree\n\nCommands:\n\n- MCP tool: delete-instance {"instanceIds":["<instanceId>"]}\n\n## List text/expression children\n\nCommands:\n\n- MCP tool: list-texts {"pagePath":"/"}\n\n## Update text child\n\nCommands:\n\n- MCP tool: update-text {"instanceId":"<instanceId>","childIndex":0,"text":"Launch faster"}\n\n## Replace bounded literal text\n\nCommands:\n\n- MCP tool: replace-text {"find":"Start free","replace":"Get started","match":"exact","pagePath":"/pricing","limit":20}\n\nNotes:\n\n- This changes only literal text children, never expression children. Scope it to pagePath or pageId and set a limit before a broad replacement.\n\n## Replace bounded static prop text\n\nCommands:\n\n- MCP tool: replace-prop-text {"find":"old.example.com","replace":"www.example.com","match":"substring","names":["href","code"],"limit":20}\n\nNotes:\n\n- This changes only static string props such as href, alt, aria-label, title, and HTML embed code. It never changes expressions, resources, actions, assets, or other dynamic bindings. Use names or instanceIds and a limit to narrow the change.\n\n## Replace bounded resource text\n\nCommands:\n\n- MCP tool: replace-resource-text {"find":"api.old.example.com","replace":"api.example.com","fields":["url"],"limit":20}\n\nNotes:\n\n- This changes resource names and fixed URL literals only. It skips dynamic URL expressions, headers, search parameters, request bodies, and GraphQL query code.\n\n## Update props\n\nCommands:\n\n- MCP tool: update-props {"updates":"props.json contents"}\n- MCP tool: replace-prop-text {"find":"Old label","replace":"New label","names":["aria-label","title"],"limit":20}\n\nNotes:\n\n- Use this for fixed prop values such as `aria-label`, `alt`, `id`, static `href`, and other direct string/number/boolean/json prop values.\n\n## Add JSON-LD structured data\n\nCommands:\n\n- MCP tool: components.get {"component":"JsonLd"}\n- MCP tool: insert-component {"parentInstanceId":"<headSlotInstanceId>","component":"JsonLd"}\n- MCP tool: update-props {"updates":[{"instanceId":"<jsonLdInstanceId>","name":"code","type":"string","value":"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Organization\\",\\"name\\":\\"Acme\\"}"}]}\n- MCP tool: bind-props {"bindings":[{"instanceId":"<jsonLdInstanceId>","name":"code","binding":{"type":"expression","value":"({ \'@context\': \'https://schema.org\', \'@type\': \'Article\', headline: post.title })"}}]}\n- MCP tool: audit {"scopes":["seo"],"pagePath":"/"}\n\nNotes:\n\n- Prefer placing `JsonLd` inside `HeadSlot`.\n- For fixed structured data, store `code` as a JSON object or array encoded as a compact string. The Builder formats it for editing.\n- For structured data containing runtime values, use `bind-props` with an expression that evaluates directly to an object or array. Do not call `JSON.stringify` or assemble JSON with string concatenation. Webstudio stores the expression as source text, evaluates it at runtime, and the `JsonLd` component validates and serializes the resulting value.\n- The semantic prop update rejects malformed JSON and structurally invalid fixed JSON-LD with a precise JSON path.\n- The SEO audit also warns about a missing top-level `@context`, unknown or superseded Schema.org terms, properties unsupported by the supplied type, and incompatible primitive value types.\n- Schema.org vocabulary findings are warnings because custom vocabularies and extensions remain valid. Dynamic JSON-LD is marked as skipped for rendered validation.\n- Do not use bindings just to set static text.\n\n## Delete props\n\nCommands:\n\n- MCP tool: delete-props {"deletions":"props.json contents"}\n\n## Bind props to expressions/resources/actions\n\nCommands:\n\n- MCP tool: bind-props {"bindings":"bindings.json contents"}\n\nNotes:\n\n- Use this only when the prop should remain dynamic: expression, resource, action, or an existing scoped runtime context value such as `system`.\n- For a fixed string value, use `update-props` with `type:"string"` and a direct `value` instead.\n\n## Read styles\n\nCommands:\n\n- MCP tool: get-styles {"instanceIds":["<instanceId>"],"includeTokens":true}\n\n## Update local styles\n\nCommands:\n\n- MCP tool: update-styles {"updates":"styles.json contents"}\n\n## Delete local styles\n\nCommands:\n\n- MCP tool: delete-styles {"deletions":"styles.json contents"}\n\n## Replace matching style values\n\nCommands:\n\n- MCP tool: replace-styles {"property":"color","fromValue":{"type":"keyword","value":"red"},"toValue":{"type":"keyword","value":"blue"}}\n\n## List design tokens\n\nCommands:\n\n- MCP tool: list-design-tokens {}\n- MCP tool: list-design-tokens {"withUsage":true}\n- MCP tool: list-design-tokens {"verbose":true}\n\nNotes:\n\n- The default response is compact and includes token id, name, declaration count, and optional usage count.\n- Use `verbose:true` only when you need the full inline style declarations.\n\n## Create design tokens\n\nCommands:\n\n- MCP tool: create-design-token {"tokens":"tokens.json contents"}\n\n## Update design token styles\n\nCommands:\n\n- MCP tool: update-design-token-styles {"designTokenId":"<tokenId>","updates":"styles.json contents"}\n\n## Delete design token styles\n\nCommands:\n\n- MCP tool: delete-design-token-styles {"designTokenId":"<tokenId>","deletions":"styles.json contents"}\n\n## Attach design token to instances\n\nCommands:\n\n- MCP tool: attach-design-token {"designTokenId":"<tokenId>","instanceIds":"instances.json contents"}\n\n## Detach design token from instances\n\nCommands:\n\n- MCP tool: detach-design-token {"designTokenId":"<tokenId>","instanceIds":"instances.json contents"}\n\n## Extract design token from local styles\n\nCommands:\n\n- MCP tool: extract-design-token {"instanceIds":["<instanceId>"],"name":"Brand Primary","removeLocalProps":["color"]}\n\n## List CSS variables\n\nCommands:\n\n- MCP tool: list-css-variables {"withUsage":true}\n\n## Define CSS variables\n\nCommands:\n\n- MCP tool: define-css-variable {"vars":{"--color-primary":"#2d3748","--color-accent":"#e53e3e","--space-card":"1.5rem"},"overwrite":true}\n\nNotes:\n\n- Define or overwrite multiple CSS variables atomically by including every name and value in one `vars` object.\n- Pass colors as CSS strings. Structured `hex` color components use normalized values from `0` to `1`, not `0` to `255`.\n\n## Delete CSS variables\n\nCommands:\n\n- MCP tool: delete-css-variable {"names":["--color-primary","--color-accent","--space-card"],"force":true}\n\nNotes:\n\n- Delete multiple CSS variables atomically by including every name in one `names` array. Destructive MCP calls still require the returned confirmation token before they commit.\n\n## Rewrite CSS variable references\n\nCommands:\n\n- MCP tool: rewrite-css-variable-refs {"map":"variables.json contents"}\n\n## List data variables\n\nCommands:\n\n- MCP tool: list-variables {}\n- MCP tool: list-variables {"scopeInstanceId":"<instanceId>"}\n\nNotes:\n\n- Data variables live in the internal `dataSources` namespace.\n- For raw `snapshot`, request the public `variables` namespace rather than the internal `dataSources` name. Raw patch payloads still use `dataSources` when applying direct changes.\n- Scope variables to the instance where they should become available. Descendants can use them in expressions, and nested variables with the same name mask outer variables.\n\n## Create data variable\n\nCommands:\n\n- MCP tool: create-variable {"scopeInstanceId":"<instanceId>","name":"title","value":{"type":"string","value":"Hello"}}\n- MCP tool: create-variable {"scopeInstanceId":"<instanceId>","name":"count","value":{"type":"number","value":3}}\n- MCP tool: create-variable {"scopeInstanceId":"<instanceId>","name":"featured","value":{"type":"boolean","value":true}}\n- MCP tool: create-variable {"scopeInstanceId":"<instanceId>","name":"tags","value":{"type":"json","value":["news","product"]}}\n- MCP tool: create-variable {"scopeInstanceId":"<instanceId>","name":"filters","value":{"type":"json","value":{"tag":"news"}}}\n\nNotes:\n\n- Data variable values support `string`, `number`, `boolean`, and `json`. Use `json` for all arrays and objects.\n- Parameters are internal scoped runtime values provided by pages, collections, or components. They are not a public authoring surface: do not create, update, or delete parameter records. Use data variables/resources for user-authored data, and reference documented context values such as `system` only where they are already in scope.\n\n## Update data variable\n\nCommands:\n\n- MCP tool: update-variable {"dataSourceId":"<variableId>","values":{"value":{"type":"json","value":{"count":1}}}}\n\n## Delete data variable\n\nCommands:\n\n- MCP tool: delete-variable {"dataSourceId":"<variableId>"}\n\n## List resources\n\nCommands:\n\n- MCP tool: list-resources {}\n- MCP tool: list-resources {"scopeInstanceId":"<instanceId>"}\n\n## Create resource\n\nCommands:\n\n- MCP tool: create-resource {"resource":{"name":"Posts","method":"get","url":"https://api.example.com/posts","headers":[]}}\n- MCP tool: create-resource {"resource":{"name":"Posts","method":"get","url":"\'https://api.example.com/posts?tag=\' + filters.tag","headers":[]},"scopeInstanceId":"<instanceId>","dataSourceName":"posts"}\n- MCP tool: create-resource {"resource":{"name":"Filtered Posts","method":"get","url":"https://api.example.com/posts","searchParams":[{"name":"tag","value":"filters.tag"},{"name":"source","value":{"type":"literal","value":"website"}}],"headers":[{"name":"Authorization","value":"\'Bearer \' + auth.token"}]},"scopeInstanceId":"<instanceId>","dataSourceName":"posts"}\n- MCP tool: create-resource {"resource":{"name":"Post GraphQL","control":"graphql","method":"post","url":"https://api.example.com/graphql","headers":[{"name":"Content-Type","value":{"type":"literal","value":"application/json"}}],"body":"{ query: \'query Post($slug: String!) { post(slug: $slug) { title } }\', variables: { slug: system.params.slug } }"},"scopeInstanceId":"<instanceId>","dataSourceName":"post"}\n- MCP tool: create-resource {"resource":{"name":"Current Date","control":"system","method":"get","url":"/$resources/current-date","headers":[]},"scopeInstanceId":"<instanceId>","dataSourceName":"currentDate"}\n\nNotes:\n\n- Resource `url` accepts plain fixed URLs and paths such as `https://api.example.com/posts` and `/$resources/current-date`.\n- Resource `url` can also be a JavaScript expression when it is computed, such as `"https://api.example.com/posts?tag=" + filters.tag`.\n- Header values, search parameter values, and body accept expressions for dynamic values. For fixed text, use `{"type":"literal","value":"application/json"}`; Webstudio stores the required string expression for you.\n- Search parameter values, header values, and body expressions can read scoped variables and documented runtime context values such as `system` when they are available at the resource scope.\n- Add `scopeInstanceId` and `dataSourceName` when the resource result should be exposed as a scoped read data variable. Scoped resources are generated into the page resource `data` map and may be loaded during page rendering. Use this for read-oriented resources such as GET CMS/API data.\n- For submit/write/action resources, create the resource without `scopeInstanceId`, then bind a component prop such as a Form `action` with `bind-props` and `binding.type: "resource"`. Prop-bound resources are generated into the page resource `action` map instead of the read `data` map. Use this for POST, PUT, DELETE, webhooks, GraphQL submissions, and other explicit action flows.\n- Resource `method` can be `get`, `post`, `put`, or `delete`. Use GET for read data, POST for creates/GraphQL/webhooks/form submissions, PUT for full updates or replacements, and DELETE for deletion actions.\n- Optional `control` values are `graphql` and `system`. Use `graphql` for GraphQL-style requests, usually POST with a query body. Use `system` for built-in resources such as `"/$resources/sitemap.xml"`, `"/$resources/current-date"`, and `"/$resources/assets"` and when the resource should use the built-in `system` parameter. System fields are `system.origin`, `system.pathname`, `system.params`, and `system.search`.\n\n## Update resource\n\nCommands:\n\n- MCP tool: update-resource {"resourceId":"<resourceId>","values":{"url":"https://api.example.com/posts"}}\n- MCP tool: replace-resource-text {"find":"api.old.example.com","replace":"api.example.com","fields":["url"],"limit":20}\n\n## Query Markdown assets\n\nCommands:\n\n- MCP tool: get-asset-field-catalog {}\n- MCP tool: validate-asset-query {"query":{"where":{"all":[{"field":["extension"],"operator":"eq","value":"md"},{"field":["properties","draft"],"operator":"ne","value":true}]},"limit":20}}\n- MCP tool: create-assets-resource {"name":"All assets","scopeInstanceId":"<instanceId>","dataSourceName":"assets"}\n- MCP tool: create-assets-resource {"name":"Published posts","scopeInstanceId":"<instanceId>","dataSourceName":"posts","query":{"where":{"all":[{"field":["extension"],"operator":"eq","value":{"type":"literal","value":"md"}},{"field":["properties","draft"],"operator":"ne","value":{"type":"literal","value":true}}]},"sort":[{"field":["properties","publishedAt"],"direction":"desc"}],"limit":{"type":"literal","value":20},"output":{"mode":"fields","includeMetadata":false,"fields":[["properties","title"],["properties","slug"],["properties","publishedAt"],["properties","excerpt"]]},"content":{"mode":"none"}}}\n- MCP tool: create-assets-resource {"name":"Post by slug or ID","scopeInstanceId":"<instanceId>","dataSourceName":"post","query":{"where":{"all":[{"field":["extension"],"operator":"eq","value":{"type":"literal","value":"md"}},{"field":["properties","slug"],"operator":"eq","value":"system.params.slug"}]},"limit":{"type":"literal","value":1},"output":{"mode":"fields","includeMetadata":false,"fields":[["properties","title"],["properties","publishedAt"]]},"content":{"mode":"markdown-body-ref"}}}\n- MCP tool: list-assets-resources {}\n- MCP tool: get-assets-resource {"resourceId":"<resourceId>"}\n- MCP tool: update-assets-resource {"resourceId":"<resourceId>","values":{"query":null}}\n- MCP tool: preview-asset-query {"query":{"where":{"all":[{"field":["extension"],"operator":"eq","value":"md"},{"field":["properties","slug"],"operator":"eq","value":"hello-world"}]},"limit":1,"output":{"mode":"fields","includeMetadata":false,"fields":[["properties","title"]]},"content":{"mode":"markdown-body-ref"}}}\n\nNotes:\n\n- Read the field catalog before authoring unfamiliar queries. It includes dynamic schema-less frontmatter paths such as `properties.author.name`, observed types, optionality, and mixed-type state without downloading Markdown files.\n- Minimize the deployed content database by using `output.mode:"fields"` and selecting only fields the rendered page needs. Keep `includeMetadata:false` unless the rendered value needs file metadata such as name, path, MIME type, or creation date, and avoid `output.mode:"all"` as a convenience default. Query diagnostics are returned separately and do not require metadata output. Filters and sorting may still require their referenced fields in the database.\n- Every reachable Assets data source contributes to the shared published database. Keep one final resource per rendered query. Update an existing scoped resource rather than creating a placeholder, preview copy, or repair replacement, and remove obsolete duplicates.\n- Keep bounded overview filters, limits, and offsets literal and add a deterministic ID tie-breaker to the sort. With `content.mode:"none"`, compilation can materialize the small overview result instead of retaining its output fields across every candidate document. Use runtime expressions only for genuinely dynamic values such as the detail slug.\n- Combine filters with `where.all` (AND) and `where.any` (OR), including nested groups. Filter values, limit, and offset on a saved resource may be Webstudio expressions evaluated at render time. Preview queries use concrete JSON values.\n- Query Markdown files directly and use `content.mode:"markdown-body-ref"` when rendering their bodies. The published database retains metadata and a document reference, then fetches only the selected Markdown files from Asset storage at runtime; it does not embed their bodies. Filter and paginate before content is loaded.\n- `full` and bounded `range` continue to request embedded file bytes. Use them only when the caller requires the complete source or a byte range.\n- In Markdown, reference sibling Assets with conventional relative URLs such as `../images/hero.png`. Deferred `markdown-body-ref` content resolves matching files against the Markdown file\'s folder and emits the correct Builder or published Asset URL. Keep external URLs absolute.\n- Markdown Embed renders sanitized authored HTML for figures, captions, audio, video, and iframes. Scripts, inline event handlers, `srcdoc`, and unsafe URL protocols are removed. Executable component composition remains separate future MDX work.\n- Preview each query with concrete values before saving it. Inspect `__diagnostics__.query` for the temporary query-only footprint and `__diagnostics__.database` for the merged database built from all reachable Assets queries. Only `database.usedBytes` counts toward `database.maxBytes`; query sizes must not be summed and are not separate allowances. Compare `usedBytes`, `unboundedBytes`, and `truncated` within both scopes. A completed Markdown blog should include every source document without truncation, contain no embedded Markdown bodies, and retain only its intended materialized overview query. When merged usage approaches the limit, remove duplicate reachable resources first, then unused output fields, then narrow candidate files.\n- Read Assets from the ID-keyed map at `<dataSourceName>.data`, with collection information such as `totalCount` and `hasMore` at `<dataSourceName>.meta`. Bind a listing Collection to `posts.data` and a one-result detail Collection to `post.data`. On each value, selected file fields such as `id`, `name`, and `extension` are top-level, Markdown frontmatter and JSON fields are under `properties`, and a resolved Markdown body is at `content.text`.\n- Assets has one response shape and always executes a structured query. Omit `query` to use the default query, which selects URL and optional image dimensions. Provide query configuration to control filtering, sorting, pagination, selected fields, or file content. Set `values.query:null` to restore the default query.\n- `create-assets-resource` and `update-assets-resource` are the semantic authoring path. Do not construct the internal query URL, headers, or body expression manually.\n- The shared metadata index is maintained automatically and is emitted only when a configured Assets resource is reachable.\n\n## Delete resource\n\nCommands:\n\n- MCP tool: delete-resource {"resourceId":"<resourceId>"}\n\n## List assets\n\nCommands:\n\n- MCP tool: list-assets {"withUsage":true}\n- MCP tool: list-assets {"verbose":true}\n\nNotes:\n\n- Compact results include each asset\'s folder id. Use `verbose:true` to include complete records for a page of assets, or `get-asset` to read one complete record including description, folder, creation time, and image/font metadata.\n- Image asset descriptions are the default alt text for asset-backed Image components.\n- To generate missing descriptions, inspect the image in its rendered page or asset source, write a concise description of its purpose, and save it on the asset rather than duplicating it on each Image instance.\n\n## Get asset\n\nCommands:\n\n- MCP tool: get-asset {"assetId":"<assetId>"}\n\n## List asset folders\n\nCommands:\n\n- MCP tool: list-asset-folders {}\n\n## Create asset folder\n\nCommands:\n\n- MCP tool: create-asset-folder {"name":"Marketing"}\n- MCP tool: create-asset-folder {"name":"Photos","parentId":"<parentFolderId>"}\n\n## Update asset folder\n\nCommands:\n\n- MCP tool: update-asset-folder {"folderId":"<folderId>","values":{"name":"Brand"}}\n- MCP tool: update-asset-folder {"folderId":"<folderId>","values":{"parentId":"<parentFolderId>"}}\n- MCP tool: update-asset-folder {"folderId":"<folderId>","values":{"parentId":null}}\n\nNotes:\n\n- Updating `parentId` is the folder equivalent of cut and paste. Use `null` to move a folder to Root.\n\n## Duplicate asset folder\n\nCommands:\n\n- MCP tool: duplicate-asset-folder {"folderId":"<folderId>"}\n- MCP tool: duplicate-asset-folder {"folderId":"<folderId>","parentId":"<targetFolderId>"}\n\nNotes:\n\n- Duplication recursively copies descendant folders and assets. This is the folder equivalent of copy and paste.\n\n## Delete asset folder\n\nCommands:\n\n- MCP tool: delete-asset-folder {"folderId":"<folderId>"}\n\nNotes:\n\n- Deleting a folder recursively deletes its descendant folders and assets.\n\n## Update asset metadata\n\nCommands:\n\n- MCP tool: update-asset {"assetId":"<assetId>","values":{"description":"Team collaborating around a whiteboard"}}\n- MCP tool: update-asset {"assetId":"<fontAssetId>","values":{"meta":{"family":"Rajdhani","style":"normal","weight":600}}}\n\nNotes:\n\n- Use an empty description only when the image is intentionally decorative.\n- Updating an image asset description updates the default alt text wherever that asset is used with an asset-backed alt prop.\n- Font metadata updates merge with the detected metadata and are validated before committing; use this to correct a family, style, or weight after upload.\n\n## Generate missing image descriptions with an agent\n\nCommands:\n\n- MCP tool: audit {"scopes":["accessibility"],"verbose":true}\n- MCP tool: set-image-descriptions {"updates":[{"assetId":"hero-id","description":"Team collaborating around a whiteboard"},{"assetId":"texture-id","decorative":true}]}\n- MCP tool: audit {"scopes":["accessibility"]}\n\nNotes:\n\n- Start from `missing-image-description` findings. Inspect each image in its rendered page context before writing text.\n- The vision-capable agent generates the wording; the CLI validates and stores it but does not contain its own vision model.\n- Use `decorative:true` only when the image adds no information. This intentionally stores an empty description so later audits do not report it as missing.\n- Re-run the accessibility audit after the update. Asset-backed Image components use the saved asset description as their default alt text.\n\n## Manage fonts\n\nCommands:\n\n- MCP tool: list-fonts {"includeSystem":true}\n- MCP tool: list-assets {"type":"font"}\n- MCP tool: upload-asset {"asset":{"name":"acme-sans.woff2","type":"font","format":"woff2","meta":{"family":"Acme Sans","style":"normal","weight":400}},"assetsDir":".webstudio/assets"}\n- MCP tool: update-styles {"updates":"styles.json contents"}\n\nNotes:\n\n- Use `list-fonts` to discover uploaded families and system stacks. Upload/delete fonts through the existing asset tools, then apply a family with a `font-family` style declaration.\n\n## Upload one asset\n\nCommands:\n\n- MCP tool: upload-asset {"asset":{"name":"image.png","type":"image","format":"png","meta":{"width":1200,"height":630}},"assetsDir":".webstudio/assets"}\n- MCP tool: upload-asset {"asset":{"name":"image.png","type":"image","format":"png","folderId":"<folderId>","meta":{"width":1200,"height":630}},"assetsDir":".webstudio/assets"}\n\n## Upload asset batch\n\nCommands:\n\n- MCP tool: upload-assets {"assets":[{"name":"image.png","type":"image","format":"png","meta":{"width":1200,"height":630}}],"assetsDir":".webstudio/assets"}\n\n## Duplicate asset\n\nCommands:\n\n- MCP tool: duplicate-asset {"assetId":"<assetId>"}\n- MCP tool: duplicate-asset {"assetId":"<assetId>","folderId":"<targetFolderId>"}\n- MCP tool: duplicate-asset {"assetId":"<assetId>","folderId":null}\n\nNotes:\n\n- Duplication is the asset equivalent of copy and paste. Updating `folderId` is the equivalent of cut and paste; use `null` for Root.\n\n## Download asset\n\nCommands:\n\n- MCP tool: download-asset {"assetId":"<assetId>"}\n\n## Find asset usage\n\nCommands:\n\n- MCP tool: find-asset-usage {"assetId":"<assetId>"}\n\n## Replace asset references\n\nCommands:\n\n- MCP tool: replace-asset {"fromAssetId":"<oldAssetId>","toAssetId":"<newAssetId>"}\n\n## Delete assets\n\nCommands:\n\n- MCP tool: delete-asset {"assetIdsOrPrefixes":["<assetId>"],"force":true}\n\n## Publish project\n\nCommands:\n\n- webstudio publish deploy --target production --json\n\n## List publishes\n\nCommands:\n\n- webstudio publish list --json\n\n## Check publish job\n\nCommands:\n\n- webstudio publish status --job <buildId> --json\n\n## Unpublish\n\nCommands:\n\n- webstudio publish unpublish --target production --confirm --json\n\n## List domains\n\nCommands:\n\n- webstudio domains list --json\n\n## Create domain\n\nCommands:\n\n- webstudio domains create --domain example.com --json\n\n## Update domain\n\nCommands:\n\n- webstudio domains update --domain-id <domainId> --domain www.example.com --json\n\n## Delete domain\n\nCommands:\n\n- webstudio domains delete --domain-id <domainId> --confirm --json\n\n## Verify domain\n\nCommands:\n\n- webstudio domains verify --domain-id <domainId> --json\n\n## Make arbitrary store-level changes\n\nCommands:\n\n- MCP tool: inspect {}\n- MCP tool: snapshot {"include":["<namespace>"]}\n- MCP tool: apply-patch {"baseVersion":"<version>","transactions":"patch.json contents"}\n\nNotes:\n\n- Use only when no semantic command exists.\n\n## Manage marketplace metadata\n\nCommands:\n\n- MCP tool: get-marketplace-product {}\n- MCP tool: update-marketplace-product {"category":"pageTemplates","name":"Acme Template","thumbnailAssetId":"asset-id","author":"Acme Studio","email":"hello@example.com","website":"https://example.com","issues":"","description":"Reusable template project for Acme landing pages."}\n- MCP tool: submit-marketplace-product {"acknowledgePublicSubmission":true}\n\nPatch namespaces:\n\n- marketplaceProduct\n\n## Search and inspect safely\n\nCommands:\n\n- MCP tool: search-project {"query":"pricing"}\n- MCP tool: search-project {"query":"api.example.com","scopes":["resources"]}\n- MCP tool: list-instances {"pagePath":"/","maxDepth":5}\n- MCP tool: inspect-instance {"instanceId":"<instanceId>","include":["props","styles","children"]}\n- MCP tool: list-texts {"pagePath":"/"}\n- MCP tool: list-assets {"withUsage":true}\n- MCP tool: find-asset-usage {"assetId":"<assetId>"}\n- MCP tool: snapshot {"include":["pages","instances","props","resources","assets"]}\n\nNotes:\n\n- Use `search-project` for query-driven lookup across labels, text, prop values, resource URLs, asset metadata, and styles. Use `audit` for project health findings.\n\n## Audit project quality\n\nCommands:\n\n- webstudio audit --json\n- webstudio audit --scopes accessibility --scopes seo --json\n- webstudio audit --page-path /pricing --json\n- webstudio audit --scopes accessibility --verbose --json\n- webstudio audit --rendered --page-path /pricing --json\n- webstudio audit --rendered --route-example post=/blog/hello --json\n- webstudio audit --rendered --image-domain images.example.com --json\n- MCP tool: audit {}\n- MCP tool: audit {"scopes":["accessibility","security"],"severities":["error","warning"]}\n- MCP tool: audit {"scopes":["accessibility"],"verbose":true}\n- MCP tool: audit {"scopes":["craft"],"verbose":true}\n- MCP tool: audit {"rendered":true,"verbose":true}\n\nNotes:\n\n- With no scopes, `audit` checks accessibility, security, SEO, performance settings, unused assets, ineffective Collection styles, non-GET resources exposed as render-time data, and unused or duplicate style data.\n- Craft is opt-in and read-only. Run `audit` with `scopes:["craft"]` to detect whether the project is not using Craft, partially compatible, or compatible with the versioned Craft 1.2 profile. `profileStatuses` includes the University-doc provenance and the smallest safe next action. The audit never installs Craft or changes a non-Craft project.\n- The `performance` scope reports disabled atomic CSS generation. A rendered audit also measures broken, eager below-fold, and oversized images, browser-marked render-blocking resources, and legacy font formats.\n- Rendered image and resource metrics run only when the selected scopes include `performance`; responsive layout dimensions remain available whenever `rendered:true` is requested.\n- Compact findings include stable ids, severity, message, and location. Use `--verbose` or `{"verbose":true}` for evidence, explanation, suggested remediation, skipped-check details, and manual-check workflows.\n- `summary` counts all findings before severity filtering and pagination.\n- `contractVersion` identifies the audit response contract. Handle a new value before assuming existing fields retain the same meaning.\n- Expression-, resource-, and parameter-backed values that cannot be checked reliably appear in `skippedChecks`; they are not treated as passing or failing.\n- Page filters apply to page-owned accessibility, security, and SEO checks. Asset and style usage remain project-wide to avoid false unused findings.\n- Continue paginated results with `cursor`. Restart the audit if the project version changes.\n- Verbose skipped-check and manual-check details are included on the first findings page only; their total counts remain available on every page.\n- `manualChecks` describes responsive, hierarchy, and contrast checks that require preview screenshots and vision.\n- Focused audits return only manual checks relevant to their selected scopes.\n- In a long-lived MCP session, `{"rendered":true}` reuses preview and screenshot\n tools to capture every static page at mobile, desktop, and Builder breakpoint\n edges. Compact output reports rendered check/issue/failure counts; verbose\n output includes screenshot paths and measured layout dimensions.\n- Dynamic route templates are skipped unless `--route-example <pageId>=<path>`\n (or MCP `routeExamples`) supplies a concrete path. The path must not contain\n unresolved `:` or `*` parameters.\n- Plans above 120 captures return a short-lived confirmation token. Review the\n unchanged plan, then rerun with `--confirm-large-run` and\n `--confirmation-token`.\n- Detailed rendered evidence is stored in a versioned manifest under\n `.webstudio/audits`; compact output includes its path and screenshot count.\n- Rendered checks also report broken images, eager images below the fold, and\n image sources more than 2x their rendered dimensions in both axes, including\n Webstudio instance ids and measured dimensions when available.\n- Rendered checks include sanitized Resource Timing evidence and report\n browser-marked render-blocking resources plus legacy `.ttf`, `.otf`, and\n `.woff` fonts without applying a universal transfer-size threshold.\n- Fix findings through semantic mutation commands, then rerun `audit` to confirm their deterministic finding ids disappeared.\n\n## Verify dynamic bindings\n\nCommands:\n\n- webstudio verify-bindings --json\n- MCP tool: verify-bindings {"pagePath":"/pricing"}\n- MCP tool: verify-bindings {"instanceId":"<instanceId>","limit":50}\n\nNotes:\n\n- Statically checks persisted text expressions, expression/action/resource/parameter props, resource expressions, and page metadata.\n- Findings distinguish invalid syntax, unknown or out-of-scope variables, stale internal data-source ids, and missing resource or parameter references.\n- Page and instance filters can be combined when the instance belongs to the selected page. Continue findings with `cursor`.\n- This operation does not resolve rendered values or execute external resources. Preview representative loading, empty, error, and populated states after static findings are fixed.\n\n## Refactor targeted content\n\nCommands:\n\n- MCP tool: list-instances {"pagePath":"/"}\n- MCP tool: list-texts {"pagePath":"/"}\n- MCP tool: update-text {"instanceId":"<instanceId>","childIndex":0,"text":"Launch faster"}\n- MCP tool: replace-text {"find":"Old headline","replace":"New headline","match":"exact","pagePath":"/pricing","limit":20}\n- MCP tool: update-props {"updates":"props.json contents"}\n- MCP tool: update-page {"pageId":"<pageId>","values":{"title":"Pricing","meta":{"description":"Plans"}}}\n- MCP tool: update-resource {"resourceId":"<resourceId>","values":{"url":"https://api.example.com/posts"}}\n- MCP tool: replace-asset {"fromAssetId":"<oldAssetId>","toAssetId":"<newAssetId>"}\n- MCP tool: replace-styles {"property":"color","fromValue":{"type":"keyword","value":"red"},"toValue":{"type":"keyword","value":"blue"}}\n- MCP tool: rewrite-css-variable-refs {"map":"variables.json contents"}\n\nNotes:\n\n- Use focused reads first, then mutate only matching instances, props, metadata, resource URLs, assets, or style references. Use `replace-text` for bounded literal text changes, `replace-prop-text` for bounded static prop text, `replace-resource-text` for fixed resource names/URLs, and `update-text` for one known child or expressions.\n\n## Optimize existing project\n\nCommands:\n\n- MCP tool: list-pages {}\n- MCP tool: list-folders {}\n- MCP tool: update-page {"pageId":"<pageId>","values":{"title":"Pricing","meta":{"description":"Plans"}}}\n- MCP tool: update-props {"updates":"props.json contents"}\n- MCP tool: list-breakpoints {}\n- MCP tool: update-breakpoint {"breakpointId":"tablet","values":{"maxWidth":1023}}\n- MCP tool: get-styles {"instanceIds":["<instanceId>"],"includeTokens":true}\n- MCP tool: update-styles {"updates":"styles.json contents"}\n- MCP tool: attach-design-token {"designTokenId":"<tokenId>","instanceIds":"instances.json contents"}\n- MCP tool: update-project-settings {"meta":{"siteName":"Acme"}}\n\nNotes:\n\n- Use this for SEO metadata, accessibility labels, responsive behavior, token consistency, and project settings.\n\n## Connect external data\n\nCommands:\n\n- MCP tool: create-variable {"scopeInstanceId":"<instanceId>","name":"title","value":{"type":"string","value":"Hello"}}\n- MCP tool: create-variable {"scopeInstanceId":"<instanceId>","name":"tags","value":{"type":"json","value":["news","product"]}}\n- MCP tool: create-variable {"scopeInstanceId":"<instanceId>","name":"filters","value":{"type":"json","value":{"tag":"news"}}}\n- MCP tool: create-resource {"resource":{"name":"Posts","method":"get","url":"https://api.example.com/posts","searchParams":[{"name":"tag","value":"filters.tag"},{"name":"source","value":{"type":"literal","value":"website"}}],"headers":[]},"scopeInstanceId":"<instanceId>","dataSourceName":"posts"}\n- MCP tool: create-resource {"resource":{"name":"Post GraphQL","control":"graphql","method":"post","url":"https://api.example.com/graphql","headers":[{"name":"Content-Type","value":{"type":"literal","value":"application/json"}}],"body":"{ query: \'query Post($slug: String!) { post(slug: $slug) { title } }\', variables: { slug: system.params.slug } }"},"scopeInstanceId":"<instanceId>","dataSourceName":"post"}\n- MCP tool: create-resource {"resource":{"name":"Current Date","control":"system","method":"get","url":"/$resources/current-date","headers":[]},"scopeInstanceId":"<instanceId>","dataSourceName":"currentDate"}\n- MCP tool: update-resource {"resourceId":"<resourceId>","values":{"url":"https://api.example.com/posts"}}\n- MCP tool: bind-props {"bindings":"bindings.json contents"}\n- MCP tool: insert-fragment {"parentInstanceId":"<instanceId>","fragment":"<ws.collection>{/_ collection content _/}</ws.collection>"}\n\nNotes:\n\n- Use this for CMS sections, blog listings, Ghost/headless CMS pages, n8n-style integrations, and API URLs built from variables.\n- For read data, expose GET resources as scoped data variables with `scopeInstanceId`/`dataSourceName` and read the loaded result from the resource result wrapper, usually `.data`.\n- For writes, webhooks, GraphQL submissions, and deletes, prefer unscoped resources bound to Form `action` props so they become action resources instead of auto-loaded read resources.\n- Use direct props for fixed values and prop bindings only when a prop must read a data variable, resource, action, or documented runtime context value such as `system`.\n\n## Render an array or object as repeated content\n\nCommands:\n\n- MCP tool: insert-collection {"parentInstanceId":"<instanceId>","data":{"type":"expression","value":"posts.data.items"},"itemFragment":"<ws.element ws:tag=\'article\'><ws.element ws:tag=\'h2\'>{expression`collectionItem.title`}</ws.element></ws.element>"}\n- MCP tool: inspect-instance {"instanceId":"<collectionId>","include":["props","bindings","children"]}\n\nNotes:\n\n- Use Collection whenever an array or object from a resource or data variable should render a list, grid, cards, table rows, options, tabs, or other repeated UI.\n- Pass `insert-collection` the complete array or object. Do not pass the resource response wrapper or one indexed item. External resource arrays are commonly nested under the scoped resource result\'s `data` field or deeper.\n- Pass one repeated-item Webstudio JSX root. The command creates the Collection, its private current-item/current-key parameters, the iterable binding, and descendant item bindings atomically.\n- Collection renders the item root once for every entry. Use `expression` values such as `collectionItem.title` in descendant text and props. Object iteration also exposes `collectionItemKey`.\n- Wrap multiple repeated sibling instances in one Element inside Collection.\n- For repeated Radix items such as accordion items, tabs, or menu options, bind a stable unique id or slug to every required `value` prop.\n- See the [Collection documentation](https://docs.webstudio.is/university/core-components/collection) for the equivalent Builder workflow.\n\n## Support dynamic runtime behavior\n\nCommands:\n\n- MCP tool: integrate-runtime-ui {"parentInstanceId":"<instanceId>","resources":[{"resource":{"name":"Seats","method":"get","url":"https://api.example.com/seats","headers":[]},"dataSourceName":"Seats","exposeAsDataSource":true}],"structure":{"type":"collection","data":{"type":"expression","value":"Seats.data"},"itemFragment":{"children":[{"type":"id","value":"seat"}],"instances":[{"type":"instance","id":"seat","component":"Text","children":[{"type":"expression","value":"collectionItem.label"}]}],"props":[],"dataSources":[],"resources":[],"styleSources":[],"styleSourceSelections":[],"styles":[],"breakpoints":[],"assets":[]}},"retainedBehavior":[{"instanceId":"<scriptInstanceId>","responsibility":"Seat selection behavior"}]}\n- MCP tool: update-props {"updates":"props.json contents"}\n- MCP tool: bind-props {"bindings":"bindings.json contents"}\n- MCP tool: create-resource {"resource":{"name":"Seats","method":"get","url":"https://api.example.com/seats","headers":[]}}\n- MCP tool: snapshot {"include":["instances","props","resources"]}\n- MCP tool: apply-patch {"baseVersion":"<version>","transactions":"patch.json contents"}\n\nNotes:\n\n- Use `integrate-runtime-ui` to create variables/resources, insert one editable fragment or Collection, and add safe data bindings in one transaction.\n- List existing script-owned responsibilities under `retainedBehavior`. The operation preserves those instances and never evaluates or accepts replacement script bodies.\n- `unsupportedConversions` records behavior that cannot be represented safely. Dry-run returns the complete transaction and the same retained/unsupported report without changing the project.\n- New actions and HtmlEmbed scripts are intentionally rejected. Create normal editable components and data bindings; keep opaque runtime behavior in existing script instances.\n\n## Build authenticated pages\n\nCommands:\n\n- MCP tool: meta.guide {"brief":"Build a Supabase-authenticated account page"}\n- MCP tool: create-page {"name":"Account","path":"/account"}\n- MCP tool: update-page {"pageId":"<pageId>","values":{"meta":{"auth":{"method":"basic","login":"<login>","password":"<password>"}}}}\n- MCP tool: create-resource {"resource":{"name":"Session","method":"get","url":"https://api.example.com/session","headers":[]}}\n- MCP tool: create-variable {"scopeInstanceId":"<instanceId>","name":"user","value":{"type":"json","value":{}}}\n- MCP tool: update-props {"updates":"props.json contents"}\n- MCP tool: bind-props {"bindings":"bindings.json contents"}\n\nNotes:\n\n- Inspect and reuse the project\'s existing auth convention before authoring. Do\n not add a second provider or session model implicitly.\n- Model signed-out, loading, signed-in, and failed-auth states explicitly.\n- Never store credentials, service-role keys, refresh tokens, private session\n values, or authenticated response bodies in project data, command output,\n screenshots, agent instructions, or error reports. Privileged provider calls\n and authorization enforcement belong server-side.\n- Basic auth is semantic today. Provider-specific Supabase/Firebase setup still\n uses the existing resource, variable, prop, binding, and embed tools; there is\n no provider-specific installer.\n\n## Generate from design input\n\nCommands:\n\n- MCP tool: meta.guide {"brief":"Recreate this Figma design as a responsive page"}\n- MCP tool: create-page {"name":"Landing","path":"/landing"}\n- MCP tool: create-design-token {"tokens":"tokens.json contents"}\n- MCP tool: define-css-variable {"vars":"vars.json contents"}\n- MCP tool: list-breakpoints {}\n- MCP tool: insert-fragment {"parentInstanceId":"<instanceId>","fragment":"<ws.element ws:tag=\'section\'><ws.element ws:tag=\'p\'>Section copy</ws.element></ws.element>"}\n- MCP tool: update-styles {"updates":[{"instanceId":"<instanceId>","breakpointId":"<breakpointId-from-list-breakpoints>","property":"padding-left","value":{"type":"unit","unit":"px","value":24}}]}\n- MCP tool: preview.start {"host":"127.0.0.1","port":5173}\n- MCP tool: screenshot {"path":"/landing","output":"landing-desktop.png","viewport":{"width":1440,"height":900},"waitUntil":"load","waitForTimeout":250}\n- MCP tool: screenshot {"path":"/landing","output":"landing-mobile.png","viewport":{"width":390,"height":844},"waitUntil":"load","waitForTimeout":250}\n- MCP tool: screenshot {"baseUrl":"http://127.0.0.1:5177","path":"/landing","output":"landing-desktop.png","viewport":{"width":1440,"height":900},"waitUntil":"load","waitForTimeout":250}\n\nNotes:\n\n- Use this after the agent can inspect the supplied design. There is no direct\n Figma, screenshot, Inception, or `design.md` import command.\n- Inspect and reuse existing variables, tokens, styles, components, assets, and\n page patterns before authoring. Build semantic editable structure rather than\n flattening the design into an image or absolute-positioned approximation.\n- Verify one familiar viewport inside every distinct Builder breakpoint range,\n then run rendered audit and inspect the screenshots before completion.\n\n## Cross-project maintenance\n\nCommands:\n\n- webstudio mcp run .temp/projects.json\n- webstudio mcp run .temp/projects.json --dry-run\n- webstudio mcp run .temp/projects.json --approve-mutations --concurrency 2\n\nNotes:\n\n- Put shared `calls` and a `projects` array of independently linked project roots in the existing `mcp run` manifest. Project roots are relative to the manifest file.\n- Each project uses its own config, authentication, ProjectSession storage, checkpoint, and failure boundary. Confirmed successful calls are checkpointed. Reads can resume automatically; a mutation interrupted after dispatch is reported as ambiguous and is not replayed automatically, preventing silent duplicate writes.\n- Focus the manifest on bounded reads or audits first. Use per-call `dryRun`, global `--dry-run`, or explicitly approve committed mutations with `--approve-mutations` after reviewing the manifest.\n\n# Known CLI Gaps\n\n## Provider-specific authenticated pages\n\nMissing:\nCLI supports page basic auth and generic resources/props/embeds, but not guided Supabase/Firebase auth setup.\n\nCurrent fallback:\nCall `meta.guide` with the provider-authenticated page goal, then create the\npage, resources, variables, props, bindings, and embeds with existing semantic\ntools.\n\nSuggested commands:\n\n- setup-auth-page\n\n## Generate from design input\n\nMissing:\nNo command imports Figma, screenshots, Inception output, or design.md and turns it into pages/tokens/layout.\n\nCurrent fallback:\nCall `meta.guide` with the design-input goal, let the agent inspect the supplied\ndesign, then use semantic page, token, asset, fragment, style, preview,\nscreenshot, and audit tools. Use `apply-patch` only when no semantic operation\nfits.\n\nSuggested commands:\n\n- generate-from-design\n\n## Built-in cross-project maintenance\n\nMissing:\nPublic API and CLI intentionally operate on one configured project at a time; there is no built-in multi-project discovery or loop runner.\n\nCurrent fallback:\nRun the CLI from an external script that reconfigures one project/session at a time.\n\nSuggested commands:\n\n- none\n',
|
|
273735
|
+
"api-use-cases": '# CLI API Use Cases\n\n## Link/configure one project\n\nCommands:\n\n- webstudio init --link <api-share-link> --json\n\nNotes:\n\n- Writes local project id and global origin/token config.\n\n## Import synced project bundle into another project\n\nCommands:\n\n- webstudio sync\n- webstudio import --to <destination-share-link>\n- MCP tool: import {"to":"<destination-share-link>"}\n\nNotes:\n\n- Imports local `.webstudio/data.json` into the destination project.\n- Destination share link must allow build/import access.\n- Use `--skip-assets` only when asset rows and files should not be imported.\n\n## Identify current token\n\nCommands:\n\n- MCP tool: whoami {}\n\n## Check token permissions\n\nCommands:\n\n- webstudio permissions --json\n\n## Inspect project/build/version\n\nCommands:\n\n- MCP tool: inspect {}\n- MCP tool: snapshot {"include":["pages","instances","styles"]}\n\n## Discover CLI/API capabilities\n\nCommands:\n\n- webstudio schema api\n- webstudio schema mcp\n- webstudio man --json\n- webstudio man llm --json\n- MCP tool: meta.index {}\n- MCP tool: meta.guide {"brief":"Create a pricing page"}\n- MCP tool: meta.get-more-tools {"brief":"update-styles"}\n- webstudio mcp list-resources\n- webstudio mcp read-resource webstudio://project/guide\n- webstudio mcp read-resource webstudio://project/expressions\n\nNotes:\n\n- Use `webstudio schema mcp` for a compact machine-readable MCP tool overview. Add `--verbose` or use focused `meta.get-more-tools` calls only when exact input schemas are needed.\n- Use focused MCP tools for discovery first: `meta.index`, `meta.guide`, `meta.get-more-tools`, `components.list`, `components.summary`, `components.search`, `components.get`, `templates.list`, and `templates.get`. Protocol clients can use `resources/list` and `resources/read`; shell agents can use `webstudio mcp list-resources` and `webstudio mcp read-resource <uri>`. Read longer resources such as `webstudio://project/tools` and `webstudio://project/components` only when focused tools are insufficient.\n- `components.summary` returns counts by default; request `{"detail":"components","limit":20}` for paginated entries. Registry list tools return compact metadata, while `components.get` and `templates.get` return focused full details.\n- Read `webstudio://project/expressions` before authoring unfamiliar computed text, prop bindings, resource expressions, actions, or Collection item bindings.\n- From a shell, call one MCP tool with the shortcut form `webstudio <tool> \'<json>\'`, for example `webstudio components.summary`. The explicit equivalent is `webstudio mcp single-op-call <tool> \'<json>\'`. Use `--input-file` for large payloads.\n\n## Inspect external shadcn registry items\n\nCommands:\n\n- webstudio registry inspect --source https://example.com/r/registry.json --item button --json\n- webstudio registry inspect --source ./registry.json --item dialog --json\n- webstudio registry inspect --source https://example.com/r/button.json --json\n\nNotes:\n\n- Reads a local or remote registry item without installing files or changing the configured Webstudio project.\n- Returns the item name, description, package and registry dependencies, file paths/targets, available docs, and a read-only compatibility report.\n- The report explicitly says whether installation or editable-component conversion is supported, lists declared requirements and manual steps, and says when arbitrary source code was not analyzed.\n- This is an inspection step only. It does not install files or change the configured project.\n\n## Understand what MCP can do\n\nCommands:\n\n- webstudio man mcp\n- MCP tool: meta.index {}\n- MCP tool: meta.guide {"brief":"What can Webstudio MCP do?"}\n\nMCP lets agents work on one configured Webstudio project. Agents can:\n\n- Inspect the linked project, token permissions, and latest editable build.\n- Read selected project data for audits, migrations, and repair.\n- Search labels, text, props, resource URLs, asset metadata, and styles.\n- Audit accessibility, security, SEO, performance settings, unused assets, ineffective Collection styles, and unused or duplicate style data.\n- Create and edit pages, folders, redirects, breakpoints, and page templates.\n- Create pages from reusable templates.\n- Update page metadata, SEO fields, auth settings, and marketplace metadata.\n- Insert components and styled JSX sections.\n- Create data-driven lists, grids, cards, and similar repeated UI from array or object data in one Collection operation.\n- Move, copy, wrap, unwrap, convert, rename, retag, and delete elements.\n- Update text, rich text, props, bindings, and actions.\n- Create and update local styles, design tokens, style sources, and CSS variables.\n- Create static data variables and JSON variables.\n- Create HTTP, GraphQL, and system resources.\n- Use system resources for sitemap, current date, and assets.\n- Bind resources to rendered data or form/action props.\n- Manage nested asset folders and upload, inspect, move, duplicate, download, replace, delete, and inspect usage for assets.\n- Publish, unpublish, inspect publish jobs, and manage custom domains.\n- Start preview, capture screenshots, compare screenshot diffs, and use OCR when installed.\n\n## Inspect and refresh MCP session cache\n\nCommands:\n\n- MCP tool: status {}\n- MCP tool: status {"verbose":true}\n- MCP tool: refresh {"namespaces":["pages","instances","styles"]}\n- MCP tool: reset-session {}\n\nNotes:\n\n- Use status before a task to understand the cached ProjectSession state.\n- Use status with `{"verbose":true}` only when debugging full namespaces, freshness, compatibility, or diagnostics.\n- Use refresh when project data may have changed outside the current MCP session.\n- Use reset-session when local cached state is corrupt or incompatible.\n\n## Visually verify rendered work with AI vision\n\nCommands:\n\n- MCP tool: preview.start {"host":"127.0.0.1","port":5173}\n- MCP tool: preview.status {}\n- MCP tool: preview.stop {}\n- MCP tool: screenshot {"path":"/","output":".webstudio/screenshots/home-current.png","viewport":{"width":1440,"height":900},"waitUntil":"load","waitForTimeout":250}\n- MCP tool: screenshot {"path":"/pricing","output":".webstudio/screenshots/pricing-current.png","viewport":{"width":1440,"height":900},"waitUntil":"load","waitForTimeout":250}\n- MCP tool: screenshot {"baseUrl":"http://127.0.0.1:5177","path":"/pricing","output":".webstudio/screenshots/pricing-current.png","viewport":{"width":1440,"height":900},"waitUntil":"load","waitForTimeout":250}\n- MCP tool: screenshot.diff {"baselinePath":".webstudio/screenshots/home-before.png","currentPath":".webstudio/screenshots/home-current.png","outputDir":".webstudio/screenshots/diff"}\n- MCP tool: screenshot.diff {"baselinePath":".webstudio/screenshots/home-before.png","currentPath":".webstudio/screenshots/home-current.png","outputDir":".webstudio/screenshots/diff","expectedText":["Pricing","Start free"]}\n- MCP tool: screenshot.diff {"baselinePath":".webstudio/screenshots/home-before.png","currentPath":".webstudio/screenshots/home-current.png","outputDir":".webstudio/screenshots/diff","expectedVisual":{"maxMismatchPercentage":2,"maxChangedRegions":3,"dominantColorChange":{"channel":"luminance","direction":"increase","minMagnitude":10}}}\n- MCP tool: screenshot.diff {"baselinePath":".webstudio/screenshots/pricing-before.png","currentPath":".webstudio/screenshots/pricing-current.png","outputDir":".webstudio/screenshots/diff"}\n- MCP tool: vision.install-ocr {"confirm":true}\n\nNotes:\n\n- `preview.status` reports whether generated output is `stale`. When no preview is running, `url`, `pid`, and `mode` are omitted. When present, `renderedProjectVersion` is the last project version materialized into the preview.\n- A managed `screenshot` or another `preview.start` refreshes stale generated output before capture.\n\n- Use this after page/content/style mutations so a vision-capable AI can see the generated site from the current MCP session. Use `path`; never pass a Webstudio Builder/share URL or capture Builder chrome.\n- For multi-page work, capture every changed page by `path` through the same preview server; no click navigation is required.\n- Iterative mode is the default: after MCP mutations, path screenshots ensure generated project files are current, wait for the exact session version, and perform an ordinary page reload without Vite HMR. The preview server and browser stay alive. Use `{"mode":"production"}` only for release-like verification; rendered audit does this automatically.\n- Calling `preview.start` after a committed mutation restarts a stale iterative server so external browsers and HTTP clients receive the newly generated project.\n- Do not call `preview.start` through one-shot `webstudio mcp single-op-call`: it is long-lived. From a shell, use `webstudio mcp run` with preview.start, screenshot, and preview.stop in one shared process, or use a real long-running MCP client.\n- From one-shot shell calls or another process, pass `baseUrl` with `path` to capture an already-running preview/site without generating, building, starting, or restarting preview.\n- Use preview.stop only in the same long-running MCP server or `webstudio mcp run` process that started preview. A separate one-shot `single-op-call` process does not own another process\'s preview controller.\n- Use waitForSelector when the rendered app has a reliable ready marker, waitUntil:"networkidle" for network-heavy pages, and waitForTimeout only for final visual settling.\n- The screenshot timeout bounds browser capture after the preview is ready. A timeout returns `SCREENSHOT_TIMEOUT`, resets the reusable browser session, and releases the shared preview lifecycle for cleanup.\n- Preview installs generated app dependencies under `.webstudio/preview` and reuses them across regenerations.\n- Do not add generated-preview dependencies to the repository root `package.json` or `pnpm-lock.yaml`.\n- If dependency installation fails, check npm and network configuration, then reinstall or update the Webstudio CLI if the problem persists.\n- When a baseline exists, use screenshot.diff once per baseline/current page or viewport pair to get changed regions, OCR textAnalysis, and diff artifact paths before deciding whether the result matches. Pass expectedText for explicit pass/fail current-screen text assertions with found and missing text. Pass expectedVisual for pass/fail limits on pixel mismatch percentage, changed-region count, or the overall dominant color/brightness direction.\n- If screenshot.diff reports OCR unavailable and the user agrees to install it, call vision.install-ocr {"confirm":true}; otherwise continue with pixel diff and visual inspection.\n- Compare the PNG, OCR text evidence, and diff artifacts against the user\'s intent for layout, typography, colors, spacing, imagery, and responsive framing; then iterate with focused mutations.\n- Root CLI equivalent: `webstudio screenshot --path /pricing --output pricing.png` generates a temporary production preview, captures that route, and stops the server. For repeated captures, keep `webstudio preview` running and pass its absolute URL to `webstudio screenshot`.\n\n## List pages\n\nCommands:\n\n- MCP tool: list-pages {}\n- MCP tool: list-folders {}\n\n## Read page by id\n\nCommands:\n\n- MCP tool: get-page {"pageId":"<pageId>"}\n\n## Read page by path\n\nCommands:\n\n- MCP tool: get-page-by-path {"path":"/pricing"}\n\n## Create page\n\nCommands:\n\n- MCP tool: create-page {"name":"Pricing","path":"/pricing"}\n- MCP tool: create-page {"name":"Pricing","path":"/pricing","title":"Pricing","meta":{"description":"Plans for teams"}}\n\nNotes:\n\n- `name`, `path`, page `title`, and metadata text fields accept plain fixed values.\n- For computed page titles or metadata, send JavaScript expression code such as `pageTitle ?? "Pricing"`.\n\n## Update page settings/metadata\n\nCommands:\n\n- MCP tool: update-page {"pageId":"<pageId>","values":{"title":"Pricing","meta":{"description":"Plans","status":200}}}\n- MCP tool: update-page {"pageId":"<pageId>","values":{"meta":{"auth":{"method":"basic","login":"<login>","password":"<password>"}}}}\n\nNotes:\n\n- Page `title` and metadata text fields accept plain fixed values.\n- For computed page titles or metadata, send JavaScript expression code such as `pageTitle ?? "Pricing"`.\n- Page `status` accepts a fixed HTTP status code as a number from 200 through 599 or a JavaScript expression string for a dynamic status.\n\n## Read project settings\n\nCommands:\n\n- MCP tool: get-project-settings {}\n\nNotes:\n\n- Read `meta.agentInstructions` before making project changes. It contains the project\'s own guidance for AI agents.\n- Agent instructions are shared project guidance. Do not store credentials or other secrets there.\n\n## Update project settings\n\nCommands:\n\n- MCP tool: update-project-settings {"meta":{"siteName":"Acme"}}\n- MCP tool: update-project-settings {"meta":{"agentInstructions":"Use existing design tokens and keep product copy concise."}}\n\n## Read marketplace product\n\nCommands:\n\n- MCP tool: get-marketplace-product {}\n\n## Update marketplace product\n\nCommands:\n\n- MCP tool: update-marketplace-product {"category":"pageTemplates","name":"Acme Template","thumbnailAssetId":"asset-id","author":"Acme Studio","email":"hello@example.com","website":"https://example.com","issues":"","description":"Reusable template project for Acme landing pages."}\n\n## Submit marketplace product\n\nCommands:\n\n- MCP tool: upload-asset {"asset":{"name":"marketplace-thumbnail.png","type":"image","format":"png","meta":{"width":1200,"height":630}},"assetsDir":".webstudio/assets"}\n- MCP tool: update-marketplace-product {"category":"pageTemplates","name":"Acme Template","thumbnailAssetId":"<uploadedAssetId>","author":"Acme Studio","email":"hello@example.com","website":"https://example.com","issues":"","description":"Reusable template project for Acme landing pages."}\n- MCP tool: publish {"target":"production"}\n- MCP tool: submit-marketplace-product {"acknowledgePublicSubmission":true}\n\nNotes:\n\n- Wait for the production publish to complete before submitting the product for review.\n- Submission requires complete, valid marketplace metadata.\n\n## List redirects\n\nCommands:\n\n- MCP tool: list-redirects {}\n\n## Create redirect\n\nCommands:\n\n- MCP tool: create-redirect {"old":"/old","new":"/new","status":301}\n\n## Update redirect\n\nCommands:\n\n- MCP tool: update-redirect {"old":"/old","values":{"new":"/newer","status":302}}\n- MCP tool: update-redirect {"old":"/old","values":{"status":null}}\n\n## Delete redirect\n\nCommands:\n\n- MCP tool: delete-redirect {"old":"/old"}\n\n## Set redirects\n\nCommands:\n\n- MCP tool: set-redirects {"redirects":[{"old":"/old","new":"/new","status":"301"}]}\n\n## List breakpoints\n\nCommands:\n\n- MCP tool: list-breakpoints {}\n\n## Create breakpoint\n\nCommands:\n\n- MCP tool: create-breakpoint {"label":"Tablet","maxWidth":991}\n\n## Update breakpoint\n\nCommands:\n\n- MCP tool: update-breakpoint {"breakpointId":"tablet","values":{"label":"Tablet","maxWidth":1023}}\n- MCP tool: update-breakpoint {"breakpointId":"tablet","values":{"condition":null,"minWidth":768}}\n- MCP tool: update-breakpoint {"breakpointId":"tablet","values":{"minWidth":null,"maxWidth":null,"condition":"(hover: hover)"}}\n\n## Delete breakpoint\n\nCommands:\n\n- MCP tool: delete-breakpoint {"breakpointId":"tablet"}\n\n## Duplicate page\n\nCommands:\n\n- MCP tool: duplicate-page {"pageId":"<pageId>","name":"Pricing Copy","path":"/pricing-copy"}\n- MCP tool: duplicate-page {"pageId":"<pageId>","name":"Paris","path":"/paris","substitutions":{"text":{"London":"Paris"},"variables":{"city":{"type":"string","value":"Paris"}}}}\n- webstudio duplicate-page --page <pageId> --name Paris --path /paris --substitutions \'{"text":{"London":"Paris"},"variables":{"city":{"type":"string","value":"Paris"}}}\' --json\n\nNotes:\n\n- Text substitutions replace exact fixed text only in the duplicated page\'s text children, string props, title, and metadata.\n- Variable substitutions are keyed by copied source-variable name and use typed variable values. The operation rejects missing or ambiguous names without committing a partial duplicate.\n- Existing expressions and cloned variable/resource references keep their remapped ids.\n\n## List page templates\n\nCommands:\n\n- MCP tool: list-page-templates {}\n\n## Create page template\n\nCommands:\n\n- MCP tool: create-page-template {"name":"Landing Template","title":"Landing"}\n\n## Update page template\n\nCommands:\n\n- MCP tool: update-page-template {"templateId":"<templateId>","values":{"name":"Article Template","meta":{"description":"Reusable article layout"}}}\n\n## Delete page template\n\nCommands:\n\n- MCP tool: delete-page-template {"templateId":"<templateId>"}\n\n## Duplicate page template\n\nCommands:\n\n- MCP tool: duplicate-page-template {"templateId":"<templateId>"}\n\n## Reorder page template\n\nCommands:\n\n- MCP tool: reorder-page-template {"sourceTemplateId":"<sourceTemplateId>","targetTemplateId":"<targetTemplateId>","position":"before"}\n\n## Create page from template\n\nCommands:\n\n- MCP tool: create-page-from-template {"templateId":"<templateId>","name":"Landing","path":"/landing"}\n\n## Delete page\n\nCommands:\n\n- MCP tool: delete-page {"pageId":"<pageId>"}\n\n## List folders\n\nCommands:\n\n- MCP tool: list-folders {}\n- MCP tool: list-pages {}\n\n## Create folder\n\nCommands:\n\n- MCP tool: create-folder {"name":"Blog","slug":"blog"}\n\n## Update folder\n\nCommands:\n\n- MCP tool: update-folder {"folderId":"<folderId>","values":{"name":"Blog","slug":"blog"}}\n\n## Delete folder\n\nCommands:\n\n- MCP tool: delete-folder {"folderId":"<folderId>"}\n\n## List element instances\n\nCommands:\n\n- MCP tool: list-instances {"pagePath":"/","maxDepth":3}\n\n## Inspect one element instance\n\nCommands:\n\n- MCP tool: inspect-instance {"instanceId":"<instanceId>","include":["props","styles","children"]}\n\n## Insert authored JSX or one component template\n\nCommands:\n\n- MCP tool: insert-fragment {"parentInstanceId":"<instanceId>","fragment":"<ws.element ws:tag=\'section\' ws:style={css`padding: 32px;`}><ws.element ws:tag=\'h2\'>Product OS</ws.element><radix.Switch><radix.SwitchThumb /></radix.Switch></ws.element>"}\n- MCP tool: insert-component {"parentInstanceId":"<instanceId>","component":"@webstudio-is/sdk-components-react-radix:Switch"}\n- MCP tool: insert-component {"parentInstanceId":"<instanceId>","component":"Form"}\n\nNotes:\n\n- Use MCP `insert-fragment` as the default way to author styled component trees. It converts JSX to a structured fragment before mutation.\n- Use only exact component ids returned by `components.search`, `components.get`, or `templates.get`. Never derive or guess component ids.\n- The `ws:` namespace contains specific Webstudio core components; it is not HTML-tag shorthand. Use `<ws.element ws:tag="div">` for a native `div` and `<ws.element ws:tag="form">` for a native form, never `<ws.div>` or `<ws.form>`.\n- For Webstudio\'s complete form structure, discover the Form component and insert its automatic template with `insert-component` using component `"Form"`.\n- MCP receives JSX as a JSON string because MCP arguments are JSON. The CLI converts it locally before the runtime mutation, so the project session receives structured Webstudio data, not JSX source.\n- In `insert-fragment` JSX, use ``ws:style={css`...`}`` for Webstudio-native CSS, or use React-style object syntax such as `style={{ padding: 24 }}` when that is simpler. Both forms create editable Webstudio style data.\n- Do not access host globals or dynamic code APIs in JSX fragments, including `process`, `globalThis`, `eval`, `Function`, or `constructor`.\n- Use Webstudio prop names such as `class` and `for`; do not use React aliases `className` or `htmlFor`.\n- Use Webstudio actions for event/action props, for example `onClick={new ActionValue(["event"], expression\\`console.log(event)\\`)}`. Do not pass JavaScript functions such as `onClick={() => ...}`.\n- Plain prop values must be JSON-compatible: `null`, strings, booleans, finite numbers, arrays, and plain objects. Do not pass `undefined`, `Symbol`, `BigInt`, `NaN`, `Infinity`, `Date`, `Map`, `Set`, class instances, or circular objects; omit the prop, use plain data, or use `expression`/`ActionValue` when the value is dynamic.\n- Template-backed components used in JSX must include required child/part components explicitly under the same parent structure as the template, for example `<radix.Switch><radix.SwitchThumb /></radix.Switch>`.\n- Webstudio applies a registered template automatically when using `insert-component`, so composed components such as Switch include required child parts and styles.\n- Use `components.list`, `components.summary`, `components.search`, `components.get`, `templates.list`, and `templates.get` to discover known registry items, component ids, props, templates, insertability, and content model. Read `webstudio://project/components` only when those focused tools are insufficient.\n- Component/template registry items use a shadcn-compatible top-level shape plus Webstudio-specific superset metadata in `meta`. They are for Builder/MCP discovery, not a published shadcn install registry yet.\n- Known components with `contentModel.category: "none"` are not standalone-insertable; insert their root component template instead so required providers/parents are included.\n- Unknown custom component ids are a low-level extension mechanism, not a discovery fallback. Agents must not synthesize them.\n\n## Make a region editable in Content mode\n\nCommands:\n\n- MCP tool: insert-component {"parentInstanceId":"<instanceId>","component":"ws:block"}\n- MCP tool: inspect-instance {"instanceId":"<instanceId>","include":["children"]}\n\nNotes:\n\n- When a page will be handed to a Content-mode editor, wrap every region they should be able to edit in a Content Block (`ws:block`). Content-mode editors can edit text and supported props only in Content Block descendants. Content outside those blocks remains read-only, even when it looks like ordinary editable text.\n- Put reusable insertable options inside the Content Block\'s `ws:block-template` child. A template is source material, not editor content: editors cannot edit or delete it directly. When an editor inserts a template, its copy becomes a direct child of the Content Block and is editable.\n- Before handing off a page, verify with `inspect-instance` that the intended text, images, and links are inside a Content Block, and that templates include all required styling because Content-mode editors cannot use the Style panel.\n\n## Move elements\n\nCommands:\n\n- MCP tool: move-instance {"moves":"moves.json contents"}\n\nNotes:\n\n- Use `position: "end"` to append an instance. Repeating this for A and then B preserves the final order A, B.\n- A numeric `insertIndex` addresses the target parent\'s children before the moved instance is removed. Use it for exact placement; do not calculate the last index to append.\n- Moves in one `moves` array are applied sequentially in array order.\n\n## Clone element subtree\n\nCommands:\n\n- MCP tool: clone-instance {"sourceInstanceId":"<instanceId>","targetParentInstanceId":"<targetParentId>"}\n\n## Delete element subtree\n\nCommands:\n\n- MCP tool: delete-instance {"instanceIds":["<instanceId>"]}\n\n## List text/expression children\n\nCommands:\n\n- MCP tool: list-texts {"pagePath":"/"}\n\n## Update text child\n\nCommands:\n\n- MCP tool: update-text {"instanceId":"<instanceId>","childIndex":0,"text":"Launch faster"}\n\n## Replace bounded literal text\n\nCommands:\n\n- MCP tool: replace-text {"find":"Start free","replace":"Get started","match":"exact","pagePath":"/pricing","limit":20}\n\nNotes:\n\n- This changes only literal text children, never expression children. Scope it to pagePath or pageId and set a limit before a broad replacement.\n\n## Replace bounded static prop text\n\nCommands:\n\n- MCP tool: replace-prop-text {"find":"old.example.com","replace":"www.example.com","match":"substring","names":["href","code"],"limit":20}\n\nNotes:\n\n- This changes only static string props such as href, alt, aria-label, title, and HTML embed code. It never changes expressions, resources, actions, assets, or other dynamic bindings. Use names or instanceIds and a limit to narrow the change.\n\n## Replace bounded resource text\n\nCommands:\n\n- MCP tool: replace-resource-text {"find":"api.old.example.com","replace":"api.example.com","fields":["url"],"limit":20}\n\nNotes:\n\n- This changes resource names and fixed URL literals only. It skips dynamic URL expressions, headers, search parameters, request bodies, and GraphQL query code.\n\n## Update props\n\nCommands:\n\n- MCP tool: update-props {"updates":"props.json contents"}\n- MCP tool: replace-prop-text {"find":"Old label","replace":"New label","names":["aria-label","title"],"limit":20}\n\nNotes:\n\n- Use this for fixed prop values such as `aria-label`, `alt`, `id`, static `href`, and other direct string/number/boolean/json prop values.\n\n## Add JSON-LD structured data\n\nCommands:\n\n- MCP tool: components.get {"component":"JsonLd"}\n- MCP tool: insert-component {"parentInstanceId":"<headSlotInstanceId>","component":"JsonLd"}\n- MCP tool: update-props {"updates":[{"instanceId":"<jsonLdInstanceId>","name":"code","type":"string","value":"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Organization\\",\\"name\\":\\"Acme\\"}"}]}\n- MCP tool: bind-props {"bindings":[{"instanceId":"<jsonLdInstanceId>","name":"code","binding":{"type":"expression","value":"({ \'@context\': \'https://schema.org\', \'@type\': \'Article\', headline: post.title })"}}]}\n- MCP tool: audit {"scopes":["seo"],"pagePath":"/"}\n\nNotes:\n\n- Prefer placing `JsonLd` inside `HeadSlot`.\n- For fixed structured data, store `code` as a JSON object or array encoded as a compact string. The Builder formats it for editing.\n- For structured data containing runtime values, use `bind-props` with an expression that evaluates directly to an object or array. Do not call `JSON.stringify` or assemble JSON with string concatenation. Webstudio stores the expression as source text, evaluates it at runtime, and the `JsonLd` component validates and serializes the resulting value.\n- The semantic prop update rejects malformed JSON and structurally invalid fixed JSON-LD with a precise JSON path.\n- The SEO audit also warns about a missing top-level `@context`, unknown or superseded Schema.org terms, properties unsupported by the supplied type, and incompatible primitive value types.\n- Schema.org vocabulary findings are warnings because custom vocabularies and extensions remain valid. Dynamic JSON-LD is marked as skipped for rendered validation.\n- Do not use bindings just to set static text.\n\n## Delete props\n\nCommands:\n\n- MCP tool: delete-props {"deletions":"props.json contents"}\n\n## Bind props to expressions/resources/actions\n\nCommands:\n\n- MCP tool: bind-props {"bindings":"bindings.json contents"}\n\nNotes:\n\n- Use this only when the prop should remain dynamic: expression, resource, action, or an existing scoped runtime context value such as `system`.\n- For a fixed string value, use `update-props` with `type:"string"` and a direct `value` instead.\n\n## Read styles\n\nCommands:\n\n- MCP tool: get-styles {"instanceIds":["<instanceId>"],"includeTokens":true}\n\n## Update local styles\n\nCommands:\n\n- MCP tool: update-styles {"updates":"styles.json contents"}\n\n## Delete local styles\n\nCommands:\n\n- MCP tool: delete-styles {"deletions":"styles.json contents"}\n\n## Replace matching style values\n\nCommands:\n\n- MCP tool: replace-styles {"property":"color","fromValue":{"type":"keyword","value":"red"},"toValue":{"type":"keyword","value":"blue"}}\n\n## List design tokens\n\nCommands:\n\n- MCP tool: list-design-tokens {}\n- MCP tool: list-design-tokens {"withUsage":true}\n- MCP tool: list-design-tokens {"verbose":true}\n\nNotes:\n\n- The default response is compact and includes token id, name, declaration count, and optional usage count.\n- Use `verbose:true` only when you need the full inline style declarations.\n\n## Create design tokens\n\nCommands:\n\n- MCP tool: create-design-token {"tokens":"tokens.json contents"}\n\n## Update design token styles\n\nCommands:\n\n- MCP tool: update-design-token-styles {"designTokenId":"<tokenId>","updates":"styles.json contents"}\n\n## Delete design token styles\n\nCommands:\n\n- MCP tool: delete-design-token-styles {"designTokenId":"<tokenId>","deletions":"styles.json contents"}\n\n## Attach design token to instances\n\nCommands:\n\n- MCP tool: attach-design-token {"designTokenId":"<tokenId>","instanceIds":"instances.json contents"}\n\n## Detach design token from instances\n\nCommands:\n\n- MCP tool: detach-design-token {"designTokenId":"<tokenId>","instanceIds":"instances.json contents"}\n\n## Extract design token from local styles\n\nCommands:\n\n- MCP tool: extract-design-token {"instanceIds":["<instanceId>"],"name":"Brand Primary","removeLocalProps":["color"]}\n\n## List CSS variables\n\nCommands:\n\n- MCP tool: list-css-variables {"withUsage":true}\n\n## Define CSS variables\n\nCommands:\n\n- MCP tool: define-css-variable {"vars":{"--color-primary":"#2d3748","--color-accent":"#e53e3e","--space-card":"1.5rem"},"overwrite":true}\n\nNotes:\n\n- Define or overwrite multiple CSS variables atomically by including every name and value in one `vars` object.\n- Pass colors as CSS strings. Structured `hex` color components use normalized values from `0` to `1`, not `0` to `255`.\n\n## Delete CSS variables\n\nCommands:\n\n- MCP tool: delete-css-variable {"names":["--color-primary","--color-accent","--space-card"],"force":true}\n\nNotes:\n\n- Delete multiple CSS variables atomically by including every name in one `names` array. Destructive MCP calls still require the returned confirmation token before they commit.\n\n## Rewrite CSS variable references\n\nCommands:\n\n- MCP tool: rewrite-css-variable-refs {"map":"variables.json contents"}\n\n## List data variables\n\nCommands:\n\n- MCP tool: list-variables {}\n- MCP tool: list-variables {"scopeInstanceId":"<instanceId>"}\n\nNotes:\n\n- Data variables live in the internal `dataSources` namespace.\n- For raw `snapshot`, request the public `variables` namespace rather than the internal `dataSources` name. Raw patch payloads still use `dataSources` when applying direct changes.\n- Scope variables to the instance where they should become available. Descendants can use them in expressions, and nested variables with the same name mask outer variables.\n\n## Create data variable\n\nCommands:\n\n- MCP tool: create-variable {"scopeInstanceId":"<instanceId>","name":"title","value":{"type":"string","value":"Hello"}}\n- MCP tool: create-variable {"scopeInstanceId":"<instanceId>","name":"count","value":{"type":"number","value":3}}\n- MCP tool: create-variable {"scopeInstanceId":"<instanceId>","name":"featured","value":{"type":"boolean","value":true}}\n- MCP tool: create-variable {"scopeInstanceId":"<instanceId>","name":"tags","value":{"type":"json","value":["news","product"]}}\n- MCP tool: create-variable {"scopeInstanceId":"<instanceId>","name":"filters","value":{"type":"json","value":{"tag":"news"}}}\n\nNotes:\n\n- Data variable values support `string`, `number`, `boolean`, and `json`. Use `json` for all arrays and objects.\n- Parameters are internal scoped runtime values provided by pages, collections, or components. They are not a public authoring surface: do not create, update, or delete parameter records. Use data variables/resources for user-authored data, and reference documented context values such as `system` only where they are already in scope.\n\n## Update data variable\n\nCommands:\n\n- MCP tool: update-variable {"dataSourceId":"<variableId>","values":{"value":{"type":"json","value":{"count":1}}}}\n\n## Delete data variable\n\nCommands:\n\n- MCP tool: delete-variable {"dataSourceId":"<variableId>"}\n\n## List resources\n\nCommands:\n\n- MCP tool: list-resources {}\n- MCP tool: list-resources {"scopeInstanceId":"<instanceId>"}\n\n## Create resource\n\nCommands:\n\n- MCP tool: create-resource {"resource":{"name":"Posts","method":"get","url":"https://api.example.com/posts","headers":[]}}\n- MCP tool: create-resource {"resource":{"name":"Posts","method":"get","url":"\'https://api.example.com/posts?tag=\' + filters.tag","headers":[]},"scopeInstanceId":"<instanceId>","dataSourceName":"posts"}\n- MCP tool: create-resource {"resource":{"name":"Filtered Posts","method":"get","url":"https://api.example.com/posts","searchParams":[{"name":"tag","value":"filters.tag"},{"name":"source","value":{"type":"literal","value":"website"}}],"headers":[{"name":"Authorization","value":"\'Bearer \' + auth.token"}]},"scopeInstanceId":"<instanceId>","dataSourceName":"posts"}\n- MCP tool: create-resource {"resource":{"name":"Post GraphQL","control":"graphql","method":"post","url":"https://api.example.com/graphql","headers":[{"name":"Content-Type","value":{"type":"literal","value":"application/json"}}],"body":"{ query: \'query Post($slug: String!) { post(slug: $slug) { title } }\', variables: { slug: system.params.slug } }"},"scopeInstanceId":"<instanceId>","dataSourceName":"post"}\n- MCP tool: create-resource {"resource":{"name":"Current Date","control":"system","method":"get","url":"/$resources/current-date","headers":[]},"scopeInstanceId":"<instanceId>","dataSourceName":"currentDate"}\n\nNotes:\n\n- Resource `url` accepts plain fixed URLs and paths such as `https://api.example.com/posts` and `/$resources/current-date`.\n- Resource `url` can also be a JavaScript expression when it is computed, such as `"https://api.example.com/posts?tag=" + filters.tag`.\n- Header values, search parameter values, and body accept expressions for dynamic values. For fixed text, use `{"type":"literal","value":"application/json"}`; Webstudio stores the required string expression for you.\n- Search parameter values, header values, and body expressions can read scoped variables and documented runtime context values such as `system` when they are available at the resource scope.\n- Add `scopeInstanceId` and `dataSourceName` when the resource result should be exposed as a scoped read data variable. Scoped resources are generated into the page resource `data` map and may be loaded during page rendering. Use this for read-oriented resources such as GET CMS/API data.\n- For submit/write/action resources, create the resource without `scopeInstanceId`, then bind a component prop such as a Form `action` with `bind-props` and `binding.type: "resource"`. Prop-bound resources are generated into the page resource `action` map instead of the read `data` map. Use this for POST, PUT, DELETE, webhooks, GraphQL submissions, and other explicit action flows.\n- Resource `method` can be `get`, `post`, `put`, or `delete`. Use GET for read data, POST for creates/GraphQL/webhooks/form submissions, PUT for full updates or replacements, and DELETE for deletion actions.\n- Optional `control` values are `graphql` and `system`. Use `graphql` for GraphQL-style requests, usually POST with a query body. Use `system` for built-in resources such as `"/$resources/sitemap.xml"`, `"/$resources/current-date"`, and `"/$resources/assets"` and when the resource should use the built-in `system` parameter. System fields are `system.origin`, `system.pathname`, `system.params`, and `system.search`.\n\n## Update resource\n\nCommands:\n\n- MCP tool: update-resource {"resourceId":"<resourceId>","values":{"url":"https://api.example.com/posts"}}\n- MCP tool: replace-resource-text {"find":"api.old.example.com","replace":"api.example.com","fields":["url"],"limit":20}\n\n## Query Markdown assets\n\nCommands:\n\n- MCP tool: get-asset-field-catalog {}\n- MCP tool: validate-asset-query {"query":{"where":{"all":[{"field":["extension"],"operator":"eq","value":"md"},{"field":["properties","draft"],"operator":"ne","value":true}]},"limit":20}}\n- MCP tool: create-assets-resource {"name":"All assets","scopeInstanceId":"<instanceId>","dataSourceName":"assets"}\n- MCP tool: create-assets-resource {"name":"Published posts","scopeInstanceId":"<instanceId>","dataSourceName":"posts","query":{"result":"many","where":{"all":[{"field":["extension"],"operator":"eq","value":{"type":"literal","value":"md"}},{"field":["properties","draft"],"operator":"ne","value":{"type":"literal","value":true}}]},"sort":[{"field":["properties","publishedAt"],"direction":"desc"}],"limit":{"type":"literal","value":20},"output":{"mode":"fields","includeMetadata":false,"fields":[["properties","title"],["properties","slug"],["properties","publishedAt"],["properties","excerpt"]]},"content":{"mode":"none"}}}\n- MCP tool: create-assets-resource {"name":"Post by slug or ID","scopeInstanceId":"<instanceId>","dataSourceName":"post","query":{"result":"one","where":{"all":[{"field":["extension"],"operator":"eq","value":{"type":"literal","value":"md"}},{"field":["properties","slug"],"operator":"eq","value":"system.params.slug"}]},"output":{"mode":"fields","includeMetadata":false,"fields":[["properties","title"],["properties","publishedAt"]]},"content":{"mode":"markdown-body-ref"}}}\n- MCP tool: list-assets-resources {}\n- MCP tool: get-assets-resource {"resourceId":"<resourceId>"}\n- MCP tool: update-assets-resource {"resourceId":"<resourceId>","values":{"query":null}}\n- MCP tool: preview-asset-query {"query":{"result":"one","where":{"all":[{"field":["extension"],"operator":"eq","value":"md"},{"field":["properties","slug"],"operator":"eq","value":"hello-world"}]},"output":{"mode":"fields","includeMetadata":false,"fields":[["properties","title"]]},"content":{"mode":"markdown-body-ref"}}}\n\nNotes:\n\n- Read the field catalog before authoring unfamiliar queries. It includes dynamic schema-less frontmatter paths such as `properties.author.name`, observed types, optionality, and mixed-type state without downloading Markdown files.\n- Minimize the deployed content database by using `output.mode:"fields"` and selecting only fields the rendered page needs. Keep `includeMetadata:false` unless the rendered value needs file metadata such as name, path, MIME type, or creation date, and avoid `output.mode:"all"` as a convenience default. Query diagnostics are returned separately and do not require metadata output. Filters and sorting may still require their referenced fields in the database.\n- Every reachable Assets data source contributes to the shared published database. Keep one final resource per rendered query. Update an existing scoped resource rather than creating a placeholder, preview copy, or repair replacement, and remove obsolete duplicates.\n- Keep bounded overview filters, limits, and offsets literal and add a deterministic ID tie-breaker to the sort. With `content.mode:"none"`, compilation can materialize the small overview result instead of retaining its output fields across every candidate document. Use runtime expressions only for genuinely dynamic values such as the detail slug.\n- Combine filters with `where.all` (AND) and `where.any` (OR), including nested groups. Filter values, limit, and offset on a saved resource may be Webstudio expressions evaluated at render time. Preview queries use concrete JSON values.\n- Query Markdown files directly and use `content.mode:"markdown-body-ref"` when rendering their bodies. The published database retains metadata and a document reference, then fetches only the selected Markdown files from Asset storage at runtime; it does not embed their bodies. Filter and paginate before content is loaded.\n- `full` and bounded `range` continue to request embedded file bytes. Use them only when the caller requires the complete source or a byte range.\n- In Markdown, reference sibling Assets with conventional relative URLs such as `../images/hero.png`. Deferred `markdown-body-ref` content resolves matching files against the Markdown file\'s folder and emits the correct Builder or published Asset URL. Keep external URLs absolute.\n- Markdown Embed renders sanitized authored HTML for figures, captions, audio, video, and iframes. Scripts, inline event handlers, `srcdoc`, and unsafe URL protocols are removed. Executable component composition remains separate future MDX work.\n- Preview each query with concrete values before saving it. Inspect `__diagnostics__.query` for the temporary query-only footprint and `__diagnostics__.database` for the merged database built from all reachable Assets queries. Only `database.usedBytes` counts toward `database.maxBytes`; query sizes must not be summed and are not separate allowances. Compare `usedBytes`, `unboundedBytes`, and `truncated` within both scopes. A completed Markdown blog should include every source document without truncation, contain no embedded Markdown bodies, and retain only its intended materialized overview query. When merged usage approaches the limit, remove duplicate reachable resources first, then unused output fields, then narrow candidate files.\n- Use `result:"many"` for listings. It returns the ID-keyed map at `<dataSourceName>.data`, with `totalCount` and `hasMore` at `<dataSourceName>.meta`, and remains the default for existing queries. Bind a listing Collection to `posts.data`.\n- Use `result:"one"` for a unique detail route. It returns the selected item or `null` directly at `post.data`, always includes its `id`, and omits pagination. Bind components and page settings directly with expressions such as `post.data?.properties?.title`, `post.data?.content?.text`, and `post.data ? 200 : 404`. `result:"first"` and `result:"last"` also return a direct item or `null` but require explicit sorting.\n- Assets always executes a structured query. Omit `query` to use the default many-result query, which selects URL and optional image dimensions. Set `values.query:null` to restore it.\n- `create-assets-resource` and `update-assets-resource` are the semantic authoring path. Do not construct the internal query URL, headers, or body expression manually.\n- The shared metadata index is maintained automatically and is emitted only when a configured Assets resource is reachable.\n\n## Delete resource\n\nCommands:\n\n- MCP tool: delete-resource {"resourceId":"<resourceId>"}\n\n## List assets\n\nCommands:\n\n- MCP tool: list-assets {"withUsage":true}\n- MCP tool: list-assets {"verbose":true}\n\nNotes:\n\n- Compact results include each asset\'s folder id. Use `verbose:true` to include complete records for a page of assets, or `get-asset` to read one complete record including description, folder, creation time, and image/font metadata.\n- Image asset descriptions are the default alt text for asset-backed Image components.\n- To generate missing descriptions, inspect the image in its rendered page or asset source, write a concise description of its purpose, and save it on the asset rather than duplicating it on each Image instance.\n\n## Get asset\n\nCommands:\n\n- MCP tool: get-asset {"assetId":"<assetId>"}\n\n## List asset folders\n\nCommands:\n\n- MCP tool: list-asset-folders {}\n\n## Create asset folder\n\nCommands:\n\n- MCP tool: create-asset-folder {"name":"Marketing"}\n- MCP tool: create-asset-folder {"name":"Photos","parentId":"<parentFolderId>"}\n\n## Update asset folder\n\nCommands:\n\n- MCP tool: update-asset-folder {"folderId":"<folderId>","values":{"name":"Brand"}}\n- MCP tool: update-asset-folder {"folderId":"<folderId>","values":{"parentId":"<parentFolderId>"}}\n- MCP tool: update-asset-folder {"folderId":"<folderId>","values":{"parentId":null}}\n\nNotes:\n\n- Updating `parentId` is the folder equivalent of cut and paste. Use `null` to move a folder to Root.\n\n## Duplicate asset folder\n\nCommands:\n\n- MCP tool: duplicate-asset-folder {"folderId":"<folderId>"}\n- MCP tool: duplicate-asset-folder {"folderId":"<folderId>","parentId":"<targetFolderId>"}\n\nNotes:\n\n- Duplication recursively copies descendant folders and assets. This is the folder equivalent of copy and paste.\n\n## Delete asset folder\n\nCommands:\n\n- MCP tool: delete-asset-folder {"folderId":"<folderId>"}\n\nNotes:\n\n- Deleting a folder recursively deletes its descendant folders and assets.\n\n## Update asset metadata\n\nCommands:\n\n- MCP tool: update-asset {"assetId":"<assetId>","values":{"description":"Team collaborating around a whiteboard"}}\n- MCP tool: update-asset {"assetId":"<fontAssetId>","values":{"meta":{"family":"Rajdhani","style":"normal","weight":600}}}\n\nNotes:\n\n- Use an empty description only when the image is intentionally decorative.\n- Updating an image asset description updates the default alt text wherever that asset is used with an asset-backed alt prop.\n- Font metadata updates merge with the detected metadata and are validated before committing; use this to correct a family, style, or weight after upload.\n\n## Generate missing image descriptions with an agent\n\nCommands:\n\n- MCP tool: audit {"scopes":["accessibility"],"verbose":true}\n- MCP tool: set-image-descriptions {"updates":[{"assetId":"hero-id","description":"Team collaborating around a whiteboard"},{"assetId":"texture-id","decorative":true}]}\n- MCP tool: audit {"scopes":["accessibility"]}\n\nNotes:\n\n- Start from `missing-image-description` findings. Inspect each image in its rendered page context before writing text.\n- The vision-capable agent generates the wording; the CLI validates and stores it but does not contain its own vision model.\n- Use `decorative:true` only when the image adds no information. This intentionally stores an empty description so later audits do not report it as missing.\n- Re-run the accessibility audit after the update. Asset-backed Image components use the saved asset description as their default alt text.\n\n## Manage fonts\n\nCommands:\n\n- MCP tool: list-fonts {"includeSystem":true}\n- MCP tool: list-assets {"type":"font"}\n- MCP tool: upload-asset {"asset":{"name":"acme-sans.woff2","type":"font","format":"woff2","meta":{"family":"Acme Sans","style":"normal","weight":400}},"assetsDir":".webstudio/assets"}\n- MCP tool: update-styles {"updates":"styles.json contents"}\n\nNotes:\n\n- Use `list-fonts` to discover uploaded families and system stacks. Upload/delete fonts through the existing asset tools, then apply a family with a `font-family` style declaration.\n\n## Upload one asset\n\nCommands:\n\n- MCP tool: upload-asset {"asset":{"name":"image.png","type":"image","format":"png","meta":{"width":1200,"height":630}},"assetsDir":".webstudio/assets"}\n- MCP tool: upload-asset {"asset":{"name":"image.png","type":"image","format":"png","folderId":"<folderId>","meta":{"width":1200,"height":630}},"assetsDir":".webstudio/assets"}\n\n## Upload asset batch\n\nCommands:\n\n- MCP tool: upload-assets {"assets":[{"name":"image.png","type":"image","format":"png","meta":{"width":1200,"height":630}}],"assetsDir":".webstudio/assets"}\n\n## Duplicate asset\n\nCommands:\n\n- MCP tool: duplicate-asset {"assetId":"<assetId>"}\n- MCP tool: duplicate-asset {"assetId":"<assetId>","folderId":"<targetFolderId>"}\n- MCP tool: duplicate-asset {"assetId":"<assetId>","folderId":null}\n\nNotes:\n\n- Duplication is the asset equivalent of copy and paste. Updating `folderId` is the equivalent of cut and paste; use `null` for Root.\n\n## Download asset\n\nCommands:\n\n- MCP tool: download-asset {"assetId":"<assetId>"}\n\n## Find asset usage\n\nCommands:\n\n- MCP tool: find-asset-usage {"assetId":"<assetId>"}\n\n## Replace asset references\n\nCommands:\n\n- MCP tool: replace-asset {"fromAssetId":"<oldAssetId>","toAssetId":"<newAssetId>"}\n\n## Delete assets\n\nCommands:\n\n- MCP tool: delete-asset {"assetIdsOrPrefixes":["<assetId>"],"force":true}\n\n## Publish project\n\nCommands:\n\n- webstudio publish deploy --target production --json\n\n## List publishes\n\nCommands:\n\n- webstudio publish list --json\n\n## Check publish job\n\nCommands:\n\n- webstudio publish status --job <buildId> --json\n\n## Unpublish\n\nCommands:\n\n- webstudio publish unpublish --target production --confirm --json\n\n## List domains\n\nCommands:\n\n- webstudio domains list --json\n\n## Create domain\n\nCommands:\n\n- webstudio domains create --domain example.com --json\n\n## Update domain\n\nCommands:\n\n- webstudio domains update --domain-id <domainId> --domain www.example.com --json\n\n## Delete domain\n\nCommands:\n\n- webstudio domains delete --domain-id <domainId> --confirm --json\n\n## Verify domain\n\nCommands:\n\n- webstudio domains verify --domain-id <domainId> --json\n\n## Make arbitrary store-level changes\n\nCommands:\n\n- MCP tool: inspect {}\n- MCP tool: snapshot {"include":["<namespace>"]}\n- MCP tool: apply-patch {"baseVersion":"<version>","transactions":"patch.json contents"}\n\nNotes:\n\n- Use only when no semantic command exists.\n\n## Manage marketplace metadata\n\nCommands:\n\n- MCP tool: get-marketplace-product {}\n- MCP tool: update-marketplace-product {"category":"pageTemplates","name":"Acme Template","thumbnailAssetId":"asset-id","author":"Acme Studio","email":"hello@example.com","website":"https://example.com","issues":"","description":"Reusable template project for Acme landing pages."}\n- MCP tool: submit-marketplace-product {"acknowledgePublicSubmission":true}\n\nPatch namespaces:\n\n- marketplaceProduct\n\n## Search and inspect safely\n\nCommands:\n\n- MCP tool: search-project {"query":"pricing"}\n- MCP tool: search-project {"query":"api.example.com","scopes":["resources"]}\n- MCP tool: list-instances {"pagePath":"/","maxDepth":5}\n- MCP tool: inspect-instance {"instanceId":"<instanceId>","include":["props","styles","children"]}\n- MCP tool: list-texts {"pagePath":"/"}\n- MCP tool: list-assets {"withUsage":true}\n- MCP tool: find-asset-usage {"assetId":"<assetId>"}\n- MCP tool: snapshot {"include":["pages","instances","props","resources","assets"]}\n\nNotes:\n\n- Use `search-project` for query-driven lookup across labels, text, prop values, resource URLs, asset metadata, and styles. Use `audit` for project health findings.\n\n## Audit project quality\n\nCommands:\n\n- webstudio audit --json\n- webstudio audit --scopes accessibility --scopes seo --json\n- webstudio audit --page-path /pricing --json\n- webstudio audit --scopes accessibility --verbose --json\n- webstudio audit --rendered --page-path /pricing --json\n- webstudio audit --rendered --route-example post=/blog/hello --json\n- webstudio audit --rendered --image-domain images.example.com --json\n- MCP tool: audit {}\n- MCP tool: audit {"scopes":["accessibility","security"],"severities":["error","warning"]}\n- MCP tool: audit {"scopes":["accessibility"],"verbose":true}\n- MCP tool: audit {"scopes":["craft"],"verbose":true}\n- MCP tool: audit {"rendered":true,"verbose":true}\n\nNotes:\n\n- With no scopes, `audit` checks accessibility, security, SEO, performance settings, unused assets, ineffective Collection styles, non-GET resources exposed as render-time data, and unused or duplicate style data.\n- Craft is opt-in and read-only. Run `audit` with `scopes:["craft"]` to detect whether the project is not using Craft, partially compatible, or compatible with the versioned Craft 1.2 profile. `profileStatuses` includes the University-doc provenance and the smallest safe next action. The audit never installs Craft or changes a non-Craft project.\n- The `performance` scope reports disabled atomic CSS generation. A rendered audit also measures broken, eager below-fold, and oversized images, browser-marked render-blocking resources, and legacy font formats.\n- Rendered image and resource metrics run only when the selected scopes include `performance`; responsive layout dimensions remain available whenever `rendered:true` is requested.\n- Compact findings include stable ids, severity, message, and location. Use `--verbose` or `{"verbose":true}` for evidence, explanation, suggested remediation, skipped-check details, and manual-check workflows.\n- `summary` counts all findings before severity filtering and pagination.\n- `contractVersion` identifies the audit response contract. Handle a new value before assuming existing fields retain the same meaning.\n- Expression-, resource-, and parameter-backed values that cannot be checked reliably appear in `skippedChecks`; they are not treated as passing or failing.\n- Page filters apply to page-owned accessibility, security, and SEO checks. Asset and style usage remain project-wide to avoid false unused findings.\n- Continue paginated results with `cursor`. Restart the audit if the project version changes.\n- Verbose skipped-check and manual-check details are included on the first findings page only; their total counts remain available on every page.\n- `manualChecks` describes responsive, hierarchy, and contrast checks that require preview screenshots and vision.\n- Focused audits return only manual checks relevant to their selected scopes.\n- In a long-lived MCP session, `{"rendered":true}` reuses preview and screenshot\n tools to capture every static page at mobile, desktop, and Builder breakpoint\n edges. Compact output reports rendered check/issue/failure counts; verbose\n output includes screenshot paths and measured layout dimensions.\n- Dynamic route templates are skipped unless `--route-example <pageId>=<path>`\n (or MCP `routeExamples`) supplies a concrete path. The path must not contain\n unresolved `:` or `*` parameters.\n- Plans above 120 captures return a short-lived confirmation token. Review the\n unchanged plan, then rerun with `--confirm-large-run` and\n `--confirmation-token`.\n- Detailed rendered evidence is stored in a versioned manifest under\n `.webstudio/audits`; compact output includes its path and screenshot count.\n- Rendered checks also report broken images, eager images below the fold, and\n image sources more than 2x their rendered dimensions in both axes, including\n Webstudio instance ids and measured dimensions when available.\n- Rendered checks include sanitized Resource Timing evidence and report\n browser-marked render-blocking resources plus legacy `.ttf`, `.otf`, and\n `.woff` fonts without applying a universal transfer-size threshold.\n- Fix findings through semantic mutation commands, then rerun `audit` to confirm their deterministic finding ids disappeared.\n\n## Verify dynamic bindings\n\nCommands:\n\n- webstudio verify-bindings --json\n- MCP tool: verify-bindings {"pagePath":"/pricing"}\n- MCP tool: verify-bindings {"instanceId":"<instanceId>","limit":50}\n\nNotes:\n\n- Statically checks persisted text expressions, expression/action/resource/parameter props, resource expressions, and page metadata.\n- Findings distinguish invalid syntax, unknown or out-of-scope variables, stale internal data-source ids, and missing resource or parameter references.\n- Page and instance filters can be combined when the instance belongs to the selected page. Continue findings with `cursor`.\n- This operation does not resolve rendered values or execute external resources. Preview representative loading, empty, error, and populated states after static findings are fixed.\n\n## Refactor targeted content\n\nCommands:\n\n- MCP tool: list-instances {"pagePath":"/"}\n- MCP tool: list-texts {"pagePath":"/"}\n- MCP tool: update-text {"instanceId":"<instanceId>","childIndex":0,"text":"Launch faster"}\n- MCP tool: replace-text {"find":"Old headline","replace":"New headline","match":"exact","pagePath":"/pricing","limit":20}\n- MCP tool: update-props {"updates":"props.json contents"}\n- MCP tool: update-page {"pageId":"<pageId>","values":{"title":"Pricing","meta":{"description":"Plans"}}}\n- MCP tool: update-resource {"resourceId":"<resourceId>","values":{"url":"https://api.example.com/posts"}}\n- MCP tool: replace-asset {"fromAssetId":"<oldAssetId>","toAssetId":"<newAssetId>"}\n- MCP tool: replace-styles {"property":"color","fromValue":{"type":"keyword","value":"red"},"toValue":{"type":"keyword","value":"blue"}}\n- MCP tool: rewrite-css-variable-refs {"map":"variables.json contents"}\n\nNotes:\n\n- Use focused reads first, then mutate only matching instances, props, metadata, resource URLs, assets, or style references. Use `replace-text` for bounded literal text changes, `replace-prop-text` for bounded static prop text, `replace-resource-text` for fixed resource names/URLs, and `update-text` for one known child or expressions.\n\n## Optimize existing project\n\nCommands:\n\n- MCP tool: list-pages {}\n- MCP tool: list-folders {}\n- MCP tool: update-page {"pageId":"<pageId>","values":{"title":"Pricing","meta":{"description":"Plans"}}}\n- MCP tool: update-props {"updates":"props.json contents"}\n- MCP tool: list-breakpoints {}\n- MCP tool: update-breakpoint {"breakpointId":"tablet","values":{"maxWidth":1023}}\n- MCP tool: get-styles {"instanceIds":["<instanceId>"],"includeTokens":true}\n- MCP tool: update-styles {"updates":"styles.json contents"}\n- MCP tool: attach-design-token {"designTokenId":"<tokenId>","instanceIds":"instances.json contents"}\n- MCP tool: update-project-settings {"meta":{"siteName":"Acme"}}\n\nNotes:\n\n- Use this for SEO metadata, accessibility labels, responsive behavior, token consistency, and project settings.\n\n## Connect external data\n\nCommands:\n\n- MCP tool: create-variable {"scopeInstanceId":"<instanceId>","name":"title","value":{"type":"string","value":"Hello"}}\n- MCP tool: create-variable {"scopeInstanceId":"<instanceId>","name":"tags","value":{"type":"json","value":["news","product"]}}\n- MCP tool: create-variable {"scopeInstanceId":"<instanceId>","name":"filters","value":{"type":"json","value":{"tag":"news"}}}\n- MCP tool: create-resource {"resource":{"name":"Posts","method":"get","url":"https://api.example.com/posts","searchParams":[{"name":"tag","value":"filters.tag"},{"name":"source","value":{"type":"literal","value":"website"}}],"headers":[]},"scopeInstanceId":"<instanceId>","dataSourceName":"posts"}\n- MCP tool: create-resource {"resource":{"name":"Post GraphQL","control":"graphql","method":"post","url":"https://api.example.com/graphql","headers":[{"name":"Content-Type","value":{"type":"literal","value":"application/json"}}],"body":"{ query: \'query Post($slug: String!) { post(slug: $slug) { title } }\', variables: { slug: system.params.slug } }"},"scopeInstanceId":"<instanceId>","dataSourceName":"post"}\n- MCP tool: create-resource {"resource":{"name":"Current Date","control":"system","method":"get","url":"/$resources/current-date","headers":[]},"scopeInstanceId":"<instanceId>","dataSourceName":"currentDate"}\n- MCP tool: update-resource {"resourceId":"<resourceId>","values":{"url":"https://api.example.com/posts"}}\n- MCP tool: bind-props {"bindings":"bindings.json contents"}\n- MCP tool: insert-fragment {"parentInstanceId":"<instanceId>","fragment":"<ws.collection>{/_ collection content _/}</ws.collection>"}\n\nNotes:\n\n- Use this for CMS sections, blog listings, Ghost/headless CMS pages, n8n-style integrations, and API URLs built from variables.\n- For read data, expose GET resources as scoped data variables with `scopeInstanceId`/`dataSourceName` and read the loaded result from the resource result wrapper, usually `.data`.\n- For writes, webhooks, GraphQL submissions, and deletes, prefer unscoped resources bound to Form `action` props so they become action resources instead of auto-loaded read resources.\n- Use direct props for fixed values and prop bindings only when a prop must read a data variable, resource, action, or documented runtime context value such as `system`.\n\n## Render an array or object as repeated content\n\nCommands:\n\n- MCP tool: insert-collection {"parentInstanceId":"<instanceId>","data":{"type":"expression","value":"posts.data.items"},"itemFragment":"<ws.element ws:tag=\'article\'><ws.element ws:tag=\'h2\'>{expression`collectionItem.title`}</ws.element></ws.element>"}\n- MCP tool: inspect-instance {"instanceId":"<collectionId>","include":["props","bindings","children"]}\n\nNotes:\n\n- Use Collection whenever an array or object from a resource or data variable should render a list, grid, cards, table rows, options, tabs, or other repeated UI.\n- Pass `insert-collection` the complete array or object. Do not pass the resource response wrapper or one indexed item. External resource arrays are commonly nested under the scoped resource result\'s `data` field or deeper.\n- Pass one repeated-item Webstudio JSX root. The command creates the Collection, its private current-item/current-key parameters, the iterable binding, and descendant item bindings atomically.\n- Collection renders the item root once for every entry. Use `expression` values such as `collectionItem.title` in descendant text and props. Object iteration also exposes `collectionItemKey`.\n- Wrap multiple repeated sibling instances in one Element inside Collection.\n- For repeated Radix items such as accordion items, tabs, or menu options, bind a stable unique id or slug to every required `value` prop.\n- See the [Collection documentation](https://docs.webstudio.is/university/core-components/collection) for the equivalent Builder workflow.\n\n## Support dynamic runtime behavior\n\nCommands:\n\n- MCP tool: integrate-runtime-ui {"parentInstanceId":"<instanceId>","resources":[{"resource":{"name":"Seats","method":"get","url":"https://api.example.com/seats","headers":[]},"dataSourceName":"Seats","exposeAsDataSource":true}],"structure":{"type":"collection","data":{"type":"expression","value":"Seats.data"},"itemFragment":{"children":[{"type":"id","value":"seat"}],"instances":[{"type":"instance","id":"seat","component":"Text","children":[{"type":"expression","value":"collectionItem.label"}]}],"props":[],"dataSources":[],"resources":[],"styleSources":[],"styleSourceSelections":[],"styles":[],"breakpoints":[],"assets":[]}},"retainedBehavior":[{"instanceId":"<scriptInstanceId>","responsibility":"Seat selection behavior"}]}\n- MCP tool: update-props {"updates":"props.json contents"}\n- MCP tool: bind-props {"bindings":"bindings.json contents"}\n- MCP tool: create-resource {"resource":{"name":"Seats","method":"get","url":"https://api.example.com/seats","headers":[]}}\n- MCP tool: snapshot {"include":["instances","props","resources"]}\n- MCP tool: apply-patch {"baseVersion":"<version>","transactions":"patch.json contents"}\n\nNotes:\n\n- Use `integrate-runtime-ui` to create variables/resources, insert one editable fragment or Collection, and add safe data bindings in one transaction.\n- List existing script-owned responsibilities under `retainedBehavior`. The operation preserves those instances and never evaluates or accepts replacement script bodies.\n- `unsupportedConversions` records behavior that cannot be represented safely. Dry-run returns the complete transaction and the same retained/unsupported report without changing the project.\n- New actions and HtmlEmbed scripts are intentionally rejected. Create normal editable components and data bindings; keep opaque runtime behavior in existing script instances.\n\n## Build authenticated pages\n\nCommands:\n\n- MCP tool: meta.guide {"brief":"Build a Supabase-authenticated account page"}\n- MCP tool: create-page {"name":"Account","path":"/account"}\n- MCP tool: update-page {"pageId":"<pageId>","values":{"meta":{"auth":{"method":"basic","login":"<login>","password":"<password>"}}}}\n- MCP tool: create-resource {"resource":{"name":"Session","method":"get","url":"https://api.example.com/session","headers":[]}}\n- MCP tool: create-variable {"scopeInstanceId":"<instanceId>","name":"user","value":{"type":"json","value":{}}}\n- MCP tool: update-props {"updates":"props.json contents"}\n- MCP tool: bind-props {"bindings":"bindings.json contents"}\n\nNotes:\n\n- Inspect and reuse the project\'s existing auth convention before authoring. Do\n not add a second provider or session model implicitly.\n- Model signed-out, loading, signed-in, and failed-auth states explicitly.\n- Never store credentials, service-role keys, refresh tokens, private session\n values, or authenticated response bodies in project data, command output,\n screenshots, agent instructions, or error reports. Privileged provider calls\n and authorization enforcement belong server-side.\n- Basic auth is semantic today. Provider-specific Supabase/Firebase setup still\n uses the existing resource, variable, prop, binding, and embed tools; there is\n no provider-specific installer.\n\n## Generate from design input\n\nCommands:\n\n- MCP tool: meta.guide {"brief":"Recreate this Figma design as a responsive page"}\n- MCP tool: create-page {"name":"Landing","path":"/landing"}\n- MCP tool: create-design-token {"tokens":"tokens.json contents"}\n- MCP tool: define-css-variable {"vars":"vars.json contents"}\n- MCP tool: list-breakpoints {}\n- MCP tool: insert-fragment {"parentInstanceId":"<instanceId>","fragment":"<ws.element ws:tag=\'section\'><ws.element ws:tag=\'p\'>Section copy</ws.element></ws.element>"}\n- MCP tool: update-styles {"updates":[{"instanceId":"<instanceId>","breakpointId":"<breakpointId-from-list-breakpoints>","property":"padding-left","value":{"type":"unit","unit":"px","value":24}}]}\n- MCP tool: preview.start {"host":"127.0.0.1","port":5173}\n- MCP tool: screenshot {"path":"/landing","output":"landing-desktop.png","viewport":{"width":1440,"height":900},"waitUntil":"load","waitForTimeout":250}\n- MCP tool: screenshot {"path":"/landing","output":"landing-mobile.png","viewport":{"width":390,"height":844},"waitUntil":"load","waitForTimeout":250}\n- MCP tool: screenshot {"baseUrl":"http://127.0.0.1:5177","path":"/landing","output":"landing-desktop.png","viewport":{"width":1440,"height":900},"waitUntil":"load","waitForTimeout":250}\n\nNotes:\n\n- Use this after the agent can inspect the supplied design. There is no direct\n Figma, screenshot, Inception, or `design.md` import command.\n- Inspect and reuse existing variables, tokens, styles, components, assets, and\n page patterns before authoring. Build semantic editable structure rather than\n flattening the design into an image or absolute-positioned approximation.\n- Verify one familiar viewport inside every distinct Builder breakpoint range,\n then run rendered audit and inspect the screenshots before completion.\n\n## Cross-project maintenance\n\nCommands:\n\n- webstudio mcp run .temp/projects.json\n- webstudio mcp run .temp/projects.json --dry-run\n- webstudio mcp run .temp/projects.json --approve-mutations --concurrency 2\n\nNotes:\n\n- Put shared `calls` and a `projects` array of independently linked project roots in the existing `mcp run` manifest. Project roots are relative to the manifest file.\n- Each project uses its own config, authentication, ProjectSession storage, checkpoint, and failure boundary. Confirmed successful calls are checkpointed. Reads can resume automatically; a mutation interrupted after dispatch is reported as ambiguous and is not replayed automatically, preventing silent duplicate writes.\n- Focus the manifest on bounded reads or audits first. Use per-call `dryRun`, global `--dry-run`, or explicitly approve committed mutations with `--approve-mutations` after reviewing the manifest.\n\n# Known CLI Gaps\n\n## Provider-specific authenticated pages\n\nMissing:\nCLI supports page basic auth and generic resources/props/embeds, but not guided Supabase/Firebase auth setup.\n\nCurrent fallback:\nCall `meta.guide` with the provider-authenticated page goal, then create the\npage, resources, variables, props, bindings, and embeds with existing semantic\ntools.\n\nSuggested commands:\n\n- setup-auth-page\n\n## Generate from design input\n\nMissing:\nNo command imports Figma, screenshots, Inception output, or design.md and turns it into pages/tokens/layout.\n\nCurrent fallback:\nCall `meta.guide` with the design-input goal, let the agent inspect the supplied\ndesign, then use semantic page, token, asset, fragment, style, preview,\nscreenshot, and audit tools. Use `apply-patch` only when no semantic operation\nfits.\n\nSuggested commands:\n\n- generate-from-design\n\n## Built-in cross-project maintenance\n\nMissing:\nPublic API and CLI intentionally operate on one configured project at a time; there is no built-in multi-project discovery or loop runner.\n\nCurrent fallback:\nRun the CLI from an external script that reconfigures one project/session at a time.\n\nSuggested commands:\n\n- none\n',
|
|
273237
273736
|
"manual-api": '# Webstudio API CLI Manual\n\nThe API commands operate on the single project configured by:\n\n- .webstudio/config.json: projectId\n- global Webstudio config: origin and token\n\n- Pass --json to API/discovery commands that support it. Do not add --json to top-level commands unless their help/schema documents it.\n- Never pass a project id. Commands use configured project only.\n- Read ids before writing. Do not invent ids for existing records.\n- stdout is one JSON object. stderr is diagnostics.\n- Prefer MCP semantic tools for detailed project edits. Use MCP apply-patch only when no semantic tool exists.\n\n## Start\n\n{{start}}\n\n## Read First\n\n{{readFirst}}\n\n## Project Session Cache\n\n- CLI commands use one local ProjectSession snapshot for the configured project.\n- Local-capable reads use cached namespaces when compatible and fetch only missing or stale namespaces.\n- Local-capable mutations build patches from the local snapshot, then commit with the cached build version.\n- Successful mutation commits update the local snapshot only after the remote commit succeeds.\n- Server-only commands run remotely and invalidate/refetch namespaces declared by the operation catalog.\n- Use --refresh on local-capable commands to refresh required namespaces before running.\n- Successful JSON responses include compact meta.session with operationId, buildId, version, source, committed, namespaceCounts, diagnosticCount, non-empty diagnostic summaries, and optional compatibilityVersion.\n\n## CLI Capability Inventory\n\nFor a short end-consumer summary of what MCP can do, see\n`manual mcp` / `webstudio man mcp`. The MCP inventory describes the same\nproject, page, element, style, data, asset, publish, domain, and visual\nverification capabilities without internal command names.\n\n### Top-Level Commands\n\n{{topLevelCapabilityIndex}}\n\n### High-Level API Commands By Area\n\n{{apiCapabilityIndex}}\n\n### MCP Tool Operations\n\nThese are MCP tools. From a shell, call them with the shortcut form `webstudio <tool> \'<json>\'` or with the explicit form `webstudio mcp single-op-call <tool> \'<json>\'`:\n\n{{mcpOnlyCommandIndex}}\n\n## Task Recipes\n\n{{taskRecipeIndex}}\n\n## Use Case Index\n\n{{useCaseIndex}}\n\n## Known CLI Gaps\n\n{{knownCliGapIndex}}\n\n## Input File Shapes\n\n{{inputFileShapeIndex}}\n\n## Raw Patch Fallback\n\napply-patch accepts either BuildPatchTransaction[] or { "transactions": BuildPatchTransaction[] }.\n\nEach transaction has:\n\n{\n"id": "patch-transaction-label",\n"payload": [\n{\n"namespace": "projectSettings",\n"patches": [\n{ "op": "replace", "path": ["meta", "siteName"], "value": "New Site" }\n]\n}\n]\n}\n\nThe transaction id is a patch label used for optimistic synchronization. It is\nnot a Builder record id. Do not invent ids for pages, instances, props,\nbreakpoints, resources, variables, folders, assets, or other project records.\n\nPatch paths are JSON-patch-like paths into Builder store data. Map-like namespaces use ids as the first path item.\n\nSupported namespaces:\n\n- pages: redirects, page records, and folders\n- projectSettings: project-wide metadata and compiler settings\n- instances: element instances and children, including text/expression children\n- props: element props, bindings, page references, resource bindings\n- styles: CSS declarations keyed by style declaration key\n- styleSources: local style sources and reusable design tokens\n- styleSourceSelections: instance-to-style-source connections\n- dataSources: data variables, parameters, and resource data sources\n- resources: data resource definitions\n- assets: project asset records handled by the existing asset patch path\n- breakpoints: responsive breakpoints\n- marketplaceProduct: marketplace metadata\n\n## Data Sources\n\n`dataSources` is the internal Builder namespace for variables. Public API, CLI,\nand MCP tools expose it through two user-facing groups:\n\n- data variables: `list-variables`, `create-variable`, `update-variable`, and\n `delete-variable`\n- data resources: `list-resources`, `create-resource`, `update-resource`, and\n `delete-resource`\n\nFor raw `snapshot`, request the public `variables` namespace rather than the\ninternal `dataSources` name. Raw patch payloads still use `dataSources` when\napplying direct changes.\n\nVariables can be scoped to an instance. Expressions under that instance can use\nthe variable by name; nested variables with the same name mask outer variables.\nVariable values support `string`, `number`, `boolean`, and `json`. Use `json`\nfor all arrays and objects, including tags, selected categories, and nested API\nfilter state.\nParameters are internal scoped runtime values provided by pages, collections,\nor components. They are not a public authoring surface: do not create, update,\nor delete parameter records. Public tools should preserve existing parameter\nrecords and may reference documented context values such as `system` in\nexpressions where they are already in scope.\n\nResource `url` accepts plain fixed URLs and paths, for example\n`https://api.example.com/posts` or `/$resources/current-date`. Dynamic URLs can\ncombine strings and variables, for example\n`"https://api.example.com/posts?tag=" + filters.tag`. Prefer `searchParams` for\nquery parameters that should be encoded separately:\n`[{ "name": "tag", "value": "filters.tag" }]`. Header values, search parameter\nvalues, and bodies are expressions for dynamic content. For fixed text, use\n`{ "type": "literal", "value": "application/json" }`; Webstudio stores the\nrequired string expression. Headers can still read variables such as\n`"Bearer " + auth.token`, and GraphQL bodies can return objects such as\n`{ query: "...", variables: { slug: system.params.slug } }`.\n\nCreate a GET resource with `scopeInstanceId` when the fetched resource result\nshould be available as a read data variable. Scoped GET resources default to\n`exposeAsDataSource: true`, are generated into the page resource `data` map,\nand may be loaded during page rendering. Use `dataSourceName` to choose the\nvariable name.\n\nFor submit/write/action resources, create the resource without\n`scopeInstanceId`, then bind a component prop such as a Form `action` to the\nresource with `bind-props` and `binding.type: "resource"`. Prop-bound resources\nare generated into the page resource `action` map instead of the read `data`\nmap. Use this shape for POST, PUT, DELETE, webhook, and other resources that\nshould run only from an explicit form/action flow, not merely because the page\nrendered.\n\nPOST, PUT, and DELETE resources default to `exposeAsDataSource: false` even\nwhen a scope is supplied. Set `exposeAsDataSource: true` only when a write-method\nresource intentionally provides render-time data, such as a read-only GraphQL\nPOST query. A scope is required, and the result includes a warning because the\nrequest may execute during page rendering. Set `exposeAsDataSource: false` on\n`update-resource` to detach an existing render-time data source.\n\nResource `method` can be `get`, `post`, `put`, or `delete`. Use GET for read\ndata. Use POST for creates, GraphQL requests, webhooks, and form submissions.\nUse PUT for full updates/replacements. Use DELETE for deletion actions.\nOptional `control` values are `graphql` and `system`: `graphql` marks a\nGraphQL-style resource, usually POST with a query body; `system` marks a\nresource intended to use the built-in `system` parameter or one of the built-in\nlocal resource URLs: `"/$resources/sitemap.xml"`,\n`"/$resources/current-date"`, and `"/$resources/assets"`. The system parameter\nfields are `system.origin`, `system.pathname`, `system.params`, and\n`system.search`.\n\nUse prop bindings for dynamic values that read variables or resources; use\ndirect props for static values.\n\nCommit raw patch:\n\nMCP tool: apply-patch\n\n## Raw Patch Examples\n\nRename the site:\n\n[\n{\n"id": "patch-site-name",\n"payload": [\n{\n"namespace": "projectSettings",\n"patches": [\n{ "op": "add", "path": ["meta", "siteName"], "value": "Acme Studio" }\n]\n}\n]\n}\n]\n\nUpdate page title metadata:\n\n[\n{\n"id": "patch-page-title",\n"payload": [\n{\n"namespace": "pages",\n"patches": [\n{ "op": "replace", "path": ["pages", "page-id", "meta", "title"], "value": "Pricing" }\n]\n}\n]\n}\n]\n\nUpdate a text child on an element:\n\n[\n{\n"id": "patch-text",\n"payload": [\n{\n"namespace": "instances",\n"patches": [\n{ "op": "replace", "path": ["instance-id", "children", 0, "value"], "value": "Launch faster" }\n]\n}\n]\n}\n]\n\nCreate records with semantic operations such as create-variable,\ncreate-resource, create-design-token, create-page, create-folder,\nand create-breakpoint. Raw patch rejects generated record\ncreation, collection replacement, record replacement with a different `id`, and\nrecord id field mutations in id-keyed namespaces because Webstudio must generate\nand preserve record ids.\n\n## Safety Rules\n\n- For MCP apply-patch, read the latest version with MCP snapshot before writing.\n- Reuse ids from MCP snapshot output when updating existing records.\n- Do not create generated records, replace generated record collections, replace records with different ids, or mutate record id fields with raw patch. Use semantic create operations so Webstudio generates ids.\n- If apply-patch reports a version conflict, read the latest build and regenerate the patch.\n- Prefer semantic MCP read tools for discovery, then use MCP snapshot for exact patch paths.\n\n## Command Index\n\n{{commandIndex}}\n',
|
|
273238
|
-
"manual-llm": '# Webstudio CLI Manual for LLMs\n\nUse this order. Stop only when a command returns ok:false.\n\nIf you are inside the Webstudio monorepo, the first command discovery should use\nthe local CLI exactly as `node packages/cli/local.js ...` from the repo root. Do\nnot use `packages/cli/bin.js` for local source-tree work; it is the packaged\nbuild entry and may use stale built output. Do not use `pnpm exec webstudio`,\n`pnpm --filter webstudio exec webstudio`, or a global `webstudio`: they can\nresolve an older binary.\n\nFor delegated design-system or “use every component” tasks, skip the generic warm-up sequence and start with exactly one MCP command: `webstudio workflow.next \'{"goal":"design-system-page"}\'`. Report that returned checkpoint to the parent/user and stop until continued.\n\n## Use MCP locally or optionally connect a client\n\nDo not install, register, or connect an MCP server merely because the user asks\nyou to edit a Webstudio project. If Webstudio MCP tools are already available,\nuse them. If you have shell access, use the local CLI shortcuts such as\n`webstudio meta.index` and `webstudio list-pages`; they expose the same project\noperations without changing client configuration or restarting the app.\n\nOnly when the user explicitly asks for persistent native MCP integration, run\nthe command for their client:\n\n- Claude Code: `webstudio connect claude`\n- Codex: `webstudio connect codex`\n- Cursor: `webstudio connect cursor`\n- VS Code or GitHub Copilot: `webstudio connect vscode`\n\nRun project operations from the linked project root. If the folder is not\nlinked, ask for an editable Builder share link and run\n`webstudio init --link <share-link> --json`. You can then use local CLI\nshortcuts immediately. Do not run `webstudio sync`, `webstudio connect`, or\nrestart the app unless the user specifically wants native MCP registration:\nMCP reads and edits the latest editable Builder build directly, including for\nprojects that have never been published. Treat the share link as a credential\nand do not include it in committed files, logs, screenshots, or issue reports.\n\nThe optional `connect` command verifies project access before changing client configuration. For\nClaude Code, Cursor, and VS Code it safely merges the `webstudio` server into\nthe client\'s project configuration. For Codex it runs both `codex mcp add` and\n`codex mcp get webstudio`; do not repeat those commands separately. Follow the\nreload, restart, or approval instruction printed by `connect`, then verify the\nloaded MCP connection by asking the client to use Webstudio MCP and list the\nproject pages. Use `--print` only to inspect the generated setup without\nchanging configuration or requiring project access.\n\n## Always\n\n1. webstudio permissions --json\n2. For bounded shell workflows, call MCP tools directly through the CLI shortcut, for example `webstudio meta.index` or `webstudio insert-fragment \'<json>\' --dry-run`. The explicit form `webstudio mcp single-op-call <tool> \'<json>\'` is equivalent and useful when you need to make the MCP boundary obvious. Use `webstudio mcp run \'[{"tool":"components.find","input":{"brief":"button"}}]\'` for multiple calls in one shared CLI session. Use a normal JSON file path for large batches. Use long-running `webstudio mcp` only when your environment is a real MCP client. Do not manually send raw JSON-RPC to `webstudio mcp` from a shell or PTY.\n3. Read MCP `meta.index`, for example `webstudio meta.index`.\n4. Use focused MCP calls with concrete JSON: `webstudio meta.guide \'{"brief":"Create a design system page using every component"}\'`, `webstudio meta.get-more-tools \'{"tools":["insert-fragment"]}\'`, `webstudio components.list \'{"source":"all"}\'`, `webstudio components.coverage-plan`, `webstudio components.search \'{"brief":"radix select"}\'`, `webstudio components.get \'{"component":"@webstudio-is/sdk-components-react-radix:Select"}\'`, `webstudio templates.list`, and `webstudio templates.get \'{"component":"@webstudio-is/sdk-components-react-radix:Select"}\'`.\n5. Read overview resources `webstudio://project/tools-overview` or `webstudio://project/components-overview` when useful. Read full resources `webstudio://project/tools` or `webstudio://project/components` only when focused tools are insufficient.\n6. Pick focused MCP read tool.\n7. Pick semantic MCP write tool.\n\nUse `webstudio schema mcp` for a compact MCP tool overview. Add `--verbose` only when exact input schemas for all tools are truly needed; otherwise prefer focused `meta.get-more-tools` and `components.*` calls.\n\nRun these commands from the linked project root. Use the MCP startup status line\'s absolute root for local files; write temporary scripts and artifacts under `<project root>/.temp`, not under a parent workspace.\n\nMonorepo quick path for a simple styled section:\n\n```sh\nnode packages/cli/local.js mcp single-op-call meta.index\nnode packages/cli/local.js mcp single-op-call meta.get-more-tools \'{"tools":["insert-fragment"]}\'\nnode packages/cli/local.js mcp single-op-call insert-fragment --input-file .temp/insert-fragment.json --dry-run\n```\n\nSave the readable payload in `.temp/insert-fragment.json`:\n\n```json\n{\n "parentInstanceId": "parent-id",\n "fragment": "<ws.element ws:tag=\'section\' ws:style={css`padding: 32px; display: grid; gap: 12px;`}><ws.element ws:tag=\'h2\'>Launch Kit</ws.element><ws.element ws:tag=\'p\'>A focused section created with Webstudio JSX.</ws.element><ws.element ws:tag=\'button\'>Get started</ws.element></ws.element>"\n}\n```\n\nSingle quotes inside the JSX keep the JSON valid without backslash-escaped attributes. The same local shortcut form is shorter and preferred for simple shell steps:\n\n```sh\nnode packages/cli/local.js meta.index\nnode packages/cli/local.js meta.get-more-tools \'{"tools":["insert-fragment"]}\'\nnode packages/cli/local.js insert-fragment --input-file .temp/insert-fragment.json --dry-run\n```\n\nFor this simple path, do not grep source files, dump full MCP resources, or write parser scripts first. Use `list-pages`, `get-page-by-path`, or `list-instances` only to get the target `parentInstanceId`.\n\nWhen authoring JSX for `insert-fragment`, use Webstudio component helpers and Webstudio style syntax. Use `ws:style={css\\`...\\`}`for Webstudio-native CSS. For simpler cases, use React-style object syntax such as`style={{ padding: 24 }}`. Both forms create editable Webstudio style data.\n\nWhen the task says another user will edit a page in Content mode, use a Content Block (`ws:block`) around every editable region. Content-mode users can edit text and supported props only in descendants of that block; content outside it is read-only. Put reusable insertable options in the block\'s `ws:block-template` child. Do not put intended editor content inside that template container: templates are protected source material, while an inserted template copy becomes an editable direct child of the Content Block. Verify this structure before handoff.\n\nDo not access host globals or dynamic code APIs in JSX fragments, including `process`, `globalThis`, `eval`, `Function`, or `constructor`. JSX fragments are declarative project data; use the built-in Webstudio helpers instead.\n\nUse Webstudio prop names in JSX: `class`, `for`, `aria-label`, and other HTML/Webstudio names. Do not use React-only aliases such as `className` or `htmlFor`; the runtime rejects them with the Webstudio prop name to use.\n\nUse Webstudio actions for event/action props. Do not pass JavaScript functions such as `onClick={() => ...}`; the runtime rejects them because functions cannot be persisted as Webstudio project data.\n\n```tsx\n<ws.element\n ws:tag="button"\n onClick={new ActionValue(["event"], expression`console.log(event)`)}\n>\n Open\n</ws.element>\n```\n\nPlain JSX prop values must be JSON-compatible: `null`, strings, booleans, finite numbers, arrays, and plain objects. Do not pass `undefined`, `Symbol`, `BigInt`, `NaN`, `Infinity`, `Date`, `Map`, `Set`, class instances, or circular objects; omit the prop, use plain data, or use `expression`/`ActionValue` when the value is dynamic.\n\nIf a component has a registered template with required parts, JSX must include those parts explicitly under the same parent structure as the template, for example `<radix.Switch><radix.SwitchThumb /></radix.Switch>`. Use `insert-component` when you want Webstudio to apply one component template automatically.\n\n## Animation Components\n\nBefore creating animation examples, inspect the exact components with focused discovery:\n\n```sh\nwebstudio components.get \'{"component":"@webstudio-is/sdk-components-animation:AnimateChildren"}\'\nwebstudio components.get \'{"component":"@webstudio-is/sdk-components-animation:AnimateText"}\'\nwebstudio components.get \'{"component":"@webstudio-is/sdk-components-animation:StaggerAnimation"}\'\nwebstudio components.get \'{"component":"@webstudio-is/sdk-components-animation:VideoAnimation"}\'\n```\n\nUse Animation Group (`AnimateChildren`) as the controller. Put normal instances directly inside it, or put Text Animation, Stagger Animation, or Video Animation directly inside it. Text, Stagger, and Video are helper components with `contentModel.category: "none"` and should not be used as standalone section roots.\n\nDefine timing and CSS changes on the Animation Group `action` prop. Use `type:"view"` for viewport entry/exit progress and `type:"scroll"` for scroll-progress timelines. For in animations, keep the canvas styles as the final state and use `fill:"backwards"` with keyframes that describe the starting state. For out animations, use `fill:"forwards"` with keyframes that describe the ending state.\n\nText Animation settings: `slidingWindow` defaults to `5`, `easing` defaults to `linear`, and `splitBy` defaults to `char`. Use `splitBy:"space"` for word-by-word animation. The parent Animation Group keyframes provide the actual opacity, translate, scale, or other styles.\n\nStagger Animation settings: `slidingWindow` defaults to `1` and `easing` defaults to `linear`. It applies parent Animation Group progress across its direct children. Use `slidingWindow:0` for instant sequential steps, `1` for one child at a time, and values above `1` for overlapping waves.\n\nVideo Animation settings: `timeline` is a boolean. Prefer `insert-component` for Video Animation so the Video child template is inserted, then configure the Video child asset/source. Use short, seek-friendly videos for smooth scroll-linked playback.\n\nUse JSX fragments for authored animation structures when you need styled, editable examples. Put the final visual state in `ws:style` and put the starting or ending animated state in the Animation Group `action` keyframes. Include an explicit `offset` on every keyframe: use `offset: 0` for starting-state keyframes with `fill:"backwards"` and `offset: 1` for ending-state keyframes with `fill:"forwards"`.\n\n```tsx\n<animation.AnimateChildren\n action={{\n type: "view",\n axis: "block",\n animations: [\n {\n name: "Fade up on entry",\n timing: {\n fill: "backwards",\n rangeStart: ["entry", { type: "unit", value: 0, unit: "%" }],\n rangeEnd: ["entry", { type: "unit", value: 100, unit: "%" }],\n },\n keyframes: [\n {\n offset: 0,\n styles: {\n opacity: { type: "unit", value: 0, unit: "number" },\n translate: {\n type: "tuple",\n value: [\n { type: "unit", value: 0, unit: "number" },\n { type: "unit", value: 24, unit: "px" },\n ],\n },\n },\n },\n ],\n },\n ],\n }}\n>\n <ws.element\n ws:tag="section"\n ws:style={css`\n display: grid;\n gap: 16px;\n padding: 48px;\n border-radius: 24px;\n background: #111827;\n color: white;\n `}\n >\n <ws.element ws:tag="h2">Launch metrics</ws.element>\n <ws.element ws:tag="p">\n A polished card that fades up as it enters the viewport.\n </ws.element>\n </ws.element>\n</animation.AnimateChildren>\n```\n\nFor Text Animation, keep `animation.AnimateText` as the direct child of Animation Group and place the text-containing element inside it:\n\n```tsx\n<animation.AnimateChildren\n action={{\n type: "view",\n animations: [\n {\n name: "Parallax In",\n timing: {\n fill: "backwards",\n rangeStart: ["cover", { type: "unit", value: 0, unit: "%" }],\n rangeEnd: ["cover", { type: "unit", value: 70, unit: "%" }],\n },\n keyframes: [\n {\n offset: 0,\n styles: {\n translate: {\n type: "tuple",\n value: [\n { type: "unit", value: 0, unit: "number" },\n { type: "unit", value: 100, unit: "px" },\n ],\n },\n },\n },\n ],\n },\n {\n name: "Opacity In",\n timing: {\n fill: "backwards",\n rangeStart: ["cover", { type: "unit", value: 0, unit: "%" }],\n rangeEnd: ["cover", { type: "unit", value: 70, unit: "%" }],\n },\n keyframes: [\n {\n offset: 0,\n styles: {\n opacity: { type: "unit", value: 0, unit: "number" },\n },\n },\n ],\n },\n {\n name: "Scale In",\n timing: {\n fill: "backwards",\n rangeStart: ["cover", { type: "unit", value: 0, unit: "%" }],\n rangeEnd: ["cover", { type: "unit", value: 70, unit: "%" }],\n },\n keyframes: [\n {\n offset: 0,\n styles: {\n scale: {\n type: "tuple",\n value: [\n { type: "unit", value: 5, unit: "number" },\n { type: "unit", value: 5, unit: "number" },\n ],\n },\n },\n },\n ],\n },\n {\n name: "Parallax Out",\n timing: {\n fill: "forwards",\n rangeStart: ["cover", { type: "unit", value: 50, unit: "%" }],\n rangeEnd: ["cover", { type: "unit", value: 100, unit: "%" }],\n },\n keyframes: [\n {\n offset: 1,\n styles: {\n translate: {\n type: "tuple",\n value: [\n { type: "unit", value: 0, unit: "number" },\n { type: "unit", value: -100, unit: "px" },\n ],\n },\n scale: {\n type: "tuple",\n value: [\n { type: "unit", value: 5, unit: "number" },\n { type: "unit", value: 5, unit: "number" },\n ],\n },\n opacity: { type: "unit", value: 0, unit: "number" },\n },\n },\n ],\n },\n ],\n insetStart: { type: "unit", value: 5, unit: "%" },\n insetEnd: { type: "unit", value: 5, unit: "%" },\n isPinned: true,\n }}\n>\n <animation.AnimateText\n splitBy="space"\n slidingWindow={5}\n easing="easeOutQuart"\n >\n <ws.element ws:tag="h2">Animate words with controlled rhythm</ws.element>\n </animation.AnimateText>\n</animation.AnimateChildren>\n```\n\nFor Stagger Animation, put the repeated cards or rows directly inside `animation.StaggerAnimation`:\n\n```tsx\n<animation.AnimateChildren\n action={{\n type: "view",\n animations: [\n {\n timing: {\n fill: "backwards",\n rangeStart: ["contain", { type: "unit", value: 0, unit: "%" }],\n rangeEnd: ["contain", { type: "unit", value: 30, unit: "%" }],\n },\n keyframes: [\n {\n offset: 0,\n styles: {\n opacity: { type: "unit", value: 0, unit: "number" },\n },\n },\n ],\n },\n ],\n }}\n>\n <animation.StaggerAnimation>\n <ws.element\n ws:tag="article"\n ws:style={css`\n padding: 20px;\n border: 1px solid #d1d5db;\n border-radius: 16px;\n `}\n >\n Plan\n </ws.element>\n <ws.element\n ws:tag="article"\n ws:style={css`\n padding: 20px;\n border: 1px solid #d1d5db;\n border-radius: 16px;\n `}\n >\n Build\n </ws.element>\n <ws.element\n ws:tag="article"\n ws:style={css`\n padding: 20px;\n border: 1px solid #d1d5db;\n border-radius: 16px;\n `}\n >\n Launch\n </ws.element>\n </animation.StaggerAnimation>\n</animation.AnimateChildren>\n```\n\nFor Video Animation, use the registered template via `insert-component` when possible. If you author JSX, include the Video child explicitly:\n\n```tsx\n<animation.AnimateChildren\n action={{\n type: "view",\n animations: [\n {\n name: "Video progress",\n timing: {\n fill: "both",\n rangeStart: ["cover", { type: "unit", value: 0, unit: "%" }],\n rangeEnd: ["cover", { type: "unit", value: 100, unit: "%" }],\n },\n keyframes: [{ offset: 0, styles: {} }],\n },\n ],\n }}\n>\n <animation.VideoAnimation timeline={true}>\n <$.Video\n preload="auto"\n autoPlay={true}\n muted={true}\n playsInline={true}\n crossOrigin="anonymous"\n />\n </animation.VideoAnimation>\n</animation.AnimateChildren>\n```\n\n## Command Surface Boundary\n\n- Use top-level `webstudio ...` shell commands for setup, sync/import/build/preview/screenshot, permissions, publish/domains, schema, registry inspection, man, and starting MCP.\n- Use MCP tools for Builder project data manipulation: pages, instances/components, props, text, styles, tokens, variables, resources, assets, breakpoints, redirects, and raw patches.\n- From a shell, call MCP tools with the shortcut form `webstudio <tool> \'<json>\'`, for example `webstudio insert-fragment \'<json>\' --dry-run`. The explicit equivalent is `webstudio mcp single-op-call <tool> \'<json>\'`. Use `--input-file` for large payloads.\n- Inside the Webstudio monorepo, call the local CLI as its own command: `node packages/cli/local.js ...`. Do not wrap the CLI call in `pwd && ...`, command substitution, `pnpm exec webstudio`, `pnpm --filter webstudio exec webstudio`, or a global `webstudio`.\n- For experiments, pass `--dry-run` to local-capable mutation calls. Read the computed transaction from `meta.session.transaction` and its base build version from `meta.session.version`. Copying a `.webstudio` folder is not an isolated project clone; `.webstudio/config.json` still points to the same remote project, so non-dry-run mutations can commit to that project.\n- Read `meta.session.commitStatus` before interpreting durability. Read-only results report `not-applicable` and retain `committed:false` for compatibility; dry-run plans report `planned`; failed mutations report `failed`; no-op mutations report `unchanged`; durable mutations report `committed` with `meta.session.committed:true`.\n- For bounded multi-step shell work, run inline JSON with `webstudio mcp run \'[{"tool":"components.find","input":{"brief":"button"}}]\'`; this reuses one CLI session without raw JSON-RPC. For large batches, write `{ "calls": [{ "tool": "..." }] }` to a normal JSON file and run `webstudio mcp run .temp/mcp-calls.json`.\n- Use JSON strings for `brief` fields. Never pass boolean flags such as `{"brief":true}`.\n- Treat `webstudio mcp single-op-call` and `webstudio mcp run` stderr lines as progress checkpoints; stdout remains JSON on both success and failure. On failure, parse stdout for `{ "ok": false, "error": { "code": "...", "message": "..." } }` before deciding what to fix.\n- If a CLI/MCP tool crashes, hangs, gives a confusing error, needs an undocumented workaround, or forces source-code inspection for normal usage, ask the user to report it in Discord `#help` at https://wstd.us/community. Give them a complete copy-paste report with the goal, expected behavior, actual error, exact command/tool call, stdout JSON, stderr/lifecycle logs, environment, workaround, and secrets redacted.\n- Run one-shot `webstudio mcp single-op-call` commands sequentially against a linked `.webstudio` folder. If a command returns `PROJECT_SESSION_BUSY`, another CLI/MCP process is updating the local session; wait a moment and retry sequentially.\n- In delegated or non-streaming agent environments, do not batch many MCP calls silently and do not wrap many shortcut or `webstudio mcp single-op-call` commands in a shell loop. Treat each parent-visible checkpoint as the unit of work. If the parent asks for status within 30 seconds, run exactly one shortcut command such as `webstudio meta.index` or one explicit `webstudio mcp single-op-call` command, report that command/result, then wait for the parent to continue. Do not take a broad task such as creating a full design-system page as one execution unit. Call `workflow.next {"goal":"design-system-page"}`, report the returned phase/checkpoint, wait until the parent continues, call `checkpoint.ack {"reported":true,"continueAfterReport":true,"summary":"<what you reported>"}`, complete exactly that bounded phase, and return: discovery, page creation, one dry-run JSX section, one committed JSX section, one `components.coverage-insert-next` call, or one presentation pass. Phase commands do not include nextPhase in their own output. After the parent continues, acknowledge the previous checkpoint first, then call `workflow.next` with the next phase. For all-component design-system pages, checkpoint after workflow planning, discovery, page creation, call `components.coverage-insert-next` once before checkpointing again, then finish with `workflow.next {"goal":"design-system-page","phase":"presentation-pass"}`. Coverage 72/72 is necessary but not sufficient: the page must be organized into styled, real-world examples, not raw unstyled component dumps.\n- For design-system or “use every component” tasks, start with compact `webstudio components.coverage-plan`, checkpoint, then request component coverage details with `webstudio components.coverage-plan \'{"detail":"roots","offset":20}\'` or `{"detail":"parts"}` only when needed. Do not pass `detail` to `list-pages`; use `list-pages {}` or `get-page-by-path` for page lookup.\n- MCP tool shortcuts are only for MCP tools. If a shortcut is ambiguous with a real top-level command, the real top-level command wins; use `webstudio mcp single-op-call <tool> \'<json>\'` to force the MCP path.\n\n## LLM Implementation Process\n\nUse this process for user requests that change Webstudio content, layout, styles, assets, pages, redirects, resources, or publishing state:\n\n1. Discover capabilities with `webstudio man --json`, `webstudio schema api`, `webstudio schema mcp`, MCP `meta.index`, `meta.guide`, `meta.get-more-tools`, `components.list`, `components.summary`, `components.coverage-plan`, `components.search`, `components.get`, `templates.list`, and `templates.get`. From a shell, prefer shortcut calls such as `webstudio meta.index` and `webstudio components.search \'{"brief":"button"}\'` for these focused tool calls; use `webstudio mcp single-op-call` when you need the explicit MCP form. Read full resources such as `webstudio://project/tools` and `webstudio://project/components` only when needed. Do not write scripts to parse full MCP discovery JSON for normal lookup.\n2. Inspect current project state with semantic reads such as `get-project-settings`, `list-pages`, `get-page-by-path`, `list-instances`, `inspect-instance`, `get-styles`, `list-assets`, `list-breakpoints`, and `snapshot` only when needed. Before changing a project, read `get-project-settings` and follow any non-empty `meta.agentInstructions`. These are shared project instructions, not a place for secrets.\n3. Mutate the Webstudio project with semantic MCP write tools first. Prefer MCP `insert-fragment` for authored/styled sections, use `insert-component` only for one automatic component template, then `update-text`, `update-props`, `update-styles`, `upload-asset`, `create-page`, and page/project settings tools over raw patches.\n4. Use `apply-patch` only when no semantic tool covers the required change, and only after reading the latest snapshot/version.\n5. For visual/design work, regenerate or preview the generated app, capture a screenshot, inspect it with vision, and iterate before final response.\n6. Report what changed and what verification ran.\n\n## Visual Design Workflow\n\nFor requests involving visible HTML/CSS, layout, typography, colors, imagery, responsive behavior, or screenshots:\n\n1. Read editable Webstudio structure first: pages, instances, props, styles, breakpoints, assets, and relevant text.\n2. Do not use generated route/component files as the source of truth for editable content.\n3. Make edits through Webstudio semantic commands/MCP tools so the result stays editable in Builder and survives the next `webstudio build`.\n4. Keep generated project files current, start preview, and capture the changed page with `screenshot`.\n5. Use `screenshot.diff` when a baseline exists and inspect screenshot/diff artifacts with vision before finishing.\n6. If vision or screenshot tooling is unavailable, state that explicitly and explain what fallback verification was used.\n\n## Responsive Verification Workflow\n\nFor responsive page work, use Builder breakpoints as the source of truth:\n\n1. Read breakpoints with `list-breakpoints` before deciding responsive behavior.\n2. Apply responsive styles with existing Builder breakpoint ids; do not invent CSS media queries or breakpoint names when Webstudio breakpoint data exists.\n3. Pick screenshot viewport widths from the project breakpoints: include a desktop width, each defined max-width or min-width edge, and a narrow mobile width.\n4. Capture each viewport with `screenshot`, for example `{"path":"/","output":"home-375.png","viewport":{"width":375,"height":812}}` and `{"path":"/","output":"home-1440.png","viewport":{"width":1440,"height":900}}`.\n5. Inspect every viewport screenshot with vision before finishing, checking layout, overflow, hidden content, text wrapping, and breakpoint-specific style changes.\n6. If any viewport fails, update styles through semantic Webstudio tools and repeat screenshots for the affected breakpoints.\n\n## Generated Files Guardrails\n\n- Do not edit `app/__generated__`, generated route files, generated page files, generated CSS, or build output for normal Webstudio content/design requests.\n- Do not replace generated page components with handcrafted app code unless the user explicitly asks for code-only export customization.\n- Generated files are build artifacts and may be overwritten by `webstudio build`.\n- If a task truly requires generated app customization, keep it outside `app/__generated__` where possible and explain that it is not editable Webstudio content.\n\n## Values vs Bindings\n\nBefore authoring unfamiliar expressions, read `webstudio://project/expressions` with MCP `resources/read` or `webstudio mcp read-resource webstudio://project/expressions`. It documents the supported expression subset, method allowlist, scope, Collection context, and validation limits.\n\n- Use direct value tools for fixed content. For one visible text child, use `update-text` with plain `text`. For a bounded multi-instance literal replacement, use `replace-text` with `find`, `replace`, `pagePath` or `pageId`, and `limit`; it does not change expression children. Use `replace-prop-text` for bounded changes inside static string props, optionally limited to prop names or instance ids; it never changes dynamic bindings. For static props such as `aria-label`, `alt`, `id`, `class`, `href`, or button labels stored as props, use `update-props` with the prop\'s direct type/value.\n- Use `bind-props` only when the prop must stay dynamic: an expression, resource result, action, or existing scoped runtime context such as `system`. Do not use `bind-props` just to set a fixed string.\n- Direct prop string example: `{"updates":[{"instanceId":"button-id","name":"aria-label","type":"string","value":"Open menu"}]}`.\n- Expression binding example: `{"bindings":[{"instanceId":"link-id","name":"href","binding":{"type":"expression","value":"currentPost.url"}}]}`.\n- Page metadata fields such as `title`, `description`, `language`, `redirect`, and custom meta content accept plain fixed text. For computed values, pass JavaScript expression code such as `pageTitle ?? "Pricing | Acme"`.\n- Page `status` accepts a fixed HTTP status code as a number from 200 through 599, for example `302`. For a dynamic status, pass JavaScript expression code such as `system.status`.\n- Page metadata update example: use `update-page` with `{"pageId":"page-id","values":{"title":"Pricing | Acme","meta":{"description":"Plans for teams"}}}`.\n- Draft a page with `update-page` and `{"pageId":"page-id","values":{"isDraft":true}}`. It remains editable and previewable but is omitted from every publish target, including staging, and from sitemap output.\n- Stage a draft page for a future publish with `{"pageId":"page-id","values":{"isDraft":false}}`. This clears draft state but does not deploy the site. The home page and `/*` catch-all page cannot be drafts.\n- Resource `url` accepts plain fixed URLs and paths. For computed URLs, pass JavaScript expression code such as `"https://api.example.com/items?tag=" + filters.tag`. Resource header values, search parameter values, and text bodies accept expressions for dynamic values; for fixed text, use `{ "type": "literal", "value": "application/json" }`.\n- Resource update example: use `update-resource` with `{"resourceId":"resource-id","values":{"url":"https://api.example.com/items"}}`.\n- Assets is one system resource with one response shape and always executes a structured query. `create-assets-resource` without `query` uses the default URL and optional image-dimensions output. Provide query configuration to control filtering, sorting, pagination, selected fields, or file content.\n- For a Markdown-backed blog, create exactly two Builder page definitions: a fixed `/blog` overview and one `/blog/:slug` detail page. Both pages load content through Assets resources. Never create one Builder page per post or duplicate Markdown content into static page structures. Read `get-asset-field-catalog`, validate each structured query with `validate-asset-query`, then call `create-assets-resource` or `update-assets-resource`. Set `values.query:null` to restore the default query.\n- Optimize every explicit Assets query for the deployed content-database size. Use `output.mode:"fields"` and select only fields that are actually rendered or otherwise required by the query. Keep `includeMetadata:false` unless the rendered value needs file metadata; diagnostics are returned separately. Do not use `output.mode:"all"` as a convenience default.\n- Every reachable Assets data source contributes to the shared database. Keep one final resource per rendered query. Update an existing scoped resource instead of creating a placeholder, preview copy, or repair replacement, and remove obsolete duplicate resources and data sources.\n- Make a bounded overview fully static: use literal values for its filters, limit, and offset, add a deterministic ID tie-breaker to its sort, and use `content.mode:"none"`. This lets compilation materialize the small overview result instead of retaining overview-only fields across every candidate article. Reserve runtime expressions for values that are truly dynamic, such as `system.params.slug` on the detail route.\n- Query Markdown files directly and use `content.mode:"markdown-body-ref"` when rendering their bodies. The published database keeps only metadata and document references, filters and paginates first, and fetches the selected Markdown bodies from Asset storage at runtime. Do not create companion JSON descriptors merely to avoid embedding Markdown.\n- `full` and bounded `range` request embedded file bytes. Use them only when the caller explicitly requires the complete source or a byte range.\n- Deferred Markdown bodies exclude frontmatter and resolve conventional relative links and images such as `../images/hero.png` to matching Assets. Markdown Embed permits sanitized figures, audio, video, and iframes, but removes scripts, inline event handlers, and unsafe URLs.\n- Assets expose an ID-keyed map at `<dataSourceName>.data` and collection information at `<dataSourceName>.meta`. Bind a listing Collection to `posts.data` and a one-result detail Collection to `post.data`; each item value contains selected fields and its item key is the asset ID. Read frontmatter or JSON fields from `item.properties` and the resolved Markdown body from `item.content.text`.\n- Use `preview-asset-query` with concrete values before binding expressions in the saved resource. Inspect `__diagnostics__.query` for the temporary query-only footprint and `__diagnostics__.database` for the merged database built from all reachable Assets queries. Only `database.usedBytes` counts toward `database.maxBytes`; query sizes are not separate allowances and must not be summed. Compare `usedBytes`, `unboundedBytes`, and `truncated` within both scopes. A finished Markdown blog must include every source document without truncation, contain no embedded Markdown bodies, and retain only the intended materialized overview query. When merged usage approaches the limit, remove duplicate reachable resources first, then unused output fields, then narrow candidate files. Inspect saved mode and configuration with `list-assets-resources` or `get-assets-resource`; shared index maintenance is automatic.\n- Data variable values support `string`, `number`, `boolean`, and `json`. Use `json` for all arrays, objects, filters, and nested data.\n- Parameters are internal scoped runtime values from pages, collections, or components. They are not a public authoring surface: do not create, update, or delete parameter records. Public tools should preserve existing parameter records and may reference documented context values such as `system` in expressions where they are already in scope.\n- Use scoped resources for read data. A GET resource created with `scopeInstanceId`/`dataSourceName` defaults to `exposeAsDataSource:true`, becomes a scoped resource data variable, is generated into the page resource `data` map, and may be loaded while rendering the page. Read the loaded resource result from its wrapper, usually `.data`.\n- Use prop-bound resources for actions. A resource created without `scopeInstanceId` and bound to a component prop such as Form `action` with `bind-props` and `binding.type: "resource"` becomes an action resource in the page resource `action` map. Use this for POST, PUT, DELETE, webhooks, GraphQL submissions, and anything that should run only from an explicit form/action flow.\n- POST, PUT, and DELETE resources default to `exposeAsDataSource:false`, even with a scope. Set `exposeAsDataSource:true` only for an intentional render-time read such as a GraphQL POST query; provide `scopeInstanceId` and inspect the returned warning. Set it to `false` during `update-resource` to detach existing render-time exposure.\n- For dynamic resource query parameters prefer `searchParams`, for example `{"name":"tag","value":"filters.tag"}`. Use `{"type":"literal","value":"website"}` for fixed request text. Header values can use an expression such as `"Bearer " + auth.token`. Body can be an object expression, including GraphQL payloads such as `{ query: "...", variables: { slug: system.params.slug } }`.\n- Resource methods are `get`, `post`, `put`, and `delete`. Optional resource controls are `graphql` and `system`. Use `control:"graphql"` for GraphQL POST resources with query bodies. Use `control:"system"` for built-in local resource URLs such as `"/$resources/current-date"` and for resources reading the built-in `system` parameter. The built-in system fields are `system.origin`, `system.pathname`, `system.params`, and `system.search`; do not use `system.path`.\n- Whenever an array or object from a resource or data variable should render repeated UI, call `insert-collection` with the complete iterable and one repeated-item JSX root. The command creates the Collection, private item parameters, iterable binding, and descendant item bindings atomically. Use `collectionItem` and `collectionItemKey` expressions in the item JSX. Wrap multiple repeated siblings in one Element, and give repeated Radix items stable unique `value` bindings.\n- Expressions are single JavaScript expressions, not statements or functions. Functions, arrow functions, classes, `new`, `this`, `await`, imports, arbitrary calls, increment/decrement, and assignment outside actions are unsupported. Prefer optional chaining, nullish coalescing, ternaries, property/index access, operators, and the documented string/array methods.\n\n## Pick Read Command\n\n{{readFirst}}\n\n## Pick Write Command\n\n{{taskRecipeIndex}}\n\n## Raw Patch Only If Needed\n\n1. Use MCP tool: snapshot.\n2. Write BuildPatchTransaction[].\n3. Use MCP tool: apply-patch.\n\n## MCP Argument Examples\n\nMCP tools receive JSON argument objects, not CLI flags. Use these shapes:\n\n{{mcpArgumentExampleIndex}}\n\n## Rules\n\n- Never guess ids for existing records. Read them first.\n- Never use project ids from user input. Commands use the configured project.\n- Use --refresh before a local-capable command when cached data may be stale.\n- Pass --json only to commands whose help/schema documents it. Do not add --json to top-level commands such as sync unless supported.\n- On VERSION_CONFLICT, read MCP snapshot again, regenerate the patch, then retry.\n- Treat stdout JSON as the API contract and stderr as diagnostics.\n- For visual/design work, verify the rendered result with vision before finishing.\n- Do not edit generated files for normal Webstudio content/design requests.\n- Use direct values for static strings and bindings only for dynamic expressions/resources/actions.\n- Use plain fixed text where documented. Only encode a quoted JavaScript string literal when a field is explicitly documented as an expression-only value.\n- Confirm destructive commands with --confirm only when user requested deletion/unpublish/replacement.\n- Use webstudio schema api for machine-readable top-level command metadata and webstudio schema mcp for MCP tool schemas.\n\n## Known Gaps\n\n{{knownCliGapIndex}}\n',
|
|
273239
|
-
"manual-mcp": '# Webstudio MCP Manual\n\n`webstudio mcp` starts a stdio MCP server for real MCP clients. Shell users can call MCP tools with the shortcut form `webstudio <tool> \'<json>\'`, for example `webstudio meta.index` or `webstudio insert-fragment \'<json>\' --dry-run`. `webstudio mcp single-op-call` is the explicit equivalent and prints the structured JSON result. `webstudio mcp run` runs multiple MCP tool calls from inline JSON or a normal JSON file in one shared CLI session. Do not manually type or pipe raw JSON-RPC frames into `webstudio mcp` from an interactive shell or PTY.\n\n## Startup\n\nIf you are already working with a shell-capable agent, it can use the local CLI\ndirectly. Native MCP client registration is optional. Give the editable Builder\nshare link only when the trusted agent asks for it. Treat the share link as a\ncredential: do not include it in committed files, screenshots, logs, or issue\nreports.\n\n1. Configure a project with `webstudio init --link <api-share-link> --json`.\n2. Check capabilities with `webstudio permissions --json`.\n3. Use shortcut calls such as `webstudio meta.index` and `webstudio insert-fragment \'<json>\' --dry-run` for individual MCP tool calls. Use the explicit equivalent `webstudio mcp single-op-call <tool> \'<json>\'` when you need to force the MCP path, or `webstudio mcp run \'[{"tool":"components.find","input":{"brief":"button"}}]\'` for bounded multi-call workflows. Use `webstudio mcp run .temp/mcp-calls.json` for large batches.\n4. Start discovery with `meta.index`, then call focused tools with concrete JSON, for example `webstudio mcp single-op-call meta.guide \'{"brief":"Create a design system page using every component"}\'`.\n\nDo not run `webstudio sync`, install an MCP server, change client configuration,\nor restart the app for this local CLI workflow.\n\nWhen the user explicitly wants persistent native MCP integration, run\n`webstudio connect claude`, `webstudio connect codex`, `webstudio connect\ncursor`, or `webstudio connect vscode`. This optional command changes client\nconfiguration, so follow its client-specific reload or restart instruction.\nUse `--print` to inspect the generated setup without changing configuration or\nrequiring project access. For Codex, `connect` registers and verifies the server\nthrough the Codex CLI. Before changing client configuration, `connect` verifies\nthat the saved project endpoint is reachable and its credential is accepted.\n\nStart MCP from the linked Webstudio project root. The lifecycle status line prints that absolute root; create local scripts, screenshots, and temporary artifacts under that root, for example `<project root>/.temp/script.mjs`. If the shell starts in a parent workspace, `cd` into the project root first or use absolute paths.\n\nWhen developing inside the Webstudio monorepo, start the local CLI exactly as `node packages/cli/local.js mcp` from the repo root. Do not use `pnpm exec webstudio`, `pnpm --filter webstudio exec webstudio`, or a global `webstudio`: they can resolve an older binary.\n\nWhile the server is running, stdout is reserved for MCP JSON-RPC messages. Do not print human text from the server process. The server advertises MCP `logging` capability and emits sparse `notifications/message` logs for ready state and tool lifecycle checkpoints such as `tool preview.start started`, `tool preview.start still running after 10000ms`, and `tool preview.start succeeded in 1234ms`; stderr also mirrors these sparse lifecycle fallback lines prefixed with `[webstudio mcp]`.\n\n## One-Shot Tool Calls\n\nUse the shortcut `webstudio <tool> \'<json>\'` when you are operating from a shell and need one MCP tool result. The explicit form `webstudio mcp single-op-call <tool> \'<json>\'` is equivalent and avoids writing temporary Node.js stdio client scripts.\n\nExamples:\n\n```sh\nwebstudio mcp single-op-call meta.index\nwebstudio mcp single-op-call meta.guide \'{"brief":"Create a design system page using every component"}\'\nwebstudio mcp single-op-call meta.get-more-tools \'{"tools":["insert-fragment"]}\'\nwebstudio mcp single-op-call components.list \'{"source":"all"}\'\nwebstudio mcp single-op-call components.coverage-plan\nwebstudio mcp single-op-call components.search \'{"brief":"radix select"}\'\nwebstudio mcp single-op-call components.get \'{"component":"@webstudio-is/sdk-components-react-radix:Select"}\'\nwebstudio mcp single-op-call templates.list\nwebstudio mcp single-op-call templates.get \'{"component":"@webstudio-is/sdk-components-react-radix:Select"}\'\nwebstudio mcp single-op-call insert-fragment --input-file .temp/insert-fragment.json\n```\n\nShortcut equivalents:\n\n```sh\nwebstudio meta.index\nwebstudio meta.guide \'{"brief":"Create a design system page using every component"}\'\nwebstudio meta.get-more-tools \'{"tools":["insert-fragment"]}\'\nwebstudio components.list \'{"source":"all"}\'\nwebstudio components.coverage-plan\nwebstudio components.search \'{"brief":"radix select"}\'\nwebstudio components.get \'{"component":"@webstudio-is/sdk-components-react-radix:Select"}\'\nwebstudio templates.list\nwebstudio templates.get \'{"component":"@webstudio-is/sdk-components-react-radix:Select"}\'\nwebstudio insert-fragment --input-file .temp/insert-fragment.json\n```\n\n### Tool name convention\n\nMCP tool names are opaque strings, not JavaScript property access. A dot separates a namespace from its tool name, and every segment uses lowercase kebab-case. For example, `components.coverage-insert-next` is the `coverage-insert-next` tool in the `components` namespace. Pass the complete name as one CLI argument: `webstudio components.coverage-insert-next`.\n\n### Readable fragment inputs\n\nPrefer `--input-file` for JSX so JSON and shell quoting do not obscure the fragment. For example, save this as `.temp/insert-fragment.json`:\n\n```json\n{\n "parentInstanceId": "root-id",\n "fragment": "<ws.element ws:tag=\'section\' ws:style={css`padding: 32px; display: grid; gap: 16px;`}><ws.element ws:tag=\'h2\'>Northstar Product OS</ws.element><ws.element ws:tag=\'p\'>Reusable patterns for teams.</ws.element></ws.element>"\n}\n```\n\nThen run `webstudio insert-fragment --input-file .temp/insert-fragment.json`. Single quotes inside the JSX keep the JSON valid and readable without backslash-escaped attributes.\n\nWrite and review larger fragments as JSX before placing them in the `fragment` field. Common patterns:\n\n```tsx\n<ws.element\n ws:tag="section"\n style={{ padding: 32, borderRadius: 16 }}\n>\n <ws.element ws:tag="h2">Operations Console</ws.element>\n <ws.element ws:tag="p">\n React-style object styles become editable Webstudio styles.\n </ws.element>\n</ws.element>\n\n<ws.element\n ws:tag="section"\n ws:tokens={[token("accent", css`color: #0f766e;`)]}\n>\n <ws.element\n ws:tag="button"\n onClick={new ActionValue(["event"], expression`console.log(event)`)}\n >\n Track launch\n </ws.element>\n</ws.element>\n\n<ws.element ws:tag="section">\n <radix.Switch>\n <radix.SwitchThumb />\n </radix.Switch>\n</ws.element>\n```\n\nRules:\n\n- Inside the Webstudio monorepo, replace `webstudio` in the examples above with `node packages/cli/local.js`, for example `node packages/cli/local.js meta.index`.\n- For a simple authored/styled section, run `meta.index`, then `meta.get-more-tools \'{"tools":["insert-fragment"]}\'`, then `insert-fragment`. Do not grep source files, dump full MCP resources, or write parser scripts first.\n- In `insert-fragment` JSX, use ``ws:style={css`...`}`` for Webstudio-native CSS, or use React-style object syntax such as `style={{ padding: 24 }}` when that is simpler. Both forms create editable Webstudio style data.\n- Do not access host globals or dynamic code APIs in JSX fragments, including `process`, `globalThis`, `eval`, `Function`, or `constructor`.\n- Use Webstudio prop names such as `class` and `for`; do not use React aliases `className` or `htmlFor`.\n- Use Webstudio actions for event/action props, for example `onClick={new ActionValue(["event"], expression\\`console.log(event)\\`)}`. Do not pass JavaScript functions such as `onClick={() => ...}`.\n- Plain prop values must be JSON-compatible: `null`, strings, booleans, finite numbers, arrays, and plain objects. Do not pass `undefined`, `Symbol`, `BigInt`, `NaN`, `Infinity`, `Date`, `Map`, `Set`, class instances, or circular objects; omit the prop, use plain data, or use `expression`/`ActionValue` when the value is dynamic.\n- Template-backed components used in JSX must include required child/part components explicitly under the same parent structure as the template, for example `<radix.Switch><radix.SwitchThumb /></radix.Switch>`. Use `insert-component` when you want one automatic registered component template.\n- The positional input is JSON and defaults to `{}`.\n- Use `--input-file` for large mutation payloads.\n- Use `--dry-run` with local-capable mutation tools when you need a patch plan without committing. The computed transaction is returned in `meta.session.transaction`, and `meta.session.version` is its base build version. Copying a `.webstudio` folder is not an isolated project clone; `.webstudio/config.json` still points to the same remote project, so non-dry-run mutations can commit to that project.\n- The command prints JSON to stdout for both success and failure. Success uses the same `structuredContent` shape MCP tools return: `{ "ok": true, "data": ..., "meta": ... }`. Failure prints `{ "ok": false, "error": { "code": "...", "message": "..." }, "meta": ... }` and exits nonzero.\n- The command writes sparse progress to stderr, including start, success/failure, elapsed time, and committed status when the tool returns session metadata.\n- Invalid argument types fail loudly with path-specific messages, for example `meta.guide input.brief must be a string when provided`.\n- Run one-shot shortcut or `mcp single-op-call` commands sequentially against the same linked `.webstudio` folder. If you receive `PROJECT_SESSION_BUSY`, another CLI/MCP process is updating the local session; wait a moment and retry sequentially.\n- To work with another previously linked project without changing the directory\'s default link, start MCP or a shell call with `--project <projectId>`, for example `webstudio mcp --project <projectId>` or `webstudio mcp single-op-call list-pages --project <projectId>`. Selected projects use isolated local session and checkpoint files.\n- If you are a delegated agent and your parent cannot see live stderr/stdout, do not run a long sequence of shortcut or `mcp single-op-call` commands silently and do not wrap many calls in a shell loop. Treat each parent-visible checkpoint as the unit of work. If the parent asks for status within 30 seconds, run exactly one `webstudio <tool>` or `webstudio mcp single-op-call` command, report that command/result, then wait before the next MCP command. For all-component design-system pages, checkpoint after discovery, checkpoint after page creation, call `components.coverage-insert-next` once before checkpointing again, then finish with the `presentation-pass` workflow phase. Coverage alone is not completion; organize examples into styled sections/cards.\n\n## Reporting CLI/MCP Issues\n\nIf a CLI/MCP tool gives a confusing error, crashes, hangs, produces invalid output, requires an undocumented workaround, or makes you inspect source code to understand normal usage, ask the user to report it in the Webstudio Discord `#help` channel: https://wstd.us/community.\n\nGive the user a complete copy-paste report. Include only non-secret values: never include auth tokens, private URLs, cookies, API keys, passwords, or proprietary project data. Redact them as `<redacted>`.\n\nCopy-paste template:\n\n````md\nWebstudio CLI/MCP issue report\n\nWhat I was trying to do:\n<short user goal, for example "Create a resource from an external API and render it in a collection">\n\nWhat I expected:\n<what should have happened>\n\nWhat happened instead:\n<exact error, confusing behavior, hang, missing docs, or workaround required>\n\nCommand/tool used:\n\n```sh\n<exact command or MCP tool call, with tokens/secrets redacted>\n```\n\nStructured output / error:\n\n```json\n<stdout JSON or MCP structuredContent, if available, with secrets redacted>\n```\n\nStderr / lifecycle logs:\n\n```txt\n<stderr lines, timings, checkpoint messages, or stack trace, with secrets redacted>\n```\n\nEnvironment:\n\n- CLI command path: <webstudio / node packages/cli/local.js / other>\n- Webstudio CLI version: <from command output if known>\n- OS: <macOS / Windows / Linux / unknown>\n- Node version: <node -v if known>\n- Project/session state: <linked project, local .webstudio session, preview, MCP server, or unknown>\n\nWorkaround tried:\n<what the agent/user tried next, and whether it worked>\n\nWhy this should be improved:\n<one sentence: better error message, docs, schema, tool behavior, etc.>\n````\n\n## Shared-Session Shell Runs\n\nUse `webstudio mcp run \'[{"tool":"components.find","input":{"brief":"button"}}]\'` when you are operating from a shell and need several MCP tool calls to share one CLI session without hand-writing JSON-RPC. For large batches, pass a normal JSON file path such as `.temp/mcp-calls.json`. Do not use shell process substitution like `<(...)`; use inline JSON or a real file.\n\nUse `mcp run` for long-lived tools such as `preview.start`. A one-shot `mcp single-op-call preview.start` cannot keep ownership of a preview server for a later screenshot or stop call. Put `preview.start`, `screenshot`, and `preview.stop` in one shared `mcp run` process, or use a real long-running MCP client.\n\nInput shape:\n\n```json\n{\n "calls": [\n { "tool": "meta.index" },\n { "tool": "components.find", "input": { "brief": "radix select" } }\n ]\n}\n```\n\nRules:\n\n- The command prints JSON to stdout for both success and failure. It stops at the first failed call and prints partial results in `{ "ok": false, "error": ..., "data": { "completedCalls": ..., "results": [...] }, "meta": ... }`, then exits nonzero.\n- If a call returns `checkpoint.required`, read-only discovery and inspection remain available, but mutations and state-changing session tools return `CHECKPOINT_REQUIRED`. Stop and report the checkpoint to the parent/user. Only after the parent/user continues, call `checkpoint.ack {"reported":true,"continueAfterReport":true,"summary":"<what you reported>"}` before continuing mutations.\n- For `mcp single-op-call`, checkpoint requirements persist across later one-shot CLI processes until you call `checkpoint.ack {"reported":true,"continueAfterReport":true,"summary":"<what you reported>"}`.\n- Use this instead of manually sending JSON-RPC frames to `webstudio mcp` from a shell.\n\n### Cross-project batches\n\nAdd `projects` to the same `mcp run` manifest to run focused reads, audits, or dry runs across independently linked project roots:\n\n```json\n{\n "concurrency": 2,\n "calls": [\n { "tool": "status" },\n { "tool": "audit", "input": {} },\n {\n "tool": "update-project-settings",\n "input": { "meta": { "siteName": "Reviewed" } },\n "dryRun": true\n }\n ],\n "projects": [\n { "id": "site-a", "root": "../site-a" },\n { "id": "site-b", "root": "../site-b" }\n ]\n}\n```\n\nProject roots and an optional `progressFile` are resolved relative to the manifest file. Each project may provide its own `calls` instead of using the top-level calls. Each root must already be linked with its own `.webstudio/config.json`; the runner creates an independently authenticated ProjectSession and uses root-scoped session, audit, preview-data, and checkpoint paths without changing the process working directory.\n\nConcurrency defaults to 2, is capped at 16, and can be set in the manifest or overridden with `--concurrency`. A failure is reported for that project while other projects continue. Progress is saved after every successful call; rerunning with the default `--resume` skips completed projects and starts failed projects after their last confirmed successful call. Reads and dry runs may be retried. A committed mutation interrupted after dispatch is marked `AMBIGUOUS_MUTATION_RESULT` and is never replayed automatically; inspect that project before deciding how to continue. Use `--no-resume` only to intentionally start the complete manifest over.\n\nCommitted mutation tools are rejected in a projects batch unless the command includes `--approve-mutations`. Review the complete manifest before granting approval. `--dry-run` applies to every call and does not require mutation approval. The final stdout object is compact: project counts, one status/error record per project, elapsed time, and the progress-file path rather than every tool result.\n\n## Discovery\n\nUse MCP itself after startup, or call the same tools with `webstudio mcp single-op-call`:\n\n- `tools/list`: machine-readable available tools\n- `resources/list`: available overview and full JSON resources\n- `meta.index`: concise capability catalog\n- `meta.guide`: workflow for a user goal; call with a string brief such as `{"brief":"Create a pricing page"}`\n- `meta.get-more-tools`: detailed params, examples, namespaces, and local/server behavior; prefer exact names such as `{"tools":["insert-fragment"]}` when you know them\n- `components.list`: compact registry metadata for visible components and templates; use a focused get tool for complete details\n- `components.summary`: component counts by default; use `{"detail":"components","limit":20}` for paginated entries\n- `components.coverage-plan`: compact paged plan for design-system coverage tasks that need every component; default returns counts plus the first root page, use `{"detail":"roots"}`, `{"detail":"parts"}`, or `{"detail":"full"}` for more\n- `components.coverage-status`: page-specific covered/missing component report with `missingRoots` and `missingParts`\n- `components.search`: focused component/template search by id, namespace, label, category, or content model\n- `components.find`: compatibility alias for focused component search\n- `components.get`: full metadata for one component id\n- `templates.list`: compact metadata for template-backed insertions only\n- `templates.get`: full registry item and payload metadata for one template\n\nComponent and template registry items use a shadcn-compatible top-level shape plus Webstudio-specific superset metadata in `meta`. Use `meta.runtime` for component ids, props, states, content model, and source identity; `meta.authoring` for composition and accessibility guidance; and `meta.builder` for template insertion details and expected project-data namespaces. These items are for Builder/MCP discovery and are not a published shadcn install registry yet.\n\nPrefer the focused `components.*` tools over dumping `webstudio://project/components`. Do not write local scripts to parse full MCP discovery JSON for common component lookup.\nFor “use every component” or design-system pages, start with compact `components.coverage-plan`, checkpoint, then page through roots/parts instead of dumping the full catalog.\n\n## Consumer Capabilities\n\nMCP lets agents work on one configured Webstudio project at a time. In consumer\nterms, agents can:\n\n- Check which project they are connected to.\n- Check what the share link is allowed to do.\n- Inspect project metadata and the latest editable build.\n- Read selected project data for audits and repair.\n- Apply precise project changes against a known version.\n- List, inspect, create, update, delete, duplicate, copy, and reorder pages.\n- Set the home page.\n- Preserve old page paths for redirects or history.\n- Read and update page titles, descriptions, metadata, auth settings, and SEO fields.\n- List, create, update, duplicate, move, and delete page folders.\n- List, create, update, delete, duplicate, reorder, and reuse page templates.\n- Create pages from reusable templates.\n- Read and update project site settings.\n- Read and update marketplace product metadata.\n- List, create, update, delete, and replace redirects.\n- List, create, update, and delete responsive breakpoints.\n- List and inspect page elements.\n- Insert registered components.\n- Insert styled JSX fragments.\n- Move, reparent, clone, duplicate, wrap, unwrap, convert, rename, retag, and delete elements.\n- Fill grid cells.\n- List and update text children.\n- Update plain text and expression text.\n- Update structured rich text.\n- Add, update, delete, and bind element props.\n- Bind props to expressions, resources, actions, and runtime system values.\n- Read, add, update, delete, and replace local styles.\n- Update selected style-source styles.\n- List, create, update, attach, detach, extract, duplicate, rename, lock, unlock, reorder, clear, and delete design tokens and style sources.\n- List, define, rename, delete, and rewrite CSS variables.\n- List, create, update, and delete static data variables.\n- Create string, number, boolean, and JSON variables. Arrays use JSON.\n- Delete unused data variables.\n- List, create, update, upsert, bind, and delete resources.\n- Create HTTP resources.\n- Create GraphQL resources.\n- Create system resources.\n- Use built-in system resources for sitemap, current date, and assets.\n- List and inspect complete asset metadata; upload, download, update, move, duplicate, find usage for, replace, and delete assets.\n- List, create, rename, move, recursively duplicate, and recursively delete nested asset folders.\n- Publish to staging or production.\n- Publish to selected domains.\n- List publish builds.\n- Check publish job status.\n- Unpublish staging or production deployments.\n- List, create, update, delete, and verify custom domains.\n- Start and stop preview.\n- Capture screenshots of generated pages.\n- Compare screenshots against baselines.\n- Install OCR support for richer visual checks.\n\nUseful resources:\n\n- `webstudio://project/status`: compact current ProjectSession status\n- `webstudio://project/tools-overview`: small operation overview by capability area\n- `webstudio://project/components-overview`: small component overview with ids, labels, namespaces, and categories\n- `webstudio://project/tools`: full operation catalog; read only when focused metadata is insufficient\n- `webstudio://project/components`: full component catalog with props, states, and content model composition constraints; read only when `components.summary`, `components.find`, and `components.get` are insufficient\n- `webstudio://project/guide`: concise discovery guide\n- `webstudio://project/expressions`: expression syntax, scope, supported methods, bindings, Collection iteration context, and verification\n- `webstudio://project/accessibility-review`: evidence-based LLM accessibility-review workflow using project checks, preview, and screenshots\n\n## MCP SDK Client Imports\n\nWhen writing a local Node.js MCP client script, use the official MCP SDK package and these exact ESM imports:\n\nInside the Webstudio monorepo this package is available at the repo root. In another project, install it first with `pnpm add -D @modelcontextprotocol/sdk`.\n\n```js\nimport { Client } from "@modelcontextprotocol/sdk/client/index.js";\nimport { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";\nimport { LoggingMessageNotificationSchema } from "@modelcontextprotocol/sdk/types.js";\n```\n\nMinimal stdio client for the local Webstudio CLI:\n\n```js\nconst client = new Client({ name: "webstudio-agent", version: "1.0.0" });\n\nclient.setNotificationHandler(\n LoggingMessageNotificationSchema,\n (notification) => {\n console.error(`[mcp] ${notification.params.data}`);\n }\n);\n\nconst transport = new StdioClientTransport({\n command: "node",\n args: ["packages/cli/local.js", "mcp"],\n cwd: process.cwd(),\n stderr: "inherit",\n});\n\nawait client.connect(transport);\n\nconst index = await client.callTool({\n name: "meta.index",\n arguments: {},\n});\nconsole.log(JSON.stringify(index.structuredContent, null, 2));\n\nawait client.close();\n```\n\nUse `node packages/cli/local.js mcp` from the Webstudio monorepo root for local development, or `webstudio mcp` from a linked project where the CLI is installed. Keep stdout for JSON-RPC/structured results and surface MCP logging notifications or stderr lifecycle lines as progress.\n\n## Core Rules\n\n- stdout is reserved for MCP JSON-RPC while the server is running.\n- Operate on the configured project only.\n- Read ids before writing.\n- Prefer semantic tools over `apply-patch`.\n- Use `status` and `refresh` when cached namespaces may be stale. Pass `status {"verbose":true}` only when debugging full namespace arrays, freshness, compatibility, or diagnostic details.\n- Read `meta.session.commitStatus` before interpreting durability. Read-only results report `not-applicable` and retain `committed:false` for compatibility; dry-run plans report `planned`; failed mutations report `failed`; no-op mutations report `unchanged`; durable mutations report `committed` with `meta.session.committed:true`.\n- For visual/design work, verify the rendered result with vision before finishing.\n\n## Vision Verification Loop\n\nVision-capable AI can use MCP to see what it is building:\n\n{{mcpVisionVerificationLoopMarkdown}}\n\nGenerated app setup:\n\n{{mcpGeneratedAppDependencyNotes}}\n\n## MCP argument examples\n\nExamples below show meaningful argument combinations. Tool schemas are the\nsource of truth. For tools with no required arguments, pass `{}`.\n\n{{mcpArgumentExampleIndex}}\n\n## Screenshot Verification\n\n{{screenshotVerificationSummary}}\n',
|
|
273737
|
+
"manual-llm": '# Webstudio CLI Manual for LLMs\n\nUse this order. Stop only when a command returns ok:false.\n\nIf you are inside the Webstudio monorepo, the first command discovery should use\nthe local CLI exactly as `node packages/cli/local.js ...` from the repo root. Do\nnot use `packages/cli/bin.js` for local source-tree work; it is the packaged\nbuild entry and may use stale built output. Do not use `pnpm exec webstudio`,\n`pnpm --filter webstudio exec webstudio`, or a global `webstudio`: they can\nresolve an older binary.\n\nFor delegated design-system or “use every component” tasks, skip the generic warm-up sequence and start with exactly one MCP command: `webstudio workflow.next \'{"goal":"design-system-page"}\'`. Report that returned checkpoint to the parent/user and stop until continued.\n\n## Use MCP locally or optionally connect a client\n\nDo not install, register, or connect an MCP server merely because the user asks\nyou to edit a Webstudio project. If Webstudio MCP tools are already available,\nuse them. If you have shell access, use the local CLI shortcuts such as\n`webstudio meta.index` and `webstudio list-pages`; they expose the same project\noperations without changing client configuration or restarting the app.\n\nOnly when the user explicitly asks for persistent native MCP integration, run\nthe command for their client:\n\n- Claude Code: `webstudio connect claude`\n- Codex: `webstudio connect codex`\n- Cursor: `webstudio connect cursor`\n- VS Code or GitHub Copilot: `webstudio connect vscode`\n\nRun project operations from the linked project root. If the folder is not\nlinked, ask for an editable Builder share link and run\n`webstudio init --link <share-link> --json`. You can then use local CLI\nshortcuts immediately. Do not run `webstudio sync`, `webstudio connect`, or\nrestart the app unless the user specifically wants native MCP registration:\nMCP reads and edits the latest editable Builder build directly, including for\nprojects that have never been published. Treat the share link as a credential\nand do not include it in committed files, logs, screenshots, or issue reports.\n\nThe optional `connect` command verifies project access before changing client configuration. For\nClaude Code, Cursor, and VS Code it safely merges the `webstudio` server into\nthe client\'s project configuration. For Codex it runs both `codex mcp add` and\n`codex mcp get webstudio`; do not repeat those commands separately. Follow the\nreload, restart, or approval instruction printed by `connect`, then verify the\nloaded MCP connection by asking the client to use Webstudio MCP and list the\nproject pages. Use `--print` only to inspect the generated setup without\nchanging configuration or requiring project access.\n\n## Always\n\n1. webstudio permissions --json\n2. For bounded shell workflows, call MCP tools directly through the CLI shortcut, for example `webstudio meta.index` or `webstudio insert-fragment \'<json>\' --dry-run`. The explicit form `webstudio mcp single-op-call <tool> \'<json>\'` is equivalent and useful when you need to make the MCP boundary obvious. Use `webstudio mcp run \'[{"tool":"components.find","input":{"brief":"button"}}]\'` for multiple calls in one shared CLI session. Use a normal JSON file path for large batches. Use long-running `webstudio mcp` only when your environment is a real MCP client. Do not manually send raw JSON-RPC to `webstudio mcp` from a shell or PTY.\n3. Read MCP `meta.index`, for example `webstudio meta.index`.\n4. Use focused MCP calls with concrete JSON: `webstudio meta.guide \'{"brief":"Create a design system page using every component"}\'`, `webstudio meta.get-more-tools \'{"tools":["insert-fragment"]}\'`, `webstudio components.list \'{"source":"all"}\'`, `webstudio components.coverage-plan`, `webstudio components.search \'{"brief":"radix select"}\'`, `webstudio components.get \'{"component":"@webstudio-is/sdk-components-react-radix:Select"}\'`, `webstudio templates.list`, and `webstudio templates.get \'{"component":"@webstudio-is/sdk-components-react-radix:Select"}\'`.\n5. Read overview resources `webstudio://project/tools-overview` or `webstudio://project/components-overview` when useful. Read full resources `webstudio://project/tools` or `webstudio://project/components` only when focused tools are insufficient.\n6. Pick focused MCP read tool.\n7. Pick semantic MCP write tool.\n\nUse `webstudio schema mcp` for a compact MCP tool overview. Add `--verbose` only when exact input schemas for all tools are truly needed; otherwise prefer focused `meta.get-more-tools` and `components.*` calls.\n\nRun these commands from the linked project root. Use the MCP startup status line\'s absolute root for local files; write temporary scripts and artifacts under `<project root>/.temp`, not under a parent workspace.\n\nMonorepo quick path for a simple styled section:\n\n```sh\nnode packages/cli/local.js mcp single-op-call meta.index\nnode packages/cli/local.js mcp single-op-call meta.get-more-tools \'{"tools":["insert-fragment"]}\'\nnode packages/cli/local.js mcp single-op-call insert-fragment --input-file .temp/insert-fragment.json --dry-run\n```\n\nSave the readable payload in `.temp/insert-fragment.json`:\n\n```json\n{\n "parentInstanceId": "parent-id",\n "fragment": "<ws.element ws:tag=\'section\' ws:style={css`padding: 32px; display: grid; gap: 12px;`}><ws.element ws:tag=\'h2\'>Launch Kit</ws.element><ws.element ws:tag=\'p\'>A focused section created with Webstudio JSX.</ws.element><ws.element ws:tag=\'button\'>Get started</ws.element></ws.element>"\n}\n```\n\nSingle quotes inside the JSX keep the JSON valid without backslash-escaped attributes. The same local shortcut form is shorter and preferred for simple shell steps:\n\n```sh\nnode packages/cli/local.js meta.index\nnode packages/cli/local.js meta.get-more-tools \'{"tools":["insert-fragment"]}\'\nnode packages/cli/local.js insert-fragment --input-file .temp/insert-fragment.json --dry-run\n```\n\nFor this simple path, do not grep source files, dump full MCP resources, or write parser scripts first. Use `list-pages`, `get-page-by-path`, or `list-instances` only to get the target `parentInstanceId`.\n\nWhen authoring JSX for `insert-fragment`, use Webstudio component helpers and Webstudio style syntax. Use `ws:style={css\\`...\\`}`for Webstudio-native CSS. For simpler cases, use React-style object syntax such as`style={{ padding: 24 }}`. Both forms create editable Webstudio style data.\n\nWhen the task says another user will edit a page in Content mode, use a Content Block (`ws:block`) around every editable region. Content-mode users can edit text and supported props only in descendants of that block; content outside it is read-only. Put reusable insertable options in the block\'s `ws:block-template` child. Do not put intended editor content inside that template container: templates are protected source material, while an inserted template copy becomes an editable direct child of the Content Block. Verify this structure before handoff.\n\nDo not access host globals or dynamic code APIs in JSX fragments, including `process`, `globalThis`, `eval`, `Function`, or `constructor`. JSX fragments are declarative project data; use the built-in Webstudio helpers instead.\n\nUse Webstudio prop names in JSX: `class`, `for`, `aria-label`, and other HTML/Webstudio names. Do not use React-only aliases such as `className` or `htmlFor`; the runtime rejects them with the Webstudio prop name to use.\n\nUse Webstudio actions for event/action props. Do not pass JavaScript functions such as `onClick={() => ...}`; the runtime rejects them because functions cannot be persisted as Webstudio project data.\n\n```tsx\n<ws.element\n ws:tag="button"\n onClick={new ActionValue(["event"], expression`console.log(event)`)}\n>\n Open\n</ws.element>\n```\n\nPlain JSX prop values must be JSON-compatible: `null`, strings, booleans, finite numbers, arrays, and plain objects. Do not pass `undefined`, `Symbol`, `BigInt`, `NaN`, `Infinity`, `Date`, `Map`, `Set`, class instances, or circular objects; omit the prop, use plain data, or use `expression`/`ActionValue` when the value is dynamic.\n\nIf a component has a registered template with required parts, JSX must include those parts explicitly under the same parent structure as the template, for example `<radix.Switch><radix.SwitchThumb /></radix.Switch>`. Use `insert-component` when you want Webstudio to apply one component template automatically.\n\n## Animation Components\n\nBefore creating animation examples, inspect the exact components with focused discovery:\n\n```sh\nwebstudio components.get \'{"component":"@webstudio-is/sdk-components-animation:AnimateChildren"}\'\nwebstudio components.get \'{"component":"@webstudio-is/sdk-components-animation:AnimateText"}\'\nwebstudio components.get \'{"component":"@webstudio-is/sdk-components-animation:StaggerAnimation"}\'\nwebstudio components.get \'{"component":"@webstudio-is/sdk-components-animation:VideoAnimation"}\'\n```\n\nUse Animation Group (`AnimateChildren`) as the controller. Put normal instances directly inside it, or put Text Animation, Stagger Animation, or Video Animation directly inside it. Text, Stagger, and Video are helper components with `contentModel.category: "none"` and should not be used as standalone section roots.\n\nDefine timing and CSS changes on the Animation Group `action` prop. Use `type:"view"` for viewport entry/exit progress and `type:"scroll"` for scroll-progress timelines. For in animations, keep the canvas styles as the final state and use `fill:"backwards"` with keyframes that describe the starting state. For out animations, use `fill:"forwards"` with keyframes that describe the ending state.\n\nText Animation settings: `slidingWindow` defaults to `5`, `easing` defaults to `linear`, and `splitBy` defaults to `char`. Use `splitBy:"space"` for word-by-word animation. The parent Animation Group keyframes provide the actual opacity, translate, scale, or other styles.\n\nStagger Animation settings: `slidingWindow` defaults to `1` and `easing` defaults to `linear`. It applies parent Animation Group progress across its direct children. Use `slidingWindow:0` for instant sequential steps, `1` for one child at a time, and values above `1` for overlapping waves.\n\nVideo Animation settings: `timeline` is a boolean. Prefer `insert-component` for Video Animation so the Video child template is inserted, then configure the Video child asset/source. Use short, seek-friendly videos for smooth scroll-linked playback.\n\nUse JSX fragments for authored animation structures when you need styled, editable examples. Put the final visual state in `ws:style` and put the starting or ending animated state in the Animation Group `action` keyframes. Include an explicit `offset` on every keyframe: use `offset: 0` for starting-state keyframes with `fill:"backwards"` and `offset: 1` for ending-state keyframes with `fill:"forwards"`.\n\n```tsx\n<animation.AnimateChildren\n action={{\n type: "view",\n axis: "block",\n animations: [\n {\n name: "Fade up on entry",\n timing: {\n fill: "backwards",\n rangeStart: ["entry", { type: "unit", value: 0, unit: "%" }],\n rangeEnd: ["entry", { type: "unit", value: 100, unit: "%" }],\n },\n keyframes: [\n {\n offset: 0,\n styles: {\n opacity: { type: "unit", value: 0, unit: "number" },\n translate: {\n type: "tuple",\n value: [\n { type: "unit", value: 0, unit: "number" },\n { type: "unit", value: 24, unit: "px" },\n ],\n },\n },\n },\n ],\n },\n ],\n }}\n>\n <ws.element\n ws:tag="section"\n ws:style={css`\n display: grid;\n gap: 16px;\n padding: 48px;\n border-radius: 24px;\n background: #111827;\n color: white;\n `}\n >\n <ws.element ws:tag="h2">Launch metrics</ws.element>\n <ws.element ws:tag="p">\n A polished card that fades up as it enters the viewport.\n </ws.element>\n </ws.element>\n</animation.AnimateChildren>\n```\n\nFor Text Animation, keep `animation.AnimateText` as the direct child of Animation Group and place the text-containing element inside it:\n\n```tsx\n<animation.AnimateChildren\n action={{\n type: "view",\n animations: [\n {\n name: "Parallax In",\n timing: {\n fill: "backwards",\n rangeStart: ["cover", { type: "unit", value: 0, unit: "%" }],\n rangeEnd: ["cover", { type: "unit", value: 70, unit: "%" }],\n },\n keyframes: [\n {\n offset: 0,\n styles: {\n translate: {\n type: "tuple",\n value: [\n { type: "unit", value: 0, unit: "number" },\n { type: "unit", value: 100, unit: "px" },\n ],\n },\n },\n },\n ],\n },\n {\n name: "Opacity In",\n timing: {\n fill: "backwards",\n rangeStart: ["cover", { type: "unit", value: 0, unit: "%" }],\n rangeEnd: ["cover", { type: "unit", value: 70, unit: "%" }],\n },\n keyframes: [\n {\n offset: 0,\n styles: {\n opacity: { type: "unit", value: 0, unit: "number" },\n },\n },\n ],\n },\n {\n name: "Scale In",\n timing: {\n fill: "backwards",\n rangeStart: ["cover", { type: "unit", value: 0, unit: "%" }],\n rangeEnd: ["cover", { type: "unit", value: 70, unit: "%" }],\n },\n keyframes: [\n {\n offset: 0,\n styles: {\n scale: {\n type: "tuple",\n value: [\n { type: "unit", value: 5, unit: "number" },\n { type: "unit", value: 5, unit: "number" },\n ],\n },\n },\n },\n ],\n },\n {\n name: "Parallax Out",\n timing: {\n fill: "forwards",\n rangeStart: ["cover", { type: "unit", value: 50, unit: "%" }],\n rangeEnd: ["cover", { type: "unit", value: 100, unit: "%" }],\n },\n keyframes: [\n {\n offset: 1,\n styles: {\n translate: {\n type: "tuple",\n value: [\n { type: "unit", value: 0, unit: "number" },\n { type: "unit", value: -100, unit: "px" },\n ],\n },\n scale: {\n type: "tuple",\n value: [\n { type: "unit", value: 5, unit: "number" },\n { type: "unit", value: 5, unit: "number" },\n ],\n },\n opacity: { type: "unit", value: 0, unit: "number" },\n },\n },\n ],\n },\n ],\n insetStart: { type: "unit", value: 5, unit: "%" },\n insetEnd: { type: "unit", value: 5, unit: "%" },\n isPinned: true,\n }}\n>\n <animation.AnimateText\n splitBy="space"\n slidingWindow={5}\n easing="easeOutQuart"\n >\n <ws.element ws:tag="h2">Animate words with controlled rhythm</ws.element>\n </animation.AnimateText>\n</animation.AnimateChildren>\n```\n\nFor Stagger Animation, put the repeated cards or rows directly inside `animation.StaggerAnimation`:\n\n```tsx\n<animation.AnimateChildren\n action={{\n type: "view",\n animations: [\n {\n timing: {\n fill: "backwards",\n rangeStart: ["contain", { type: "unit", value: 0, unit: "%" }],\n rangeEnd: ["contain", { type: "unit", value: 30, unit: "%" }],\n },\n keyframes: [\n {\n offset: 0,\n styles: {\n opacity: { type: "unit", value: 0, unit: "number" },\n },\n },\n ],\n },\n ],\n }}\n>\n <animation.StaggerAnimation>\n <ws.element\n ws:tag="article"\n ws:style={css`\n padding: 20px;\n border: 1px solid #d1d5db;\n border-radius: 16px;\n `}\n >\n Plan\n </ws.element>\n <ws.element\n ws:tag="article"\n ws:style={css`\n padding: 20px;\n border: 1px solid #d1d5db;\n border-radius: 16px;\n `}\n >\n Build\n </ws.element>\n <ws.element\n ws:tag="article"\n ws:style={css`\n padding: 20px;\n border: 1px solid #d1d5db;\n border-radius: 16px;\n `}\n >\n Launch\n </ws.element>\n </animation.StaggerAnimation>\n</animation.AnimateChildren>\n```\n\nFor Video Animation, use the registered template via `insert-component` when possible. If you author JSX, include the Video child explicitly:\n\n```tsx\n<animation.AnimateChildren\n action={{\n type: "view",\n animations: [\n {\n name: "Video progress",\n timing: {\n fill: "both",\n rangeStart: ["cover", { type: "unit", value: 0, unit: "%" }],\n rangeEnd: ["cover", { type: "unit", value: 100, unit: "%" }],\n },\n keyframes: [{ offset: 0, styles: {} }],\n },\n ],\n }}\n>\n <animation.VideoAnimation timeline={true}>\n <$.Video\n preload="auto"\n autoPlay={true}\n muted={true}\n playsInline={true}\n crossOrigin="anonymous"\n />\n </animation.VideoAnimation>\n</animation.AnimateChildren>\n```\n\n## Command Surface Boundary\n\n- Use top-level `webstudio ...` shell commands for setup, sync/import/build/preview/screenshot, permissions, publish/domains, schema, registry inspection, man, and starting MCP.\n- Use MCP tools for Builder project data manipulation: pages, instances/components, props, text, styles, tokens, variables, resources, assets, breakpoints, redirects, and raw patches.\n- From a shell, call MCP tools with the shortcut form `webstudio <tool> \'<json>\'`, for example `webstudio insert-fragment \'<json>\' --dry-run`. The explicit equivalent is `webstudio mcp single-op-call <tool> \'<json>\'`. Use `--input-file` for large payloads.\n- Inside the Webstudio monorepo, call the local CLI as its own command: `node packages/cli/local.js ...`. Do not wrap the CLI call in `pwd && ...`, command substitution, `pnpm exec webstudio`, `pnpm --filter webstudio exec webstudio`, or a global `webstudio`.\n- For experiments, pass `--dry-run` to local-capable mutation calls. Read the computed transaction from `meta.session.transaction` and its base build version from `meta.session.version`. Copying a `.webstudio` folder is not an isolated project clone; `.webstudio/config.json` still points to the same remote project, so non-dry-run mutations can commit to that project.\n- Read `meta.session.commitStatus` before interpreting durability. Read-only results report `not-applicable` and retain `committed:false` for compatibility; dry-run plans report `planned`; failed mutations report `failed`; no-op mutations report `unchanged`; durable mutations report `committed` with `meta.session.committed:true`.\n- For bounded multi-step shell work, run inline JSON with `webstudio mcp run \'[{"tool":"components.find","input":{"brief":"button"}}]\'`; this reuses one CLI session without raw JSON-RPC. For large batches, write `{ "calls": [{ "tool": "..." }] }` to a normal JSON file and run `webstudio mcp run .temp/mcp-calls.json`.\n- Use JSON strings for `brief` fields. Never pass boolean flags such as `{"brief":true}`.\n- Treat `webstudio mcp single-op-call` and `webstudio mcp run` stderr lines as progress checkpoints; stdout remains JSON on both success and failure. On failure, parse stdout for `{ "ok": false, "error": { "code": "...", "message": "..." } }` before deciding what to fix.\n- If a CLI/MCP tool crashes, hangs, gives a confusing error, needs an undocumented workaround, or forces source-code inspection for normal usage, ask the user to report it in Discord `#help` at https://wstd.us/community. Give them a complete copy-paste report with the goal, expected behavior, actual error, exact command/tool call, stdout JSON, stderr/lifecycle logs, environment, workaround, and secrets redacted.\n- Run one-shot `webstudio mcp single-op-call` commands sequentially against a linked `.webstudio` folder. If a command returns `PROJECT_SESSION_BUSY`, another CLI/MCP process is updating the local session; wait a moment and retry sequentially.\n- In delegated or non-streaming agent environments, do not batch many MCP calls silently and do not wrap many shortcut or `webstudio mcp single-op-call` commands in a shell loop. Treat each parent-visible checkpoint as the unit of work. If the parent asks for status within 30 seconds, run exactly one shortcut command such as `webstudio meta.index` or one explicit `webstudio mcp single-op-call` command, report that command/result, then wait for the parent to continue. Do not take a broad task such as creating a full design-system page as one execution unit. Call `workflow.next {"goal":"design-system-page"}`, report the returned phase/checkpoint, wait until the parent continues, call `checkpoint.ack {"reported":true,"continueAfterReport":true,"summary":"<what you reported>"}`, complete exactly that bounded phase, and return: discovery, page creation, one dry-run JSX section, one committed JSX section, one `components.coverage-insert-next` call, or one presentation pass. Phase commands do not include nextPhase in their own output. After the parent continues, acknowledge the previous checkpoint first, then call `workflow.next` with the next phase. For all-component design-system pages, checkpoint after workflow planning, discovery, page creation, call `components.coverage-insert-next` once before checkpointing again, then finish with `workflow.next {"goal":"design-system-page","phase":"presentation-pass"}`. Coverage 72/72 is necessary but not sufficient: the page must be organized into styled, real-world examples, not raw unstyled component dumps.\n- For design-system or “use every component” tasks, start with compact `webstudio components.coverage-plan`, checkpoint, then request component coverage details with `webstudio components.coverage-plan \'{"detail":"roots","offset":20}\'` or `{"detail":"parts"}` only when needed. Do not pass `detail` to `list-pages`; use `list-pages {}` or `get-page-by-path` for page lookup.\n- MCP tool shortcuts are only for MCP tools. If a shortcut is ambiguous with a real top-level command, the real top-level command wins; use `webstudio mcp single-op-call <tool> \'<json>\'` to force the MCP path.\n\n## LLM Implementation Process\n\nUse this process for user requests that change Webstudio content, layout, styles, assets, pages, redirects, resources, or publishing state:\n\n1. Discover capabilities with `webstudio man --json`, `webstudio schema api`, `webstudio schema mcp`, MCP `meta.index`, `meta.guide`, `meta.get-more-tools`, `components.list`, `components.summary`, `components.coverage-plan`, `components.search`, `components.get`, `templates.list`, and `templates.get`. From a shell, prefer shortcut calls such as `webstudio meta.index` and `webstudio components.search \'{"brief":"button"}\'` for these focused tool calls; use `webstudio mcp single-op-call` when you need the explicit MCP form. Read full resources such as `webstudio://project/tools` and `webstudio://project/components` only when needed. Do not write scripts to parse full MCP discovery JSON for normal lookup.\n2. Inspect current project state with semantic reads such as `get-project-settings`, `list-pages`, `get-page-by-path`, `list-instances`, `inspect-instance`, `get-styles`, `list-assets`, `list-breakpoints`, and `snapshot` only when needed. Before changing a project, read `get-project-settings` and follow any non-empty `meta.agentInstructions`. These are shared project instructions, not a place for secrets.\n3. Mutate the Webstudio project with semantic MCP write tools first. Prefer MCP `insert-fragment` for authored/styled sections, use `insert-component` only for one automatic component template, then `update-text`, `update-props`, `update-styles`, `upload-asset`, `create-page`, and page/project settings tools over raw patches.\n4. Use `apply-patch` only when no semantic tool covers the required change, and only after reading the latest snapshot/version.\n5. For visual/design work, regenerate or preview the generated app, capture a screenshot, inspect it with vision, and iterate before final response.\n6. Report what changed and what verification ran.\n\n## Visual Design Workflow\n\nFor requests involving visible HTML/CSS, layout, typography, colors, imagery, responsive behavior, or screenshots:\n\n1. Read editable Webstudio structure first: pages, instances, props, styles, breakpoints, assets, and relevant text.\n2. Do not use generated route/component files as the source of truth for editable content.\n3. Make edits through Webstudio semantic commands/MCP tools so the result stays editable in Builder and survives the next `webstudio build`.\n4. Keep generated project files current, start preview, and capture the changed page with `screenshot`.\n5. Use `screenshot.diff` when a baseline exists and inspect screenshot/diff artifacts with vision before finishing.\n6. If vision or screenshot tooling is unavailable, state that explicitly and explain what fallback verification was used.\n\n## Responsive Verification Workflow\n\nFor responsive page work, use Builder breakpoints as the source of truth:\n\n1. Read breakpoints with `list-breakpoints` before deciding responsive behavior.\n2. Apply responsive styles with existing Builder breakpoint ids; do not invent CSS media queries or breakpoint names when Webstudio breakpoint data exists.\n3. Pick screenshot viewport widths from the project breakpoints: include a desktop width, each defined max-width or min-width edge, and a narrow mobile width.\n4. Capture each viewport with `screenshot`, for example `{"path":"/","output":"home-375.png","viewport":{"width":375,"height":812}}` and `{"path":"/","output":"home-1440.png","viewport":{"width":1440,"height":900}}`.\n5. Inspect every viewport screenshot with vision before finishing, checking layout, overflow, hidden content, text wrapping, and breakpoint-specific style changes.\n6. If any viewport fails, update styles through semantic Webstudio tools and repeat screenshots for the affected breakpoints.\n\n## Generated Files Guardrails\n\n- Do not edit `app/__generated__`, generated route files, generated page files, generated CSS, or build output for normal Webstudio content/design requests.\n- Do not replace generated page components with handcrafted app code unless the user explicitly asks for code-only export customization.\n- Generated files are build artifacts and may be overwritten by `webstudio build`.\n- If a task truly requires generated app customization, keep it outside `app/__generated__` where possible and explain that it is not editable Webstudio content.\n\n## Values vs Bindings\n\nBefore authoring unfamiliar expressions, read `webstudio://project/expressions` with MCP `resources/read` or `webstudio mcp read-resource webstudio://project/expressions`. It documents the supported expression subset, method allowlist, scope, Collection context, and validation limits.\n\n- Use direct value tools for fixed content. For one visible text child, use `update-text` with plain `text`. For a bounded multi-instance literal replacement, use `replace-text` with `find`, `replace`, `pagePath` or `pageId`, and `limit`; it does not change expression children. Use `replace-prop-text` for bounded changes inside static string props, optionally limited to prop names or instance ids; it never changes dynamic bindings. For static props such as `aria-label`, `alt`, `id`, `class`, `href`, or button labels stored as props, use `update-props` with the prop\'s direct type/value.\n- Use `bind-props` only when the prop must stay dynamic: an expression, resource result, action, or existing scoped runtime context such as `system`. Do not use `bind-props` just to set a fixed string.\n- Direct prop string example: `{"updates":[{"instanceId":"button-id","name":"aria-label","type":"string","value":"Open menu"}]}`.\n- Expression binding example: `{"bindings":[{"instanceId":"link-id","name":"href","binding":{"type":"expression","value":"currentPost.url"}}]}`.\n- Page metadata fields such as `title`, `description`, `language`, `redirect`, and custom meta content accept plain fixed text. For computed values, pass JavaScript expression code such as `pageTitle ?? "Pricing | Acme"`.\n- Page `status` accepts a fixed HTTP status code as a number from 200 through 599, for example `302`. For a dynamic status, pass JavaScript expression code such as `system.status`.\n- Page metadata update example: use `update-page` with `{"pageId":"page-id","values":{"title":"Pricing | Acme","meta":{"description":"Plans for teams"}}}`.\n- Draft a page with `update-page` and `{"pageId":"page-id","values":{"isDraft":true}}`. It remains editable and previewable but is omitted from every publish target, including staging, and from sitemap output.\n- Stage a draft page for a future publish with `{"pageId":"page-id","values":{"isDraft":false}}`. This clears draft state but does not deploy the site. The home page and `/*` catch-all page cannot be drafts.\n- Resource `url` accepts plain fixed URLs and paths. For computed URLs, pass JavaScript expression code such as `"https://api.example.com/items?tag=" + filters.tag`. Resource header values, search parameter values, and text bodies accept expressions for dynamic values; for fixed text, use `{ "type": "literal", "value": "application/json" }`.\n- Resource update example: use `update-resource` with `{"resourceId":"resource-id","values":{"url":"https://api.example.com/items"}}`.\n- Assets is one system resource that always executes a structured query. `result:"many"` returns the existing ID-keyed collection shape and is the backward-compatible default. `result:"one"`, `result:"first"`, and `result:"last"` return one direct item or `null`; first and last require explicit sorting. `create-assets-resource` without `query` uses the default URL and optional image-dimensions output.\n- For a Markdown-backed blog, create exactly two Builder page definitions: a fixed `/blog` overview and one `/blog/:slug` detail page. Both pages load content through Assets resources. Never create one Builder page per post or duplicate Markdown content into static page structures. Read `get-asset-field-catalog`, validate each structured query with `validate-asset-query`, then call `create-assets-resource` or `update-assets-resource`. Set `values.query:null` to restore the default query.\n- Optimize every explicit Assets query for the deployed content-database size. Use `output.mode:"fields"` and select only fields that are actually rendered or otherwise required by the query. Keep `includeMetadata:false` unless the rendered value needs file metadata; diagnostics are returned separately. Do not use `output.mode:"all"` as a convenience default.\n- Every reachable Assets data source contributes to the shared database. Keep one final resource per rendered query. Update an existing scoped resource instead of creating a placeholder, preview copy, or repair replacement, and remove obsolete duplicate resources and data sources.\n- Make a bounded overview fully static: use literal values for its filters, limit, and offset, add a deterministic ID tie-breaker to its sort, and use `content.mode:"none"`. This lets compilation materialize the small overview result instead of retaining overview-only fields across every candidate article. Reserve runtime expressions for values that are truly dynamic, such as `system.params.slug` on the detail route.\n- Query Markdown files directly and use `content.mode:"markdown-body-ref"` when rendering their bodies. The published database keeps only metadata and document references, filters and paginates first, and fetches the selected Markdown bodies from Asset storage at runtime. Do not create companion JSON descriptors merely to avoid embedding Markdown.\n- `full` and bounded `range` request embedded file bytes. Use them only when the caller explicitly requires the complete source or a byte range.\n- Deferred Markdown bodies exclude frontmatter and resolve conventional relative links and images such as `../images/hero.png` to matching Assets. Markdown Embed permits sanitized figures, audio, video, and iframes, but removes scripts, inline event handlers, and unsafe URLs.\n- For `result:"many"`, Assets expose an ID-keyed map at `<dataSourceName>.data` and `totalCount`/`hasMore` at `<dataSourceName>.meta`; bind a listing Collection to `posts.data`. Single-result modes expose the selected item or `null` directly at `<dataSourceName>.data`, always include its `id`, and expose `totalCount` in meta. Bind detail components and page settings directly from expressions such as `post.data?.properties?.title`, `post.data?.content?.text`, and `post.data ? 200 : 404` without a Collection.\n- Use `preview-asset-query` with concrete values before binding expressions in the saved resource. Inspect `__diagnostics__.query` for the temporary query-only footprint and `__diagnostics__.database` for the merged database built from all reachable Assets queries. Only `database.usedBytes` counts toward `database.maxBytes`; query sizes are not separate allowances and must not be summed. Compare `usedBytes`, `unboundedBytes`, and `truncated` within both scopes. A finished Markdown blog must include every source document without truncation, contain no embedded Markdown bodies, and retain only the intended materialized overview query. When merged usage approaches the limit, remove duplicate reachable resources first, then unused output fields, then narrow candidate files. Inspect saved mode and configuration with `list-assets-resources` or `get-assets-resource`; shared index maintenance is automatic.\n- Data variable values support `string`, `number`, `boolean`, and `json`. Use `json` for all arrays, objects, filters, and nested data.\n- Parameters are internal scoped runtime values from pages, collections, or components. They are not a public authoring surface: do not create, update, or delete parameter records. Public tools should preserve existing parameter records and may reference documented context values such as `system` in expressions where they are already in scope.\n- Use scoped resources for read data. A GET resource created with `scopeInstanceId`/`dataSourceName` defaults to `exposeAsDataSource:true`, becomes a scoped resource data variable, is generated into the page resource `data` map, and may be loaded while rendering the page. Read the loaded resource result from its wrapper, usually `.data`.\n- Use prop-bound resources for actions. A resource created without `scopeInstanceId` and bound to a component prop such as Form `action` with `bind-props` and `binding.type: "resource"` becomes an action resource in the page resource `action` map. Use this for POST, PUT, DELETE, webhooks, GraphQL submissions, and anything that should run only from an explicit form/action flow.\n- POST, PUT, and DELETE resources default to `exposeAsDataSource:false`, even with a scope. Set `exposeAsDataSource:true` only for an intentional render-time read such as a GraphQL POST query; provide `scopeInstanceId` and inspect the returned warning. Set it to `false` during `update-resource` to detach existing render-time exposure.\n- For dynamic resource query parameters prefer `searchParams`, for example `{"name":"tag","value":"filters.tag"}`. Use `{"type":"literal","value":"website"}` for fixed request text. Header values can use an expression such as `"Bearer " + auth.token`. Body can be an object expression, including GraphQL payloads such as `{ query: "...", variables: { slug: system.params.slug } }`.\n- Resource methods are `get`, `post`, `put`, and `delete`. Optional resource controls are `graphql` and `system`. Use `control:"graphql"` for GraphQL POST resources with query bodies. Use `control:"system"` for built-in local resource URLs such as `"/$resources/current-date"` and for resources reading the built-in `system` parameter. The built-in system fields are `system.origin`, `system.pathname`, `system.params`, and `system.search`; do not use `system.path`.\n- Whenever an array or object from a resource or data variable should render repeated UI, call `insert-collection` with the complete iterable and one repeated-item JSX root. The command creates the Collection, private item parameters, iterable binding, and descendant item bindings atomically. Use `collectionItem` and `collectionItemKey` expressions in the item JSX. Wrap multiple repeated siblings in one Element, and give repeated Radix items stable unique `value` bindings.\n- Expressions are single JavaScript expressions, not statements or functions. Functions, arrow functions, classes, `new`, `this`, `await`, imports, arbitrary calls, increment/decrement, and assignment outside actions are unsupported. Prefer optional chaining, nullish coalescing, ternaries, property/index access, operators, and the documented string/array methods.\n\n## Pick Read Command\n\n{{readFirst}}\n\n## Pick Write Command\n\n{{taskRecipeIndex}}\n\n## Raw Patch Only If Needed\n\n1. Use MCP tool: snapshot.\n2. Write BuildPatchTransaction[].\n3. Use MCP tool: apply-patch.\n\n## MCP Argument Examples\n\nMCP tools receive JSON argument objects, not CLI flags. Use these shapes:\n\n{{mcpArgumentExampleIndex}}\n\n## Rules\n\n- Never guess ids for existing records. Read them first.\n- Never use project ids from user input. Commands use the configured project.\n- Use --refresh before a local-capable command when cached data may be stale.\n- Pass --json only to commands whose help/schema documents it. Do not add --json to top-level commands such as sync unless supported.\n- On VERSION_CONFLICT, read MCP snapshot again, regenerate the patch, then retry.\n- Treat stdout JSON as the API contract and stderr as diagnostics.\n- For visual/design work, verify the rendered result with vision before finishing.\n- Do not edit generated files for normal Webstudio content/design requests.\n- Use direct values for static strings and bindings only for dynamic expressions/resources/actions.\n- Use plain fixed text where documented. Only encode a quoted JavaScript string literal when a field is explicitly documented as an expression-only value.\n- Confirm destructive commands with --confirm only when user requested deletion/unpublish/replacement.\n- Use webstudio schema api for machine-readable top-level command metadata and webstudio schema mcp for MCP tool schemas.\n\n## Known Gaps\n\n{{knownCliGapIndex}}\n',
|
|
273738
|
+
"manual-mcp": '# Webstudio MCP Manual\n\n`webstudio mcp` starts a stdio MCP server for real MCP clients. Shell users can call MCP tools with the shortcut form `webstudio <tool> \'<json>\'`, for example `webstudio meta.index` or `webstudio insert-fragment \'<json>\' --dry-run`. `webstudio mcp single-op-call` is the explicit equivalent and prints the structured JSON result. `webstudio mcp run` runs multiple MCP tool calls from inline JSON or a normal JSON file in one shared CLI session. Do not manually type or pipe raw JSON-RPC frames into `webstudio mcp` from an interactive shell or PTY.\n\n## Startup\n\nIf you are already working with a shell-capable agent, it can use the local CLI\ndirectly. Native MCP client registration is optional. Give the editable Builder\nshare link only when the trusted agent asks for it. Treat the share link as a\ncredential: do not include it in committed files, screenshots, logs, or issue\nreports.\n\n1. Configure a project with `webstudio init --link <api-share-link> --json`.\n2. Check capabilities with `webstudio permissions --json`.\n3. Use shortcut calls such as `webstudio meta.index` and `webstudio insert-fragment \'<json>\' --dry-run` for individual MCP tool calls. Use the explicit equivalent `webstudio mcp single-op-call <tool> \'<json>\'` when you need to force the MCP path, or `webstudio mcp run \'[{"tool":"components.find","input":{"brief":"button"}}]\'` for bounded multi-call workflows. Use `webstudio mcp run .temp/mcp-calls.json` for large batches.\n4. Start discovery with `meta.index`, then call focused tools with concrete JSON, for example `webstudio mcp single-op-call meta.guide \'{"brief":"Create a design system page using every component"}\'`.\n\nDo not run `webstudio sync`, install an MCP server, change client configuration,\nor restart the app for this local CLI workflow.\n\nWhen the user explicitly wants persistent native MCP integration, run\n`webstudio connect claude`, `webstudio connect codex`, `webstudio connect\ncursor`, or `webstudio connect vscode`. This optional command changes client\nconfiguration, so follow its client-specific reload or restart instruction.\nUse `--print` to inspect the generated setup without changing configuration or\nrequiring project access. For Codex, `connect` registers and verifies the server\nthrough the Codex CLI. Before changing client configuration, `connect` verifies\nthat the saved project endpoint is reachable and its credential is accepted.\n\nStart MCP from the linked Webstudio project root. The lifecycle status line prints that absolute root; create local scripts, screenshots, and temporary artifacts under that root, for example `<project root>/.temp/script.mjs`. If the shell starts in a parent workspace, `cd` into the project root first or use absolute paths.\n\nWhen developing inside the Webstudio monorepo, start the local CLI exactly as `node packages/cli/local.js mcp` from the repo root. Do not use `pnpm exec webstudio`, `pnpm --filter webstudio exec webstudio`, or a global `webstudio`: they can resolve an older binary.\n\nWhile the server is running, stdout is reserved for MCP JSON-RPC messages. Do not print human text from the server process. The server advertises MCP `logging` capability and emits sparse `notifications/message` logs for ready state and tool lifecycle checkpoints such as `tool preview.start started`, `tool preview.start still running after 10000ms`, and `tool preview.start succeeded in 1234ms`; stderr also mirrors these sparse lifecycle fallback lines prefixed with `[webstudio mcp]`.\n\n## One-Shot Tool Calls\n\nUse the shortcut `webstudio <tool> \'<json>\'` when you are operating from a shell and need one MCP tool result. The explicit form `webstudio mcp single-op-call <tool> \'<json>\'` is equivalent and avoids writing temporary Node.js stdio client scripts.\n\nExamples:\n\n```sh\nwebstudio mcp single-op-call meta.index\nwebstudio mcp single-op-call meta.guide \'{"brief":"Create a design system page using every component"}\'\nwebstudio mcp single-op-call meta.get-more-tools \'{"tools":["insert-fragment"]}\'\nwebstudio mcp single-op-call components.list \'{"source":"all"}\'\nwebstudio mcp single-op-call components.coverage-plan\nwebstudio mcp single-op-call components.search \'{"brief":"radix select"}\'\nwebstudio mcp single-op-call components.get \'{"component":"@webstudio-is/sdk-components-react-radix:Select"}\'\nwebstudio mcp single-op-call templates.list\nwebstudio mcp single-op-call templates.get \'{"component":"@webstudio-is/sdk-components-react-radix:Select"}\'\nwebstudio mcp single-op-call insert-fragment --input-file .temp/insert-fragment.json\n```\n\nShortcut equivalents:\n\n```sh\nwebstudio meta.index\nwebstudio meta.guide \'{"brief":"Create a design system page using every component"}\'\nwebstudio meta.get-more-tools \'{"tools":["insert-fragment"]}\'\nwebstudio components.list \'{"source":"all"}\'\nwebstudio components.coverage-plan\nwebstudio components.search \'{"brief":"radix select"}\'\nwebstudio components.get \'{"component":"@webstudio-is/sdk-components-react-radix:Select"}\'\nwebstudio templates.list\nwebstudio templates.get \'{"component":"@webstudio-is/sdk-components-react-radix:Select"}\'\nwebstudio insert-fragment --input-file .temp/insert-fragment.json\n```\n\n### Tool name convention\n\nMCP tool names are opaque strings, not JavaScript property access. A dot separates a namespace from its tool name, and every segment uses lowercase kebab-case. For example, `components.coverage-insert-next` is the `coverage-insert-next` tool in the `components` namespace. Pass the complete name as one CLI argument: `webstudio components.coverage-insert-next`. Batch `mcp run` calls also accept the underscore form advertised by MCP protocol discovery, such as `components_coverage_insert_next`. Unknown names return near matches and direct you to `meta.index`.\n\n### Readable fragment inputs\n\nPrefer `--input-file` for JSX so JSON and shell quoting do not obscure the fragment. For example, save this as `.temp/insert-fragment.json`:\n\n```json\n{\n "parentInstanceId": "root-id",\n "fragment": "<ws.element ws:tag=\'section\' ws:style={css`padding: 32px; display: grid; gap: 16px;`}><ws.element ws:tag=\'h2\'>Northstar Product OS</ws.element><ws.element ws:tag=\'p\'>Reusable patterns for teams.</ws.element></ws.element>"\n}\n```\n\nThen run `webstudio insert-fragment --input-file .temp/insert-fragment.json`. Single quotes inside the JSX keep the JSON valid and readable without backslash-escaped attributes.\n\nWrite and review larger fragments as JSX before placing them in the `fragment` field. Common patterns:\n\n```tsx\n<ws.element\n ws:tag="section"\n style={{ padding: 32, borderRadius: 16 }}\n>\n <ws.element ws:tag="h2">Operations Console</ws.element>\n <ws.element ws:tag="p">\n React-style object styles become editable Webstudio styles.\n </ws.element>\n</ws.element>\n\n<ws.element\n ws:tag="section"\n ws:tokens={[token("accent", css`color: #0f766e;`)]}\n>\n <ws.element\n ws:tag="button"\n onClick={new ActionValue(["event"], expression`console.log(event)`)}\n >\n Track launch\n </ws.element>\n</ws.element>\n\n<ws.element ws:tag="section">\n <radix.Switch>\n <radix.SwitchThumb />\n </radix.Switch>\n</ws.element>\n```\n\nRules:\n\n- Inside the Webstudio monorepo, replace `webstudio` in the examples above with `node packages/cli/local.js`, for example `node packages/cli/local.js meta.index`.\n- For a simple authored/styled section, run `meta.index`, then `meta.get-more-tools \'{"tools":["insert-fragment"]}\'`, then `insert-fragment`. Do not grep source files, dump full MCP resources, or write parser scripts first.\n- In `insert-fragment` JSX, use ``ws:style={css`...`}`` for Webstudio-native CSS, or use React-style object syntax such as `style={{ padding: 24 }}` when that is simpler. Both forms create editable Webstudio style data.\n- Do not access host globals or dynamic code APIs in JSX fragments, including `process`, `globalThis`, `eval`, `Function`, or `constructor`.\n- Use Webstudio prop names such as `class` and `for`; do not use React aliases `className` or `htmlFor`.\n- Use Webstudio actions for event/action props, for example `onClick={new ActionValue(["event"], expression\\`console.log(event)\\`)}`. Do not pass JavaScript functions such as `onClick={() => ...}`.\n- Plain prop values must be JSON-compatible: `null`, strings, booleans, finite numbers, arrays, and plain objects. Do not pass `undefined`, `Symbol`, `BigInt`, `NaN`, `Infinity`, `Date`, `Map`, `Set`, class instances, or circular objects; omit the prop, use plain data, or use `expression`/`ActionValue` when the value is dynamic.\n- Template-backed components used in JSX must include required child/part components explicitly under the same parent structure as the template, for example `<radix.Switch><radix.SwitchThumb /></radix.Switch>`. Use `insert-component` when you want one automatic registered component template.\n- The positional input is JSON and defaults to `{}`.\n- Use `--input-file` for large mutation payloads.\n- Use `--dry-run` with local-capable mutation tools when you need a patch plan without committing. The computed transaction is returned in `meta.session.transaction`, and `meta.session.version` is its base build version. Copying a `.webstudio` folder is not an isolated project clone; `.webstudio/config.json` still points to the same remote project, so non-dry-run mutations can commit to that project.\n- The command prints JSON to stdout for both success and failure. Success uses the same `structuredContent` shape MCP tools return: `{ "ok": true, "data": ..., "meta": ... }`. Failure prints `{ "ok": false, "error": { "code": "...", "message": "..." }, "meta": ... }` and exits nonzero.\n- The command writes sparse progress to stderr, including start, success/failure, elapsed time, and committed status when the tool returns session metadata.\n- Invalid argument types fail loudly with path-specific messages, for example `meta.guide input.brief must be a string when provided`.\n- Run one-shot shortcut or `mcp single-op-call` commands sequentially against the same linked `.webstudio` folder. If you receive `PROJECT_SESSION_BUSY`, another CLI/MCP process is updating the local session; wait a moment and retry sequentially.\n- To work with another previously linked project without changing the directory\'s default link, start MCP or a shell call with `--project <projectId>`, for example `webstudio mcp --project <projectId>` or `webstudio mcp single-op-call list-pages --project <projectId>`. Selected projects use isolated local session and checkpoint files.\n- If you are a delegated agent and your parent cannot see live stderr/stdout, do not run a long sequence of shortcut or `mcp single-op-call` commands silently and do not wrap many calls in a shell loop. Treat each parent-visible checkpoint as the unit of work. If the parent asks for status within 30 seconds, run exactly one `webstudio <tool>` or `webstudio mcp single-op-call` command, report that command/result, then wait before the next MCP command. For all-component design-system pages, checkpoint after discovery, checkpoint after page creation, call `components.coverage-insert-next` once before checkpointing again, then finish with the `presentation-pass` workflow phase. Coverage alone is not completion; organize examples into styled sections/cards.\n\n## Reporting CLI/MCP Issues\n\nIf a CLI/MCP tool gives a confusing error, crashes, hangs, produces invalid output, requires an undocumented workaround, or makes you inspect source code to understand normal usage, ask the user to report it in the Webstudio Discord `#help` channel: https://wstd.us/community.\n\nGive the user a complete copy-paste report. Include only non-secret values: never include auth tokens, private URLs, cookies, API keys, passwords, or proprietary project data. Redact them as `<redacted>`.\n\nCopy-paste template:\n\n````md\nWebstudio CLI/MCP issue report\n\nWhat I was trying to do:\n<short user goal, for example "Create a resource from an external API and render it in a collection">\n\nWhat I expected:\n<what should have happened>\n\nWhat happened instead:\n<exact error, confusing behavior, hang, missing docs, or workaround required>\n\nCommand/tool used:\n\n```sh\n<exact command or MCP tool call, with tokens/secrets redacted>\n```\n\nStructured output / error:\n\n```json\n<stdout JSON or MCP structuredContent, if available, with secrets redacted>\n```\n\nStderr / lifecycle logs:\n\n```txt\n<stderr lines, timings, checkpoint messages, or stack trace, with secrets redacted>\n```\n\nEnvironment:\n\n- CLI command path: <webstudio / node packages/cli/local.js / other>\n- Webstudio CLI version: <from command output if known>\n- OS: <macOS / Windows / Linux / unknown>\n- Node version: <node -v if known>\n- Project/session state: <linked project, local .webstudio session, preview, MCP server, or unknown>\n\nWorkaround tried:\n<what the agent/user tried next, and whether it worked>\n\nWhy this should be improved:\n<one sentence: better error message, docs, schema, tool behavior, etc.>\n````\n\n## Shared-Session Shell Runs\n\nUse `webstudio mcp run \'[{"tool":"components.find","input":{"brief":"button"}}]\'` when you are operating from a shell and need several MCP tool calls to share one CLI session without hand-writing JSON-RPC. For large batches, pass a normal JSON file path such as `.temp/mcp-calls.json`. Do not use shell process substitution like `<(...)`; use inline JSON or a real file.\n\nUse `mcp run` for long-lived tools such as `preview.start`. A one-shot `mcp single-op-call preview.start` cannot keep ownership of a preview server for a later screenshot or stop call. Put `preview.start`, `screenshot`, and `preview.stop` in one shared `mcp run` process, or use a real long-running MCP client.\n\nInput shape:\n\n```json\n{\n "calls": [\n { "tool": "meta.index" },\n { "tool": "components.find", "input": { "brief": "radix select" } }\n ]\n}\n```\n\nRules:\n\n- The command prints JSON to stdout for both success and failure. It stops at the first failed call and prints partial results in `{ "ok": false, "error": ..., "data": { "completedCalls": ..., "results": [...] }, "meta": ... }`, then exits nonzero.\n- If a call returns `checkpoint.required`, read-only discovery and inspection remain available, but mutations and state-changing session tools return `CHECKPOINT_REQUIRED`. Stop and report the checkpoint to the parent/user. Only after the parent/user continues, call `checkpoint.ack {"reported":true,"continueAfterReport":true,"summary":"<what you reported>"}` before continuing mutations.\n- For `mcp single-op-call`, checkpoint requirements persist across later one-shot CLI processes until you call `checkpoint.ack {"reported":true,"continueAfterReport":true,"summary":"<what you reported>"}`.\n- Use this instead of manually sending JSON-RPC frames to `webstudio mcp` from a shell.\n\n### Cross-project batches\n\nAdd `projects` to the same `mcp run` manifest to run focused reads, audits, or dry runs across independently linked project roots:\n\n```json\n{\n "concurrency": 2,\n "calls": [\n { "tool": "status" },\n { "tool": "audit", "input": {} },\n {\n "tool": "update-project-settings",\n "input": { "meta": { "siteName": "Reviewed" } },\n "dryRun": true\n }\n ],\n "projects": [\n { "id": "site-a", "root": "../site-a" },\n { "id": "site-b", "root": "../site-b" }\n ]\n}\n```\n\nProject roots and an optional `progressFile` are resolved relative to the manifest file. Each project may provide its own `calls` instead of using the top-level calls. Each root must already be linked with its own `.webstudio/config.json`; the runner creates an independently authenticated ProjectSession and uses root-scoped session, audit, preview-data, and checkpoint paths without changing the process working directory.\n\nConcurrency defaults to 2, is capped at 16, and can be set in the manifest or overridden with `--concurrency`. A failure is reported for that project while other projects continue. Progress is saved after every successful call; rerunning with the default `--resume` skips completed projects and starts failed projects after their last confirmed successful call. Reads and dry runs may be retried. A committed mutation interrupted after dispatch is marked `AMBIGUOUS_MUTATION_RESULT` and is never replayed automatically; inspect that project before deciding how to continue. Use `--no-resume` only to intentionally start the complete manifest over.\n\nCommitted mutation tools are rejected in a projects batch unless the command includes `--approve-mutations`. Review the complete manifest before granting approval. `--dry-run` applies to every call and does not require mutation approval. The final stdout object is compact: project counts, one status/error record per project, elapsed time, and the progress-file path rather than every tool result.\n\n## Discovery\n\nUse MCP itself after startup, or call the same tools with `webstudio mcp single-op-call`:\n\n- `tools/list`: machine-readable available tools\n- `resources/list`: available overview and full JSON resources\n- `meta.index`: concise capability catalog\n- `meta.guide`: workflow for a user goal; call with a string brief such as `{"brief":"Create a pricing page"}`\n- `meta.get-more-tools`: detailed params, examples, namespaces, and local/server behavior; prefer exact names such as `{"tools":["insert-fragment"]}` when you know them\n- `components.list`: compact registry metadata for visible components and templates; use a focused get tool for complete details\n- `components.summary`: component counts by default; use `{"detail":"components","limit":20}` for paginated entries\n- `components.coverage-plan`: compact paged plan for design-system coverage tasks that need every component; default returns counts plus the first root page, use `{"detail":"roots"}`, `{"detail":"parts"}`, or `{"detail":"full"}` for more\n- `components.coverage-status`: page-specific covered/missing component report with `missingRoots` and `missingParts`\n- `components.search`: focused component/template search by id, namespace, label, category, or content model\n- `components.find`: compatibility alias for focused component search\n- `components.get`: full metadata for one component id\n- `templates.list`: compact metadata for template-backed insertions only\n- `templates.get`: full registry item and payload metadata for one template\n\nComponent and template registry items use a shadcn-compatible top-level shape plus Webstudio-specific superset metadata in `meta`. Use `meta.runtime` for component ids, props, states, content model, and source identity; `meta.authoring` for composition and accessibility guidance; and `meta.builder` for template insertion details and expected project-data namespaces. These items are for Builder/MCP discovery and are not a published shadcn install registry yet.\n\nPrefer the focused `components.*` tools over dumping `webstudio://project/components`. Do not write local scripts to parse full MCP discovery JSON for common component lookup.\nFor “use every component” or design-system pages, start with compact `components.coverage-plan`, checkpoint, then page through roots/parts instead of dumping the full catalog.\n\n## Consumer Capabilities\n\nMCP lets agents work on one configured Webstudio project at a time. In consumer\nterms, agents can:\n\n- Check which project they are connected to.\n- Check what the share link is allowed to do.\n- Inspect project metadata and the latest editable build.\n- Read selected project data for audits and repair.\n- Apply precise project changes against a known version.\n- List, inspect, create, update, delete, duplicate, copy, and reorder pages.\n- Set the home page.\n- Preserve old page paths for redirects or history.\n- Read and update page titles, descriptions, metadata, auth settings, and SEO fields.\n- List, create, update, duplicate, move, and delete page folders.\n- List, create, update, delete, duplicate, reorder, and reuse page templates.\n- Create pages from reusable templates.\n- Read and update project site settings.\n- Read and update marketplace product metadata.\n- List, create, update, delete, and replace redirects.\n- List, create, update, and delete responsive breakpoints.\n- List and inspect page elements.\n- Insert registered components.\n- Insert styled JSX fragments.\n- Move, reparent, clone, duplicate, wrap, unwrap, convert, rename, retag, and delete elements.\n- Fill grid cells.\n- List and update text children.\n- Update plain text and expression text.\n- Update structured rich text.\n- Add, update, delete, and bind element props.\n- Bind props to expressions, resources, actions, and runtime system values.\n- Read, add, update, delete, and replace local styles.\n- Update selected style-source styles.\n- List, create, update, attach, detach, extract, duplicate, rename, lock, unlock, reorder, clear, and delete design tokens and style sources.\n- List, define, rename, delete, and rewrite CSS variables.\n- List, create, update, and delete static data variables.\n- Create string, number, boolean, and JSON variables. Arrays use JSON.\n- Delete unused data variables.\n- List, create, update, upsert, bind, and delete resources.\n- Create HTTP resources.\n- Create GraphQL resources.\n- Create system resources.\n- Use built-in system resources for sitemap, current date, and assets.\n- List and inspect complete asset metadata; upload, download, update, move, duplicate, find usage for, replace, and delete assets.\n- List, create, rename, move, recursively duplicate, and recursively delete nested asset folders.\n- Publish to staging or production.\n- Publish to selected domains.\n- List publish builds.\n- Check publish job status.\n- Unpublish staging or production deployments.\n- List, create, update, delete, and verify custom domains.\n- Start and stop preview.\n- Capture screenshots of generated pages.\n- Compare screenshots against baselines.\n- Install OCR support for richer visual checks.\n\nUseful resources:\n\n- `webstudio://project/status`: compact current ProjectSession status\n- `webstudio://project/tools-overview`: small operation overview by capability area\n- `webstudio://project/components-overview`: small component overview with ids, labels, namespaces, and categories\n- `webstudio://project/tools`: full operation catalog; read only when focused metadata is insufficient\n- `webstudio://project/components`: full component catalog with props, states, and content model composition constraints; read only when `components.summary`, `components.find`, and `components.get` are insufficient\n- `webstudio://project/guide`: concise discovery guide\n- `webstudio://project/expressions`: expression syntax, scope, supported methods, bindings, Collection iteration context, and verification\n- `webstudio://project/accessibility-review`: evidence-based LLM accessibility-review workflow using project checks, preview, and screenshots\n\n## MCP SDK Client Imports\n\nWhen writing a local Node.js MCP client script, use the official MCP SDK package and these exact ESM imports:\n\nInside the Webstudio monorepo this package is available at the repo root. In another project, install it first with `pnpm add -D @modelcontextprotocol/sdk`.\n\n```js\nimport { Client } from "@modelcontextprotocol/sdk/client/index.js";\nimport { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";\nimport { LoggingMessageNotificationSchema } from "@modelcontextprotocol/sdk/types.js";\n```\n\nMinimal stdio client for the local Webstudio CLI:\n\n```js\nconst client = new Client({ name: "webstudio-agent", version: "1.0.0" });\n\nclient.setNotificationHandler(\n LoggingMessageNotificationSchema,\n (notification) => {\n console.error(`[mcp] ${notification.params.data}`);\n }\n);\n\nconst transport = new StdioClientTransport({\n command: "node",\n args: ["packages/cli/local.js", "mcp"],\n cwd: process.cwd(),\n stderr: "inherit",\n});\n\nawait client.connect(transport);\n\nconst index = await client.callTool({\n name: "meta.index",\n arguments: {},\n});\nconsole.log(JSON.stringify(index.structuredContent, null, 2));\n\nawait client.close();\n```\n\nUse `node packages/cli/local.js mcp` from the Webstudio monorepo root for local development, or `webstudio mcp` from a linked project where the CLI is installed. Keep stdout for JSON-RPC/structured results and surface MCP logging notifications or stderr lifecycle lines as progress.\n\n## Core Rules\n\n- stdout is reserved for MCP JSON-RPC while the server is running.\n- Operate on the configured project only.\n- Read ids before writing.\n- Prefer semantic tools over `apply-patch`.\n- Use `status` and `refresh` when cached namespaces may be stale. Pass `status {"verbose":true}` only when debugging full namespace arrays, freshness, compatibility, or diagnostic details.\n- Read `meta.session.commitStatus` before interpreting durability. Read-only results report `not-applicable` and retain `committed:false` for compatibility; dry-run plans report `planned`; failed mutations report `failed`; no-op mutations report `unchanged`; durable mutations report `committed` with `meta.session.committed:true`.\n- For visual/design work, verify the rendered result with vision before finishing.\n\n## Vision Verification Loop\n\nVision-capable AI can use MCP to see what it is building:\n\n{{mcpVisionVerificationLoopMarkdown}}\n\nGenerated app setup:\n\n{{mcpGeneratedAppDependencyNotes}}\n\n## MCP argument examples\n\nExamples below show meaningful argument combinations. Tool schemas are the\nsource of truth. For tools with no required arguments, pass `{}`.\n\n{{mcpArgumentExampleIndex}}\n\n## Screenshot Verification\n\n{{screenshotVerificationSummary}}\n',
|
|
273240
273739
|
"mcp-startup-epilogue": 'If you are inside the Webstudio monorepo, use the local CLI exactly as `node packages/cli/local.js ...` as your first command path. Do not use `packages/cli/bin.js` for local source-tree work; it is the packaged build entry and may use stale built output.\n\nPlain `webstudio mcp` starts the stdio MCP server for real MCP clients. Do not manually type or pipe raw JSON-RPC frames into it from an interactive shell or PTY. From a shell, use shortcut calls such as `webstudio meta.index` and `webstudio insert-fragment \'<json>\' --dry-run` for one bounded tool call. The explicit equivalent is `webstudio mcp single-op-call <tool> \'<json>\'`. Use `webstudio mcp run \'[{"tool":"components.find","input":{"brief":"button"}}]\'` for small multi-call batches in one shared CLI session. For large batches, pass a normal JSON file path such as `.temp/mcp-calls.json`. Do not use shell process substitution like `<(...)`; use inline JSON or a real file.\n\nPut each local CLI call in its own shell command; do not chain helper commands with `&&`, `;`, command substitution, or shell wrappers around the CLI when reporting a CLI step. Do not use `pnpm exec webstudio`, `pnpm --filter webstudio exec webstudio`, or a global `webstudio`; those can resolve an older binary. For example:\n\n```sh\nnode packages/cli/local.js mcp single-op-call meta.index\nnode packages/cli/local.js mcp single-op-call meta.get-more-tools \'{"tools":["insert-fragment"]}\'\nnode packages/cli/local.js mcp single-op-call insert-fragment --input-file .temp/insert-fragment.json --dry-run\n```\n\nSave the input as `.temp/insert-fragment.json`; use single quotes for JSX attributes so the JSON needs no backslash escaping:\n\n```json\n{\n "parentInstanceId": "parent-id",\n "fragment": "<ws.element ws:tag=\'section\' ws:style={css`padding: 32px; display: grid; gap: 12px;`}><ws.element ws:tag=\'h2\'>Launch Kit</ws.element><ws.element ws:tag=\'p\'>A focused section created with Webstudio JSX.</ws.element><ws.element ws:tag=\'button\'>Get started</ws.element></ws.element>"\n}\n```\n\nThe shorter local shortcut form is preferred for simple shell steps:\n\n```sh\nnode packages/cli/local.js meta.index\nnode packages/cli/local.js meta.get-more-tools \'{"tools":["insert-fragment"]}\'\nnode packages/cli/local.js insert-fragment --input-file .temp/insert-fragment.json --dry-run\n```\n\nFor simple authored/styled sections, run the three commands above. Do not grep source files, dump full MCP resources, or write parser scripts first. Use `list-pages`, `get-page-by-path`, or `list-instances` only when you still need the target `parentInstanceId`.\n\nWhen building for a Content-mode editor, use a Content Block (`ws:block`) around every region that editor must change. Content-mode text and supported props are editable only in Content Block descendants; content outside is read-only. Keep reusable insertable source options in the block\'s `ws:block-template` child. Templates themselves are protected; an editor\'s inserted copy becomes an editable direct child of the Content Block. Verify this structure before handoff.\n\nRun it from the linked Webstudio project root. The startup status line prints that absolute root; use it for local files such as `<project root>/.temp/script.mjs`, screenshots, and generated artifacts.\n\nFor experiments, pass `--dry-run` to local-capable mutation calls. Read the computed transaction from `meta.session.transaction` and its base build version from `meta.session.version`. Copying a `.webstudio` folder is not an isolated project clone; `.webstudio/config.json` still points to the same remote project, so non-dry-run mutations can commit to that project.\n\nStartup marks cached ProjectSession data stale so MCP tools read the current Builder dev build.\n\nAfter startup, use focused discovery tools with concrete JSON. For delegated design-system or “use every component” tasks, start with exactly one command: `webstudio workflow.next \'{"goal":"design-system-page"}\'`; report the returned checkpoint to the parent/user and stop until continued. For other tasks, read `meta.index` first. From a shell, `webstudio mcp list-tools` is a concise alias for the initial tool catalog. Then call shortcuts such as `webstudio meta.guide \'{"brief":"Create a design system page using every component"}\'`, `webstudio workflow.next \'{"goal":"design-system-page"}\'`, `webstudio meta.get-more-tools \'{"tools":["insert-fragment"]}\'`, `webstudio components.list \'{"source":"all"}\'`, `webstudio components.summary`, `webstudio components.search \'{"brief":"radix select"}\'`, `webstudio components.get \'{"component":"@webstudio-is/sdk-components-react-radix:Select"}\'`, `webstudio templates.list`, and `webstudio templates.get \'{"component":"@webstudio-is/sdk-components-react-radix:Select"}\'`. Use `webstudio mcp single-op-call` when you need the explicit MCP form. Prefer `insert-fragment` for authored/styled sections; use `insert-component` only when inserting one automatic component template. In JSX, use `ws:style={css\\`...\\`}`for styles,`class`/`for`instead of`className`/`htmlFor`, `ActionValue`for actions instead of JavaScript functions such as`onClick={() => ...}`, only JSON-compatible plain prop values, and no host globals or dynamic code APIs. For bounded multi-step shell work, run inline JSON like `webstudio mcp run \'[{"tool":"meta.index"},{"tool":"components.search","input":{"brief":"button"}}]\'`; this reuses one CLI session without raw JSON-RPC. For larger batches, write `{ "calls": [{ "tool": "..." }] }`to`.temp/mcp-calls.json`and run`webstudio mcp run .temp/mcp-calls.json`. If any call returns `checkpoint.required`, `mcp run`stops immediately before later calls; report the checkpoint and call`checkpoint.ack {"reported":true,"continueAfterReport":true,"summary":"<what you reported>"}`before continuing. For design-system or “use every component” tasks, call`workflow.next {"goal":"design-system-page"}`for the next bounded phase, report its checkpoint, wait until the parent/user continues, call`checkpoint.ack {"reported":true,"continueAfterReport":true,"summary":"<what you reported>"}`, then run only that phase. Start coverage phases with compact`webstudio components.coverage-plan`, checkpoint, then request component coverage details with `components.coverage-plan {"detail":"roots"}`or`components.coverage-plan {"detail":"parts"}`. Do not pass `detail`to`list-pages`; use `list-pages {}`or`get-page-by-path`for page lookup. Read MCP overview resources`webstudio://project/guide`, `webstudio://project/tools-overview`, and `webstudio://project/components-overview`when useful. Read full resources`webstudio://project/tools`and`webstudio://project/components` only when focused tools are insufficient; do not dump or parse full discovery JSON for common lookup.\n\nAfter startup, MCP clients discover capabilities with `tools/list`, `resources/list`, `meta.index`, `meta.guide`, and `meta.get-more-tools`.\n\nstdout is reserved for MCP JSON-RPC messages while the server is running.\n\nThe server advertises MCP `logging` capability and sends sparse `notifications/message` logs for ready state and tool lifecycle checkpoints such as `tool preview.start started`, `tool preview.start still running after 10000ms`, and `tool preview.start succeeded in 1234ms`. It also writes the same sparse lifecycle status lines to stderr, prefixed with `[webstudio mcp]`, including a final ready line. Treat the process as healthy and long-running after either ready signal; surface tool lifecycle logs as progress checkpoints and interact through MCP JSON-RPC on stdin/stdout.\n\n`webstudio mcp single-op-call` also writes sparse lifecycle lines to stderr for each one-shot call. Use those stderr lines as progress checkpoints and parse stdout as JSON on both success and failure. Failed one-shot calls print `{ "ok": false, "error": { "code": "...", "message": "..." }, "meta": ... }` to stdout and exit nonzero. If a one-shot call returns checkpoint.required, read-only discovery remains available, while later mutations fail with `CHECKPOINT_REQUIRED` until you report the checkpoint and call `webstudio mcp single-op-call checkpoint.ack \'{"reported":true,"continueAfterReport":true,"summary":"<what you reported>"}\'`.\n\n`webstudio mcp run` also writes sparse lifecycle lines to stderr and prints one JSON result on success or failure. It stops at the first failed call or checkpoint requirement, including `CHECKPOINT_REQUIRED`, and preserves partial results so delegated agents can report exactly where they stopped.\n\nWhen `report-issue` is available, use it when the user explicitly asks to report a problem. Also use it automatically after a Webstudio-controlled tool crash, hang, malformed result, misleading success, repeated failure of the same documented operation, failed documented recovery, or required undocumented workaround. Do not automatically report expected confirmation/checkpoint flow, a clearly explained invalid input, missing permissions, `PROJECT_SESSION_BUSY`, a recovered version conflict, rate limiting, cancellation, or an offline environment. Write a complete anonymous report with the generalized user story, attempted workflow, expected behavior, actual result, recovery attempts, impact, technical context, acceptance criteria, exact model identifier, and reasoning effort. Never include names, usernames, emails, phone numbers, organizations, project or resource ids, domains, URLs, IP addresses, local paths, credentials, tokens, customer content, exact unique values, or raw contextual tool data.\n\nIf you are running as a delegated or non-streaming agent whose parent cannot see live stderr/stdout, do not batch many MCP calls silently and do not run long shell loops of shortcut or `webstudio mcp single-op-call` commands. Treat each parent-visible checkpoint as the unit of work. If the parent asks for status within 30 seconds, run exactly one shortcut command such as `webstudio meta.index` or one explicit `webstudio mcp single-op-call` command, return a concise checkpoint with that command/result, and wait for the parent to continue. Do not take a broad task such as creating a full design-system page as one execution unit. Call `workflow.next {"goal":"design-system-page"}`, report the returned phase/checkpoint, wait until the parent/user continues, call `checkpoint.ack {"reported":true,"continueAfterReport":true,"summary":"<what you reported>"}`, complete exactly that bounded phase, and return: discovery, page creation, one dry-run JSX section, one committed JSX section, one `components.coverage-insert-next` call, or one presentation pass. Phase commands do not include nextPhase in their own output. After the parent continues, acknowledge the previous checkpoint first, then call `workflow.next` with the next phase. For all-component design-system pages, checkpoint after workflow planning, discovery, page creation, call `components.coverage-insert-next` once before checkpointing again, then finish with the `presentation-pass` workflow phase.\n',
|
|
273241
|
-
"mcp-vision": '# MCP Vision Verification\n\n## Generated App Dependency Notes\n\n- `preview.start` and `webstudio preview` install generated app dependencies under `.webstudio/preview` and reuse them across regenerations.\n- Do not add generated-preview dependencies to the repository root `package.json` or `pnpm-lock.yaml`.\n- If dependency installation fails, check npm and network configuration, then reinstall or update the Webstudio CLI if the problem persists.\n\n## Visual Verification Rule\n\nFor visual/design work, use `preview.start` and `screenshot({ path })` so vision can inspect the current MCP session before finishing and verify that generated project files are current. Iterative preview is the default: it keeps one generated-site server and browser alive, regenerates changed files, and performs an ordinary page reload without Vite HMR. Use `mode: "production"` only for release-like verification; rendered audit selects it automatically. `preview.start` is long-lived and cannot be used through one-shot `mcp single-op-call`; from a shell, use `webstudio mcp run` for preview.start/screenshot/preview.stop in one shared process, or use a long-running MCP server. When a baseline exists, use screenshot.diff to get pixel regions, OCR text changes, and diff PNG artifacts.\n\nAn authenticated project share URL is used with `webstudio init --link`; it is\nnot a generated-site preview URL. Project screenshots and rendered audits use\nthe generated local preview owned by the current CLI/MCP process. Do not pass a\nBuilder/share URL to `screenshot`, even without query parameters. Use `path`\nafter starting preview, or use `baseUrl` only for an intentional generated site\nthat is already running. Path captures verify the generated-site root marker\nand fail instead of returning a screenshot of Builder chrome.\n\n## Vision Verification Loop\n\n- Make focused page/content/style changes with semantic MCP tools.\n- Call preview.start once to keep the iterative generated site running. In shell-driven workflows, run preview.start, screenshot, and preview.stop inside one `webstudio mcp run` call so they share the same preview owner.\n- Read `preview.status.stale` before relying on generated output. When present, `renderedProjectVersion` identifies the last project version materialized into the preview; a stale preview refreshes automatically on the next managed screenshot or `preview.start` call.\n- {{dependency-notes}}\n- After MCP mutations, path-based screenshots regenerate the current session in place, wait for its exact project version, and normally reload the route. The server and browser remain alive. From one-shot shell calls or another process, pass `baseUrl` with `path` to capture an already-running generated site without starting it. Use preview.stop only in the same long-running MCP server or `webstudio mcp run` process that started preview; a separate one-shot `single-op-call` process does not own another process\'s preview controller.\n- For multi-page work, capture each changed page by path through the same preview server, for example screenshot({ path: "/" }), screenshot({ path: "/pricing" }), and screenshot({ path: "/about" }). The screenshot tool navigates directly to the requested route; no browser click navigation is required.\n- For responsive work, call list-breakpoints first, then capture screenshots at viewport widths based on the Builder breakpoints plus a narrow mobile and desktop width.\n- Call screenshot with { path: "/" } or the changed page path and viewport such as { width: 375, height: 812 } and { width: 1440, height: 900 }. For an existing preview in another process, call screenshot with { baseUrl: "http://127.0.0.1:5177", path: "/" }. Use waitForSelector when the page has a reliable ready marker, waitUntil:"networkidle" for network-heavy pages, and waitForTimeout only for final visual settling.\n- {{diff}} When a baseline PNG exists, call screenshot.diff with baselinePath, currentPath, and outputDir for each page/viewport pair. Add expectedText when a specific visible phrase must be present; its assertions report pass/fail plus found and missing text. Add expectedVisual to set pass/fail limits for mismatch percentage, the number of changed regions, or an overall dominant color/brightness direction.\n- {{diff}} Read screenshot.diff textAnalysis: it reports OCR status plus text that appeared, disappeared, moved, changed content, or changed font/style geometry. If OCR is unavailable, expectedText assertions fail and textAnalysis reports why; ask the user for permission to install Tesseract, then call vision.install-ocr with { "confirm": true }, or rely on visual inspection.\n- Inspect every viewport PNG and any diff artifacts with vision, then compare layout, OCR text evidence, color, spacing, imagery, and responsive framing against the user intent.\n- If the screenshot does not match, apply another focused mutation and repeat screenshot verification.\n\n## Workflow Summary With Diff\n\nFor visual/design work, call preview.start once to start the iterative generated-site preview, then screenshot({ path, viewport }) after each focused mutation; path screenshots regenerate changed files and reload the route in the existing server and browser. For responsive work, use list-breakpoints and capture each changed page at Builder breakpoint widths plus mobile and desktop widths. Use screenshot.diff on each baseline/current page or viewport pair when a baseline exists, then inspect pixel regions, OCR textAnalysis, and PNG/diff artifacts with vision before finishing. Use mode: "production" only for release-like verification.\n\n## Workflow Summary Without Diff\n\nFor visual/design work, call preview.start once to start the iterative generated-site preview, then screenshot({ path, viewport }) after each focused mutation; path screenshots regenerate changed files and reload the route in the existing server and browser. For responsive work, use list-breakpoints and capture each changed page at Builder breakpoint widths plus mobile and desktop widths. Inspect every PNG with vision before finishing. Use mode: "production" only for release-like verification.\n\nEach screenshot result includes rendered `layout` metrics when the local browser\nprovides them. `layout.horizontalOverflow: true` is deterministic evidence that\nthe rendered document exceeds the requested viewport width. Use vision for\nclipping, wrapping, hierarchy, and other judgments that layout dimensions alone\ncannot establish.\n\nPass `includeImageMetrics: true` when an ordinary screenshot needs\n`layout.images`; rendered audit enables it automatically. The array includes\neach rendered image\'s Webstudio instance id when available, loading mode,\ncompletion state, natural dimensions, rendered dimensions, and document\nposition. Rendered audits use this evidence to report broken images, eager\nloading below the fold, and sources more than 2x the rendered dimensions in\nboth axes. Oversized-source results are optimization evidence, not universal\nperformance conformance.\n\nPass `includeResourceMetrics: true` when an ordinary screenshot needs sanitized\nResource Timing evidence; rendered audit enables it automatically. Resource\nmetrics contain only the URL pathname, initiator type, transfer/body sizes,\nduration, and browser-provided render-blocking status. Origins and query strings\nare omitted. Rendered audits report explicitly blocking resources and legacy\n`.ttf`, `.otf`, or `.woff` font files without applying a universal byte-size\nbudget.\n\n## Screenshot Verification Summary\n\nInside a long-running MCP server, call preview.start once, then use screenshot({ path, viewport }) for fast repeated checks across multiple pages. Iterative mode is the default: after MCP mutations, path screenshots regenerate changed files and reload the requested route while keeping the server and browser alive. Use mode: "production" only for release-like verification. From one-shot shell calls or another process, use screenshot({ baseUrl, path, viewport }) to capture an already-running preview/site without generating, building, starting, or restarting preview. Use path values such as "/", "/pricing", or "/about" to capture specific generated routes. For responsive work, read list-breakpoints and capture one familiar device viewport inside each Builder breakpoint range before using vision. Screenshot waits for load by default, then fonts and two layout frames; pass waitForSelector for app readiness, waitUntil:"networkidle" for network-heavy pages, and waitForTimeout for final settling. When a baseline exists, use screenshot.diff for changed regions, OCR textAnalysis, and diff artifacts on each baseline/current screenshot pair. Outside MCP, use `webstudio screenshot --path /pricing --output pricing.png` for one temporary generated preview capture, or keep `webstudio preview` running and pass its absolute URL to `webstudio screenshot` for repeated captures.\n\n## Screenshot Diff Evidence\n\n- Pixel evidence: total mismatch, changed regions, dominant color/luminance direction, diffPath, and contextDiffPath.\n- OCR evidence: textAnalysis.status, provider, and changes for appeared/disappeared/content_changed/moved/font_changed text. expectedText adds pass/fail assertions plus found and missing current-screen text. expectedVisual adds pass/fail quantitative checks for mismatch percentage, changed-region count, and the overall dominant color/brightness direction.\n- OCR dependency: screenshot.diff uses the system tesseract binary when available. If missing, it returns ocr_unavailable_tesseract_not_found_or_failed and still returns pixel evidence.\n- OCR install: MCP cannot prompt. Ask the user first; if they agree, call vision.install-ocr with { "confirm": true }. If automatic install is unavailable, follow the returned installUrl.\n- Final judgment: OCR and pixel diff are evidence. A vision-capable model must still inspect screenshots/diff artifacts and compare the rendered result to user intent.\n'
|
|
273740
|
+
"mcp-vision": '# MCP Vision Verification\n\n## Generated App Dependency Notes\n\n- `preview.start` and `webstudio preview` install generated app dependencies under `.webstudio/preview` and reuse them across regenerations.\n- Dependency installation honors `npm_config_cache`, including a caller-provided writable cache on Windows.\n- Do not add generated-preview dependencies to the repository root `package.json` or `pnpm-lock.yaml`.\n- If dependency installation fails, the error includes sanitized npm diagnostics. Check the reported npm and network configuration, then reinstall or update the Webstudio CLI if the problem persists.\n\n## Visual Verification Rule\n\nFor visual/design work, use `preview.start` and `screenshot({ path })` so vision can inspect the current MCP session before finishing and verify that generated project files are current. Iterative preview is the default: it keeps one generated-site server and browser alive, regenerates changed files, and performs an ordinary page reload without Vite HMR. Use `mode: "production"` only for release-like verification; rendered audit selects it automatically. `preview.start` is long-lived and cannot be used through one-shot `mcp single-op-call`; from a shell, use `webstudio mcp run` for preview.start/screenshot/preview.stop in one shared process, or use a long-running MCP server. When a baseline exists, use screenshot.diff to get pixel regions, OCR text changes, and diff PNG artifacts.\n\nAn authenticated project share URL is used with `webstudio init --link`; it is\nnot a generated-site preview URL. Project screenshots and rendered audits use\nthe generated local preview owned by the current CLI/MCP process. Do not pass a\nBuilder/share URL to `screenshot`, even without query parameters. Use `path`\nafter starting preview, or use `baseUrl` only for an intentional generated site\nthat is already running. Path captures verify the generated-site root marker\nand fail instead of returning a screenshot of Builder chrome.\n\n## Vision Verification Loop\n\n- Make focused page/content/style changes with semantic MCP tools.\n- Call preview.start once to keep the iterative generated site running. In shell-driven workflows, run preview.start, screenshot, and preview.stop inside one `webstudio mcp run` call so they share the same preview owner.\n- Read `preview.status.stale` before relying on generated output. When present, `renderedProjectVersion` identifies the last project version materialized into the preview; a stale preview refreshes automatically on the next managed screenshot or `preview.start` call.\n- {{dependency-notes}}\n- After MCP mutations, path-based screenshots regenerate the current session in place, wait for its exact project version, and normally reload the route. The server and browser remain alive. From one-shot shell calls or another process, pass `baseUrl` with `path` to capture an already-running generated site without starting it. Use preview.stop only in the same long-running MCP server or `webstudio mcp run` process that started preview; a separate one-shot `single-op-call` process does not own another process\'s preview controller.\n- For multi-page work, capture each changed page by path through the same preview server, for example screenshot({ path: "/" }), screenshot({ path: "/pricing" }), and screenshot({ path: "/about" }). The screenshot tool navigates directly to the requested route; no browser click navigation is required.\n- For responsive work, call list-breakpoints first, then capture screenshots at viewport widths based on the Builder breakpoints plus a narrow mobile and desktop width.\n- Call screenshot with { path: "/" } or the changed page path and viewport such as { width: 375, height: 812 } and { width: 1440, height: 900 }. For an existing preview in another process, call screenshot with { baseUrl: "http://127.0.0.1:5177", path: "/" }. Use waitForSelector when the page has a reliable ready marker, waitUntil:"networkidle" for network-heavy pages, and waitForTimeout only for final visual settling.\n- An explicit occupied `port` fails immediately with `PREVIEW_PORT_IN_USE`. To capture a generated site already running in another process, pass its `baseUrl` with `path`; otherwise choose another port.\n- Automatic browser discovery checks system installations, configured browser paths, and Chromium installations in the Playwright browser cache.\n- The screenshot timeout bounds browser capture after the preview is ready. A timeout returns `SCREENSHOT_TIMEOUT`, resets the reusable browser session, and releases the shared preview lifecycle for cleanup.\n- {{diff}} When a baseline PNG exists, call screenshot.diff with baselinePath, currentPath, and outputDir for each page/viewport pair. Add expectedText when a specific visible phrase must be present; its assertions report pass/fail plus found and missing text. Add expectedVisual to set pass/fail limits for mismatch percentage, the number of changed regions, or an overall dominant color/brightness direction.\n- {{diff}} Read screenshot.diff textAnalysis: it reports OCR status plus text that appeared, disappeared, moved, changed content, or changed font/style geometry. If OCR is unavailable, expectedText assertions fail and textAnalysis reports why; ask the user for permission to install Tesseract, then call vision.install-ocr with { "confirm": true }, or rely on visual inspection.\n- Inspect every viewport PNG and any diff artifacts with vision, then compare layout, OCR text evidence, color, spacing, imagery, and responsive framing against the user intent.\n- If the screenshot does not match, apply another focused mutation and repeat screenshot verification.\n\n## Workflow Summary With Diff\n\nFor visual/design work, call preview.start once to start the iterative generated-site preview, then screenshot({ path, viewport }) after each focused mutation; path screenshots regenerate changed files and reload the route in the existing server and browser. For responsive work, use list-breakpoints and capture each changed page at Builder breakpoint widths plus mobile and desktop widths. Use screenshot.diff on each baseline/current page or viewport pair when a baseline exists, then inspect pixel regions, OCR textAnalysis, and PNG/diff artifacts with vision before finishing. Use mode: "production" only for release-like verification.\n\n## Workflow Summary Without Diff\n\nFor visual/design work, call preview.start once to start the iterative generated-site preview, then screenshot({ path, viewport }) after each focused mutation; path screenshots regenerate changed files and reload the route in the existing server and browser. For responsive work, use list-breakpoints and capture each changed page at Builder breakpoint widths plus mobile and desktop widths. Inspect every PNG with vision before finishing. Use mode: "production" only for release-like verification.\n\nEach screenshot result includes rendered `layout` metrics when the local browser\nprovides them. `layout.horizontalOverflow: true` is deterministic evidence that\nthe rendered document exceeds the requested viewport width. Use vision for\nclipping, wrapping, hierarchy, and other judgments that layout dimensions alone\ncannot establish.\n\nPass `includeImageMetrics: true` when an ordinary screenshot needs\n`layout.images`; rendered audit enables it automatically. The array includes\neach rendered image\'s Webstudio instance id when available, loading mode,\ncompletion state, natural dimensions, rendered dimensions, and document\nposition. Rendered audits use this evidence to report broken images, eager\nloading below the fold, and sources more than 2x the rendered dimensions in\nboth axes. Oversized-source results are optimization evidence, not universal\nperformance conformance.\n\nPass `includeResourceMetrics: true` when an ordinary screenshot needs sanitized\nResource Timing evidence; rendered audit enables it automatically. Resource\nmetrics contain only the URL pathname, initiator type, transfer/body sizes,\nduration, and browser-provided render-blocking status. Origins and query strings\nare omitted. Rendered audits report explicitly blocking resources and legacy\n`.ttf`, `.otf`, or `.woff` font files without applying a universal byte-size\nbudget.\n\n## Screenshot Verification Summary\n\nInside a long-running MCP server, call preview.start once, then use screenshot({ path, viewport }) for fast repeated checks across multiple pages. Iterative mode is the default: after MCP mutations, path screenshots regenerate changed files and reload the requested route while keeping the server and browser alive. Use mode: "production" only for release-like verification. From one-shot shell calls or another process, use screenshot({ baseUrl, path, viewport }) to capture an already-running preview/site without generating, building, starting, or restarting preview. Use path values such as "/", "/pricing", or "/about" to capture specific generated routes. For responsive work, read list-breakpoints and capture one familiar device viewport inside each Builder breakpoint range before using vision. Screenshot waits for load by default, then fonts and two layout frames; pass waitForSelector for app readiness, waitUntil:"networkidle" for network-heavy pages, and waitForTimeout for final settling. When a baseline exists, use screenshot.diff for changed regions, OCR textAnalysis, and diff artifacts on each baseline/current screenshot pair. Outside MCP, use `webstudio screenshot --path /pricing --output pricing.png` for one temporary generated preview capture, or keep `webstudio preview` running and pass its absolute URL to `webstudio screenshot` for repeated captures.\n\n## Screenshot Diff Evidence\n\n- Pixel evidence: total mismatch, changed regions, dominant color/luminance direction, diffPath, and contextDiffPath.\n- OCR evidence: textAnalysis.status, provider, and changes for appeared/disappeared/content_changed/moved/font_changed text. expectedText adds pass/fail assertions plus found and missing current-screen text. expectedVisual adds pass/fail quantitative checks for mismatch percentage, changed-region count, and the overall dominant color/brightness direction.\n- OCR dependency: screenshot.diff uses the system tesseract binary when available. If missing, it returns ocr_unavailable_tesseract_not_found_or_failed and still returns pixel evidence.\n- OCR install: MCP cannot prompt. Ask the user first; if they agree, call vision.install-ocr with { "confirm": true }. If automatic install is unavailable, follow the returned installUrl.\n- Final judgment: OCR and pixel diff are evidence. A vision-capable model must still inspect screenshots/diff artifacts and compare the rendered result to user intent.\n'
|
|
273242
273741
|
};
|
|
273243
273742
|
const cliDocTitles = {
|
|
273244
273743
|
"api-use-cases": "CLI API Use Cases",
|
|
@@ -273307,7 +273806,7 @@ const cliDocSections = {
|
|
|
273307
273806
|
'Stage a draft page for a future publish with {"pageId":"page-id","values":{"isDraft":false}}. This clears draft state but does not deploy the site. The home page and /* catch-all page cannot be drafts.',
|
|
273308
273807
|
'Resource url accepts plain fixed URLs and paths. For computed URLs, pass JavaScript expression code such as "https://api.example.com/items?tag=" + filters.tag. Resource header values, search parameter values, and text bodies accept expressions for dynamic values; for fixed text, use { "type": "literal", "value": "application/json" }.',
|
|
273309
273808
|
'Resource update example: use update-resource with {"resourceId":"resource-id","values":{"url":"https://api.example.com/items"}}.',
|
|
273310
|
-
|
|
273809
|
+
'Assets is one system resource that always executes a structured query. result:"many" returns the existing ID-keyed collection shape and is the backward-compatible default. result:"one", result:"first", and result:"last" return one direct item or null; first and last require explicit sorting. create-assets-resource without query uses the default URL and optional image-dimensions output.',
|
|
273311
273810
|
"For a Markdown-backed blog, create exactly two Builder page definitions: a fixed /blog overview and one /blog/:slug detail page. Both pages load content through Assets resources. Never create one Builder page per post or duplicate Markdown content into static page structures. Read get-asset-field-catalog, validate each structured query with validate-asset-query, then call create-assets-resource or update-assets-resource. Set values.query:null to restore the default query.",
|
|
273312
273811
|
'Optimize every explicit Assets query for the deployed content-database size. Use output.mode:"fields" and select only fields that are actually rendered or otherwise required by the query. Keep includeMetadata:false unless the rendered value needs file metadata; diagnostics are returned separately. Do not use output.mode:"all" as a convenience default.',
|
|
273313
273812
|
"Every reachable Assets data source contributes to the shared database. Keep one final resource per rendered query. Update an existing scoped resource instead of creating a placeholder, preview copy, or repair replacement, and remove obsolete duplicate resources and data sources.",
|
|
@@ -273315,7 +273814,7 @@ const cliDocSections = {
|
|
|
273315
273814
|
'Query Markdown files directly and use content.mode:"markdown-body-ref" when rendering their bodies. The published database keeps only metadata and document references, filters and paginates first, and fetches the selected Markdown bodies from Asset storage at runtime. Do not create companion JSON descriptors merely to avoid embedding Markdown.',
|
|
273316
273815
|
"full and bounded range request embedded file bytes. Use them only when the caller explicitly requires the complete source or a byte range.",
|
|
273317
273816
|
"Deferred Markdown bodies exclude frontmatter and resolve conventional relative links and images such as ../images/hero.png to matching Assets. Markdown Embed permits sanitized figures, audio, video, and iframes, but removes scripts, inline event handlers, and unsafe URLs.",
|
|
273318
|
-
"Assets expose an ID-keyed map at <dataSourceName>.data and
|
|
273817
|
+
'For result:"many", Assets expose an ID-keyed map at <dataSourceName>.data and totalCount/hasMore at <dataSourceName>.meta; bind a listing Collection to posts.data. Single-result modes expose the selected item or null directly at <dataSourceName>.data, always include its id, and expose totalCount in meta. Bind detail components and page settings directly from expressions such as post.data?.properties?.title, post.data?.content?.text, and post.data ? 200 : 404 without a Collection.',
|
|
273319
273818
|
"Use preview-asset-query with concrete values before binding expressions in the saved resource. Inspect __diagnostics__.query for the temporary query-only footprint and __diagnostics__.database for the merged database built from all reachable Assets queries. Only database.usedBytes counts toward database.maxBytes; query sizes are not separate allowances and must not be summed. Compare usedBytes, unboundedBytes, and truncated within both scopes. A finished Markdown blog must include every source document without truncation, contain no embedded Markdown bodies, and retain only the intended materialized overview query. When merged usage approaches the limit, remove duplicate reachable resources first, then unused output fields, then narrow candidate files. Inspect saved mode and configuration with list-assets-resources or get-assets-resource; shared index maintenance is automatic.",
|
|
273320
273819
|
"Data variable values support string, number, boolean, and json. Use json for all arrays, objects, filters, and nested data.",
|
|
273321
273820
|
"Parameters are internal scoped runtime values from pages, collections, or components. They are not a public authoring surface: do not create, update, or delete parameter records. Public tools should preserve existing parameter records and may reference documented context values such as system in expressions where they are already in scope.",
|
|
@@ -280586,8 +281085,8 @@ const getIssueReportRuntime = () => ({
|
|
|
280586
281085
|
executionMode: "mcp",
|
|
280587
281086
|
apiContractVersion: publicApiContractVersion
|
|
280588
281087
|
});
|
|
280589
|
-
const addIssueReportRuntime = (
|
|
280590
|
-
if (
|
|
281088
|
+
const addIssueReportRuntime = (command, input2, runtime = getIssueReportRuntime()) => {
|
|
281089
|
+
if (command !== "report-issue" || isPlainRecord(input2) === false) {
|
|
280591
281090
|
return input2;
|
|
280592
281091
|
}
|
|
280593
281092
|
return { ...input2, runtime };
|
|
@@ -280609,7 +281108,7 @@ const executePublicServerOperation = async ({
|
|
|
280609
281108
|
}
|
|
280610
281109
|
return await client({
|
|
280611
281110
|
...connection,
|
|
280612
|
-
...addIssueReportRuntime(
|
|
281111
|
+
...addIssueReportRuntime(operation.command, input2),
|
|
280613
281112
|
projectId: connection.projectId
|
|
280614
281113
|
});
|
|
280615
281114
|
};
|
|
@@ -280844,6 +281343,15 @@ const loadCliProjectSessionAssetIndex = async (snapshot, connection, assetsDirec
|
|
|
280844
281343
|
return artifact;
|
|
280845
281344
|
};
|
|
280846
281345
|
const createLocalProjectBundleFromSessionSnapshot = (snapshot, options = {}) => {
|
|
281346
|
+
const missing = getMissingBuilderStateNamespaces(
|
|
281347
|
+
snapshot.state,
|
|
281348
|
+
webstudioDataNamespaces
|
|
281349
|
+
);
|
|
281350
|
+
if (missing.length > 0) {
|
|
281351
|
+
throw new Error(
|
|
281352
|
+
`Project session is missing preview data: ${missing.join(", ")}`
|
|
281353
|
+
);
|
|
281354
|
+
}
|
|
280847
281355
|
const pages2 = snapshot.state.pages;
|
|
280848
281356
|
if (pages2 === void 0) {
|
|
280849
281357
|
throw new Error("Project session pages namespace is missing.");
|
|
@@ -280881,6 +281389,23 @@ const writeCliProjectSessionDataFile = async (snapshot, path2 = join$1(cwd$1(),
|
|
|
280881
281389
|
await writeFileAtomic(path2, `${JSON.stringify(data2, void 0, 2)}
|
|
280882
281390
|
`);
|
|
280883
281391
|
};
|
|
281392
|
+
const writeCliProjectSessionPreviewDataFile = async ({
|
|
281393
|
+
session,
|
|
281394
|
+
connection,
|
|
281395
|
+
path: path2,
|
|
281396
|
+
assetsDirectory = LOCAL_ASSETS_DIR
|
|
281397
|
+
}) => {
|
|
281398
|
+
const snapshot = await session.ensureNamespaces(builderNamespaces);
|
|
281399
|
+
const assetIndex = await loadCliProjectSessionAssetIndex(
|
|
281400
|
+
snapshot,
|
|
281401
|
+
connection,
|
|
281402
|
+
assetsDirectory
|
|
281403
|
+
);
|
|
281404
|
+
await writeCliProjectSessionDataFile(snapshot, path2, {
|
|
281405
|
+
origin: connection.origin,
|
|
281406
|
+
assetIndex
|
|
281407
|
+
});
|
|
281408
|
+
};
|
|
280884
281409
|
const uniqueNamespaces = (namespaces) => [...new Set(namespaces)];
|
|
280885
281410
|
const getSessionError = (envelope) => envelope.diagnostics.find((diagnostic) => diagnostic.level === "error");
|
|
280886
281411
|
const createProjectSessionApiError = (diagnostic) => {
|
|
@@ -287118,7 +287643,7 @@ const clampToByte = (value2) => {
|
|
|
287118
287643
|
}
|
|
287119
287644
|
return Math.round(value2);
|
|
287120
287645
|
};
|
|
287121
|
-
const execFileAsync$
|
|
287646
|
+
const execFileAsync$1 = promisify(execFile);
|
|
287122
287647
|
const OCR_TIMEOUT_MS = 1e4;
|
|
287123
287648
|
const OCR_MAX_BUFFER_BYTES = 32 * 1024 * 1024;
|
|
287124
287649
|
const TESSERACT_BINARY = "tesseract";
|
|
@@ -287211,7 +287736,7 @@ const parseTesseractTsv = (tsv) => {
|
|
|
287211
287736
|
return Array.from(wordsByLine.values()).map(toTextBlock);
|
|
287212
287737
|
};
|
|
287213
287738
|
const recognizeTsv = async (imagePath) => {
|
|
287214
|
-
const { stdout: stdout2 } = await execFileAsync$
|
|
287739
|
+
const { stdout: stdout2 } = await execFileAsync$1(
|
|
287215
287740
|
TESSERACT_BINARY,
|
|
287216
287741
|
[
|
|
287217
287742
|
imagePath,
|
|
@@ -287670,29 +288195,7 @@ const valueTokensCompatible = (left, right) => {
|
|
|
287670
288195
|
return leftTokens.length === 0 && rightTokens.length === 0 || leftTokens.length === rightTokens.length && leftTokens.every((token2, index2) => token2 === rightTokens[index2]);
|
|
287671
288196
|
};
|
|
287672
288197
|
const valueTokens = (text2) => text2.match(new RegExp("(?<!\\p{L})(?:[$€£+\\-/])?\\p{N}+(?:[.,]\\p{N}+)?%?(?!\\p{L})", "gu")) ?? [];
|
|
287673
|
-
const normalizedTextSimilarity = (left, right) => left === right ? 1 : 1 -
|
|
287674
|
-
const levenshteinDistance = (left, right) => {
|
|
287675
|
-
const previous2 = new Array(right.length + 1);
|
|
287676
|
-
const current4 = new Array(right.length + 1);
|
|
287677
|
-
for (let index2 = 0; index2 <= right.length; index2++) {
|
|
287678
|
-
previous2[index2] = index2;
|
|
287679
|
-
}
|
|
287680
|
-
for (let leftIndex = 1; leftIndex <= left.length; leftIndex++) {
|
|
287681
|
-
current4[0] = leftIndex;
|
|
287682
|
-
for (let rightIndex = 1; rightIndex <= right.length; rightIndex++) {
|
|
287683
|
-
const cost = left[leftIndex - 1] === right[rightIndex - 1] ? 0 : 1;
|
|
287684
|
-
current4[rightIndex] = Math.min(
|
|
287685
|
-
current4[rightIndex - 1] + 1,
|
|
287686
|
-
previous2[rightIndex] + 1,
|
|
287687
|
-
previous2[rightIndex - 1] + cost
|
|
287688
|
-
);
|
|
287689
|
-
}
|
|
287690
|
-
for (let index2 = 0; index2 <= right.length; index2++) {
|
|
287691
|
-
previous2[index2] = current4[index2];
|
|
287692
|
-
}
|
|
287693
|
-
}
|
|
287694
|
-
return previous2[right.length];
|
|
287695
|
-
};
|
|
288198
|
+
const normalizedTextSimilarity = (left, right) => left === right ? 1 : 1 - distance(left, right) / Math.max(left.length, right.length, 1);
|
|
287696
288199
|
const boundsDelta = (baseline, current4) => ({
|
|
287697
288200
|
x: current4.x - baseline.x,
|
|
287698
288201
|
y: current4.y - baseline.y,
|
|
@@ -288588,6 +289091,26 @@ const formatPercentage = (value2) => `${value2.toFixed(2)}%`;
|
|
|
288588
289091
|
const formatNormalized = (value2) => Math.min(1, Math.max(0, value2)).toFixed(3);
|
|
288589
289092
|
const defaultPreviewServerDependencies = {
|
|
288590
289093
|
spawn,
|
|
289094
|
+
killProcess: process.kill,
|
|
289095
|
+
killWindowsProcessTree: (pid) => new Promise((resolve2, reject) => {
|
|
289096
|
+
execFile(
|
|
289097
|
+
"taskkill.exe",
|
|
289098
|
+
["/pid", String(pid), "/T", "/F"],
|
|
289099
|
+
{ windowsHide: true },
|
|
289100
|
+
(error) => {
|
|
289101
|
+
if (error === null) {
|
|
289102
|
+
resolve2(true);
|
|
289103
|
+
return;
|
|
289104
|
+
}
|
|
289105
|
+
if (error.code === 128) {
|
|
289106
|
+
resolve2(false);
|
|
289107
|
+
return;
|
|
289108
|
+
}
|
|
289109
|
+
reject(error);
|
|
289110
|
+
}
|
|
289111
|
+
);
|
|
289112
|
+
}),
|
|
289113
|
+
parentProcess: process,
|
|
288591
289114
|
fetch,
|
|
288592
289115
|
cp: cp$1,
|
|
288593
289116
|
mkdir,
|
|
@@ -288599,24 +289122,12 @@ const defaultPreviewServerDependencies = {
|
|
|
288599
289122
|
npmExecPath: process.env.npm_execpath,
|
|
288600
289123
|
platform: process.platform
|
|
288601
289124
|
};
|
|
288602
|
-
const findAvailablePort = (host = "127.0.0.1") =>
|
|
288603
|
-
|
|
288604
|
-
|
|
288605
|
-
|
|
288606
|
-
|
|
288607
|
-
const address2 = server.address();
|
|
288608
|
-
if (address2 === null || typeof address2 === "string") {
|
|
288609
|
-
server.close();
|
|
288610
|
-
reject(new Error("Could not allocate a local preview port."));
|
|
288611
|
-
return;
|
|
288612
|
-
}
|
|
288613
|
-
server.close(
|
|
288614
|
-
(error) => error === void 0 ? resolve2(address2.port) : reject(error)
|
|
288615
|
-
);
|
|
288616
|
-
});
|
|
288617
|
-
});
|
|
289125
|
+
const findAvailablePort = (host = "127.0.0.1") => getPort({ host });
|
|
289126
|
+
const isPreviewPortAvailable = async (host, port) => {
|
|
289127
|
+
const availablePort = await detectPort({ hostname: host, port });
|
|
289128
|
+
return availablePort === port;
|
|
289129
|
+
};
|
|
288618
289130
|
const processEnv = () => process.env;
|
|
288619
|
-
const pathKey = () => process.platform === "win32" ? "Path" : "PATH";
|
|
288620
289131
|
const getAncestorBinPaths = (directory) => {
|
|
288621
289132
|
const paths = [];
|
|
288622
289133
|
let currentDirectory = directory;
|
|
@@ -288636,7 +289147,7 @@ const getPreviewEnv = (cwd2, extraEnv) => {
|
|
|
288636
289147
|
if (cwd2 === void 0) {
|
|
288637
289148
|
return extraEnv;
|
|
288638
289149
|
}
|
|
288639
|
-
const key2 = pathKey();
|
|
289150
|
+
const key2 = pathKey({ env: extraEnv });
|
|
288640
289151
|
return {
|
|
288641
289152
|
...extraEnv,
|
|
288642
289153
|
[key2]: [...getAncestorBinPaths(cwd2), extraEnv[key2]].filter(Boolean).join(delimiter)
|
|
@@ -288658,17 +289169,34 @@ const getPreviewStartArgs = (options) => options.mode === "iterative" ? [
|
|
|
288658
289169
|
String(options.port),
|
|
288659
289170
|
"--strictPort"
|
|
288660
289171
|
] : ["run", "start"];
|
|
288661
|
-
const getPreviewCommand = (platform2 = process.platform) => platform2 === "win32" ? "npm.cmd" : "npm";
|
|
288662
289172
|
const getNpmInvocation = (args, {
|
|
288663
289173
|
nodeExecPath = process.execPath,
|
|
288664
289174
|
npmExecPath = process.env.npm_execpath,
|
|
288665
289175
|
platform: platform2 = process.platform
|
|
288666
289176
|
} = {}) => {
|
|
288667
289177
|
const npmCliName = npmExecPath === void 0 ? void 0 : platform2 === "win32" ? win32.basename(npmExecPath) : basename$2(npmExecPath);
|
|
288668
|
-
if (npmExecPath !== void 0 && npmCliName
|
|
288669
|
-
|
|
289178
|
+
if (npmExecPath !== void 0 && npmCliName !== void 0) {
|
|
289179
|
+
const npmCliPath = npmCliName === "npm-cli.js" ? npmExecPath : npmCliName === "npx-cli.js" ? platform2 === "win32" ? win32.join(win32.dirname(npmExecPath), "npm-cli.js") : join$1(dirname$1(npmExecPath), "npm-cli.js") : void 0;
|
|
289180
|
+
if (npmCliPath !== void 0) {
|
|
289181
|
+
return { command: nodeExecPath, args: [npmCliPath, ...args] };
|
|
289182
|
+
}
|
|
288670
289183
|
}
|
|
288671
|
-
|
|
289184
|
+
if (platform2 === "win32") {
|
|
289185
|
+
return {
|
|
289186
|
+
command: nodeExecPath,
|
|
289187
|
+
args: [
|
|
289188
|
+
win32.join(
|
|
289189
|
+
win32.dirname(nodeExecPath),
|
|
289190
|
+
"node_modules",
|
|
289191
|
+
"npm",
|
|
289192
|
+
"bin",
|
|
289193
|
+
"npm-cli.js"
|
|
289194
|
+
),
|
|
289195
|
+
...args
|
|
289196
|
+
]
|
|
289197
|
+
};
|
|
289198
|
+
}
|
|
289199
|
+
return { command: "npm", args };
|
|
288672
289200
|
};
|
|
288673
289201
|
const runPreviewBuild = async (dependencies2 = defaultPreviewServerDependencies, cwd2, stdio = "inherit") => {
|
|
288674
289202
|
const invocation = getNpmInvocation(getPreviewBuildArgs(), dependencies2);
|
|
@@ -288731,6 +289259,7 @@ const startPreviewServer = (options, dependencies2 = defaultPreviewServerDepende
|
|
|
288731
289259
|
invocation.args,
|
|
288732
289260
|
{
|
|
288733
289261
|
cwd: options.cwd,
|
|
289262
|
+
...options.detached === void 0 ? {} : { detached: options.detached },
|
|
288734
289263
|
stdio: options.stdio ?? "inherit",
|
|
288735
289264
|
env: getPreviewEnv(options.cwd, {
|
|
288736
289265
|
...processEnv(),
|
|
@@ -288747,6 +289276,22 @@ const startPreviewServer = (options, dependencies2 = defaultPreviewServerDepende
|
|
|
288747
289276
|
process: previewProcess
|
|
288748
289277
|
};
|
|
288749
289278
|
};
|
|
289279
|
+
const killPreviewProcess = async (previewProcess, signal, dependencies2) => {
|
|
289280
|
+
if (dependencies2.platform === "win32" && previewProcess.pid !== void 0) {
|
|
289281
|
+
return await dependencies2.killWindowsProcessTree(previewProcess.pid);
|
|
289282
|
+
}
|
|
289283
|
+
if (dependencies2.platform !== "win32" && previewProcess.pid !== void 0) {
|
|
289284
|
+
try {
|
|
289285
|
+
return dependencies2.killProcess(-previewProcess.pid, signal);
|
|
289286
|
+
} catch (error) {
|
|
289287
|
+
if (error instanceof Error && "code" in error && error.code === "ESRCH") {
|
|
289288
|
+
return false;
|
|
289289
|
+
}
|
|
289290
|
+
throw error;
|
|
289291
|
+
}
|
|
289292
|
+
}
|
|
289293
|
+
return previewProcess.kill(signal);
|
|
289294
|
+
};
|
|
288750
289295
|
const waitForPreviewExit = async (process2) => {
|
|
288751
289296
|
const code2 = await new Promise((resolve2, reject) => {
|
|
288752
289297
|
process2.once("error", reject);
|
|
@@ -288902,21 +289447,66 @@ Port is already in use. Stop the existing preview server for ${url2}, or start p
|
|
|
288902
289447
|
Preview server output:
|
|
288903
289448
|
${output}${portHint}`;
|
|
288904
289449
|
};
|
|
288905
|
-
const createPreviewController = (defaults2, dependencies2 = defaultPreviewServerDependencies) => {
|
|
289450
|
+
const createPreviewController = (defaults2, dependencies2 = defaultPreviewServerDependencies, { manageProcessSignals = true } = {}) => {
|
|
288906
289451
|
let server;
|
|
288907
289452
|
let currentOptions = defaults2;
|
|
288908
289453
|
let currentCwd = defaults2.cwd;
|
|
288909
289454
|
let serverOutput = "";
|
|
289455
|
+
const terminationSignals = ["SIGHUP", "SIGINT", "SIGTERM"];
|
|
289456
|
+
let isTerminating = false;
|
|
289457
|
+
const terminationHandlers = /* @__PURE__ */ new Map();
|
|
289458
|
+
const removeTerminationHandlers = () => {
|
|
289459
|
+
for (const [signal, handler] of terminationHandlers) {
|
|
289460
|
+
dependencies2.parentProcess.off(signal, handler);
|
|
289461
|
+
}
|
|
289462
|
+
terminationHandlers.clear();
|
|
289463
|
+
};
|
|
289464
|
+
const installTerminationHandlers = () => {
|
|
289465
|
+
if (manageProcessSignals === false || terminationHandlers.size > 0) {
|
|
289466
|
+
return;
|
|
289467
|
+
}
|
|
289468
|
+
for (const signal of terminationSignals) {
|
|
289469
|
+
const handler = () => {
|
|
289470
|
+
if (isTerminating) {
|
|
289471
|
+
return;
|
|
289472
|
+
}
|
|
289473
|
+
isTerminating = true;
|
|
289474
|
+
const activeServer = server;
|
|
289475
|
+
server = void 0;
|
|
289476
|
+
removeTerminationHandlers();
|
|
289477
|
+
if (activeServer === void 0) {
|
|
289478
|
+
dependencies2.parentProcess.kill(
|
|
289479
|
+
dependencies2.parentProcess.pid,
|
|
289480
|
+
signal
|
|
289481
|
+
);
|
|
289482
|
+
return;
|
|
289483
|
+
}
|
|
289484
|
+
void killPreviewProcess(activeServer.process, "SIGTERM", dependencies2).catch(() => void 0).finally(() => {
|
|
289485
|
+
dependencies2.parentProcess.kill(
|
|
289486
|
+
dependencies2.parentProcess.pid,
|
|
289487
|
+
signal
|
|
289488
|
+
);
|
|
289489
|
+
});
|
|
289490
|
+
};
|
|
289491
|
+
terminationHandlers.set(signal, handler);
|
|
289492
|
+
dependencies2.parentProcess.once(signal, handler);
|
|
289493
|
+
}
|
|
289494
|
+
};
|
|
288910
289495
|
const appendServerOutput = (chunk) => {
|
|
288911
289496
|
serverOutput = `${serverOutput}${String(chunk)}`.slice(-4e3);
|
|
288912
289497
|
};
|
|
288913
289498
|
const isRunning = () => server !== void 0 && server.process.killed === false && server.process.exitCode === null && server.process.signalCode === null;
|
|
288914
|
-
const getStatus = () =>
|
|
288915
|
-
|
|
288916
|
-
|
|
288917
|
-
|
|
288918
|
-
|
|
288919
|
-
|
|
289499
|
+
const getStatus = () => {
|
|
289500
|
+
if (isRunning() === false) {
|
|
289501
|
+
return { running: false };
|
|
289502
|
+
}
|
|
289503
|
+
return {
|
|
289504
|
+
url: getPreviewUrl(currentOptions),
|
|
289505
|
+
pid: server?.process.pid,
|
|
289506
|
+
running: true,
|
|
289507
|
+
mode: currentOptions.mode ?? "production"
|
|
289508
|
+
};
|
|
289509
|
+
};
|
|
288920
289510
|
const resolveOptions = (options) => {
|
|
288921
289511
|
const running = isRunning();
|
|
288922
289512
|
return {
|
|
@@ -288943,16 +289533,18 @@ const createPreviewController = (defaults2, dependencies2 = defaultPreviewServer
|
|
|
288943
289533
|
}
|
|
288944
289534
|
const process2 = server.process;
|
|
288945
289535
|
server = void 0;
|
|
289536
|
+
removeTerminationHandlers();
|
|
288946
289537
|
if (process2.killed || process2.exitCode !== null || process2.signalCode !== null) {
|
|
288947
289538
|
return;
|
|
288948
289539
|
}
|
|
288949
|
-
|
|
289540
|
+
const exited = new Promise((resolve2, reject) => {
|
|
288950
289541
|
process2.once("error", reject);
|
|
288951
289542
|
process2.once("exit", () => resolve2());
|
|
288952
|
-
if (process2.kill() === false) {
|
|
288953
|
-
resolve2();
|
|
288954
|
-
}
|
|
288955
289543
|
});
|
|
289544
|
+
if (await killPreviewProcess(process2, "SIGTERM", dependencies2) === false) {
|
|
289545
|
+
return;
|
|
289546
|
+
}
|
|
289547
|
+
await exited;
|
|
288956
289548
|
};
|
|
288957
289549
|
const start = async (options = {}) => {
|
|
288958
289550
|
const running = isRunning();
|
|
@@ -288988,16 +289580,19 @@ const createPreviewController = (defaults2, dependencies2 = defaultPreviewServer
|
|
|
288988
289580
|
const startedServer = startPreviewServer(
|
|
288989
289581
|
{
|
|
288990
289582
|
...nextOptions,
|
|
289583
|
+
detached: dependencies2.platform !== "win32",
|
|
288991
289584
|
stdio: ["ignore", "pipe", "pipe"]
|
|
288992
289585
|
},
|
|
288993
289586
|
dependencies2
|
|
288994
289587
|
);
|
|
288995
289588
|
server = startedServer;
|
|
289589
|
+
installTerminationHandlers();
|
|
288996
289590
|
startedServer.process.stdout?.on("data", appendServerOutput);
|
|
288997
289591
|
startedServer.process.stderr?.on("data", appendServerOutput);
|
|
288998
289592
|
startedServer.process.once("exit", () => {
|
|
288999
289593
|
if (server === startedServer) {
|
|
289000
289594
|
server = void 0;
|
|
289595
|
+
removeTerminationHandlers();
|
|
289001
289596
|
}
|
|
289002
289597
|
});
|
|
289003
289598
|
return getStatus();
|
|
@@ -289012,10 +289607,11 @@ const createPreviewController = (defaults2, dependencies2 = defaultPreviewServer
|
|
|
289012
289607
|
dependencies2
|
|
289013
289608
|
);
|
|
289014
289609
|
const result2 = await start(options);
|
|
289610
|
+
const url2 = result2.url ?? getPreviewUrl(currentOptions);
|
|
289015
289611
|
const requiredAssetNames = result2.mode === "production" ? await getPreviewCssAssetNames(currentCwd, dependencies2) : [];
|
|
289016
289612
|
try {
|
|
289017
289613
|
await waitForPreviewReady(
|
|
289018
|
-
|
|
289614
|
+
url2,
|
|
289019
289615
|
{ isRunning, requiredAssetNames, requiredProject },
|
|
289020
289616
|
dependencies2
|
|
289021
289617
|
);
|
|
@@ -289026,7 +289622,7 @@ const createPreviewController = (defaults2, dependencies2 = defaultPreviewServer
|
|
|
289026
289622
|
formatPreviewServerStartupError({
|
|
289027
289623
|
message: error.message,
|
|
289028
289624
|
output,
|
|
289029
|
-
url:
|
|
289625
|
+
url: url2
|
|
289030
289626
|
})
|
|
289031
289627
|
);
|
|
289032
289628
|
}
|
|
@@ -289044,6 +289640,35 @@ const createPreviewController = (defaults2, dependencies2 = defaultPreviewServer
|
|
|
289044
289640
|
}
|
|
289045
289641
|
};
|
|
289046
289642
|
};
|
|
289643
|
+
const createExclusiveAsyncRunner = () => {
|
|
289644
|
+
let queue = Promise.resolve();
|
|
289645
|
+
return async (callback) => {
|
|
289646
|
+
const previousRun = queue;
|
|
289647
|
+
let releaseCurrentRun = () => void 0;
|
|
289648
|
+
queue = new Promise((resolve2) => {
|
|
289649
|
+
releaseCurrentRun = resolve2;
|
|
289650
|
+
});
|
|
289651
|
+
await previousRun.catch(() => void 0);
|
|
289652
|
+
try {
|
|
289653
|
+
return await callback();
|
|
289654
|
+
} finally {
|
|
289655
|
+
releaseCurrentRun();
|
|
289656
|
+
}
|
|
289657
|
+
};
|
|
289658
|
+
};
|
|
289659
|
+
const withTimeout = async (operation, timeout, createTimeoutError2) => {
|
|
289660
|
+
let timeoutId;
|
|
289661
|
+
const timeoutPromise = new Promise((_2, reject) => {
|
|
289662
|
+
timeoutId = setTimeout(() => reject(createTimeoutError2()), timeout);
|
|
289663
|
+
});
|
|
289664
|
+
try {
|
|
289665
|
+
return await Promise.race([operation, timeoutPromise]);
|
|
289666
|
+
} finally {
|
|
289667
|
+
if (timeoutId !== void 0) {
|
|
289668
|
+
clearTimeout(timeoutId);
|
|
289669
|
+
}
|
|
289670
|
+
}
|
|
289671
|
+
};
|
|
289047
289672
|
const getNavigationUrl = (options) => {
|
|
289048
289673
|
if (options.httpCredentials === void 0) {
|
|
289049
289674
|
return options.url;
|
|
@@ -289086,22 +289711,14 @@ const lifecycleEventByWaitUntil = {
|
|
|
289086
289711
|
networkidle: "networkIdle"
|
|
289087
289712
|
};
|
|
289088
289713
|
const delay = (ms) => new Promise((resolveDelay) => setTimeout(resolveDelay, ms));
|
|
289089
|
-
const createTimeoutError = (message, timeout) => new Error(`${message} within ${timeout}ms.`)
|
|
289090
|
-
|
|
289091
|
-
|
|
289092
|
-
|
|
289093
|
-
|
|
289094
|
-
|
|
289095
|
-
|
|
289096
|
-
|
|
289097
|
-
try {
|
|
289098
|
-
return await Promise.race([promise, timeoutPromise]);
|
|
289099
|
-
} finally {
|
|
289100
|
-
if (timeoutId !== void 0) {
|
|
289101
|
-
clearTimeout(timeoutId);
|
|
289102
|
-
}
|
|
289103
|
-
}
|
|
289104
|
-
};
|
|
289714
|
+
const createTimeoutError = (message, timeout) => Object.assign(new Error(`${message} within ${timeout}ms.`), {
|
|
289715
|
+
code: "SCREENSHOT_TIMEOUT"
|
|
289716
|
+
});
|
|
289717
|
+
const withDeadline = async (promise, message, timeout) => await withTimeout(
|
|
289718
|
+
promise,
|
|
289719
|
+
timeout,
|
|
289720
|
+
() => createTimeoutError(message, timeout)
|
|
289721
|
+
);
|
|
289105
289722
|
const measureDuration = async (operation) => {
|
|
289106
289723
|
const startedAt = Date.now();
|
|
289107
289724
|
const value2 = await operation();
|
|
@@ -289795,7 +290412,8 @@ const getScreenshotCaptureParams = async ({
|
|
|
289795
290412
|
};
|
|
289796
290413
|
class BrowserSessionClosedError extends Error {
|
|
289797
290414
|
}
|
|
289798
|
-
const
|
|
290415
|
+
const getBrowserExitMessage = (message, reason) => `${message}${reason === void 0 ? "" : ` (${reason})`}. Check the browser installation or set WEBSTUDIO_BROWSER_PATH to a supported Chromium executable.`;
|
|
290416
|
+
const startBrowserRuntimeOnce = async (options, dependencies2) => {
|
|
289799
290417
|
const userDataDir = await dependencies2.mkdtemp(
|
|
289800
290418
|
join$1(tmpdir(), "webstudio-browser-")
|
|
289801
290419
|
);
|
|
@@ -289810,19 +290428,30 @@ const startBrowserRuntime = async (options, dependencies2) => {
|
|
|
289810
290428
|
);
|
|
289811
290429
|
let running = true;
|
|
289812
290430
|
const browserClosed = new Promise((resolveClosed) => {
|
|
289813
|
-
const close2 = () => {
|
|
290431
|
+
const close2 = (reason) => {
|
|
289814
290432
|
running = false;
|
|
289815
|
-
resolveClosed();
|
|
290433
|
+
resolveClosed(reason);
|
|
289816
290434
|
};
|
|
289817
|
-
browserProcess.once(
|
|
289818
|
-
|
|
290435
|
+
browserProcess.once(
|
|
290436
|
+
"exit",
|
|
290437
|
+
(code2, signal) => close2(
|
|
290438
|
+
typeof code2 === "number" ? `exit code ${code2}` : typeof signal === "string" ? `signal ${signal}` : void 0
|
|
290439
|
+
)
|
|
290440
|
+
);
|
|
290441
|
+
browserProcess.once("error", (error) => {
|
|
290442
|
+
const code2 = "code" in error ? error.code : void 0;
|
|
290443
|
+
close2(code2 === void 0 ? void 0 : `spawn error ${code2}`);
|
|
290444
|
+
});
|
|
289819
290445
|
});
|
|
289820
290446
|
try {
|
|
289821
290447
|
const { port } = await Promise.race([
|
|
289822
290448
|
waitForDevToolsPort(userDataDir, dependencies2, options.timeout),
|
|
289823
|
-
browserClosed.then(() => {
|
|
290449
|
+
browserClosed.then((reason) => {
|
|
289824
290450
|
throw new BrowserSessionClosedError(
|
|
289825
|
-
|
|
290451
|
+
getBrowserExitMessage(
|
|
290452
|
+
"Browser exited before its DevTools endpoint became ready",
|
|
290453
|
+
reason
|
|
290454
|
+
)
|
|
289826
290455
|
);
|
|
289827
290456
|
})
|
|
289828
290457
|
]);
|
|
@@ -289861,6 +290490,16 @@ const startBrowserRuntime = async (options, dependencies2) => {
|
|
|
289861
290490
|
throw error;
|
|
289862
290491
|
}
|
|
289863
290492
|
};
|
|
290493
|
+
const startBrowserRuntime = async (options, dependencies2) => {
|
|
290494
|
+
try {
|
|
290495
|
+
return await startBrowserRuntimeOnce(options, dependencies2);
|
|
290496
|
+
} catch (error) {
|
|
290497
|
+
if (error instanceof BrowserSessionClosedError === false) {
|
|
290498
|
+
throw error;
|
|
290499
|
+
}
|
|
290500
|
+
}
|
|
290501
|
+
return await startBrowserRuntimeOnce(options, dependencies2);
|
|
290502
|
+
};
|
|
289864
290503
|
const capturePageWithBrowserRuntime = async (runtime, optionsList, dependencies2) => {
|
|
289865
290504
|
const firstOptions = optionsList[0];
|
|
289866
290505
|
if (firstOptions === void 0) {
|
|
@@ -289871,9 +290510,12 @@ const capturePageWithBrowserRuntime = async (runtime, optionsList, dependencies2
|
|
|
289871
290510
|
const layouts = [];
|
|
289872
290511
|
try {
|
|
289873
290512
|
await Promise.race([
|
|
289874
|
-
runtime.browserClosed.then(() => {
|
|
290513
|
+
runtime.browserClosed.then((reason) => {
|
|
289875
290514
|
throw new BrowserSessionClosedError(
|
|
289876
|
-
|
|
290515
|
+
getBrowserExitMessage(
|
|
290516
|
+
"Browser exited before screenshot capture completed",
|
|
290517
|
+
reason
|
|
290518
|
+
)
|
|
289877
290519
|
);
|
|
289878
290520
|
}),
|
|
289879
290521
|
(async () => {
|
|
@@ -290138,12 +290780,18 @@ const captureWithBrowserRuntime = async (runtime, options, dependencies2) => {
|
|
|
290138
290780
|
const createBrowserScreenshotSession = async (options, dependencies2 = defaultBrowserScreenshotDependencies) => {
|
|
290139
290781
|
let runtime = await startBrowserRuntime(options, dependencies2);
|
|
290140
290782
|
let restartPromise;
|
|
290783
|
+
let closed = false;
|
|
290141
290784
|
const restart = async (failedRuntime) => {
|
|
290142
290785
|
if (runtime !== failedRuntime) {
|
|
290143
290786
|
return runtime;
|
|
290144
290787
|
}
|
|
290145
290788
|
restartPromise ??= (async () => {
|
|
290146
290789
|
await failedRuntime.close();
|
|
290790
|
+
if (closed) {
|
|
290791
|
+
throw new BrowserSessionClosedError(
|
|
290792
|
+
"Browser screenshot session was closed."
|
|
290793
|
+
);
|
|
290794
|
+
}
|
|
290147
290795
|
const next = await startBrowserRuntime(options, dependencies2);
|
|
290148
290796
|
runtime = next;
|
|
290149
290797
|
restartPromise = void 0;
|
|
@@ -290152,12 +290800,17 @@ const createBrowserScreenshotSession = async (options, dependencies2 = defaultBr
|
|
|
290152
290800
|
return await restartPromise;
|
|
290153
290801
|
};
|
|
290154
290802
|
const captureWithRestart = async (capture) => {
|
|
290803
|
+
if (closed) {
|
|
290804
|
+
throw new BrowserSessionClosedError(
|
|
290805
|
+
"Browser screenshot session was closed."
|
|
290806
|
+
);
|
|
290807
|
+
}
|
|
290155
290808
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
290156
290809
|
const activeRuntime = runtime;
|
|
290157
290810
|
try {
|
|
290158
290811
|
return await capture(activeRuntime);
|
|
290159
290812
|
} catch (error) {
|
|
290160
|
-
if (attempt === 1 || error instanceof BrowserSessionClosedError === false && activeRuntime.running) {
|
|
290813
|
+
if (closed || attempt === 1 || error instanceof BrowserSessionClosedError === false && activeRuntime.running) {
|
|
290161
290814
|
throw error;
|
|
290162
290815
|
}
|
|
290163
290816
|
await restart(activeRuntime);
|
|
@@ -290185,6 +290838,7 @@ const createBrowserScreenshotSession = async (options, dependencies2 = defaultBr
|
|
|
290185
290838
|
);
|
|
290186
290839
|
},
|
|
290187
290840
|
async close() {
|
|
290841
|
+
closed = true;
|
|
290188
290842
|
await runtime.close();
|
|
290189
290843
|
}
|
|
290190
290844
|
};
|
|
@@ -290197,7 +290851,6 @@ const captureBrowserScreenshot = async (options, dependencies2 = defaultBrowserS
|
|
|
290197
290851
|
await session.close();
|
|
290198
290852
|
}
|
|
290199
290853
|
};
|
|
290200
|
-
const execFileAsync$1 = promisify(execFile);
|
|
290201
290854
|
class BrowserNotFoundError extends Error {
|
|
290202
290855
|
checked;
|
|
290203
290856
|
constructor(checked) {
|
|
@@ -290217,15 +290870,7 @@ const defaultScreenshotDependencies = {
|
|
|
290217
290870
|
platform: process.platform,
|
|
290218
290871
|
access,
|
|
290219
290872
|
mkdir,
|
|
290220
|
-
async which(command)
|
|
290221
|
-
const lookup = process.platform === "win32" ? "where" : "which";
|
|
290222
|
-
try {
|
|
290223
|
-
const { stdout: stdout2 } = await execFileAsync$1(lookup, [command]);
|
|
290224
|
-
return stdout2.split(/\r?\n/).find((path2) => path2.length > 0);
|
|
290225
|
-
} catch {
|
|
290226
|
-
return void 0;
|
|
290227
|
-
}
|
|
290228
|
-
},
|
|
290873
|
+
which: async (command) => await which(command, { nothrow: true }) ?? void 0,
|
|
290229
290874
|
getChromeLauncherInstallations() {
|
|
290230
290875
|
try {
|
|
290231
290876
|
return Launcher.getInstallations();
|
|
@@ -290233,6 +290878,7 @@ const defaultScreenshotDependencies = {
|
|
|
290233
290878
|
return [];
|
|
290234
290879
|
}
|
|
290235
290880
|
},
|
|
290881
|
+
getPlaywrightInstallations: () => getPlaywrightInstallations(),
|
|
290236
290882
|
...defaultBrowserScreenshotDependencies,
|
|
290237
290883
|
async installCommand(file2, args) {
|
|
290238
290884
|
await new Promise((resolve2, reject) => {
|
|
@@ -290247,6 +290893,15 @@ const defaultScreenshotDependencies = {
|
|
|
290247
290893
|
});
|
|
290248
290894
|
});
|
|
290249
290895
|
},
|
|
290896
|
+
async readArtifactByte(path2) {
|
|
290897
|
+
const file2 = await open(path2, "r");
|
|
290898
|
+
try {
|
|
290899
|
+
const { bytesRead } = await file2.read(new Uint8Array(1), 0, 1, 0);
|
|
290900
|
+
return bytesRead;
|
|
290901
|
+
} finally {
|
|
290902
|
+
await file2.close();
|
|
290903
|
+
}
|
|
290904
|
+
},
|
|
290250
290905
|
getuid: () => process.getuid?.(),
|
|
290251
290906
|
now: () => Date.now()
|
|
290252
290907
|
};
|
|
@@ -290263,6 +290918,47 @@ const pathCommandCandidates = [
|
|
|
290263
290918
|
{ command: "brave-browser", browser: "brave" },
|
|
290264
290919
|
{ command: "brave", browser: "brave" }
|
|
290265
290920
|
];
|
|
290921
|
+
const getPlaywrightInstallations = async ({
|
|
290922
|
+
env = process.env,
|
|
290923
|
+
platform: platform2 = process.platform,
|
|
290924
|
+
homeDirectory = homedir()
|
|
290925
|
+
} = {}) => {
|
|
290926
|
+
const configuredCache = env.PLAYWRIGHT_BROWSERS_PATH;
|
|
290927
|
+
const defaultCacheDirectory = platform2 === "darwin" ? join$1(homeDirectory, "Library", "Caches", "ms-playwright") : platform2 === "win32" ? join$1(
|
|
290928
|
+
env.LOCALAPPDATA ?? join$1(homeDirectory, "AppData", "Local"),
|
|
290929
|
+
"ms-playwright"
|
|
290930
|
+
) : join$1(
|
|
290931
|
+
env.XDG_CACHE_HOME ?? join$1(homeDirectory, ".cache"),
|
|
290932
|
+
"ms-playwright"
|
|
290933
|
+
);
|
|
290934
|
+
const cacheDirectory = configuredCache === void 0 || configuredCache === "" ? defaultCacheDirectory : configuredCache === "0" ? void 0 : resolve(configuredCache);
|
|
290935
|
+
if (cacheDirectory === void 0) {
|
|
290936
|
+
return [];
|
|
290937
|
+
}
|
|
290938
|
+
const entries = await readdir(cacheDirectory, { withFileTypes: true }).catch(
|
|
290939
|
+
() => []
|
|
290940
|
+
);
|
|
290941
|
+
const executablePaths = platform2 === "win32" ? [["chrome-win", "chrome.exe"]] : platform2 === "darwin" ? [
|
|
290942
|
+
["chrome-mac", "Chromium.app", "Contents", "MacOS", "Chromium"],
|
|
290943
|
+
[
|
|
290944
|
+
"chrome-mac-arm64",
|
|
290945
|
+
"Chromium.app",
|
|
290946
|
+
"Contents",
|
|
290947
|
+
"MacOS",
|
|
290948
|
+
"Chromium"
|
|
290949
|
+
]
|
|
290950
|
+
] : [
|
|
290951
|
+
["chrome-linux64", "chrome"],
|
|
290952
|
+
["chrome-linux", "chrome"]
|
|
290953
|
+
];
|
|
290954
|
+
return entries.filter(
|
|
290955
|
+
(entry2) => entry2.isDirectory() && entry2.name.startsWith("chromium-")
|
|
290956
|
+
).flatMap(
|
|
290957
|
+
(entry2) => executablePaths.map(
|
|
290958
|
+
(segments) => join$1(cacheDirectory, entry2.name, ...segments)
|
|
290959
|
+
)
|
|
290960
|
+
);
|
|
290961
|
+
};
|
|
290266
290962
|
const platformPathCandidates = {
|
|
290267
290963
|
aix: [],
|
|
290268
290964
|
android: [],
|
|
@@ -290408,6 +291104,15 @@ const resolveScreenshotBrowser = async (options, dependencies2 = defaultScreensh
|
|
|
290408
291104
|
(candidate) => matchesBrowser(candidate, options.browser)
|
|
290409
291105
|
)
|
|
290410
291106
|
);
|
|
291107
|
+
if (options.browser === "auto" || options.browser === "chromium") {
|
|
291108
|
+
candidates.push(
|
|
291109
|
+
...(await dependencies2.getPlaywrightInstallations()).map((path2) => ({
|
|
291110
|
+
path: path2,
|
|
291111
|
+
source: "playwright",
|
|
291112
|
+
browser: "chromium"
|
|
291113
|
+
}))
|
|
291114
|
+
);
|
|
291115
|
+
}
|
|
290411
291116
|
candidates.push(
|
|
290412
291117
|
...dependencies2.getChromeLauncherInstallations().map((path2) => ({
|
|
290413
291118
|
path: path2,
|
|
@@ -290568,6 +291273,22 @@ const getBrowserScreenshotOptions = (options, browserPath, output, dependencies2
|
|
|
290568
291273
|
quality: options.quality,
|
|
290569
291274
|
scale: options.scale
|
|
290570
291275
|
});
|
|
291276
|
+
const validateScreenshotArtifact = async (output, dependencies2) => {
|
|
291277
|
+
let bytesRead;
|
|
291278
|
+
try {
|
|
291279
|
+
bytesRead = await dependencies2.readArtifactByte(output);
|
|
291280
|
+
} catch (cause) {
|
|
291281
|
+
throw Object.assign(
|
|
291282
|
+
new Error(`Screenshot artifact is unreadable: ${output}`),
|
|
291283
|
+
{ code: "SCREENSHOT_ARTIFACT_UNREADABLE", cause }
|
|
291284
|
+
);
|
|
291285
|
+
}
|
|
291286
|
+
if (bytesRead === 0) {
|
|
291287
|
+
throw Object.assign(new Error(`Screenshot artifact is empty: ${output}`), {
|
|
291288
|
+
code: "SCREENSHOT_ARTIFACT_EMPTY"
|
|
291289
|
+
});
|
|
291290
|
+
}
|
|
291291
|
+
};
|
|
290571
291292
|
const captureResolvedScreenshot = async (options, browser, dependencies2, browserSession) => {
|
|
290572
291293
|
const startedAt = dependencies2.now();
|
|
290573
291294
|
const output = getScreenshotOutputPath({
|
|
@@ -290586,6 +291307,7 @@ const captureResolvedScreenshot = async (options, browser, dependencies2, browse
|
|
|
290586
291307
|
browserScreenshotOptions,
|
|
290587
291308
|
dependencies2
|
|
290588
291309
|
);
|
|
291310
|
+
await validateScreenshotArtifact(output, dependencies2);
|
|
290589
291311
|
return {
|
|
290590
291312
|
output,
|
|
290591
291313
|
browser,
|
|
@@ -290693,6 +291415,11 @@ const createScreenshotCaptureSession = (dependencies2 = defaultScreenshotDepende
|
|
|
290693
291415
|
const layouts = await activeBrowserSession.capturePage(
|
|
290694
291416
|
captures.map((capture) => capture.browserOptions)
|
|
290695
291417
|
);
|
|
291418
|
+
await Promise.all(
|
|
291419
|
+
captures.map(
|
|
291420
|
+
({ output }) => validateScreenshotArtifact(output, dependencies2)
|
|
291421
|
+
)
|
|
291422
|
+
);
|
|
290696
291423
|
return captures.map(({ options, output }, index2) => {
|
|
290697
291424
|
const layout = layouts[index2];
|
|
290698
291425
|
if (layout === void 0) {
|
|
@@ -290977,6 +291704,69 @@ const runMcpProjectBatch = async ({
|
|
|
290977
291704
|
await pendingWrite;
|
|
290978
291705
|
return reports;
|
|
290979
291706
|
};
|
|
291707
|
+
const checkpointFilename = "mcp-checkpoint.json";
|
|
291708
|
+
const getMcpCheckpointPath = ({
|
|
291709
|
+
projectRoot = cwd$1(),
|
|
291710
|
+
projectId
|
|
291711
|
+
} = {}) => path$2.join(
|
|
291712
|
+
getLocalProjectStateDirectory(projectRoot, projectId),
|
|
291713
|
+
checkpointFilename
|
|
291714
|
+
);
|
|
291715
|
+
const readPersistedMcpCheckpoint = async (scope2 = {}) => {
|
|
291716
|
+
try {
|
|
291717
|
+
return JSON.parse(
|
|
291718
|
+
await readFile(getMcpCheckpointPath(scope2), "utf8")
|
|
291719
|
+
);
|
|
291720
|
+
} catch (error) {
|
|
291721
|
+
if (error.code === "ENOENT") {
|
|
291722
|
+
return;
|
|
291723
|
+
}
|
|
291724
|
+
throw error;
|
|
291725
|
+
}
|
|
291726
|
+
};
|
|
291727
|
+
const writePersistedMcpCheckpoint = async (checkpoint, scope2 = {}) => {
|
|
291728
|
+
const checkpointPath = getMcpCheckpointPath(scope2);
|
|
291729
|
+
await mkdir(path$2.dirname(checkpointPath), { recursive: true });
|
|
291730
|
+
await writeFile(checkpointPath, JSON.stringify(checkpoint, void 0, 2));
|
|
291731
|
+
};
|
|
291732
|
+
const clearPersistedMcpCheckpoint = async (scope2 = {}) => {
|
|
291733
|
+
await rm(getMcpCheckpointPath(scope2), { force: true });
|
|
291734
|
+
};
|
|
291735
|
+
const assertPersistedMcpCheckpointAcknowledged = async (tool, tools, scope2 = {}) => {
|
|
291736
|
+
if (tool === "checkpoint.ack" || isReadOnlyProjectSessionMcpToolCall(tool, tools)) {
|
|
291737
|
+
return;
|
|
291738
|
+
}
|
|
291739
|
+
const checkpoint = await readPersistedMcpCheckpoint(scope2);
|
|
291740
|
+
if (checkpoint === void 0) {
|
|
291741
|
+
return;
|
|
291742
|
+
}
|
|
291743
|
+
throw Object.assign(
|
|
291744
|
+
new Error(
|
|
291745
|
+
`CHECKPOINT_REQUIRED: ${checkpoint.message} Stop now and report the previous checkpoint to the parent/user. Only after the parent/user continues, call checkpoint.ack {"reported":true,"continueAfterReport":true,"summary":"<what you reported>"} before calling "${tool}".`
|
|
291746
|
+
),
|
|
291747
|
+
{ code: "CHECKPOINT_REQUIRED" }
|
|
291748
|
+
);
|
|
291749
|
+
};
|
|
291750
|
+
const getResultCheckpoint = (tool, structuredContent) => {
|
|
291751
|
+
if (isPlainRecord(structuredContent) === false) {
|
|
291752
|
+
return;
|
|
291753
|
+
}
|
|
291754
|
+
return getProjectSessionMcpCheckpoint(tool, structuredContent.data);
|
|
291755
|
+
};
|
|
291756
|
+
const updatePersistedMcpCheckpoint = async ({
|
|
291757
|
+
tool,
|
|
291758
|
+
structuredContent,
|
|
291759
|
+
scope: scope2 = {}
|
|
291760
|
+
}) => {
|
|
291761
|
+
if (tool === "checkpoint.ack") {
|
|
291762
|
+
await clearPersistedMcpCheckpoint(scope2);
|
|
291763
|
+
return;
|
|
291764
|
+
}
|
|
291765
|
+
const checkpoint = getResultCheckpoint(tool, structuredContent);
|
|
291766
|
+
if (checkpoint !== void 0) {
|
|
291767
|
+
await writePersistedMcpCheckpoint(checkpoint, scope2);
|
|
291768
|
+
}
|
|
291769
|
+
};
|
|
290980
291770
|
const gzipAsync = promisify(gzip);
|
|
290981
291771
|
const compressibleExtensions = /* @__PURE__ */ new Set([
|
|
290982
291772
|
".css",
|
|
@@ -291058,22 +291848,6 @@ const inspectGeneratedBuildMetrics = async (projectDirectory) => {
|
|
|
291058
291848
|
largestFiles: files2.slice(0, 20)
|
|
291059
291849
|
};
|
|
291060
291850
|
};
|
|
291061
|
-
const createExclusiveAsyncRunner = () => {
|
|
291062
|
-
let queue = Promise.resolve();
|
|
291063
|
-
return async (callback) => {
|
|
291064
|
-
const previousRun = queue;
|
|
291065
|
-
let releaseCurrentRun = () => void 0;
|
|
291066
|
-
queue = new Promise((resolve2) => {
|
|
291067
|
-
releaseCurrentRun = resolve2;
|
|
291068
|
-
});
|
|
291069
|
-
await previousRun.catch(() => void 0);
|
|
291070
|
-
try {
|
|
291071
|
-
return await callback();
|
|
291072
|
-
} finally {
|
|
291073
|
-
releaseCurrentRun();
|
|
291074
|
-
}
|
|
291075
|
-
};
|
|
291076
|
-
};
|
|
291077
291851
|
const previewDefaultTemplate = ["react-router"];
|
|
291078
291852
|
const previewSources = projectPreviewSources;
|
|
291079
291853
|
const getPreviewTemplates = (template) => {
|
|
@@ -291152,6 +291926,21 @@ const cliNodeModulesCandidates = getNodeModulesSearchPaths(import.meta.url);
|
|
|
291152
291926
|
const execFileAsync = promisify(execFile);
|
|
291153
291927
|
const dependencyMarker = ".webstudio-preview-dependencies";
|
|
291154
291928
|
const developmentCliVersion = "0.0.0-webstudio-version";
|
|
291929
|
+
const previewDependencyInstallTimeout = 2 * 6e4;
|
|
291930
|
+
const getPreviewInstallFailureDiagnostics = (error) => {
|
|
291931
|
+
if (typeof error !== "object" || error === null) {
|
|
291932
|
+
return sanitizeValidationDetail(String(error)).trim().slice(-2e3);
|
|
291933
|
+
}
|
|
291934
|
+
const record2 = error;
|
|
291935
|
+
const output = [record2.stderr, record2.stdout, record2.message].find(
|
|
291936
|
+
(value2) => typeof value2 === "string" && value2.trim().length > 0
|
|
291937
|
+
);
|
|
291938
|
+
const diagnostics = sanitizeValidationDetail(
|
|
291939
|
+
typeof output === "string" ? output : String(error)
|
|
291940
|
+
).trim().slice(-2e3);
|
|
291941
|
+
const code2 = typeof record2.code === "string" || typeof record2.code === "number" ? String(record2.code) : void 0;
|
|
291942
|
+
return `${code2 === void 0 ? "" : `npm exit code ${code2}. `}${diagnostics}`;
|
|
291943
|
+
};
|
|
291155
291944
|
const getPreviewProjectDir = (projectDir = cwd$1()) => join$1(projectDir, ".webstudio", "preview");
|
|
291156
291945
|
const ensurePreviewDependencies = async (previewProjectDir, dependencies2 = {}) => {
|
|
291157
291946
|
const operations = {
|
|
@@ -291165,6 +291954,7 @@ const ensurePreviewDependencies = async (previewProjectDir, dependencies2 = {})
|
|
|
291165
291954
|
nodeExecPath: process.execPath,
|
|
291166
291955
|
npmExecPath: process.env.npm_execpath,
|
|
291167
291956
|
platform: process.platform,
|
|
291957
|
+
env: process.env,
|
|
291168
291958
|
...dependencies2
|
|
291169
291959
|
};
|
|
291170
291960
|
const previewNodeModules = join$1(previewProjectDir, "node_modules");
|
|
@@ -291223,11 +292013,16 @@ const ensurePreviewDependencies = async (previewProjectDir, dependencies2 = {})
|
|
|
291223
292013
|
const invocation = getNpmInvocation(installArgs, operations);
|
|
291224
292014
|
try {
|
|
291225
292015
|
await operations.execFile(invocation.command, invocation.args, {
|
|
291226
|
-
cwd: previewProjectDir
|
|
292016
|
+
cwd: previewProjectDir,
|
|
292017
|
+
env: operations.env,
|
|
292018
|
+
timeout: previewDependencyInstallTimeout
|
|
291227
292019
|
});
|
|
291228
292020
|
} catch (error) {
|
|
291229
292021
|
throw new Error(
|
|
291230
|
-
|
|
292022
|
+
`PREVIEW_DEPENDENCY_INSTALL_FAILED: Could not install the generated preview dependencies. Check the npm/network configuration, then reinstall or update webstudio if the problem persists.
|
|
292023
|
+
|
|
292024
|
+
Package-manager diagnostics:
|
|
292025
|
+
${getPreviewInstallFailureDiagnostics(error)}`,
|
|
291231
292026
|
{ cause: error }
|
|
291232
292027
|
);
|
|
291233
292028
|
}
|
|
@@ -291357,24 +292152,21 @@ const preparePreviewProject = async ({
|
|
|
291357
292152
|
includeDraftPages = false,
|
|
291358
292153
|
preserveGeneratedProject = false,
|
|
291359
292154
|
prepareForIncrementalGeneration = false,
|
|
292155
|
+
reportProgress,
|
|
291360
292156
|
prepareSessionDataFile = async () => {
|
|
291361
292157
|
const connection = await resolveApiConnection();
|
|
291362
292158
|
const session = createCliProjectSession({ connection });
|
|
291363
292159
|
await session.initialize();
|
|
291364
|
-
|
|
291365
|
-
|
|
291366
|
-
snapshot,
|
|
292160
|
+
await writeCliProjectSessionPreviewDataFile({
|
|
292161
|
+
session,
|
|
291367
292162
|
connection,
|
|
291368
|
-
join$1(cwd$1(), LOCAL_ASSETS_DIR)
|
|
291369
|
-
);
|
|
291370
|
-
await writeCliProjectSessionDataFile(snapshot, void 0, {
|
|
291371
|
-
origin: connection.origin,
|
|
291372
|
-
assetIndex
|
|
292163
|
+
assetsDirectory: join$1(cwd$1(), LOCAL_ASSETS_DIR)
|
|
291373
292164
|
});
|
|
291374
292165
|
}
|
|
291375
292166
|
}) => {
|
|
291376
292167
|
const projectDir = cwd$1();
|
|
291377
292168
|
if (source === "session") {
|
|
292169
|
+
reportProgress?.("materializing session project data");
|
|
291378
292170
|
await prepareSessionDataFile();
|
|
291379
292171
|
}
|
|
291380
292172
|
try {
|
|
@@ -291403,6 +292195,13 @@ const preparePreviewProject = async ({
|
|
|
291403
292195
|
if (generate2 === false) {
|
|
291404
292196
|
return { cwd: projectDir };
|
|
291405
292197
|
}
|
|
292198
|
+
const prepareDependencies = async () => {
|
|
292199
|
+
reportProgress?.(
|
|
292200
|
+
`checking or installing generated preview dependencies (${previewDependencyInstallTimeout / 6e4} minute timeout)`
|
|
292201
|
+
);
|
|
292202
|
+
await ensureDependencies(previewProjectDir);
|
|
292203
|
+
reportProgress?.("generated preview project is ready");
|
|
292204
|
+
};
|
|
291406
292205
|
const buildCacheKey = await getBuildCacheKey({
|
|
291407
292206
|
projectDir,
|
|
291408
292207
|
assets,
|
|
@@ -291417,29 +292216,28 @@ const preparePreviewProject = async ({
|
|
|
291417
292216
|
"utf8"
|
|
291418
292217
|
).catch(() => void 0);
|
|
291419
292218
|
if (cachedBuildKey === buildCacheKey && canReuseCachedProject) {
|
|
291420
|
-
await
|
|
292219
|
+
await prepareDependencies();
|
|
291421
292220
|
return { cwd: previewProjectDir, buildCacheKey, buildRequired: false };
|
|
291422
292221
|
}
|
|
291423
292222
|
}
|
|
291424
|
-
|
|
291425
|
-
|
|
291426
|
-
|
|
291427
|
-
|
|
291428
|
-
|
|
291429
|
-
|
|
291430
|
-
|
|
291431
|
-
|
|
291432
|
-
|
|
291433
|
-
|
|
291434
|
-
|
|
291435
|
-
|
|
291436
|
-
|
|
291437
|
-
|
|
291438
|
-
});
|
|
292223
|
+
await runExclusive(async () => {
|
|
292224
|
+
const reuseGeneratedProject = preserveGeneratedProject && hasIncrementalInputs;
|
|
292225
|
+
await preparePreviewDirectory(projectDir, reuseGeneratedProject);
|
|
292226
|
+
await runInDirectory(previewProjectDir, async () => {
|
|
292227
|
+
reportProgress?.("generating preview files");
|
|
292228
|
+
await prebuildProject({
|
|
292229
|
+
assets,
|
|
292230
|
+
template: getPreviewTemplates(template),
|
|
292231
|
+
previewIdentity: true,
|
|
292232
|
+
sourceAssetsDirectory: join$1(projectDir, LOCAL_ASSETS_DIR),
|
|
292233
|
+
...silent ? { silent: true } : {},
|
|
292234
|
+
...includeDraftPages ? { includeDraftPages: true } : {},
|
|
292235
|
+
...reuseGeneratedProject ? { incremental: true } : {},
|
|
292236
|
+
...prepareForIncrementalGeneration ? { preserveRouteTemplates: true } : {}
|
|
291439
292237
|
});
|
|
291440
|
-
await ensureDependencies(previewProjectDir);
|
|
291441
292238
|
});
|
|
291442
|
-
|
|
292239
|
+
await prepareDependencies();
|
|
292240
|
+
});
|
|
291443
292241
|
return {
|
|
291444
292242
|
cwd: previewProjectDir,
|
|
291445
292243
|
buildRequired: true,
|
|
@@ -291481,6 +292279,9 @@ const preview = async (options) => {
|
|
|
291481
292279
|
});
|
|
291482
292280
|
await waitForPreviewExit(server.process);
|
|
291483
292281
|
};
|
|
292282
|
+
const getPreparationProgress = (progress2, tool) => progress2 === void 0 ? {} : {
|
|
292283
|
+
reportProgress: (message) => progress2.report(`tool ${tool} ${message}`)
|
|
292284
|
+
};
|
|
291484
292285
|
const createPreviewFreshness = () => {
|
|
291485
292286
|
let revision = 0;
|
|
291486
292287
|
let freshRevision = -1;
|
|
@@ -291510,6 +292311,80 @@ const getCaptureSessionConfig = (input2) => ({
|
|
|
291510
292311
|
const isSameCaptureSessionConfig = (left, right) => left.browser === right.browser && left.browserPath === right.browserPath;
|
|
291511
292312
|
const defaultPreviewSource = "session";
|
|
291512
292313
|
const defaultSleep = (durationMs) => new Promise((resolve2) => setTimeout(resolve2, durationMs));
|
|
292314
|
+
const resolveMcpPreviewInput = async (input2, getAvailablePort = findAvailablePort) => {
|
|
292315
|
+
if (input2.port !== void 0 && input2.port !== 0) {
|
|
292316
|
+
return input2;
|
|
292317
|
+
}
|
|
292318
|
+
return {
|
|
292319
|
+
...input2,
|
|
292320
|
+
port: await getAvailablePort(input2.host ?? "127.0.0.1")
|
|
292321
|
+
};
|
|
292322
|
+
};
|
|
292323
|
+
const getRunningPreviewPort = (previewStatus, host) => {
|
|
292324
|
+
if (previewStatus.running === false || previewStatus.url === void 0) {
|
|
292325
|
+
return;
|
|
292326
|
+
}
|
|
292327
|
+
const previewUrl = new URL(previewStatus.url);
|
|
292328
|
+
if (host !== void 0 && previewUrl.hostname !== host) {
|
|
292329
|
+
return;
|
|
292330
|
+
}
|
|
292331
|
+
const defaultPort = previewUrl.protocol === "http:" ? 80 : previewUrl.protocol === "https:" ? 443 : void 0;
|
|
292332
|
+
const port = previewUrl.port === "" ? defaultPort : Number(previewUrl.port);
|
|
292333
|
+
return port !== void 0 && Number.isInteger(port) && port > 0 ? port : void 0;
|
|
292334
|
+
};
|
|
292335
|
+
const resolveMcpScreenshotInput = async (input2, previewStatus, {
|
|
292336
|
+
getAvailablePort = findAvailablePort,
|
|
292337
|
+
isPortAvailable = isPreviewPortAvailable
|
|
292338
|
+
} = {}) => {
|
|
292339
|
+
if (input2.url !== void 0 || input2.baseUrl !== void 0 || input2.source === "local") {
|
|
292340
|
+
return input2;
|
|
292341
|
+
}
|
|
292342
|
+
const runningPreviewPort = getRunningPreviewPort(previewStatus, input2.host);
|
|
292343
|
+
if (input2.port !== void 0 && input2.port !== 0) {
|
|
292344
|
+
const host2 = input2.host ?? "127.0.0.1";
|
|
292345
|
+
if (runningPreviewPort === input2.port) {
|
|
292346
|
+
return input2;
|
|
292347
|
+
}
|
|
292348
|
+
if (await isPortAvailable(host2, input2.port) === false) {
|
|
292349
|
+
throw Object.assign(
|
|
292350
|
+
new Error(
|
|
292351
|
+
`Preview port ${input2.port} is already in use on ${host2}. Pass baseUrl with path to capture that existing site, or choose another port.`
|
|
292352
|
+
),
|
|
292353
|
+
{ code: "PREVIEW_PORT_IN_USE" }
|
|
292354
|
+
);
|
|
292355
|
+
}
|
|
292356
|
+
return input2;
|
|
292357
|
+
}
|
|
292358
|
+
const host = input2.host ?? "127.0.0.1";
|
|
292359
|
+
if (runningPreviewPort !== void 0) {
|
|
292360
|
+
return { ...input2, port: runningPreviewPort };
|
|
292361
|
+
}
|
|
292362
|
+
return { ...input2, port: await getAvailablePort(host) };
|
|
292363
|
+
};
|
|
292364
|
+
const startMcpPreview = async ({
|
|
292365
|
+
input: input2,
|
|
292366
|
+
startPreview,
|
|
292367
|
+
getAvailablePort = findAvailablePort,
|
|
292368
|
+
sleep = defaultSleep
|
|
292369
|
+
}) => {
|
|
292370
|
+
const attempts = input2.port === void 0 || input2.port === 0 ? 5 : 1;
|
|
292371
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
292372
|
+
const resolvedInput = await resolveMcpPreviewInput(input2, getAvailablePort);
|
|
292373
|
+
try {
|
|
292374
|
+
return await startPreview(resolvedInput);
|
|
292375
|
+
} catch (error) {
|
|
292376
|
+
if (attempt === attempts - 1) {
|
|
292377
|
+
throw error;
|
|
292378
|
+
}
|
|
292379
|
+
await sleep(500);
|
|
292380
|
+
}
|
|
292381
|
+
}
|
|
292382
|
+
throw new Error("MCP preview did not start.");
|
|
292383
|
+
};
|
|
292384
|
+
const createScreenshotTimeoutError = (timeout) => Object.assign(
|
|
292385
|
+
new Error(`Screenshot capture did not finish within ${timeout}ms.`),
|
|
292386
|
+
{ code: "SCREENSHOT_TIMEOUT" }
|
|
292387
|
+
);
|
|
291513
292388
|
const getPreviewSource = (source) => source ?? defaultPreviewSource;
|
|
291514
292389
|
const isManagedSessionPreviewCapture = (input2) => input2.url === void 0 && input2.baseUrl === void 0 && getPreviewSource(input2.source) !== "local";
|
|
291515
292390
|
const getPreviewTarget = (input2) => ({
|
|
@@ -291522,7 +292397,8 @@ const getPreviewTarget = (input2) => ({
|
|
|
291522
292397
|
const isSamePreviewTarget = (left, right) => left.source === right.source && left.mode === right.mode && left.host === right.host && left.port === right.port && arePreviewImageDomainsEqual(left.imageDomains, right.imageDomains);
|
|
291523
292398
|
const prepareDefaultPreviewProject = (source = defaultPreviewSource, prepareSessionDataFile, {
|
|
291524
292399
|
preserveGeneratedProject = false,
|
|
291525
|
-
prepareForIncrementalGeneration = false
|
|
292400
|
+
prepareForIncrementalGeneration = false,
|
|
292401
|
+
reportProgress
|
|
291526
292402
|
} = {}) => preparePreviewProject({
|
|
291527
292403
|
assets: true,
|
|
291528
292404
|
template: [...previewDefaultTemplate],
|
|
@@ -291533,7 +292409,8 @@ const prepareDefaultPreviewProject = (source = defaultPreviewSource, prepareSess
|
|
|
291533
292409
|
includeDraftPages: true,
|
|
291534
292410
|
prepareSessionDataFile,
|
|
291535
292411
|
preserveGeneratedProject,
|
|
291536
|
-
prepareForIncrementalGeneration
|
|
292412
|
+
prepareForIncrementalGeneration,
|
|
292413
|
+
reportProgress
|
|
291537
292414
|
});
|
|
291538
292415
|
const createMcpPreviewHandlers = ({
|
|
291539
292416
|
preview: preview2,
|
|
@@ -291560,6 +292437,20 @@ const createMcpPreviewHandlers = ({
|
|
|
291560
292437
|
captureSessionConfig = void 0;
|
|
291561
292438
|
}
|
|
291562
292439
|
};
|
|
292440
|
+
const captureWithTimeout = async (operation, timeout, resetSession) => {
|
|
292441
|
+
try {
|
|
292442
|
+
return await withTimeout(
|
|
292443
|
+
operation(),
|
|
292444
|
+
timeout,
|
|
292445
|
+
() => createScreenshotTimeoutError(timeout)
|
|
292446
|
+
);
|
|
292447
|
+
} catch (error) {
|
|
292448
|
+
if (resetSession !== void 0) {
|
|
292449
|
+
await resetSession().catch(() => void 0);
|
|
292450
|
+
}
|
|
292451
|
+
throw error;
|
|
292452
|
+
}
|
|
292453
|
+
};
|
|
291563
292454
|
const getCaptureSession = async (input2) => {
|
|
291564
292455
|
if (createCaptureSession === void 0) {
|
|
291565
292456
|
throw new Error("Reusable screenshot capture is unavailable.");
|
|
@@ -291629,15 +292520,16 @@ const createMcpPreviewHandlers = ({
|
|
|
291629
292520
|
});
|
|
291630
292521
|
progress2?.report("tool screenshot preparing generated preview project");
|
|
291631
292522
|
const freshness = captureFreshness();
|
|
291632
|
-
const projectVersion = getProjectVersion();
|
|
291633
292523
|
const previewProject = await preparePreview(
|
|
291634
292524
|
source,
|
|
291635
292525
|
prepareSessionDataFile,
|
|
291636
292526
|
{
|
|
291637
292527
|
preserveGeneratedProject: canReusePreview && mode === "iterative",
|
|
291638
|
-
prepareForIncrementalGeneration: mode === "iterative"
|
|
292528
|
+
prepareForIncrementalGeneration: mode === "iterative",
|
|
292529
|
+
...getPreparationProgress(progress2, "screenshot")
|
|
291639
292530
|
}
|
|
291640
292531
|
);
|
|
292532
|
+
const projectVersion = getProjectVersion();
|
|
291641
292533
|
progress2?.report(
|
|
291642
292534
|
`tool screenshot ${canReusePreview ? "refreshing" : "starting"} ${mode} preview server`
|
|
291643
292535
|
);
|
|
@@ -291686,13 +292578,16 @@ const createMcpPreviewHandlers = ({
|
|
|
291686
292578
|
}
|
|
291687
292579
|
return result2;
|
|
291688
292580
|
};
|
|
292581
|
+
const getManagedPreviewMetadata = (input2) => input2.path !== void 0 && input2.baseUrl === void 0 ? { previewMode: preview2.status().mode } : {};
|
|
291689
292582
|
return {
|
|
291690
292583
|
async startPreview(input2, progress2) {
|
|
291691
292584
|
return await runPreviewLifecycle(async () => {
|
|
291692
292585
|
const mode = input2.mode ?? "iterative";
|
|
291693
292586
|
const source = getPreviewSource(input2.source);
|
|
292587
|
+
const previewWasStale = isStale();
|
|
291694
292588
|
const canReusePreview = mode === "iterative" && isPreviewTargetCompatible(input2, mode, preview2.status());
|
|
291695
|
-
|
|
292589
|
+
const restartPreview = canReusePreview === false || previewWasStale;
|
|
292590
|
+
if (restartPreview) {
|
|
291696
292591
|
await closeCaptureSession();
|
|
291697
292592
|
}
|
|
291698
292593
|
validatePreviewServerOptions({
|
|
@@ -291704,15 +292599,16 @@ const createMcpPreviewHandlers = ({
|
|
|
291704
292599
|
"tool preview.start preparing generated preview project"
|
|
291705
292600
|
);
|
|
291706
292601
|
const freshness = captureFreshness();
|
|
291707
|
-
const projectVersion = getProjectVersion();
|
|
291708
292602
|
const previewProject = await preparePreview(
|
|
291709
292603
|
source,
|
|
291710
292604
|
prepareSessionDataFile,
|
|
291711
292605
|
{
|
|
291712
292606
|
preserveGeneratedProject: canReusePreview && mode === "iterative",
|
|
291713
|
-
prepareForIncrementalGeneration: mode === "iterative"
|
|
292607
|
+
prepareForIncrementalGeneration: mode === "iterative",
|
|
292608
|
+
...getPreparationProgress(progress2, "preview.start")
|
|
291714
292609
|
}
|
|
291715
292610
|
);
|
|
292611
|
+
const projectVersion = getProjectVersion();
|
|
291716
292612
|
progress2?.report(
|
|
291717
292613
|
`tool preview.start ${canReusePreview ? "refreshing" : "starting"} ${mode} preview server`
|
|
291718
292614
|
);
|
|
@@ -291721,7 +292617,7 @@ const createMcpPreviewHandlers = ({
|
|
|
291721
292617
|
mode,
|
|
291722
292618
|
cwd: previewProject.cwd,
|
|
291723
292619
|
buildCacheKey: previewProject.buildCacheKey,
|
|
291724
|
-
restart:
|
|
292620
|
+
restart: restartPreview
|
|
291725
292621
|
});
|
|
291726
292622
|
activeSource = source;
|
|
291727
292623
|
markFresh(freshness, projectVersion);
|
|
@@ -291742,30 +292638,37 @@ const createMcpPreviewHandlers = ({
|
|
|
291742
292638
|
const previewReadyAt = Date.now();
|
|
291743
292639
|
progress2?.report(`tool screenshot capturing ${url2}`);
|
|
291744
292640
|
const captureOptions = getCaptureOptions(input2, url2);
|
|
291745
|
-
|
|
291746
|
-
|
|
291747
|
-
|
|
291748
|
-
|
|
291749
|
-
|
|
291750
|
-
|
|
291751
|
-
|
|
291752
|
-
|
|
291753
|
-
|
|
291754
|
-
|
|
291755
|
-
|
|
291756
|
-
|
|
291757
|
-
|
|
291758
|
-
|
|
291759
|
-
|
|
291760
|
-
|
|
291761
|
-
|
|
291762
|
-
|
|
291763
|
-
|
|
291764
|
-
|
|
292641
|
+
const managedCapture = isManagedSessionPreviewCapture(input2);
|
|
292642
|
+
const timeout = input2.timeout ?? defaultScreenshotTimeout;
|
|
292643
|
+
const result2 = await captureWithTimeout(
|
|
292644
|
+
async () => {
|
|
292645
|
+
if (managedCapture && createCaptureSession !== void 0) {
|
|
292646
|
+
const captureSession2 = await getCaptureSession(input2);
|
|
292647
|
+
let captureResult = await captureSession2.capture(captureOptions);
|
|
292648
|
+
for (let retry = 0; retry < 2 && captureResult.navigation?.generatedSiteRootPresent === false; retry += 1) {
|
|
292649
|
+
progress2?.report(
|
|
292650
|
+
"tool screenshot waiting for refreshed generated route"
|
|
292651
|
+
);
|
|
292652
|
+
await sleep(1e3);
|
|
292653
|
+
captureResult = await captureSession2.capture(captureOptions);
|
|
292654
|
+
}
|
|
292655
|
+
return captureResult;
|
|
292656
|
+
}
|
|
292657
|
+
return await captureScreenshot2({
|
|
292658
|
+
...captureOptions,
|
|
292659
|
+
isJson: false,
|
|
292660
|
+
isMcp: true,
|
|
292661
|
+
isInteractive: false,
|
|
292662
|
+
confirmInstall: async () => false
|
|
292663
|
+
});
|
|
292664
|
+
},
|
|
292665
|
+
timeout,
|
|
292666
|
+
managedCapture ? closeCaptureSession : void 0
|
|
292667
|
+
);
|
|
291765
292668
|
const completedAt = Date.now();
|
|
291766
292669
|
return {
|
|
291767
292670
|
...assertGeneratedSiteCapture(input2, result2),
|
|
291768
|
-
...input2
|
|
292671
|
+
...getManagedPreviewMetadata(input2),
|
|
291769
292672
|
lifecycleTimings: {
|
|
291770
292673
|
previewRefreshMs: previewReadyAt - startedAt,
|
|
291771
292674
|
captureMs: completedAt - previewReadyAt,
|
|
@@ -291798,14 +292701,24 @@ const createMcpPreviewHandlers = ({
|
|
|
291798
292701
|
progress2?.report(
|
|
291799
292702
|
`tool screenshot capturing ${new Set(urls).size} pages across ${inputs.length} viewport widths`
|
|
291800
292703
|
);
|
|
291801
|
-
const
|
|
291802
|
-
inputs.map((input2
|
|
291803
|
-
|
|
291804
|
-
|
|
291805
|
-
|
|
291806
|
-
|
|
291807
|
-
return
|
|
291808
|
-
|
|
292704
|
+
const timeout = Math.max(
|
|
292705
|
+
...inputs.map((input2) => input2.timeout ?? defaultScreenshotTimeout)
|
|
292706
|
+
);
|
|
292707
|
+
const results = await captureWithTimeout(
|
|
292708
|
+
async () => {
|
|
292709
|
+
const captureSession2 = await getCaptureSession(firstInput);
|
|
292710
|
+
return await captureSession2.capturePage(
|
|
292711
|
+
inputs.map((input2, index2) => {
|
|
292712
|
+
const url2 = urls[index2];
|
|
292713
|
+
if (url2 === void 0) {
|
|
292714
|
+
throw new Error("Screenshot URL resolution was incomplete.");
|
|
292715
|
+
}
|
|
292716
|
+
return getCaptureOptions(input2, url2);
|
|
292717
|
+
})
|
|
292718
|
+
);
|
|
292719
|
+
},
|
|
292720
|
+
timeout,
|
|
292721
|
+
closeCaptureSession
|
|
291809
292722
|
);
|
|
291810
292723
|
return results.map((result2, index2) => {
|
|
291811
292724
|
const input2 = inputs[index2];
|
|
@@ -291814,7 +292727,7 @@ const createMcpPreviewHandlers = ({
|
|
|
291814
292727
|
}
|
|
291815
292728
|
return {
|
|
291816
292729
|
...assertGeneratedSiteCapture(input2, result2),
|
|
291817
|
-
...input2
|
|
292730
|
+
...getManagedPreviewMetadata(input2)
|
|
291818
292731
|
};
|
|
291819
292732
|
});
|
|
291820
292733
|
});
|
|
@@ -291862,7 +292775,6 @@ const prepareMcpProjectSession = async (session) => {
|
|
|
291862
292775
|
await session.initialize();
|
|
291863
292776
|
};
|
|
291864
292777
|
const mcpStatusPrefix = "[webstudio mcp]";
|
|
291865
|
-
const mcpCheckpointFilename = "mcp-checkpoint.json";
|
|
291866
292778
|
const renderedAuditArtifactDirectory = ".webstudio/audits";
|
|
291867
292779
|
const formatMcpStatusLine = (message) => `${mcpStatusPrefix} ${message}`;
|
|
291868
292780
|
const createMcpStatusReporter = (write = (line) => {
|
|
@@ -291946,67 +292858,6 @@ const getMcpUpdateAssetContentInput = (input2) => {
|
|
|
291946
292858
|
readFile
|
|
291947
292859
|
});
|
|
291948
292860
|
};
|
|
291949
|
-
const getMcpCheckpointPath = ({
|
|
291950
|
-
projectRoot = cwd$1(),
|
|
291951
|
-
projectId
|
|
291952
|
-
} = {}) => path$2.join(
|
|
291953
|
-
getLocalProjectStateDirectory(projectRoot, projectId),
|
|
291954
|
-
mcpCheckpointFilename
|
|
291955
|
-
);
|
|
291956
|
-
const readPersistedMcpCheckpoint = async (scope2 = {}) => {
|
|
291957
|
-
try {
|
|
291958
|
-
return JSON.parse(
|
|
291959
|
-
await readFile(getMcpCheckpointPath(scope2), "utf8")
|
|
291960
|
-
);
|
|
291961
|
-
} catch (error) {
|
|
291962
|
-
if (error.code === "ENOENT") {
|
|
291963
|
-
return;
|
|
291964
|
-
}
|
|
291965
|
-
throw error;
|
|
291966
|
-
}
|
|
291967
|
-
};
|
|
291968
|
-
const writePersistedMcpCheckpoint = async (checkpoint, scope2 = {}) => {
|
|
291969
|
-
const checkpointPath = getMcpCheckpointPath(scope2);
|
|
291970
|
-
await mkdir(path$2.dirname(checkpointPath), { recursive: true });
|
|
291971
|
-
await writeFile(checkpointPath, JSON.stringify(checkpoint, void 0, 2));
|
|
291972
|
-
};
|
|
291973
|
-
const clearPersistedMcpCheckpoint = async (scope2 = {}) => {
|
|
291974
|
-
await rm(getMcpCheckpointPath(scope2), { force: true });
|
|
291975
|
-
};
|
|
291976
|
-
const assertPersistedMcpCheckpointAcknowledged = async (tool, tools, scope2 = {}) => {
|
|
291977
|
-
if (tool === "checkpoint.ack" || isReadOnlyProjectSessionMcpToolCall(tool, tools)) {
|
|
291978
|
-
return;
|
|
291979
|
-
}
|
|
291980
|
-
const checkpoint = await readPersistedMcpCheckpoint(scope2);
|
|
291981
|
-
if (checkpoint === void 0) {
|
|
291982
|
-
return;
|
|
291983
|
-
}
|
|
291984
|
-
throw createMcpInputError(
|
|
291985
|
-
`CHECKPOINT_REQUIRED: ${checkpoint.message} Stop now and report the previous checkpoint to the parent/user. Only after the parent/user continues, call checkpoint.ack {"reported":true,"continueAfterReport":true,"summary":"<what you reported>"} before calling "${tool}".`,
|
|
291986
|
-
"CHECKPOINT_REQUIRED"
|
|
291987
|
-
);
|
|
291988
|
-
};
|
|
291989
|
-
const getResultCheckpoint = (tool, structuredContent) => {
|
|
291990
|
-
if (isPlainRecord(structuredContent) === false) {
|
|
291991
|
-
return;
|
|
291992
|
-
}
|
|
291993
|
-
const data2 = structuredContent.data;
|
|
291994
|
-
return getProjectSessionMcpCheckpoint(tool, data2);
|
|
291995
|
-
};
|
|
291996
|
-
const updatePersistedMcpCheckpoint = async ({
|
|
291997
|
-
tool,
|
|
291998
|
-
structuredContent,
|
|
291999
|
-
scope: scope2 = {}
|
|
292000
|
-
}) => {
|
|
292001
|
-
if (tool === "checkpoint.ack") {
|
|
292002
|
-
await clearPersistedMcpCheckpoint(scope2);
|
|
292003
|
-
return;
|
|
292004
|
-
}
|
|
292005
|
-
const checkpoint = getResultCheckpoint(tool, structuredContent);
|
|
292006
|
-
if (checkpoint !== void 0) {
|
|
292007
|
-
await writePersistedMcpCheckpoint(checkpoint, scope2);
|
|
292008
|
-
}
|
|
292009
|
-
};
|
|
292010
292861
|
const getMcpOperationInput = (command, input2) => {
|
|
292011
292862
|
if (command === "upload-asset") {
|
|
292012
292863
|
return getMcpUploadAssetInput(input2);
|
|
@@ -292345,6 +293196,131 @@ const createMcpRunErrorPayload = ({
|
|
|
292345
293196
|
elapsedMs
|
|
292346
293197
|
}
|
|
292347
293198
|
});
|
|
293199
|
+
const mcpRunTerminationCleanupTimeout = 5e3;
|
|
293200
|
+
const reportMcpRunTermination = ({
|
|
293201
|
+
termination,
|
|
293202
|
+
activeCall,
|
|
293203
|
+
totalCalls,
|
|
293204
|
+
results,
|
|
293205
|
+
elapsedMs,
|
|
293206
|
+
writeStatus = (message) => stderr.write(`${message}
|
|
293207
|
+
`),
|
|
293208
|
+
writeResult = printJson,
|
|
293209
|
+
setExitCode = (code2) => {
|
|
293210
|
+
process.exitCode = code2;
|
|
293211
|
+
}
|
|
293212
|
+
}) => {
|
|
293213
|
+
const error = {
|
|
293214
|
+
code: "MCP_RUN_TERMINATED",
|
|
293215
|
+
message: `MCP run terminated before call ${activeCall.number}/${totalCalls} ${activeCall.tool} returned a result.`
|
|
293216
|
+
};
|
|
293217
|
+
const payload = createMcpRunErrorPayload({
|
|
293218
|
+
error,
|
|
293219
|
+
completedCalls: results.length,
|
|
293220
|
+
totalCalls,
|
|
293221
|
+
results,
|
|
293222
|
+
elapsedMs
|
|
293223
|
+
});
|
|
293224
|
+
writeStatus(
|
|
293225
|
+
formatMcpStatusLine(
|
|
293226
|
+
`run ${activeCall.number}/${totalCalls} ${activeCall.tool} terminated: ${error.message}`
|
|
293227
|
+
)
|
|
293228
|
+
);
|
|
293229
|
+
writeResult({
|
|
293230
|
+
...payload,
|
|
293231
|
+
data: { ...payload.data, unfinishedCall: activeCall },
|
|
293232
|
+
meta: {
|
|
293233
|
+
...payload.meta,
|
|
293234
|
+
termination
|
|
293235
|
+
}
|
|
293236
|
+
});
|
|
293237
|
+
if (termination.type === "beforeExit") {
|
|
293238
|
+
setExitCode(1);
|
|
293239
|
+
}
|
|
293240
|
+
};
|
|
293241
|
+
const createMcpRunTerminationController = ({
|
|
293242
|
+
getActiveCall,
|
|
293243
|
+
totalCalls,
|
|
293244
|
+
results,
|
|
293245
|
+
startedAt,
|
|
293246
|
+
disposeHost,
|
|
293247
|
+
reportTermination = reportMcpRunTermination,
|
|
293248
|
+
cleanupTimeout = mcpRunTerminationCleanupTimeout,
|
|
293249
|
+
exitWithSignal = (signal) => {
|
|
293250
|
+
process.kill(process.pid, signal);
|
|
293251
|
+
}
|
|
293252
|
+
}) => {
|
|
293253
|
+
let isTerminating = false;
|
|
293254
|
+
const beginTermination = (termination) => {
|
|
293255
|
+
if (isTerminating) {
|
|
293256
|
+
return;
|
|
293257
|
+
}
|
|
293258
|
+
const activeCall = getActiveCall();
|
|
293259
|
+
if (activeCall === void 0 && termination.type === "beforeExit") {
|
|
293260
|
+
return;
|
|
293261
|
+
}
|
|
293262
|
+
isTerminating = true;
|
|
293263
|
+
if (activeCall !== void 0) {
|
|
293264
|
+
reportTermination({
|
|
293265
|
+
termination,
|
|
293266
|
+
activeCall,
|
|
293267
|
+
totalCalls,
|
|
293268
|
+
results,
|
|
293269
|
+
elapsedMs: Date.now() - startedAt
|
|
293270
|
+
});
|
|
293271
|
+
}
|
|
293272
|
+
const cleanup = withTimeout(
|
|
293273
|
+
Promise.resolve().then(disposeHost),
|
|
293274
|
+
cleanupTimeout,
|
|
293275
|
+
() => new Error("MCP run cleanup timed out.")
|
|
293276
|
+
).catch(() => void 0);
|
|
293277
|
+
if (termination.type === "signal") {
|
|
293278
|
+
void cleanup.finally(() => exitWithSignal(termination.signal));
|
|
293279
|
+
}
|
|
293280
|
+
};
|
|
293281
|
+
return {
|
|
293282
|
+
beforeExit: (exitCode) => beginTermination({ type: "beforeExit", exitCode }),
|
|
293283
|
+
signal: (signal) => beginTermination({ type: "signal", signal })
|
|
293284
|
+
};
|
|
293285
|
+
};
|
|
293286
|
+
const installMcpRunTerminationHandlers = ({
|
|
293287
|
+
getActiveCall,
|
|
293288
|
+
totalCalls,
|
|
293289
|
+
results,
|
|
293290
|
+
startedAt,
|
|
293291
|
+
disposeHost
|
|
293292
|
+
}) => {
|
|
293293
|
+
const controller = createMcpRunTerminationController({
|
|
293294
|
+
getActiveCall,
|
|
293295
|
+
totalCalls,
|
|
293296
|
+
results,
|
|
293297
|
+
startedAt,
|
|
293298
|
+
disposeHost
|
|
293299
|
+
});
|
|
293300
|
+
const terminationSignals = ["SIGHUP", "SIGINT", "SIGTERM"];
|
|
293301
|
+
const signalHandlers = /* @__PURE__ */ new Map();
|
|
293302
|
+
const beforeExit = (exitCode) => {
|
|
293303
|
+
dispose();
|
|
293304
|
+
controller.beforeExit(exitCode);
|
|
293305
|
+
};
|
|
293306
|
+
const dispose = () => {
|
|
293307
|
+
process.off("beforeExit", beforeExit);
|
|
293308
|
+
for (const [signal, handler] of signalHandlers) {
|
|
293309
|
+
process.off(signal, handler);
|
|
293310
|
+
}
|
|
293311
|
+
signalHandlers.clear();
|
|
293312
|
+
};
|
|
293313
|
+
process.once("beforeExit", beforeExit);
|
|
293314
|
+
for (const signal of terminationSignals) {
|
|
293315
|
+
const handler = () => {
|
|
293316
|
+
dispose();
|
|
293317
|
+
controller.signal(signal);
|
|
293318
|
+
};
|
|
293319
|
+
signalHandlers.set(signal, handler);
|
|
293320
|
+
process.once(signal, handler);
|
|
293321
|
+
}
|
|
293322
|
+
return dispose;
|
|
293323
|
+
};
|
|
292348
293324
|
const reportMcpRunPreflightFailure = ({
|
|
292349
293325
|
error,
|
|
292350
293326
|
startedAt,
|
|
@@ -292390,54 +293366,10 @@ const assertSingleOpCallToolSupported = (tool) => {
|
|
|
292390
293366
|
);
|
|
292391
293367
|
}
|
|
292392
293368
|
};
|
|
292393
|
-
const resolveMcpPreviewInput = async (input2, getAvailablePort = findAvailablePort) => {
|
|
292394
|
-
if (input2.port !== void 0 && input2.port !== 0) {
|
|
292395
|
-
return input2;
|
|
292396
|
-
}
|
|
292397
|
-
return {
|
|
292398
|
-
...input2,
|
|
292399
|
-
port: await getAvailablePort(input2.host ?? "127.0.0.1")
|
|
292400
|
-
};
|
|
292401
|
-
};
|
|
292402
|
-
const resolveMcpScreenshotInput = async (input2, previewStatus, getAvailablePort = findAvailablePort) => {
|
|
292403
|
-
if (input2.url !== void 0 || input2.baseUrl !== void 0 || input2.source === "local" || input2.port !== void 0 && input2.port !== 0) {
|
|
292404
|
-
return input2;
|
|
292405
|
-
}
|
|
292406
|
-
const host = input2.host ?? "127.0.0.1";
|
|
292407
|
-
if (previewStatus.running) {
|
|
292408
|
-
const previewUrl = new URL(previewStatus.url);
|
|
292409
|
-
if (input2.host === void 0 || previewUrl.hostname === input2.host) {
|
|
292410
|
-
const previewPort = Number(previewUrl.port);
|
|
292411
|
-
if (Number.isInteger(previewPort) && previewPort > 0) {
|
|
292412
|
-
return { ...input2, port: previewPort };
|
|
292413
|
-
}
|
|
292414
|
-
}
|
|
292415
|
-
}
|
|
292416
|
-
return { ...input2, port: await getAvailablePort(host) };
|
|
292417
|
-
};
|
|
292418
|
-
const startMcpPreview = async ({
|
|
292419
|
-
input: input2,
|
|
292420
|
-
startPreview,
|
|
292421
|
-
getAvailablePort = findAvailablePort,
|
|
292422
|
-
sleep = async (durationMs) => await new Promise((resolve2) => setTimeout(resolve2, durationMs))
|
|
292423
|
-
}) => {
|
|
292424
|
-
const attempts = input2.port === void 0 || input2.port === 0 ? 5 : 1;
|
|
292425
|
-
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
292426
|
-
const resolvedInput = await resolveMcpPreviewInput(input2, getAvailablePort);
|
|
292427
|
-
try {
|
|
292428
|
-
return await startPreview(resolvedInput);
|
|
292429
|
-
} catch (error) {
|
|
292430
|
-
if (attempt === attempts - 1) {
|
|
292431
|
-
throw error;
|
|
292432
|
-
}
|
|
292433
|
-
await sleep(500);
|
|
292434
|
-
}
|
|
292435
|
-
}
|
|
292436
|
-
throw new Error("MCP preview did not start.");
|
|
292437
|
-
};
|
|
292438
293369
|
const createCliMcpHost = async ({
|
|
292439
293370
|
projectRoot = cwd$1(),
|
|
292440
|
-
projectId
|
|
293371
|
+
projectId,
|
|
293372
|
+
managePreviewProcessSignals = true
|
|
292441
293373
|
} = {}) => {
|
|
292442
293374
|
const connection = await resolveApiConnection(
|
|
292443
293375
|
void 0,
|
|
@@ -292469,7 +293401,11 @@ const createCliMcpHost = async ({
|
|
|
292469
293401
|
getCliProjectRestorePointsFile(projectRoot, projectId)
|
|
292470
293402
|
);
|
|
292471
293403
|
await prepareMcpProjectSession(session);
|
|
292472
|
-
const preview2 = createPreviewController(
|
|
293404
|
+
const preview2 = createPreviewController(
|
|
293405
|
+
{ host: "127.0.0.1", port: 5173 },
|
|
293406
|
+
void 0,
|
|
293407
|
+
{ manageProcessSignals: managePreviewProcessSignals }
|
|
293408
|
+
);
|
|
292473
293409
|
const previewFreshness = createPreviewFreshness();
|
|
292474
293410
|
const previewHandlers = createMcpPreviewHandlers({
|
|
292475
293411
|
preview: preview2,
|
|
@@ -292483,17 +293419,12 @@ const createCliMcpHost = async ({
|
|
|
292483
293419
|
pagePath2
|
|
292484
293420
|
),
|
|
292485
293421
|
prepareSessionDataFile: async () => {
|
|
292486
|
-
|
|
292487
|
-
|
|
292488
|
-
|
|
292489
|
-
|
|
292490
|
-
path$2.join(projectRoot, LOCAL_ASSETS_DIR)
|
|
292491
|
-
);
|
|
292492
|
-
await writeCliProjectSessionDataFile(
|
|
292493
|
-
snapshot,
|
|
292494
|
-
path$2.join(projectRoot, LOCAL_DATA_FILE),
|
|
292495
|
-
{ origin: connection.origin, assetIndex }
|
|
292496
|
-
);
|
|
293422
|
+
await writeCliProjectSessionPreviewDataFile({
|
|
293423
|
+
session,
|
|
293424
|
+
connection: apiConnection,
|
|
293425
|
+
path: path$2.join(projectRoot, LOCAL_DATA_FILE),
|
|
293426
|
+
assetsDirectory: path$2.join(projectRoot, LOCAL_ASSETS_DIR)
|
|
293427
|
+
});
|
|
292497
293428
|
}
|
|
292498
293429
|
});
|
|
292499
293430
|
const toProjectSessionScreenshotResult = (result2) => {
|
|
@@ -292611,7 +293542,17 @@ const createCliMcpHost = async ({
|
|
|
292611
293542
|
input: input2,
|
|
292612
293543
|
startPreview: async (resolvedInput) => await previewHandlers.startPreview(resolvedInput, progress2)
|
|
292613
293544
|
});
|
|
292614
|
-
|
|
293545
|
+
if (result2.running === false || result2.url === void 0 || result2.mode === void 0) {
|
|
293546
|
+
throw new Error("Preview server did not start.");
|
|
293547
|
+
}
|
|
293548
|
+
return {
|
|
293549
|
+
...result2,
|
|
293550
|
+
...previewFreshness.status(),
|
|
293551
|
+
url: result2.url,
|
|
293552
|
+
pid: result2.pid,
|
|
293553
|
+
running: true,
|
|
293554
|
+
mode: result2.mode
|
|
293555
|
+
};
|
|
292615
293556
|
},
|
|
292616
293557
|
async getPreviewStatus() {
|
|
292617
293558
|
return { ...preview2.status(), ...previewFreshness.status() };
|
|
@@ -292682,6 +293623,9 @@ const createCliMcpHost = async ({
|
|
|
292682
293623
|
}
|
|
292683
293624
|
stderr.write(`${formatMcpStatusLine(message)}
|
|
292684
293625
|
`);
|
|
293626
|
+
},
|
|
293627
|
+
async dispose() {
|
|
293628
|
+
await previewHandlers.stopPreview();
|
|
292685
293629
|
}
|
|
292686
293630
|
};
|
|
292687
293631
|
};
|
|
@@ -292692,6 +293636,14 @@ const createCliMcpCore = (host) => createProjectSessionMcpCore({
|
|
|
292692
293636
|
`);
|
|
292693
293637
|
}
|
|
292694
293638
|
});
|
|
293639
|
+
const withMcpHost = async (createHost, callback) => {
|
|
293640
|
+
const host = await createHost();
|
|
293641
|
+
try {
|
|
293642
|
+
return await callback(host);
|
|
293643
|
+
} finally {
|
|
293644
|
+
await host.dispose().catch(() => void 0);
|
|
293645
|
+
}
|
|
293646
|
+
};
|
|
292695
293647
|
const assertMcpToolServerSupport = (tool, contract) => {
|
|
292696
293648
|
const operation = publicApiOperationByCommand.get(tool);
|
|
292697
293649
|
if (operation !== void 0 && publicApiOperationRequiresServerSupport(operation)) {
|
|
@@ -292725,105 +293677,74 @@ const executeMcpRunCall = async ({
|
|
|
292725
293677
|
});
|
|
292726
293678
|
return { result: result2, checkpoint };
|
|
292727
293679
|
};
|
|
292728
|
-
const __testing__ = {
|
|
292729
|
-
createMcpStatusReporter,
|
|
292730
|
-
formatMcpStatusLine,
|
|
292731
|
-
assertSingleOpCallToolSupported,
|
|
292732
|
-
assertPersistedMcpCheckpointAcknowledged,
|
|
292733
|
-
clearPersistedMcpCheckpoint,
|
|
292734
|
-
createMcpSingleOpCallErrorPayload,
|
|
292735
|
-
createMcpResourceErrorPayload: (error, elapsedMs) => ({
|
|
292736
|
-
ok: false,
|
|
292737
|
-
error: {
|
|
292738
|
-
code: getStableErrorCode(error) ?? "MCP_RESOURCE_FAILED",
|
|
292739
|
-
message: error instanceof Error ? error.message : String(error)
|
|
292740
|
-
},
|
|
292741
|
-
meta: { elapsedMs }
|
|
292742
|
-
}),
|
|
292743
|
-
createMcpRunErrorPayload,
|
|
292744
|
-
createMcpRunCheckpointStopPayload,
|
|
292745
|
-
getLoadedProjectSessionSnapshot,
|
|
292746
|
-
getResultCheckpoint,
|
|
292747
|
-
getMcpOperationInput,
|
|
292748
|
-
parseMcpSingleOpCallInput,
|
|
292749
|
-
validateSingleOpCallInput,
|
|
292750
|
-
isMcpToolCallFailure,
|
|
292751
|
-
getMcpToolCallError,
|
|
292752
|
-
applyMcpRunOptions,
|
|
292753
|
-
parseMcpRunCalls,
|
|
292754
|
-
parseMcpRunInput,
|
|
292755
|
-
readPersistedMcpCheckpoint,
|
|
292756
|
-
updatePersistedMcpCheckpoint,
|
|
292757
|
-
executeMcpRunCall,
|
|
292758
|
-
resolveMcpPreviewInput,
|
|
292759
|
-
resolveMcpScreenshotInput,
|
|
292760
|
-
startMcpPreview
|
|
292761
|
-
};
|
|
292762
293680
|
const mcpSingleOpCall = async (options) => {
|
|
292763
293681
|
if (options.tool === void 0 || options.tool === "") {
|
|
292764
293682
|
throw new Error("mcp single-op-call requires a tool name.");
|
|
292765
293683
|
}
|
|
293684
|
+
const tool = options.tool;
|
|
292766
293685
|
const startedAt = Date.now();
|
|
292767
293686
|
stderr.write(
|
|
292768
293687
|
`${formatMcpStatusLine(
|
|
292769
|
-
`single-op-call ${
|
|
293688
|
+
`single-op-call ${tool} started${options.dryRun === true ? " (dry run)" : ""}`
|
|
292770
293689
|
)}
|
|
292771
293690
|
`
|
|
292772
293691
|
);
|
|
292773
293692
|
try {
|
|
292774
|
-
assertSingleOpCallToolSupported(
|
|
293693
|
+
assertSingleOpCallToolSupported(tool);
|
|
292775
293694
|
const input2 = await parseMcpSingleOpCallInput(options);
|
|
292776
|
-
validateSingleOpCallInput(
|
|
292777
|
-
|
|
292778
|
-
projectId: options.project
|
|
292779
|
-
|
|
292780
|
-
|
|
292781
|
-
|
|
292782
|
-
|
|
292783
|
-
|
|
292784
|
-
|
|
292785
|
-
|
|
292786
|
-
|
|
292787
|
-
|
|
292788
|
-
|
|
292789
|
-
|
|
292790
|
-
|
|
292791
|
-
|
|
292792
|
-
|
|
292793
|
-
|
|
292794
|
-
|
|
292795
|
-
|
|
292796
|
-
|
|
292797
|
-
|
|
292798
|
-
|
|
292799
|
-
|
|
292800
|
-
|
|
293695
|
+
validateSingleOpCallInput(tool, input2);
|
|
293696
|
+
await withMcpHost(
|
|
293697
|
+
() => createCliMcpHost({ projectId: options.project }),
|
|
293698
|
+
async ({ host, apiContract, scope: scope2 }) => {
|
|
293699
|
+
assertMcpToolServerSupport(tool, apiContract);
|
|
293700
|
+
const core2 = createCliMcpCore(host);
|
|
293701
|
+
const persistedCheckpoint = tool === "checkpoint.ack" ? await readPersistedMcpCheckpoint(scope2) : void 0;
|
|
293702
|
+
await assertPersistedMcpCheckpointAcknowledged(
|
|
293703
|
+
tool,
|
|
293704
|
+
core2.listTools(),
|
|
293705
|
+
scope2
|
|
293706
|
+
);
|
|
293707
|
+
if (options.refresh === true && tool !== "refresh") {
|
|
293708
|
+
await core2.callTool({ name: "refresh" });
|
|
293709
|
+
}
|
|
293710
|
+
const result2 = await core2.callTool({
|
|
293711
|
+
name: tool,
|
|
293712
|
+
input: input2,
|
|
293713
|
+
dryRun: options.dryRun
|
|
293714
|
+
});
|
|
293715
|
+
if (isMcpToolCallFailure(result2)) {
|
|
293716
|
+
stderr.write(
|
|
293717
|
+
`${formatMcpStatusLine(
|
|
293718
|
+
`single-op-call ${tool} failed in ${Date.now() - startedAt}ms`
|
|
293719
|
+
)}
|
|
292801
293720
|
`
|
|
292802
|
-
|
|
292803
|
-
|
|
292804
|
-
|
|
292805
|
-
|
|
292806
|
-
|
|
292807
|
-
|
|
292808
|
-
|
|
292809
|
-
|
|
292810
|
-
|
|
292811
|
-
|
|
292812
|
-
|
|
292813
|
-
|
|
292814
|
-
|
|
292815
|
-
|
|
292816
|
-
|
|
292817
|
-
|
|
292818
|
-
|
|
292819
|
-
|
|
293721
|
+
);
|
|
293722
|
+
printJson(result2.structuredContent);
|
|
293723
|
+
throw new HandledCliError();
|
|
293724
|
+
}
|
|
293725
|
+
if (tool === "checkpoint.ack" && persistedCheckpoint?.nextCommand !== void 0 && isPlainRecord(result2.structuredContent.data)) {
|
|
293726
|
+
result2.structuredContent.data.nextCommand = persistedCheckpoint.nextCommand;
|
|
293727
|
+
}
|
|
293728
|
+
await updatePersistedMcpCheckpoint({
|
|
293729
|
+
tool,
|
|
293730
|
+
structuredContent: result2.structuredContent,
|
|
293731
|
+
scope: scope2
|
|
293732
|
+
});
|
|
293733
|
+
const session = result2.structuredContent.meta.session;
|
|
293734
|
+
const committed = session === void 0 ? "" : `; committed=${session.committed}`;
|
|
293735
|
+
stderr.write(
|
|
293736
|
+
`${formatMcpStatusLine(
|
|
293737
|
+
`single-op-call ${tool} succeeded in ${Date.now() - startedAt}ms${committed}`
|
|
293738
|
+
)}
|
|
292820
293739
|
`
|
|
293740
|
+
);
|
|
293741
|
+
if (options.printSuccess === void 0) {
|
|
293742
|
+
printJson(result2.structuredContent);
|
|
293743
|
+
} else {
|
|
293744
|
+
options.printSuccess(result2.structuredContent.data);
|
|
293745
|
+
}
|
|
293746
|
+
}
|
|
292821
293747
|
);
|
|
292822
|
-
if (options.printSuccess === void 0) {
|
|
292823
|
-
printJson(result2.structuredContent);
|
|
292824
|
-
} else {
|
|
292825
|
-
options.printSuccess(result2.structuredContent.data);
|
|
292826
|
-
}
|
|
292827
293748
|
} catch (error) {
|
|
292828
293749
|
if (isHandledCliError(error)) {
|
|
292829
293750
|
throw error;
|
|
@@ -292834,7 +293755,7 @@ const mcpSingleOpCall = async (options) => {
|
|
|
292834
293755
|
});
|
|
292835
293756
|
stderr.write(
|
|
292836
293757
|
`${formatMcpStatusLine(
|
|
292837
|
-
`single-op-call ${
|
|
293758
|
+
`single-op-call ${tool} failed in ${payload.meta.elapsedMs}ms: ${payload.error.message}`
|
|
292838
293759
|
)}
|
|
292839
293760
|
`
|
|
292840
293761
|
);
|
|
@@ -292872,39 +293793,49 @@ const runMcpProjectsBatch = async ({
|
|
|
292872
293793
|
)}
|
|
292873
293794
|
`
|
|
292874
293795
|
);
|
|
292875
|
-
|
|
292876
|
-
projectRoot: project.root
|
|
292877
|
-
|
|
292878
|
-
|
|
292879
|
-
|
|
292880
|
-
|
|
292881
|
-
|
|
292882
|
-
|
|
292883
|
-
|
|
292884
|
-
|
|
292885
|
-
|
|
292886
|
-
|
|
292887
|
-
|
|
292888
|
-
|
|
292889
|
-
|
|
292890
|
-
|
|
292891
|
-
|
|
292892
|
-
|
|
292893
|
-
|
|
292894
|
-
|
|
292895
|
-
|
|
292896
|
-
|
|
292897
|
-
|
|
292898
|
-
|
|
292899
|
-
|
|
292900
|
-
|
|
292901
|
-
|
|
292902
|
-
|
|
292903
|
-
|
|
292904
|
-
|
|
292905
|
-
|
|
293796
|
+
await withMcpHost(
|
|
293797
|
+
() => createCliMcpHost({ projectRoot: project.root }),
|
|
293798
|
+
async ({ host, apiContract }) => {
|
|
293799
|
+
const core2 = createCliMcpCore(host);
|
|
293800
|
+
const tools = new Map(
|
|
293801
|
+
core2.listTools().map((tool) => [tool.name, tool])
|
|
293802
|
+
);
|
|
293803
|
+
for (const call of project.calls.slice(startCall)) {
|
|
293804
|
+
assertMcpToolServerSupport(call.tool, apiContract);
|
|
293805
|
+
const tool = tools.get(call.tool);
|
|
293806
|
+
assertMcpBatchMutationApproved({
|
|
293807
|
+
projectId: project.id,
|
|
293808
|
+
call,
|
|
293809
|
+
method: tool?.annotations.method,
|
|
293810
|
+
approved: options.approveMutations === true
|
|
293811
|
+
});
|
|
293812
|
+
}
|
|
293813
|
+
for (let index2 = startCall; index2 < project.calls.length; index2++) {
|
|
293814
|
+
const call = project.calls[index2];
|
|
293815
|
+
const tool = tools.get(call.tool);
|
|
293816
|
+
await callStarted(
|
|
293817
|
+
index2,
|
|
293818
|
+
call.dryRun || tool?.annotations.method !== "mutation"
|
|
293819
|
+
);
|
|
293820
|
+
const { checkpoint } = await executeMcpRunCall({
|
|
293821
|
+
core: core2,
|
|
293822
|
+
call,
|
|
293823
|
+
scope: { projectRoot: project.root }
|
|
293824
|
+
});
|
|
293825
|
+
await callSucceeded(index2 + 1);
|
|
293826
|
+
const nextTool = project.calls[index2 + 1]?.tool;
|
|
293827
|
+
if (checkpoint !== void 0 && index2 + 1 < project.calls.length && (nextTool === void 0 || isReadOnlyProjectSessionMcpToolCall(
|
|
293828
|
+
nextTool,
|
|
293829
|
+
core2.listTools()
|
|
293830
|
+
) === false)) {
|
|
293831
|
+
throw createMcpInputError(
|
|
293832
|
+
checkpoint.message,
|
|
293833
|
+
"CHECKPOINT_REQUIRED"
|
|
293834
|
+
);
|
|
293835
|
+
}
|
|
293836
|
+
}
|
|
292906
293837
|
}
|
|
292907
|
-
|
|
293838
|
+
);
|
|
292908
293839
|
}
|
|
292909
293840
|
});
|
|
292910
293841
|
const succeeded = reports.filter(
|
|
@@ -292970,17 +293901,21 @@ const mcpRun = async (options) => {
|
|
|
292970
293901
|
const results = [];
|
|
292971
293902
|
let core2;
|
|
292972
293903
|
let scope2 = {};
|
|
293904
|
+
let disposeHost = async () => void 0;
|
|
292973
293905
|
try {
|
|
292974
293906
|
const mcpHost = await createCliMcpHost({
|
|
292975
|
-
projectId: options.project
|
|
293907
|
+
projectId: options.project,
|
|
293908
|
+
managePreviewProcessSignals: false
|
|
292976
293909
|
});
|
|
292977
293910
|
const { host, apiContract } = mcpHost;
|
|
293911
|
+
disposeHost = mcpHost.dispose;
|
|
292978
293912
|
scope2 = mcpHost.scope;
|
|
292979
293913
|
for (const call of calls) {
|
|
292980
293914
|
assertMcpToolServerSupport(call.tool, apiContract);
|
|
292981
293915
|
}
|
|
292982
293916
|
core2 = createCliMcpCore(host);
|
|
292983
293917
|
} catch (error) {
|
|
293918
|
+
await disposeHost().catch(() => void 0);
|
|
292984
293919
|
reportMcpRunPreflightFailure({
|
|
292985
293920
|
error,
|
|
292986
293921
|
startedAt,
|
|
@@ -292988,78 +293923,94 @@ const mcpRun = async (options) => {
|
|
|
292988
293923
|
});
|
|
292989
293924
|
throw new HandledCliError();
|
|
292990
293925
|
}
|
|
292991
|
-
|
|
292992
|
-
|
|
292993
|
-
|
|
292994
|
-
|
|
292995
|
-
|
|
292996
|
-
|
|
292997
|
-
|
|
292998
|
-
|
|
292999
|
-
|
|
293000
|
-
|
|
293001
|
-
|
|
293002
|
-
|
|
293003
|
-
scope: scope2
|
|
293004
|
-
});
|
|
293005
|
-
const session = result2.structuredContent.meta.session;
|
|
293006
|
-
const committed = session === void 0 ? "" : `; committed=${session.committed}`;
|
|
293926
|
+
let activeCall;
|
|
293927
|
+
const disposeTerminationHandlers = installMcpRunTerminationHandlers({
|
|
293928
|
+
getActiveCall: () => activeCall,
|
|
293929
|
+
totalCalls: calls.length,
|
|
293930
|
+
results,
|
|
293931
|
+
startedAt,
|
|
293932
|
+
disposeHost: () => disposeHost()
|
|
293933
|
+
});
|
|
293934
|
+
try {
|
|
293935
|
+
for (const [index2, call] of calls.entries()) {
|
|
293936
|
+
const callNumber = index2 + 1;
|
|
293937
|
+
activeCall = { number: callNumber, tool: call.tool };
|
|
293007
293938
|
stderr.write(
|
|
293008
293939
|
`${formatMcpStatusLine(
|
|
293009
|
-
`run ${callNumber}/${calls.length} ${call.tool}
|
|
293940
|
+
`run ${callNumber}/${calls.length} ${call.tool} started${call.dryRun ? " (dry run)" : ""}`
|
|
293010
293941
|
)}
|
|
293011
293942
|
`
|
|
293012
293943
|
);
|
|
293013
|
-
|
|
293014
|
-
|
|
293015
|
-
|
|
293016
|
-
|
|
293017
|
-
|
|
293018
|
-
|
|
293019
|
-
|
|
293020
|
-
const
|
|
293021
|
-
|
|
293022
|
-
|
|
293944
|
+
try {
|
|
293945
|
+
const { result: result2, checkpoint } = await executeMcpRunCall({
|
|
293946
|
+
core: core2,
|
|
293947
|
+
call,
|
|
293948
|
+
scope: scope2
|
|
293949
|
+
});
|
|
293950
|
+
const session = result2.structuredContent.meta.session;
|
|
293951
|
+
const committed = session === void 0 ? "" : `; committed=${session.committed}`;
|
|
293952
|
+
stderr.write(
|
|
293953
|
+
`${formatMcpStatusLine(
|
|
293954
|
+
`run ${callNumber}/${calls.length} ${call.tool} succeeded${committed}`
|
|
293955
|
+
)}
|
|
293956
|
+
`
|
|
293957
|
+
);
|
|
293958
|
+
results.push({
|
|
293959
|
+
tool: call.tool,
|
|
293960
|
+
ok: true,
|
|
293961
|
+
structuredContent: result2.structuredContent
|
|
293962
|
+
});
|
|
293963
|
+
activeCall = void 0;
|
|
293964
|
+
const nextTool = calls[callNumber]?.tool;
|
|
293965
|
+
if (checkpoint !== void 0 && callNumber < calls.length && (nextTool === void 0 || isReadOnlyProjectSessionMcpToolCall(nextTool, core2.listTools()) === false)) {
|
|
293966
|
+
const checkpointStopPayload = createMcpRunCheckpointStopPayload({
|
|
293967
|
+
checkpoint,
|
|
293968
|
+
completedCalls: callNumber,
|
|
293969
|
+
totalCalls: calls.length,
|
|
293970
|
+
results,
|
|
293971
|
+
elapsedMs: Date.now() - startedAt
|
|
293972
|
+
});
|
|
293973
|
+
stderr.write(
|
|
293974
|
+
`${formatMcpStatusLine(
|
|
293975
|
+
`run stopped after ${callNumber}/${calls.length} ${call.tool}: ${checkpointStopPayload.error.message}`
|
|
293976
|
+
)}
|
|
293977
|
+
`
|
|
293978
|
+
);
|
|
293979
|
+
printJson(checkpointStopPayload);
|
|
293980
|
+
throw new McpRunCheckpointStop();
|
|
293981
|
+
}
|
|
293982
|
+
} catch (error) {
|
|
293983
|
+
activeCall = void 0;
|
|
293984
|
+
if (error instanceof McpRunCheckpointStop) {
|
|
293985
|
+
throw new HandledCliError();
|
|
293986
|
+
}
|
|
293987
|
+
const structuredError2 = getMcpRunError(error);
|
|
293988
|
+
results.push({
|
|
293989
|
+
tool: call.tool,
|
|
293990
|
+
ok: false,
|
|
293991
|
+
error: structuredError2
|
|
293992
|
+
});
|
|
293993
|
+
const payload = createMcpRunErrorPayload({
|
|
293994
|
+
error: structuredError2,
|
|
293995
|
+
completedCalls: index2,
|
|
293996
|
+
failedCall: callNumber,
|
|
293023
293997
|
totalCalls: calls.length,
|
|
293024
293998
|
results,
|
|
293025
293999
|
elapsedMs: Date.now() - startedAt
|
|
293026
294000
|
});
|
|
293027
294001
|
stderr.write(
|
|
293028
294002
|
`${formatMcpStatusLine(
|
|
293029
|
-
`run
|
|
294003
|
+
`run ${callNumber}/${calls.length} ${call.tool} failed: ${payload.error.message}`
|
|
293030
294004
|
)}
|
|
293031
294005
|
`
|
|
293032
294006
|
);
|
|
293033
|
-
printJson(
|
|
293034
|
-
throw new McpRunCheckpointStop();
|
|
293035
|
-
}
|
|
293036
|
-
} catch (error) {
|
|
293037
|
-
if (error instanceof McpRunCheckpointStop) {
|
|
294007
|
+
printJson(payload);
|
|
293038
294008
|
throw new HandledCliError();
|
|
293039
294009
|
}
|
|
293040
|
-
const structuredError2 = getMcpRunError(error);
|
|
293041
|
-
results.push({
|
|
293042
|
-
tool: call.tool,
|
|
293043
|
-
ok: false,
|
|
293044
|
-
error: structuredError2
|
|
293045
|
-
});
|
|
293046
|
-
const payload = createMcpRunErrorPayload({
|
|
293047
|
-
error: structuredError2,
|
|
293048
|
-
completedCalls: index2,
|
|
293049
|
-
failedCall: callNumber,
|
|
293050
|
-
totalCalls: calls.length,
|
|
293051
|
-
results,
|
|
293052
|
-
elapsedMs: Date.now() - startedAt
|
|
293053
|
-
});
|
|
293054
|
-
stderr.write(
|
|
293055
|
-
`${formatMcpStatusLine(
|
|
293056
|
-
`run ${callNumber}/${calls.length} ${call.tool} failed: ${payload.error.message}`
|
|
293057
|
-
)}
|
|
293058
|
-
`
|
|
293059
|
-
);
|
|
293060
|
-
printJson(payload);
|
|
293061
|
-
throw new HandledCliError();
|
|
293062
294010
|
}
|
|
294011
|
+
} finally {
|
|
294012
|
+
disposeTerminationHandlers();
|
|
294013
|
+
await disposeHost().catch(() => void 0);
|
|
293063
294014
|
}
|
|
293064
294015
|
stderr.write(
|
|
293065
294016
|
`${formatMcpStatusLine(
|
|
@@ -293130,19 +294081,30 @@ const mcpReadResource = async (options) => {
|
|
|
293130
294081
|
const mcp = async (options = {}) => {
|
|
293131
294082
|
const status = createMcpStatusReporter();
|
|
293132
294083
|
let didReportClose = false;
|
|
294084
|
+
let disposeHost = async () => void 0;
|
|
293133
294085
|
const reportClose = () => {
|
|
293134
294086
|
if (didReportClose) {
|
|
293135
294087
|
return;
|
|
293136
294088
|
}
|
|
293137
294089
|
didReportClose = true;
|
|
293138
294090
|
status.connectionClosed();
|
|
294091
|
+
void disposeHost().catch((error) => {
|
|
294092
|
+
status.connectionError(
|
|
294093
|
+
error instanceof Error ? error : new Error(String(error))
|
|
294094
|
+
);
|
|
294095
|
+
});
|
|
293139
294096
|
};
|
|
293140
294097
|
stdin.once("end", reportClose);
|
|
293141
294098
|
stdin.once("close", reportClose);
|
|
293142
294099
|
status.starting();
|
|
293143
|
-
const { host, toolCount, reportLog, apiContract } = await createCliMcpHost({
|
|
294100
|
+
const { host, toolCount, reportLog, apiContract, dispose } = await createCliMcpHost({
|
|
293144
294101
|
projectId: options.project
|
|
293145
294102
|
});
|
|
294103
|
+
disposeHost = dispose;
|
|
294104
|
+
if (didReportClose) {
|
|
294105
|
+
await disposeHost().catch(() => void 0);
|
|
294106
|
+
return;
|
|
294107
|
+
}
|
|
293146
294108
|
status.sessionReady();
|
|
293147
294109
|
status.apiContract(apiContract);
|
|
293148
294110
|
status.ready(toolCount);
|
|
@@ -293160,6 +294122,35 @@ const mcp = async (options = {}) => {
|
|
|
293160
294122
|
status.connectionError(error);
|
|
293161
294123
|
};
|
|
293162
294124
|
};
|
|
294125
|
+
const __testing__ = {
|
|
294126
|
+
createMcpStatusReporter,
|
|
294127
|
+
formatMcpStatusLine,
|
|
294128
|
+
assertSingleOpCallToolSupported,
|
|
294129
|
+
createMcpSingleOpCallErrorPayload,
|
|
294130
|
+
createMcpResourceErrorPayload: (error, elapsedMs) => ({
|
|
294131
|
+
ok: false,
|
|
294132
|
+
error: {
|
|
294133
|
+
code: getStableErrorCode(error) ?? "MCP_RESOURCE_FAILED",
|
|
294134
|
+
message: error instanceof Error ? error.message : String(error)
|
|
294135
|
+
},
|
|
294136
|
+
meta: { elapsedMs }
|
|
294137
|
+
}),
|
|
294138
|
+
createMcpRunErrorPayload,
|
|
294139
|
+
reportMcpRunTermination,
|
|
294140
|
+
createMcpRunTerminationController,
|
|
294141
|
+
createMcpRunCheckpointStopPayload,
|
|
294142
|
+
getLoadedProjectSessionSnapshot,
|
|
294143
|
+
getMcpOperationInput,
|
|
294144
|
+
parseMcpSingleOpCallInput,
|
|
294145
|
+
validateSingleOpCallInput,
|
|
294146
|
+
isMcpToolCallFailure,
|
|
294147
|
+
getMcpToolCallError,
|
|
294148
|
+
applyMcpRunOptions,
|
|
294149
|
+
parseMcpRunCalls,
|
|
294150
|
+
parseMcpRunInput,
|
|
294151
|
+
executeMcpRunCall,
|
|
294152
|
+
withMcpHost
|
|
294153
|
+
};
|
|
293163
294154
|
const screenshotOptions = (yargs) => yargs.positional("url", {
|
|
293164
294155
|
type: "string",
|
|
293165
294156
|
describe: "Absolute URL to capture"
|