@muloka/figma-console-mcp 0.5.2 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/dist/apps/design-system-dashboard/mcp-app.html +521 -0
- package/dist/apps/token-browser/mcp-app.html +410 -0
- package/dist/cloudflare/apps/design-system-dashboard/scoring/accessibility.js +277 -0
- package/dist/cloudflare/apps/design-system-dashboard/scoring/component-metadata.js +357 -0
- package/dist/cloudflare/apps/design-system-dashboard/scoring/consistency.js +341 -0
- package/dist/cloudflare/apps/design-system-dashboard/scoring/coverage.js +230 -0
- package/dist/cloudflare/apps/design-system-dashboard/scoring/engine.js +92 -0
- package/dist/cloudflare/apps/design-system-dashboard/scoring/naming-semantics.js +308 -0
- package/dist/cloudflare/apps/design-system-dashboard/scoring/token-architecture.js +349 -0
- package/dist/cloudflare/apps/design-system-dashboard/scoring/types.js +40 -0
- package/dist/cloudflare/apps/design-system-dashboard/server.js +159 -0
- package/dist/cloudflare/apps/token-browser/server.js +136 -0
- package/dist/cloudflare/browser-manager.js +157 -0
- package/dist/cloudflare/core/accessibility-tools.js +361 -0
- package/dist/cloudflare/core/annotation-tools.js +230 -0
- package/dist/cloudflare/core/cloud-websocket-connector.js +419 -0
- package/dist/cloudflare/core/cloud-websocket-relay.js +198 -0
- package/dist/cloudflare/core/comment-tools.js +292 -0
- package/dist/cloudflare/core/config.js +153 -0
- package/dist/cloudflare/core/console-monitor.js +427 -0
- package/dist/cloudflare/core/deep-component-tools.js +128 -0
- package/dist/cloudflare/core/design-code-tools.js +2793 -0
- package/dist/cloudflare/core/design-system-manifest.js +265 -0
- package/dist/cloudflare/core/design-system-tools.js +882 -0
- package/dist/cloudflare/core/diagnose-tool.js +100 -0
- package/dist/cloudflare/core/diff/changelog-formatter.js +275 -0
- package/dist/cloudflare/core/diff/diff-engine.js +334 -0
- package/dist/cloudflare/core/diff/property-compare.js +36 -0
- package/dist/cloudflare/core/diff/version-cache.js +74 -0
- package/dist/cloudflare/core/enrichment/enrichment-service.js +374 -0
- package/dist/cloudflare/core/enrichment/index.js +7 -0
- package/dist/cloudflare/core/enrichment/relationship-mapper.js +351 -0
- package/dist/cloudflare/core/enrichment/style-resolver.js +346 -0
- package/dist/cloudflare/core/figjam-tools.js +547 -0
- package/dist/cloudflare/core/figma-api.js +509 -0
- package/dist/cloudflare/core/figma-connector.js +7 -0
- package/dist/cloudflare/core/figma-reconstruction-spec.js +402 -0
- package/dist/cloudflare/core/figma-style-extractor.js +311 -0
- package/dist/cloudflare/core/figma-tools.js +3353 -0
- package/dist/cloudflare/core/identity.js +96 -0
- package/dist/cloudflare/core/library-tools.js +580 -0
- package/dist/cloudflare/core/logger.js +53 -0
- package/dist/cloudflare/core/port-discovery.js +935 -0
- package/dist/cloudflare/core/resolve-package-root.js +11 -0
- package/dist/cloudflare/core/slides-tools.js +714 -0
- package/dist/cloudflare/core/slot-tools.js +334 -0
- package/dist/cloudflare/core/snippet-injector.js +96 -0
- package/dist/cloudflare/core/tokens/alias-resolver.js +205 -0
- package/dist/cloudflare/core/tokens/config.js +294 -0
- package/dist/cloudflare/core/tokens/dialect.js +232 -0
- package/dist/cloudflare/core/tokens/figma-converter.js +323 -0
- package/dist/cloudflare/core/tokens/formatters/css-vars.js +338 -0
- package/dist/cloudflare/core/tokens/formatters/dtcg.js +376 -0
- package/dist/cloudflare/core/tokens/formatters/index.js +45 -0
- package/dist/cloudflare/core/tokens/formatters/json.js +205 -0
- package/dist/cloudflare/core/tokens/formatters/less.js +4 -0
- package/dist/cloudflare/core/tokens/formatters/scss.js +258 -0
- package/dist/cloudflare/core/tokens/formatters/stubs.js +13 -0
- package/dist/cloudflare/core/tokens/formatters/style-dictionary-v3.js +213 -0
- package/dist/cloudflare/core/tokens/formatters/tailwind-v3.js +237 -0
- package/dist/cloudflare/core/tokens/formatters/tailwind-v4.js +335 -0
- package/dist/cloudflare/core/tokens/formatters/tokens-studio.js +256 -0
- package/dist/cloudflare/core/tokens/formatters/ts-module.js +198 -0
- package/dist/cloudflare/core/tokens/index.js +16 -0
- package/dist/cloudflare/core/tokens/parsers/css-vars.js +4 -0
- package/dist/cloudflare/core/tokens/parsers/dtcg.js +280 -0
- package/dist/cloudflare/core/tokens/parsers/index.js +138 -0
- package/dist/cloudflare/core/tokens/parsers/json.js +7 -0
- package/dist/cloudflare/core/tokens/parsers/scss.js +4 -0
- package/dist/cloudflare/core/tokens/parsers/stubs.js +20 -0
- package/dist/cloudflare/core/tokens/parsers/style-dictionary-v3.js +4 -0
- package/dist/cloudflare/core/tokens/parsers/tailwind-v3.js +4 -0
- package/dist/cloudflare/core/tokens/parsers/tailwind-v4.js +4 -0
- package/dist/cloudflare/core/tokens/parsers/tokens-studio.js +4 -0
- package/dist/cloudflare/core/tokens/schemas.js +152 -0
- package/dist/cloudflare/core/tokens/transforms/color.js +12 -0
- package/dist/cloudflare/core/tokens/transforms/index.js +29 -0
- package/dist/cloudflare/core/tokens/transforms/size.js +7 -0
- package/dist/cloudflare/core/tokens/types.js +18 -0
- package/dist/cloudflare/core/tokens-tools.js +1788 -0
- package/dist/cloudflare/core/types/design-code.js +4 -0
- package/dist/cloudflare/core/types/enriched.js +5 -0
- package/dist/cloudflare/core/types/index.js +4 -0
- package/dist/cloudflare/core/variable-resolver.js +85 -0
- package/dist/cloudflare/core/version-tools.js +1199 -0
- package/dist/cloudflare/core/websocket-connector.js +436 -0
- package/dist/cloudflare/core/websocket-server.js +1009 -0
- package/dist/cloudflare/core/write-tools.js +2506 -0
- package/dist/cloudflare/index.js +3010 -0
- package/dist/core/accessibility-tools.d.ts.map +1 -1
- package/dist/core/accessibility-tools.js +7 -6
- package/dist/core/accessibility-tools.js.map +1 -1
- package/dist/core/http-request-guard.d.ts +20 -0
- package/dist/core/http-request-guard.d.ts.map +1 -0
- package/dist/core/http-request-guard.js +61 -0
- package/dist/core/http-request-guard.js.map +1 -0
- package/dist/core/tokens/schemas.d.ts +6 -6
- package/dist/local.d.ts.map +1 -1
- package/dist/local.js +4 -0
- package/dist/local.js.map +1 -1
- package/figma-desktop-bridge/ui.html +7 -0
- package/package.json +2 -2
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Design System Dashboard MCP App - Server Registration
|
|
3
|
+
*
|
|
4
|
+
* Registers tools and resource for the Design System Dashboard MCP App.
|
|
5
|
+
* Uses the official @modelcontextprotocol/ext-apps helpers for proper
|
|
6
|
+
* MCP Apps protocol compatibility with Claude Desktop.
|
|
7
|
+
*
|
|
8
|
+
* Data flow:
|
|
9
|
+
* 1. LLM calls figma_audit_design_system → server fetches + scores data,
|
|
10
|
+
* returns SHORT summary to LLM (avoids context exhaustion)
|
|
11
|
+
* 2. UI opens, connects, calls ds_dashboard_refresh (app-only visibility)
|
|
12
|
+
* 3. ds_dashboard_refresh returns full JSON → UI renders
|
|
13
|
+
*/
|
|
14
|
+
import { readFile } from "node:fs/promises";
|
|
15
|
+
import { dirname, resolve } from "node:path";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
import { RESOURCE_MIME_TYPE, registerAppResource, registerAppTool, } from "@modelcontextprotocol/ext-apps/server";
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
import { scoreDesignSystem } from "./scoring/engine.js";
|
|
20
|
+
const DASHBOARD_URI = "ui://figma-console/design-system-dashboard";
|
|
21
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
22
|
+
const __dirname = dirname(__filename);
|
|
23
|
+
// Shared state
|
|
24
|
+
let lastFileUrl;
|
|
25
|
+
/**
|
|
26
|
+
* Register the Design System Dashboard MCP App with the server.
|
|
27
|
+
*
|
|
28
|
+
* @param server - The MCP server instance
|
|
29
|
+
* @param getDesignSystemData - Function to fetch raw design system data from Figma
|
|
30
|
+
* @param getCurrentUrl - Optional function to get the current browser URL (for lastFileUrl tracking)
|
|
31
|
+
*/
|
|
32
|
+
export function registerDesignSystemDashboardApp(server, getDesignSystemData, getCurrentUrl) {
|
|
33
|
+
// Tool: fetches + scores data, returns SHORT summary to LLM
|
|
34
|
+
registerAppTool(server, "figma_audit_design_system", {
|
|
35
|
+
title: "Audit Design System Health",
|
|
36
|
+
description: "Analyze the health and AI-readiness of a Figma file's design system. Produces a scored dashboard evaluating naming conventions, token architecture, component metadata, accessibility, consistency, and coverage. Results are displayed in the dashboard UI.",
|
|
37
|
+
inputSchema: {
|
|
38
|
+
fileUrl: z
|
|
39
|
+
.string()
|
|
40
|
+
.url()
|
|
41
|
+
.optional()
|
|
42
|
+
.describe("Figma file URL. If not provided, uses the currently active file."),
|
|
43
|
+
},
|
|
44
|
+
_meta: {
|
|
45
|
+
ui: { resourceUri: DASHBOARD_URI },
|
|
46
|
+
},
|
|
47
|
+
}, async ({ fileUrl }) => {
|
|
48
|
+
try {
|
|
49
|
+
// Track the actual URL used (explicit or current browser URL)
|
|
50
|
+
// This ensures ds_dashboard_refresh uses the correct file
|
|
51
|
+
lastFileUrl = fileUrl || getCurrentUrl?.() || undefined;
|
|
52
|
+
const data = await getDesignSystemData(fileUrl);
|
|
53
|
+
const scored = scoreDesignSystem(data);
|
|
54
|
+
const categorySummaries = scored.categories
|
|
55
|
+
.map((c) => `${c.label}: ${c.score}/100`)
|
|
56
|
+
.join(", ");
|
|
57
|
+
const fileName = scored.fileInfo?.name || "Unknown file";
|
|
58
|
+
const unavailableNote = scored.dataAvailability && !scored.dataAvailability.variables
|
|
59
|
+
? " Note: Variable/token data was unavailable (requires Enterprise plan or Desktop Bridge). Token Architecture scores reflect missing data, not actual quality."
|
|
60
|
+
: "";
|
|
61
|
+
return {
|
|
62
|
+
content: [
|
|
63
|
+
{
|
|
64
|
+
type: "text",
|
|
65
|
+
text: `Design System: ${fileName}. Health: ${scored.overall}/100 — ${scored.status}. ${categorySummaries}.${unavailableNote} The dashboard UI is now showing detailed results.`,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
72
|
+
return {
|
|
73
|
+
content: [
|
|
74
|
+
{
|
|
75
|
+
type: "text",
|
|
76
|
+
text: `Design System Dashboard error: ${errorMessage}`,
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
isError: true,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
// Tool: returns full JSON data (app-only, hidden from LLM)
|
|
84
|
+
registerAppTool(server, "ds_dashboard_refresh", {
|
|
85
|
+
title: "Dashboard Refresh",
|
|
86
|
+
description: "Refresh dashboard data (called from MCP App UI)",
|
|
87
|
+
inputSchema: {
|
|
88
|
+
fileUrl: z
|
|
89
|
+
.string()
|
|
90
|
+
.url()
|
|
91
|
+
.optional()
|
|
92
|
+
.describe("Figma file URL to refresh data for."),
|
|
93
|
+
},
|
|
94
|
+
_meta: {
|
|
95
|
+
ui: {
|
|
96
|
+
resourceUri: DASHBOARD_URI,
|
|
97
|
+
visibility: ["app"],
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
}, async ({ fileUrl }) => {
|
|
101
|
+
try {
|
|
102
|
+
const url = fileUrl || lastFileUrl;
|
|
103
|
+
const data = await getDesignSystemData(url);
|
|
104
|
+
const scored = scoreDesignSystem(data);
|
|
105
|
+
return {
|
|
106
|
+
content: [
|
|
107
|
+
{
|
|
108
|
+
type: "text",
|
|
109
|
+
text: JSON.stringify(scored),
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
116
|
+
return {
|
|
117
|
+
content: [
|
|
118
|
+
{
|
|
119
|
+
type: "text",
|
|
120
|
+
text: JSON.stringify({
|
|
121
|
+
error: errorMessage,
|
|
122
|
+
overall: 0,
|
|
123
|
+
status: "poor",
|
|
124
|
+
categories: [],
|
|
125
|
+
summary: [],
|
|
126
|
+
meta: {
|
|
127
|
+
componentCount: 0,
|
|
128
|
+
variableCount: 0,
|
|
129
|
+
collectionCount: 0,
|
|
130
|
+
styleCount: 0,
|
|
131
|
+
componentSetCount: 0,
|
|
132
|
+
standaloneCount: 0,
|
|
133
|
+
variantCount: 0,
|
|
134
|
+
timestamp: Date.now(),
|
|
135
|
+
},
|
|
136
|
+
}),
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
isError: true,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
// Resource: serves the Vite-built HTML
|
|
144
|
+
registerAppResource(server, "Design System Dashboard App", DASHBOARD_URI, {
|
|
145
|
+
description: "Interactive dashboard for evaluating design system health and AI-readiness",
|
|
146
|
+
}, async () => {
|
|
147
|
+
const htmlPath = resolve(__dirname, "mcp-app.html");
|
|
148
|
+
const html = await readFile(htmlPath, "utf-8");
|
|
149
|
+
return {
|
|
150
|
+
contents: [
|
|
151
|
+
{
|
|
152
|
+
uri: DASHBOARD_URI,
|
|
153
|
+
mimeType: RESOURCE_MIME_TYPE,
|
|
154
|
+
text: html,
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
};
|
|
158
|
+
});
|
|
159
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token Browser MCP App - Server Registration
|
|
3
|
+
*
|
|
4
|
+
* Registers tools and resource for the Token Browser MCP App.
|
|
5
|
+
* Uses the official @modelcontextprotocol/ext-apps helpers for proper
|
|
6
|
+
* MCP Apps protocol compatibility with Claude Desktop.
|
|
7
|
+
*
|
|
8
|
+
* Data flow:
|
|
9
|
+
* 1. LLM calls figma_browse_tokens → server fetches + caches data,
|
|
10
|
+
* returns SHORT summary to LLM (avoids context exhaustion)
|
|
11
|
+
* 2. UI opens, connects, calls token_browser_refresh (app-only visibility)
|
|
12
|
+
* 3. token_browser_refresh returns full JSON → UI renders
|
|
13
|
+
*/
|
|
14
|
+
import { readFile } from "node:fs/promises";
|
|
15
|
+
import { dirname, resolve } from "node:path";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
import { RESOURCE_MIME_TYPE, registerAppResource, registerAppTool, } from "@modelcontextprotocol/ext-apps/server";
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
const TOKEN_BROWSER_URI = "ui://figma-console/token-browser";
|
|
20
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
21
|
+
const __dirname = dirname(__filename);
|
|
22
|
+
/**
|
|
23
|
+
* Register the Token Browser MCP App with the server.
|
|
24
|
+
*
|
|
25
|
+
* @param server - The MCP server instance
|
|
26
|
+
* @param getVariablesHandler - Function to fetch variables data
|
|
27
|
+
*/
|
|
28
|
+
export function registerTokenBrowserApp(server, getVariablesHandler) {
|
|
29
|
+
// Shared state: remember last URL so refresh tool can reuse it
|
|
30
|
+
let lastFileUrl;
|
|
31
|
+
// Tool: fetches + caches data, returns SHORT summary to LLM
|
|
32
|
+
registerAppTool(server, "figma_browse_tokens", {
|
|
33
|
+
title: "Browse Design Tokens",
|
|
34
|
+
description: "Open an interactive browser to explore design tokens (variables) from a Figma file. Shows tokens organized by collection with search, filtering, and mode switching. Data is displayed in the UI — do not attempt to list or describe the tokens in chat.",
|
|
35
|
+
inputSchema: {
|
|
36
|
+
fileUrl: z
|
|
37
|
+
.string()
|
|
38
|
+
.url()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe("Figma file URL. If not provided, uses the currently active file."),
|
|
41
|
+
},
|
|
42
|
+
_meta: {
|
|
43
|
+
ui: { resourceUri: TOKEN_BROWSER_URI },
|
|
44
|
+
},
|
|
45
|
+
}, async ({ fileUrl }) => {
|
|
46
|
+
try {
|
|
47
|
+
lastFileUrl = fileUrl;
|
|
48
|
+
const result = await getVariablesHandler(fileUrl);
|
|
49
|
+
const varCount = result.variables?.length || 0;
|
|
50
|
+
const colCount = result.collections?.length || 0;
|
|
51
|
+
// Count by type
|
|
52
|
+
const colors = result.variables?.filter((v) => v.resolvedType === "COLOR")
|
|
53
|
+
.length || 0;
|
|
54
|
+
const numbers = result.variables?.filter((v) => v.resolvedType === "FLOAT")
|
|
55
|
+
.length || 0;
|
|
56
|
+
return {
|
|
57
|
+
content: [
|
|
58
|
+
{
|
|
59
|
+
type: "text",
|
|
60
|
+
text: `Token Browser opened: ${varCount} tokens across ${colCount} collections (${colors} colors, ${numbers} numbers). The interactive UI is now displaying the results — users can search, filter by type, and browse by collection.`,
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
67
|
+
return {
|
|
68
|
+
content: [
|
|
69
|
+
{
|
|
70
|
+
type: "text",
|
|
71
|
+
text: `Token Browser error: ${errorMessage}`,
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
isError: true,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
// Tool: returns full JSON data (app-only, hidden from LLM)
|
|
79
|
+
registerAppTool(server, "token_browser_refresh", {
|
|
80
|
+
title: "Token Browser Refresh",
|
|
81
|
+
description: "Refresh token data (called from MCP App UI)",
|
|
82
|
+
inputSchema: {
|
|
83
|
+
fileUrl: z
|
|
84
|
+
.string()
|
|
85
|
+
.url()
|
|
86
|
+
.optional()
|
|
87
|
+
.describe("Figma file URL to refresh data for."),
|
|
88
|
+
},
|
|
89
|
+
_meta: {
|
|
90
|
+
ui: {
|
|
91
|
+
resourceUri: TOKEN_BROWSER_URI,
|
|
92
|
+
visibility: ["app"],
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
}, async ({ fileUrl }) => {
|
|
96
|
+
try {
|
|
97
|
+
const url = fileUrl || lastFileUrl;
|
|
98
|
+
const result = await getVariablesHandler(url);
|
|
99
|
+
return {
|
|
100
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
105
|
+
return {
|
|
106
|
+
content: [
|
|
107
|
+
{
|
|
108
|
+
type: "text",
|
|
109
|
+
text: JSON.stringify({
|
|
110
|
+
error: errorMessage,
|
|
111
|
+
variables: [],
|
|
112
|
+
collections: [],
|
|
113
|
+
}),
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
isError: true,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
// Resource: serves the Vite-built HTML
|
|
121
|
+
registerAppResource(server, "Token Browser App", TOKEN_BROWSER_URI, {
|
|
122
|
+
description: "Interactive browser for exploring Figma design tokens and variables",
|
|
123
|
+
}, async () => {
|
|
124
|
+
const htmlPath = resolve(__dirname, "mcp-app.html");
|
|
125
|
+
const html = await readFile(htmlPath, "utf-8");
|
|
126
|
+
return {
|
|
127
|
+
contents: [
|
|
128
|
+
{
|
|
129
|
+
uri: TOKEN_BROWSER_URI,
|
|
130
|
+
mimeType: RESOURCE_MIME_TYPE,
|
|
131
|
+
text: html,
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Manager
|
|
3
|
+
* Manages Puppeteer browser instance lifecycle for Cloudflare Browser Rendering API
|
|
4
|
+
*/
|
|
5
|
+
import puppeteer from '@cloudflare/puppeteer';
|
|
6
|
+
import { createChildLogger } from './core/logger.js';
|
|
7
|
+
const logger = createChildLogger({ component: 'browser-manager' });
|
|
8
|
+
/**
|
|
9
|
+
* Browser Manager
|
|
10
|
+
* Handles browser instance creation, page management, and navigation
|
|
11
|
+
*/
|
|
12
|
+
export class BrowserManager {
|
|
13
|
+
constructor(env, config) {
|
|
14
|
+
this.browser = null;
|
|
15
|
+
this.page = null;
|
|
16
|
+
this.env = env;
|
|
17
|
+
this.config = config;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Launch browser instance
|
|
21
|
+
*/
|
|
22
|
+
async launch() {
|
|
23
|
+
if (this.browser) {
|
|
24
|
+
logger.info('Browser already running, reusing instance');
|
|
25
|
+
return this.browser;
|
|
26
|
+
}
|
|
27
|
+
logger.info('Launching browser with Cloudflare Browser Rendering API');
|
|
28
|
+
try {
|
|
29
|
+
this.browser = await puppeteer.launch(this.env.BROWSER, {
|
|
30
|
+
keep_alive: 600000, // Keep alive for 10 minutes
|
|
31
|
+
});
|
|
32
|
+
logger.info('Browser launched successfully');
|
|
33
|
+
return this.browser;
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
logger.error({ error }, 'Failed to launch browser');
|
|
37
|
+
throw new Error(`Browser launch failed: ${error}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get or create a page instance
|
|
42
|
+
*/
|
|
43
|
+
async getPage() {
|
|
44
|
+
if (!this.browser) {
|
|
45
|
+
await this.launch();
|
|
46
|
+
}
|
|
47
|
+
if (this.page && !this.page.isClosed()) {
|
|
48
|
+
return this.page;
|
|
49
|
+
}
|
|
50
|
+
logger.info('Creating new browser page');
|
|
51
|
+
this.page = await this.browser.newPage();
|
|
52
|
+
// Set viewport size
|
|
53
|
+
await this.page.setViewport({
|
|
54
|
+
width: 1920,
|
|
55
|
+
height: 1080,
|
|
56
|
+
deviceScaleFactor: 1,
|
|
57
|
+
});
|
|
58
|
+
logger.info('Browser page created');
|
|
59
|
+
return this.page;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Navigate to Figma URL
|
|
63
|
+
*/
|
|
64
|
+
async navigateToFigma(figmaUrl) {
|
|
65
|
+
const page = await this.getPage();
|
|
66
|
+
// Default to Figma homepage if no URL provided
|
|
67
|
+
const url = figmaUrl || 'https://www.figma.com';
|
|
68
|
+
logger.info({ url }, 'Navigating to Figma');
|
|
69
|
+
try {
|
|
70
|
+
await page.goto(url, {
|
|
71
|
+
waitUntil: 'networkidle2',
|
|
72
|
+
timeout: 30000,
|
|
73
|
+
});
|
|
74
|
+
logger.info({ url }, 'Navigation successful');
|
|
75
|
+
return { page, action: 'navigated', url };
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
logger.error({ error, url }, 'Navigation failed');
|
|
79
|
+
throw new Error(`Failed to navigate to ${url}: ${error}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Reload current page
|
|
84
|
+
*/
|
|
85
|
+
async reload(hardReload = false) {
|
|
86
|
+
if (!this.page || this.page.isClosed()) {
|
|
87
|
+
throw new Error('No active page to reload');
|
|
88
|
+
}
|
|
89
|
+
logger.info({ hardReload }, 'Reloading page');
|
|
90
|
+
try {
|
|
91
|
+
await this.page.reload({
|
|
92
|
+
waitUntil: 'networkidle2',
|
|
93
|
+
timeout: 30000,
|
|
94
|
+
});
|
|
95
|
+
logger.info('Page reloaded successfully');
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
logger.error({ error }, 'Page reload failed');
|
|
99
|
+
throw new Error(`Page reload failed: ${error}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Execute JavaScript in page context
|
|
104
|
+
*/
|
|
105
|
+
async evaluate(fn) {
|
|
106
|
+
const page = await this.getPage();
|
|
107
|
+
return page.evaluate(fn);
|
|
108
|
+
}
|
|
109
|
+
// Screenshot functionality removed - use Figma REST API's getImages() instead
|
|
110
|
+
// See: figma_take_screenshot and figma_get_component_image tools
|
|
111
|
+
/**
|
|
112
|
+
* Check if browser is running
|
|
113
|
+
*/
|
|
114
|
+
isRunning() {
|
|
115
|
+
return this.browser !== null && this.browser.isConnected();
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Close browser instance
|
|
119
|
+
*/
|
|
120
|
+
async close() {
|
|
121
|
+
if (!this.browser) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
logger.info('Closing browser');
|
|
125
|
+
try {
|
|
126
|
+
await this.browser.close();
|
|
127
|
+
this.browser = null;
|
|
128
|
+
this.page = null;
|
|
129
|
+
logger.info('Browser closed successfully');
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
logger.error({ error }, 'Failed to close browser');
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Get current page URL
|
|
138
|
+
*/
|
|
139
|
+
getCurrentUrl() {
|
|
140
|
+
if (!this.page || this.page.isClosed()) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
return this.page.url();
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Wait for navigation
|
|
147
|
+
*/
|
|
148
|
+
async waitForNavigation(timeout = 30000) {
|
|
149
|
+
if (!this.page || this.page.isClosed()) {
|
|
150
|
+
throw new Error('No active page');
|
|
151
|
+
}
|
|
152
|
+
await this.page.waitForNavigation({
|
|
153
|
+
waitUntil: 'networkidle2',
|
|
154
|
+
timeout,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|