multisite-cms-mcp 1.0.13 → 1.0.16

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 CHANGED
@@ -13,7 +13,10 @@ Add to your MCP configuration (e.g., `.cursor/mcp.json`):
13
13
  "mcpServers": {
14
14
  "multisite-cms": {
15
15
  "command": "npx",
16
- "args": ["-y", "multisite-cms-mcp"]
16
+ "args": ["-y", "multisite-cms-mcp"],
17
+ "env": {
18
+ "FASTMODE_API_URL": "https://api.fastmode.ai"
19
+ }
17
20
  }
18
21
  }
19
22
  }
@@ -31,7 +34,63 @@ Then configure:
31
34
  {
32
35
  "mcpServers": {
33
36
  "multisite-cms": {
34
- "command": "multisite-cms-mcp"
37
+ "command": "multisite-cms-mcp",
38
+ "env": {
39
+ "FASTMODE_API_URL": "https://api.fastmode.ai"
40
+ }
41
+ }
42
+ }
43
+ }
44
+ ```
45
+
46
+ ## Authentication
47
+
48
+ The MCP server uses **automatic browser-based authentication**. When you use a tool that requires authentication (`list_projects`, `get_tenant_schema`), the server will:
49
+
50
+ 1. **Open your browser** to app.fastmode.ai/device
51
+ 2. **Display a code** in your terminal (e.g., "ABCD-1234")
52
+ 3. You **log in** (if not already) and **click Authorize**
53
+ 4. **Done!** Credentials are saved and auto-refresh
54
+
55
+ ### How It Works
56
+
57
+ ```
58
+ AI: Let me list your projects...
59
+ [calls list_projects]
60
+
61
+ MCP: Opening browser for authentication...
62
+ Visit: https://app.fastmode.ai/device
63
+ Code: ABCD-1234
64
+
65
+ Waiting for authorization...
66
+
67
+ [Browser opens, you click "Authorize"]
68
+
69
+ MCP: Authenticated as you@email.com
70
+ Credentials saved to ~/.fastmode/credentials.json
71
+
72
+ [Returns project list]
73
+ ```
74
+
75
+ ### Credentials
76
+
77
+ - Stored in `~/.fastmode/credentials.json`
78
+ - Access tokens auto-refresh (90-day refresh tokens)
79
+ - No need to manually copy/paste tokens!
80
+
81
+ ### Manual Token (Optional)
82
+
83
+ If you prefer manual setup, you can still set `FASTMODE_AUTH_TOKEN`:
84
+
85
+ ```json
86
+ {
87
+ "mcpServers": {
88
+ "multisite-cms": {
89
+ "command": "npx",
90
+ "args": ["-y", "multisite-cms-mcp"],
91
+ "env": {
92
+ "FASTMODE_AUTH_TOKEN": "your-token-here"
93
+ }
35
94
  }
36
95
  }
37
96
  }
@@ -47,6 +106,49 @@ AI: What fields are available for blog posts?
47
106
  [calls get_schema]
48
107
  ```
49
108
 
109
+ ### `list_projects`
110
+ List all FastMode projects you have access to. Use this to discover project IDs for `get_tenant_schema`.
111
+
112
+ **Triggers authentication if needed.**
113
+
114
+ ```
115
+ AI: Let me see what projects you have access to...
116
+ [calls list_projects]
117
+
118
+ Response:
119
+ # Your FastMode Projects
120
+
121
+ Found 2 projects:
122
+
123
+ ## 1. My Business Site
124
+ - Project ID: abc-123-def-456
125
+ - Subdomain: mybusiness.fastmode.ai
126
+ - Your Role: owner
127
+
128
+ ## 2. Portfolio
129
+ - Project ID: xyz-789-uvw-012
130
+ - Subdomain: portfolio.fastmode.ai
131
+ - Your Role: editor
132
+ ```
133
+
134
+ ### `get_tenant_schema`
135
+ Fetch the complete schema for a specific project, including custom collections and custom fields on built-in collections.
136
+
137
+ **Triggers authentication if needed.**
138
+
139
+ **Parameters:**
140
+ - `projectId` (string, required): The project ID (UUID from `list_projects`) or project name
141
+
142
+ ```
143
+ AI: Let me get the schema for your "My Business Site" project...
144
+ [calls get_tenant_schema with projectId: "My Business Site"]
145
+
146
+ Response: Returns full schema including:
147
+ - Built-in collections with any custom fields added
148
+ - All custom collections with their fields
149
+ - Correct token syntax for each field
150
+ ```
151
+
50
152
  ### `validate_manifest`
51
153
  Validate a manifest.json file structure.
52
154
 
@@ -103,6 +205,16 @@ Get the complete conversion documentation.
103
205
  **Parameters:**
104
206
  - `section` (string, optional): One of: `full`, `analysis`, `structure`, `manifest`, `templates`, `tokens`, `forms`, `assets`, `checklist`
105
207
 
208
+ ## Custom Collections & Custom Fields
209
+
210
+ The CMS supports:
211
+
212
+ 1. **Custom Collections** - Tenant-defined content types (e.g., "services", "testimonials")
213
+ 2. **Custom Fields on Built-in Collections** - Additional fields added to Blog Posts, Authors, Team, Downloads
214
+
215
+ Use `get_schema` for static documentation about how these work.
216
+ Use `list_projects` + `get_tenant_schema` to fetch the actual custom collections and fields for a specific project.
217
+
106
218
  ## CMS Template Configuration
107
219
 
108
220
  Templates can be configured two ways:
@@ -132,20 +244,64 @@ This allows for a faster workflow: upload pages first, then configure templates
132
244
  When converting a website, an AI agent can:
133
245
 
134
246
  1. **Start** → Call `get_conversion_guide` for requirements
135
- 2. **Check fields** → Call `get_schema` to see exact token names
136
- 3. **Need example?** → Call `get_example` for specific pattern
137
- 4. **Create manifest** → Call `validate_manifest` to check it
138
- 5. **Create template** → Call `validate_template` to verify tokens
139
- 6. **Final check** → Call `validate_package` for full validation
247
+ 2. **Check generic fields** → Call `get_schema` to see built-in token names
248
+ 3. **Discover projects** → Call `list_projects` (triggers auth if needed)
249
+ 4. **Get project schema** → Call `get_tenant_schema` with project ID/name for custom collections & fields
250
+ 5. **Need example?** → Call `get_example` for specific pattern
251
+ 6. **Create manifest** → Call `validate_manifest` to check it
252
+ 7. **Create template** → Call `validate_template` to verify tokens
253
+ 8. **Final check** → Call `validate_package` for full validation
254
+
255
+ ### Example Conversion Flow
256
+
257
+ ```
258
+ User: Convert example.com for the "My Business" project
259
+
260
+ AI: Let me first check what projects you have access to...
261
+ [calls list_projects]
262
+
263
+ [Browser opens for auth if needed]
264
+ [User clicks Authorize]
265
+
266
+ → Found "My Business" with ID: abc-123
267
+
268
+ AI: Now let me get the custom schema for this project...
269
+ [calls get_tenant_schema with projectId: "abc-123"]
270
+ → Found custom collection "Services" with fields: title, description, icon
271
+ → Found custom field "category" on Blog Posts
272
+
273
+ AI: I'll create templates using these tokens...
274
+ - {{#each services}} {{title}} {{description}} {{/each}}
275
+ - {{#each blogs}} {{name}} {{category}} {{/each}}
276
+ ```
277
+
278
+ ## Managing Device Sessions
279
+
280
+ You can manage your authorized devices from the FastMode dashboard:
281
+
282
+ 1. Go to **app.fastmode.ai → Settings → Security**
283
+ 2. View all authorized MCP/CLI sessions
284
+ 3. Revoke access to any device
285
+
286
+ Credentials are stored locally at `~/.fastmode/credentials.json`.
140
287
 
141
288
  ## Key Benefits
142
289
 
290
+ - **One-click authentication** - No manual token copying
291
+ - **Auto-refresh** - Tokens refresh automatically (90-day sessions)
292
+ - **Project discovery** - `list_projects` shows all accessible projects
293
+ - **Tenant-specific schemas** - `get_tenant_schema` fetches exact custom fields
143
294
  - **Validates as you work** - Catch errors during conversion, not after
144
295
  - **Correct field names** - No more guessing `{{title}}` vs `{{name}}`
145
296
  - **Triple brace reminders** - Ensures rich text uses `{{{...}}}`
146
297
  - **Structure validation** - Confirms package layout is correct
147
- - **Settings UI support** - Templates can be configured after upload
148
- - **Editor warnings** - Detects unconfigured CMS paths
298
+
299
+ ## Environment Variables
300
+
301
+ | Variable | Required | Default | Description |
302
+ |----------|----------|---------|-------------|
303
+ | `FASTMODE_API_URL` | No | `https://api.fastmode.ai` | API base URL |
304
+ | `FASTMODE_AUTH_TOKEN` | No | - | Manual auth token (optional, device flow is preferred) |
149
305
 
150
306
  ## License
151
307
 
package/dist/index.js CHANGED
@@ -10,6 +10,8 @@ const validate_template_1 = require("./tools/validate-template");
10
10
  const validate_package_1 = require("./tools/validate-package");
11
11
  const get_example_1 = require("./tools/get-example");
12
12
  const get_conversion_guide_1 = require("./tools/get-conversion-guide");
13
+ const get_tenant_schema_1 = require("./tools/get-tenant-schema");
14
+ const list_projects_1 = require("./tools/list-projects");
13
15
  const server = new index_js_1.Server({
14
16
  name: 'multisite-cms',
15
17
  version: '1.0.0',
@@ -139,6 +141,29 @@ const TOOLS = [
139
141
  required: [],
140
142
  },
141
143
  },
144
+ {
145
+ name: 'list_projects',
146
+ description: 'List all FastMode projects you have access to. Use this to discover project IDs and names for use with get_tenant_schema. Requires FASTMODE_AUTH_TOKEN environment variable to be set.',
147
+ inputSchema: {
148
+ type: 'object',
149
+ properties: {},
150
+ required: [],
151
+ },
152
+ },
153
+ {
154
+ name: 'get_tenant_schema',
155
+ description: 'Fetch the complete schema for a specific project including custom collections and custom fields on built-in collections. Use this when converting a website for an existing project to see their specific CMS configuration. Requires FASTMODE_AUTH_TOKEN environment variable.',
156
+ inputSchema: {
157
+ type: 'object',
158
+ properties: {
159
+ projectId: {
160
+ type: 'string',
161
+ description: 'The project ID (UUID from list_projects) or project name',
162
+ },
163
+ },
164
+ required: ['projectId'],
165
+ },
166
+ },
142
167
  ];
143
168
  // Handle list tools request
144
169
  server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
@@ -179,6 +204,12 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
179
204
  case 'get_conversion_guide':
180
205
  result = await (0, get_conversion_guide_1.getConversionGuide)(params.section || 'full');
181
206
  break;
207
+ case 'list_projects':
208
+ result = await (0, list_projects_1.listProjects)();
209
+ break;
210
+ case 'get_tenant_schema':
211
+ result = await (0, get_tenant_schema_1.getTenantSchema)(params.projectId);
212
+ break;
182
213
  default:
183
214
  return {
184
215
  content: [{ type: 'text', text: `Unknown tool: ${name}` }],
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Centralized API client for FastMode API requests
3
+ * Uses stored credentials with automatic refresh, or falls back to env var
4
+ */
5
+ export interface ApiConfig {
6
+ apiUrl: string;
7
+ authToken: string | null;
8
+ }
9
+ /**
10
+ * Get API URL from environment or default
11
+ */
12
+ export declare function getApiUrl(): string;
13
+ /**
14
+ * Get API configuration - tries stored credentials first, then env var
15
+ */
16
+ export declare function getApiConfigAsync(): Promise<ApiConfig>;
17
+ /**
18
+ * Synchronous version - only checks env var (for quick checks)
19
+ */
20
+ export declare function getApiConfig(): ApiConfig;
21
+ /**
22
+ * Check if authentication is configured (either stored credentials or env var)
23
+ */
24
+ export declare function isAuthConfigured(): boolean;
25
+ /**
26
+ * Check if we need to trigger the device flow
27
+ */
28
+ export declare function needsAuthentication(): Promise<boolean>;
29
+ /**
30
+ * Get a helpful message for authentication
31
+ */
32
+ export declare function getAuthRequiredMessage(): string;
33
+ /**
34
+ * API request options
35
+ */
36
+ export interface ApiRequestOptions {
37
+ tenantId?: string;
38
+ method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
39
+ body?: unknown;
40
+ }
41
+ /**
42
+ * API error response
43
+ */
44
+ export interface ApiError {
45
+ success: false;
46
+ error: string;
47
+ statusCode: number;
48
+ needsAuth?: boolean;
49
+ }
50
+ /**
51
+ * Make an authenticated API request
52
+ * Uses stored credentials with auto-refresh, or falls back to env var
53
+ */
54
+ export declare function apiRequest<T>(endpoint: string, options?: ApiRequestOptions): Promise<{
55
+ data: T;
56
+ } | ApiError>;
57
+ /**
58
+ * Check if a response is an error
59
+ */
60
+ export declare function isApiError(response: unknown): response is ApiError;
61
+ /**
62
+ * Check if error requires authentication
63
+ */
64
+ export declare function needsAuthError(error: ApiError): boolean;
65
+ //# sourceMappingURL=api-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../../src/lib/api-client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED;;GAEG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,SAAS,CAAC,CAgB5D;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,SAAS,CAKxC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C;AAED;;GAEG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO5D;AAED;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAS/C;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;IAC3C,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAChC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC;IAAE,IAAI,EAAE,CAAC,CAAA;CAAE,GAAG,QAAQ,CAAC,CAoEjC;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,OAAO,GAAG,QAAQ,IAAI,QAAQ,CAOlE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAEvD"}
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+ /**
3
+ * Centralized API client for FastMode API requests
4
+ * Uses stored credentials with automatic refresh, or falls back to env var
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.getApiUrl = getApiUrl;
8
+ exports.getApiConfigAsync = getApiConfigAsync;
9
+ exports.getApiConfig = getApiConfig;
10
+ exports.isAuthConfigured = isAuthConfigured;
11
+ exports.needsAuthentication = needsAuthentication;
12
+ exports.getAuthRequiredMessage = getAuthRequiredMessage;
13
+ exports.apiRequest = apiRequest;
14
+ exports.isApiError = isApiError;
15
+ exports.needsAuthError = needsAuthError;
16
+ const credentials_1 = require("./credentials");
17
+ /**
18
+ * Get API URL from environment or default
19
+ */
20
+ function getApiUrl() {
21
+ return process.env.FASTMODE_API_URL || 'https://api.fastmode.ai';
22
+ }
23
+ /**
24
+ * Get API configuration - tries stored credentials first, then env var
25
+ */
26
+ async function getApiConfigAsync() {
27
+ // First, try to get valid stored credentials
28
+ const credentials = await (0, credentials_1.getValidCredentials)();
29
+ if (credentials) {
30
+ return {
31
+ apiUrl: getApiUrl(),
32
+ authToken: credentials.accessToken,
33
+ };
34
+ }
35
+ // Fall back to environment variable (for backward compatibility)
36
+ return {
37
+ apiUrl: getApiUrl(),
38
+ authToken: process.env.FASTMODE_AUTH_TOKEN || null,
39
+ };
40
+ }
41
+ /**
42
+ * Synchronous version - only checks env var (for quick checks)
43
+ */
44
+ function getApiConfig() {
45
+ return {
46
+ apiUrl: getApiUrl(),
47
+ authToken: process.env.FASTMODE_AUTH_TOKEN || null,
48
+ };
49
+ }
50
+ /**
51
+ * Check if authentication is configured (either stored credentials or env var)
52
+ */
53
+ function isAuthConfigured() {
54
+ return (0, credentials_1.hasCredentials)() || !!process.env.FASTMODE_AUTH_TOKEN;
55
+ }
56
+ /**
57
+ * Check if we need to trigger the device flow
58
+ */
59
+ async function needsAuthentication() {
60
+ const credentials = await (0, credentials_1.getValidCredentials)();
61
+ if (credentials)
62
+ return false;
63
+ if (process.env.FASTMODE_AUTH_TOKEN)
64
+ return false;
65
+ return true;
66
+ }
67
+ /**
68
+ * Get a helpful message for authentication
69
+ */
70
+ function getAuthRequiredMessage() {
71
+ return `# Authentication Required
72
+
73
+ You need to authenticate to use this tool.
74
+
75
+ **Starting authentication flow...**
76
+
77
+ A browser window will open for you to log in. If it doesn't open automatically, you'll see a URL to visit.
78
+ `;
79
+ }
80
+ /**
81
+ * Make an authenticated API request
82
+ * Uses stored credentials with auto-refresh, or falls back to env var
83
+ */
84
+ async function apiRequest(endpoint, options = {}) {
85
+ const config = await getApiConfigAsync();
86
+ if (!config.authToken) {
87
+ return {
88
+ success: false,
89
+ error: 'Not authenticated',
90
+ statusCode: 401,
91
+ needsAuth: true,
92
+ };
93
+ }
94
+ const url = `${config.apiUrl}${endpoint.startsWith('/') ? endpoint : '/' + endpoint}`;
95
+ const headers = {
96
+ 'Authorization': `Bearer ${config.authToken}`,
97
+ 'Content-Type': 'application/json',
98
+ };
99
+ if (options.tenantId) {
100
+ headers['X-Tenant-Id'] = options.tenantId;
101
+ }
102
+ try {
103
+ const response = await fetch(url, {
104
+ method: options.method || 'GET',
105
+ headers,
106
+ body: options.body ? JSON.stringify(options.body) : undefined,
107
+ });
108
+ const data = await response.json();
109
+ if (!response.ok) {
110
+ // Check if this is an auth error
111
+ if (response.status === 401) {
112
+ return {
113
+ success: false,
114
+ error: 'Authentication expired or invalid',
115
+ statusCode: 401,
116
+ needsAuth: true,
117
+ };
118
+ }
119
+ return {
120
+ success: false,
121
+ error: data.error || `API request failed with status ${response.status}`,
122
+ statusCode: response.status,
123
+ };
124
+ }
125
+ return data;
126
+ }
127
+ catch (error) {
128
+ const message = error instanceof Error ? error.message : String(error);
129
+ if (message.includes('fetch') || message.includes('network') || message.includes('ECONNREFUSED')) {
130
+ return {
131
+ success: false,
132
+ error: `Network error: Unable to connect to ${config.apiUrl}`,
133
+ statusCode: 0,
134
+ };
135
+ }
136
+ return {
137
+ success: false,
138
+ error: message,
139
+ statusCode: 0,
140
+ };
141
+ }
142
+ }
143
+ /**
144
+ * Check if a response is an error
145
+ */
146
+ function isApiError(response) {
147
+ return (typeof response === 'object' &&
148
+ response !== null &&
149
+ 'success' in response &&
150
+ response.success === false);
151
+ }
152
+ /**
153
+ * Check if error requires authentication
154
+ */
155
+ function needsAuthError(error) {
156
+ return error.needsAuth === true || error.statusCode === 401;
157
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Stored credentials for the MCP server
3
+ */
4
+ export interface StoredCredentials {
5
+ accessToken: string;
6
+ refreshToken: string;
7
+ expiresAt: string;
8
+ email: string;
9
+ name?: string;
10
+ }
11
+ /**
12
+ * Get the path to the credentials file
13
+ */
14
+ export declare function getCredentialsPath(): string;
15
+ /**
16
+ * Load stored credentials from disk
17
+ */
18
+ export declare function loadCredentials(): StoredCredentials | null;
19
+ /**
20
+ * Save credentials to disk
21
+ */
22
+ export declare function saveCredentials(credentials: StoredCredentials): void;
23
+ /**
24
+ * Delete stored credentials
25
+ */
26
+ export declare function deleteCredentials(): void;
27
+ /**
28
+ * Check if credentials exist
29
+ */
30
+ export declare function hasCredentials(): boolean;
31
+ /**
32
+ * Check if the access token is expired or about to expire
33
+ */
34
+ export declare function isTokenExpired(credentials: StoredCredentials, bufferMinutes?: number): boolean;
35
+ /**
36
+ * Get the API URL from environment or default
37
+ */
38
+ export declare function getApiUrl(): string;
39
+ /**
40
+ * Refresh the access token using the refresh token
41
+ */
42
+ export declare function refreshAccessToken(credentials: StoredCredentials): Promise<StoredCredentials | null>;
43
+ /**
44
+ * Get valid credentials, refreshing if needed
45
+ * Returns null if no credentials or refresh fails
46
+ */
47
+ export declare function getValidCredentials(): Promise<StoredCredentials | null>;
48
+ /**
49
+ * Get the current auth token (access token) if available and valid
50
+ */
51
+ export declare function getAuthToken(): Promise<string | null>;
52
+ //# sourceMappingURL=credentials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../src/lib/credentials.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAG3C;AAYD;;GAEG;AACH,wBAAgB,eAAe,IAAI,iBAAiB,GAAG,IAAI,CAoB1D;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,iBAAiB,GAAG,IAAI,CAUpE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAUxC;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,iBAAiB,EAAE,aAAa,GAAE,MAAU,GAAG,OAAO,CAIjG;AAED;;GAEG;AACH,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,WAAW,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAyC1G;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAoB7E;AAED;;GAEG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAG3D"}