mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-02 18:18:35 +09:00
This PR expands Webstudio MCP and CLI into a more complete agent authoring interface. Agents can discover project capabilities, edit real Webstudio content, use data/resources, verify visual output, and report useful issues when workflows fail. ## MCP User Capabilities - Create, update, duplicate, reorder, and delete pages. - Create pages from templates and manage reusable page templates. - Inspect project pages, instances, props, styles, assets, variables, resources, and metadata. - Insert Webstudio components and authored JSX fragments into pages. - Edit text, props, styles, breakpoints, design tokens, and CSS variables. - Update page metadata, SEO fields, redirects, marketplace metadata, and project settings. - Create and use static data variables. - Create and use dynamic resources fetched over HTTP. - Use REST, GraphQL, and built-in system resources in authored pages. - Bind data variables and resources to visible page content. - Create resource-backed pages such as sitemap XML, structured SEO data, and API-driven content sections. - Preview current project state without requiring a publish step. - Capture screenshots for visual verification. - Verify responsive designs across desktop, tablet, and mobile breakpoints. - Use screenshot diff and OCR-style checks to inspect rendered output. - Reset and refresh local MCP project state when needed. - Discover available CLI/MCP commands from generated manuals. - Use clearer examples for resources, data variables, JSX authoring, previewing, and visual checks. - Read marketplace product metadata without requiring a full project snapshot. - Get safer guidance when using low-level patch tools. - Get a ready-to-copy Discord report template when MCP/CLI behavior is confusing or broken.
99 lines
2.2 KiB
TypeScript
99 lines
2.2 KiB
TypeScript
import type { Page } from "playwright";
|
|
|
|
type SyncStatusDotStatus = "idle" | "pending" | "saved" | "error";
|
|
|
|
const getSyncStatusDot = (page: Page) =>
|
|
page.getByRole("status", { name: /^Sync status:/ });
|
|
|
|
const syncStatusDotSelector = '[role="status"][aria-label^="Sync status:"]';
|
|
|
|
const getSyncStatusDotByStatus = ({
|
|
page,
|
|
status,
|
|
}: {
|
|
page: Page;
|
|
status: SyncStatusDotStatus;
|
|
}) =>
|
|
page.locator(
|
|
`[role="status"][aria-label^="Sync status:"][data-sync-status="${status}"]`
|
|
);
|
|
|
|
const getSyncStatusLabel = async (page: Page) =>
|
|
(await getSyncStatusDot(page).getAttribute("aria-label")) ??
|
|
"Sync status: error";
|
|
|
|
const waitForAnySyncStatus = async ({
|
|
page,
|
|
statuses,
|
|
timeout,
|
|
}: {
|
|
page: Page;
|
|
statuses: SyncStatusDotStatus[];
|
|
timeout: number;
|
|
}) => {
|
|
const handle = await page.waitForFunction(
|
|
({ selector, statuses }) => {
|
|
const dot = document.querySelector<HTMLElement>(selector);
|
|
const status = dot?.dataset.syncStatus;
|
|
if (status === undefined) {
|
|
return false;
|
|
}
|
|
if ((statuses as readonly string[]).includes(status)) {
|
|
return status;
|
|
}
|
|
return false;
|
|
},
|
|
{ selector: syncStatusDotSelector, statuses },
|
|
{ timeout }
|
|
);
|
|
return (await handle.jsonValue()) as SyncStatusDotStatus;
|
|
};
|
|
|
|
export const waitForSyncStatus = async ({
|
|
page,
|
|
status,
|
|
timeout = 10_000,
|
|
}: {
|
|
page: Page;
|
|
status: SyncStatusDotStatus;
|
|
timeout?: number;
|
|
}) => {
|
|
await getSyncStatusDot(page).waitFor({
|
|
state: "attached",
|
|
timeout,
|
|
});
|
|
await getSyncStatusDotByStatus({ page, status }).waitFor({ timeout });
|
|
};
|
|
|
|
export const waitForChangeToBeSaved = async ({
|
|
page,
|
|
timeout = 10_000,
|
|
}: {
|
|
page: Page;
|
|
timeout?: number;
|
|
}) => {
|
|
const firstStatus = await waitForAnySyncStatus({
|
|
page,
|
|
statuses: ["pending", "error"],
|
|
timeout,
|
|
});
|
|
|
|
if (firstStatus === "error") {
|
|
throw new Error(
|
|
`Expected change to save, received ${await getSyncStatusLabel(page)}`
|
|
);
|
|
}
|
|
|
|
const finalStatus = await waitForAnySyncStatus({
|
|
page,
|
|
statuses: ["saved", "idle", "error"],
|
|
timeout,
|
|
});
|
|
|
|
if (finalStatus === "error") {
|
|
throw new Error(
|
|
`Expected change to save, received ${await getSyncStatusLabel(page)}`
|
|
);
|
|
}
|
|
};
|