@visualsentinel/mcp-server 0.1.0 → 0.2.2
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/CHANGELOG.md +46 -0
- package/README.md +54 -7
- package/dist/.metadata_never_index +0 -0
- package/dist/api-client.d.ts +32 -0
- package/dist/api-client.js +79 -0
- package/dist/api-client.js.map +1 -0
- package/dist/index.js +457 -16
- package/dist/index.js.map +1 -1
- package/dist/prompts.d.ts +22 -0
- package/dist/prompts.js +105 -0
- package/dist/prompts.js.map +1 -0
- package/dist/resources.d.ts +18 -0
- package/dist/resources.js +625 -0
- package/dist/resources.js.map +1 -0
- package/dist/tools.d.ts +38 -0
- package/dist/tools.js +546 -0
- package/dist/tools.js.map +1 -0
- package/dist/version.d.ts +4 -0
- package/dist/version.js +8 -0
- package/dist/version.js.map +1 -0
- package/package.json +15 -11
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,52 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@visualsentinel/mcp-server`. Format follows [Keep a Changelog](https://keepachangelog.com/) and the package uses [Semantic Versioning](https://semver.org/).
|
|
4
4
|
|
|
5
|
+
## [0.2.2] - 2026-11-06
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
* Four MCP prompts: `check_website_health`, `investigate_broken_site`, `pre_launch_checklist`, `triage_open_incidents`.
|
|
10
|
+
* Three read-only MCP resources: `visualsentinel://plans`, `visualsentinel://help`, `visualsentinel://tools`. The server now advertises the `prompts` and `resources` capabilities.
|
|
11
|
+
* LobeHub badge and homepage link in the README (the LobeHub claim route).
|
|
12
|
+
* `mcpName` is now `com.visualsentinel/mcp-server` (the registry namespace is verified through a DNS record on visualsentinel.com).
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
* `package.json` `homepage` is now https://visualsentinel.com/mcp, so npm and its mirrors link to the product page instead of this repository.
|
|
17
|
+
* Account tools called without an API key now explain that API keys are available on the Business and Agency plans and that the five public tools work without one.
|
|
18
|
+
* `SERVER_VERSION` lives in `src/version.ts`; a test keeps it equal to `package.json`.
|
|
19
|
+
|
|
20
|
+
## [0.2.1] - 2026-10-16
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
* `vs_ssl_check` sent `?host=` but the API reads `?domain=`, so every call returned a 400. The tool now takes `domain` (`host` still works as an alias). The unsupported `port` input is removed.
|
|
25
|
+
* `vs_dns_check` sent `recordType`, which the API ignored. It now sends `type`, so the record-type filter works.
|
|
26
|
+
* The server now reports its real version in the MCP handshake instead of a fixed `0.1.0`.
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
* Every tool has a `title` and the MCP annotations `readOnlyHint`, `destructiveHint`, `idempotentHint` and `openWorldHint`, as AI directories require.
|
|
31
|
+
* `mcpName` (`io.github.VisualSentinel/mcp-server`) and `server.json` for the official MCP Registry.
|
|
32
|
+
* `.cursor-plugin/plugin.json`, `.claude-plugin/plugin.json`, `.mcp.json` and `mcp.json` for the Cursor Marketplace and Grok Bot, `llms-install.md` for Cline, a `Dockerfile` for the Docker MCP Catalog and Glama, and `gemini-extension.json`.
|
|
33
|
+
|
|
34
|
+
## [0.2.0] - 2026-06-04
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
* New tool `vs_monitors_metrics_bulk`: fetches uptime or response-time metrics for up to 100 monitors in a single API call. Accepts `monitorIds` (array), `metric` (`uptime` | `response-time`), and either a preset `window` or custom `from`+`to` ISO 8601 range. POSTs to `POST /api/monitors/metrics`.
|
|
39
|
+
* `vs_monitors_uptime`: added `from` and `to` input properties for custom date ranges. When both are supplied the handler sends them as query params instead of `period`, allowing arbitrary historical windows.
|
|
40
|
+
* `all` (boolean) input on `vs_monitors_uptime` and `vs_monitors_metrics_bulk`: requests the full available history from the earliest stored data. Precedence mirrors the API: `all` > `from`/`to` > `window`/`period`. This makes the documented full-history capability actually reachable through the tools.
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
|
|
44
|
+
* `vs_monitors_uptime`: input schema now includes `from`, `to`, and expands `window` description to list `1h | 6h | 24h | 7d | 30d | 90d | 365d`. Handler now correctly maps `window` → `period` query param (previously `window` was sent as-is and silently ignored by the API). Default period remains `30d`.
|
|
45
|
+
* `tsup.config.ts`: added `src/tools.ts` and `src/api-client.ts` as explicit entry points so they emit separate dist files importable by tests without bundling index.ts.
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
* `vs_monitors_uptime` handler was sending `{ window: ... }` in the query string instead of `{ period: ... }`. The Visual Sentinel API only accepts `period`; the old behaviour silently fell back to the server default.
|
|
50
|
+
|
|
5
51
|
## [0.1.0] - 2026-04-29
|
|
6
52
|
|
|
7
53
|
Initial public release.
|
package/README.md
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
# Visual Sentinel MCP Server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://lobehub.com/mcp/visualsentinel-mcp-server)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Model Context Protocol server for [Visual Sentinel](https://visualsentinel.com). Visual Sentinel catches pages that look broken even when the site is up. It monitors six layers: uptime, performance, SSL, DNS, visual regression, and content change.
|
|
6
6
|
|
|
7
|
+
Connect Claude Desktop, Claude Code, Cursor, Cline, Continue, or any MCP-compatible client so an AI assistant can run DNS, SSL and speed checks on any site with no account. With a Visual Sentinel account and an API key (Business and Agency plans) it can also list and create monitors, fetch incident history, and acknowledge alerts on your behalf.
|
|
8
|
+
|
|
9
|
+
* **Homepage and docs:** https://visualsentinel.com/mcp
|
|
7
10
|
* **npm:** `@visualsentinel/mcp-server`
|
|
8
11
|
* **OpenAPI spec:** https://github.com/VisualSentinel/openapi
|
|
9
|
-
* **
|
|
12
|
+
* **API key (Business and Agency plans):** https://visualsentinel.com/settings/api-keys
|
|
10
13
|
* **Hosted API reference:** https://visualsentinel.com/docs
|
|
11
14
|
|
|
12
15
|
## What you get
|
|
13
16
|
|
|
14
|
-
|
|
17
|
+
17 tools spanning the public Visual Sentinel API:
|
|
15
18
|
|
|
16
19
|
**Public tools (no API key required)**
|
|
17
20
|
|
|
@@ -31,7 +34,8 @@ Connect Claude Desktop, Claude Code, Cline, Continue, or any MCP-compatible clie
|
|
|
31
34
|
| `vs_monitors_get` | `GET /api/monitors/{id}` | Fetch a single monitor with current status |
|
|
32
35
|
| `vs_monitors_create` | `POST /api/monitors` | Create a new monitor |
|
|
33
36
|
| `vs_monitors_check_now` | `POST /api/monitors/{id}/check` | Trigger an immediate check |
|
|
34
|
-
| `vs_monitors_uptime` | `GET /api/monitors/{id}/uptime` | Uptime percentage
|
|
37
|
+
| `vs_monitors_uptime` | `GET /api/monitors/{id}/uptime` | Uptime percentage for a preset window, a custom date range, or the full history |
|
|
38
|
+
| `vs_monitors_metrics_bulk` | `POST /api/monitors/metrics` | Uptime or response-time metrics for up to 100 monitors in one call |
|
|
35
39
|
| `vs_incidents_list` | `GET /api/incidents` | List incidents, filterable by status or monitor |
|
|
36
40
|
| `vs_incidents_get` | `GET /api/incidents/{id}` | Fetch an incident with full check history |
|
|
37
41
|
| `vs_alerts_list` | `GET /api/alerts` | List alerts by status |
|
|
@@ -39,6 +43,25 @@ Connect Claude Desktop, Claude Code, Cline, Continue, or any MCP-compatible clie
|
|
|
39
43
|
| `vs_status_pages_list` | `GET /api/status-pages` | List your public status pages |
|
|
40
44
|
| `vs_servers_list` | `GET /api/servers` | List registered origin servers |
|
|
41
45
|
|
|
46
|
+
## Prompts and resources
|
|
47
|
+
|
|
48
|
+
Besides the 17 tools, the server offers four ready-made prompts and three read-only resources. None of them need an API key except the last prompt.
|
|
49
|
+
|
|
50
|
+
| Prompt | What it does |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `check_website_health` | Runs the website, SSL, DNS and speed checks on one site and summarises what needs attention. |
|
|
53
|
+
| `investigate_broken_site` | Works through DNS, certificate, response and speed to find the layer at fault; reads incidents and alerts too when an API key is set. |
|
|
54
|
+
| `pre_launch_checklist` | Pass, Warn or Fail checklist for a new or migrated domain. |
|
|
55
|
+
| `triage_open_incidents` | Ranks open incidents and unacknowledged alerts (needs an API key). |
|
|
56
|
+
|
|
57
|
+
| Resource | Contents |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `visualsentinel://plans` | Plan limits, and which plans include API keys and the account tools. |
|
|
60
|
+
| `visualsentinel://help` | Setup without an account, adding an API key, links. |
|
|
61
|
+
| `visualsentinel://tools` | Every tool, whether it needs a key, and whether it changes data. |
|
|
62
|
+
|
|
63
|
+
Without an API key the server still starts and lists everything. The five public tools work; each account tool returns a clear message that it needs an API key (Business and Agency plans) instead of failing.
|
|
64
|
+
|
|
42
65
|
## Installation
|
|
43
66
|
|
|
44
67
|
### Claude Desktop
|
|
@@ -59,7 +82,7 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
|
|
|
59
82
|
}
|
|
60
83
|
```
|
|
61
84
|
|
|
62
|
-
Restart Claude Desktop. The
|
|
85
|
+
Restart Claude Desktop. The 17 tools appear under the tools menu.
|
|
63
86
|
|
|
64
87
|
### Claude Code (CLI)
|
|
65
88
|
|
|
@@ -70,6 +93,30 @@ claude mcp add visualsentinel \
|
|
|
70
93
|
|
|
71
94
|
Then export `VS_API_KEY` in the shell where Claude Code runs.
|
|
72
95
|
|
|
96
|
+
### Cursor and Grok Bot
|
|
97
|
+
|
|
98
|
+
This repository is also an installable plugin: `.cursor-plugin/plugin.json` and `mcp.json` for the Cursor Marketplace, and `.claude-plugin/plugin.json` and `.mcp.json` for Grok and Claude Code (`grok plugin install VisualSentinel/mcp-server`). Or add the server by hand to `~/.cursor/mcp.json`:
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"mcpServers": {
|
|
103
|
+
"visualsentinel": {
|
|
104
|
+
"command": "npx",
|
|
105
|
+
"args": ["-y", "@visualsentinel/mcp-server"]
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Docker
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
docker build -t visualsentinel-mcp-server .
|
|
115
|
+
docker run -i --rm -e VS_API_KEY visualsentinel-mcp-server
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The server also ships as a Gemini CLI extension (`gemini-extension.json`).
|
|
119
|
+
|
|
73
120
|
### Cline (VS Code extension)
|
|
74
121
|
|
|
75
122
|
In Cline's MCP settings, add:
|
|
@@ -115,7 +162,7 @@ Two environment variables, both optional except in the noted cases:
|
|
|
115
162
|
|
|
116
163
|
| Variable | Default | Purpose |
|
|
117
164
|
|---|---|---|
|
|
118
|
-
| `VS_API_KEY` | (none) | Required for the
|
|
165
|
+
| `VS_API_KEY` | (none) | Required for the 12 authenticated tools (API keys are available on the Business and Agency plans). Public tools work without it. Generate at https://visualsentinel.com/settings/api-keys. |
|
|
119
166
|
| `VS_API_BASE` | `https://visualsentinel.com` | Override only for testing against a local development instance. |
|
|
120
167
|
|
|
121
168
|
If a tool that needs auth is called without `VS_API_KEY`, the server returns a clear error rather than silently failing.
|
|
File without changes
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tiny HTTP client for the Visual Sentinel public API.
|
|
3
|
+
*
|
|
4
|
+
* Reads the API key from `VS_API_KEY` (or `VISUALSENTINEL_API_KEY`) once at
|
|
5
|
+
* construction time. All requests are routed at `https://visualsentinel.com`
|
|
6
|
+
* unless `VS_API_BASE` overrides it (useful for testing against a local
|
|
7
|
+
* dev instance).
|
|
8
|
+
*/
|
|
9
|
+
interface ApiClientOptions {
|
|
10
|
+
apiKey?: string | undefined;
|
|
11
|
+
baseUrl?: string | undefined;
|
|
12
|
+
userAgent?: string;
|
|
13
|
+
}
|
|
14
|
+
declare class VsApiError extends Error {
|
|
15
|
+
readonly status: number;
|
|
16
|
+
readonly body: unknown;
|
|
17
|
+
constructor(message: string, status: number, body: unknown);
|
|
18
|
+
}
|
|
19
|
+
declare class ApiClient {
|
|
20
|
+
private readonly apiKey;
|
|
21
|
+
private readonly baseUrl;
|
|
22
|
+
private readonly userAgent;
|
|
23
|
+
constructor(opts?: ApiClientOptions);
|
|
24
|
+
hasApiKey(): boolean;
|
|
25
|
+
request<T = unknown>(method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', path: string, options?: {
|
|
26
|
+
query?: Record<string, string | number | boolean | undefined>;
|
|
27
|
+
body?: unknown;
|
|
28
|
+
auth?: boolean;
|
|
29
|
+
}): Promise<T>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export { ApiClient, type ApiClientOptions, VsApiError };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/api-client.ts
|
|
4
|
+
var DEFAULT_BASE = "https://visualsentinel.com";
|
|
5
|
+
var VsApiError = class extends Error {
|
|
6
|
+
constructor(message, status, body) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.body = body;
|
|
10
|
+
this.name = "VsApiError";
|
|
11
|
+
}
|
|
12
|
+
status;
|
|
13
|
+
body;
|
|
14
|
+
};
|
|
15
|
+
var ApiClient = class {
|
|
16
|
+
apiKey;
|
|
17
|
+
baseUrl;
|
|
18
|
+
userAgent;
|
|
19
|
+
constructor(opts = {}) {
|
|
20
|
+
this.apiKey = opts.apiKey ?? process.env.VS_API_KEY ?? process.env.VISUALSENTINEL_API_KEY ?? void 0;
|
|
21
|
+
this.baseUrl = (opts.baseUrl ?? process.env.VS_API_BASE ?? DEFAULT_BASE).replace(
|
|
22
|
+
/\/+$/,
|
|
23
|
+
""
|
|
24
|
+
);
|
|
25
|
+
this.userAgent = opts.userAgent ?? "@visualsentinel/mcp-server";
|
|
26
|
+
}
|
|
27
|
+
hasApiKey() {
|
|
28
|
+
return typeof this.apiKey === "string" && this.apiKey.length > 0;
|
|
29
|
+
}
|
|
30
|
+
async request(method, path, options = {}) {
|
|
31
|
+
const auth = options.auth ?? true;
|
|
32
|
+
if (auth && !this.hasApiKey()) {
|
|
33
|
+
throw new VsApiError(
|
|
34
|
+
"This tool needs a Visual Sentinel API key, which is available on the Business and Agency plans. Create one at https://visualsentinel.com/settings/api-keys and set it as VS_API_KEY. The DNS, SSL, speed, website-check and health tools work without a key.",
|
|
35
|
+
401,
|
|
36
|
+
null
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
const url = new URL(path.startsWith("/") ? path : `/${path}`, this.baseUrl);
|
|
40
|
+
if (options.query) {
|
|
41
|
+
for (const [k, v] of Object.entries(options.query)) {
|
|
42
|
+
if (v !== void 0 && v !== null) url.searchParams.set(k, String(v));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const headers = {
|
|
46
|
+
Accept: "application/json",
|
|
47
|
+
"User-Agent": this.userAgent
|
|
48
|
+
};
|
|
49
|
+
if (auth && this.apiKey) headers["X-API-Key"] = this.apiKey;
|
|
50
|
+
if (options.body !== void 0) headers["Content-Type"] = "application/json";
|
|
51
|
+
const res = await fetch(url, {
|
|
52
|
+
method,
|
|
53
|
+
headers,
|
|
54
|
+
body: options.body !== void 0 ? JSON.stringify(options.body) : void 0
|
|
55
|
+
});
|
|
56
|
+
const text = await res.text();
|
|
57
|
+
let parsed = null;
|
|
58
|
+
if (text.length > 0) {
|
|
59
|
+
try {
|
|
60
|
+
parsed = JSON.parse(text);
|
|
61
|
+
} catch {
|
|
62
|
+
parsed = text;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (!res.ok) {
|
|
66
|
+
throw new VsApiError(
|
|
67
|
+
`Visual Sentinel API ${method} ${path} returned ${res.status}`,
|
|
68
|
+
res.status,
|
|
69
|
+
parsed
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return parsed;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
export {
|
|
76
|
+
ApiClient,
|
|
77
|
+
VsApiError
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=api-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/api-client.ts"],"sourcesContent":["/**\n * Tiny HTTP client for the Visual Sentinel public API.\n *\n * Reads the API key from `VS_API_KEY` (or `VISUALSENTINEL_API_KEY`) once at\n * construction time. All requests are routed at `https://visualsentinel.com`\n * unless `VS_API_BASE` overrides it (useful for testing against a local\n * dev instance).\n */\n\nconst DEFAULT_BASE = 'https://visualsentinel.com';\n\nexport interface ApiClientOptions {\n apiKey?: string | undefined;\n baseUrl?: string | undefined;\n userAgent?: string;\n}\n\nexport class VsApiError extends Error {\n constructor(\n message: string,\n public readonly status: number,\n public readonly body: unknown,\n ) {\n super(message);\n this.name = 'VsApiError';\n }\n}\n\nexport class ApiClient {\n private readonly apiKey: string | undefined;\n private readonly baseUrl: string;\n private readonly userAgent: string;\n\n constructor(opts: ApiClientOptions = {}) {\n this.apiKey =\n opts.apiKey ??\n process.env.VS_API_KEY ??\n process.env.VISUALSENTINEL_API_KEY ??\n undefined;\n this.baseUrl = (opts.baseUrl ?? process.env.VS_API_BASE ?? DEFAULT_BASE).replace(\n /\\/+$/,\n '',\n );\n this.userAgent = opts.userAgent ?? '@visualsentinel/mcp-server';\n }\n\n hasApiKey(): boolean {\n return typeof this.apiKey === 'string' && this.apiKey.length > 0;\n }\n\n async request<T = unknown>(\n method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',\n path: string,\n options: { query?: Record<string, string | number | boolean | undefined>; body?: unknown; auth?: boolean } = {},\n ): Promise<T> {\n const auth = options.auth ?? true;\n if (auth && !this.hasApiKey()) {\n throw new VsApiError(\n 'This tool needs a Visual Sentinel API key, which is available on the Business and Agency plans. Create one at https://visualsentinel.com/settings/api-keys and set it as VS_API_KEY. The DNS, SSL, speed, website-check and health tools work without a key.',\n 401,\n null,\n );\n }\n\n const url = new URL(path.startsWith('/') ? path : `/${path}`, this.baseUrl);\n if (options.query) {\n for (const [k, v] of Object.entries(options.query)) {\n if (v !== undefined && v !== null) url.searchParams.set(k, String(v));\n }\n }\n\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'User-Agent': this.userAgent,\n };\n if (auth && this.apiKey) headers['X-API-Key'] = this.apiKey;\n if (options.body !== undefined) headers['Content-Type'] = 'application/json';\n\n const res = await fetch(url, {\n method,\n headers,\n body: options.body !== undefined ? JSON.stringify(options.body) : undefined,\n });\n\n const text = await res.text();\n let parsed: unknown = null;\n if (text.length > 0) {\n try {\n parsed = JSON.parse(text);\n } catch {\n parsed = text;\n }\n }\n\n if (!res.ok) {\n throw new VsApiError(\n `Visual Sentinel API ${method} ${path} returned ${res.status}`,\n res.status,\n parsed,\n );\n }\n\n return parsed as T;\n }\n}\n"],"mappings":";;;AASA,IAAM,eAAe;AAQd,IAAM,aAAN,cAAyB,MAAM;AAAA,EACpC,YACE,SACgB,QACA,MAChB;AACA,UAAM,OAAO;AAHG;AACA;AAGhB,SAAK,OAAO;AAAA,EACd;AAAA,EALkB;AAAA,EACA;AAKpB;AAEO,IAAM,YAAN,MAAgB;AAAA,EACJ;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,OAAyB,CAAC,GAAG;AACvC,SAAK,SACH,KAAK,UACL,QAAQ,IAAI,cACZ,QAAQ,IAAI,0BACZ;AACF,SAAK,WAAW,KAAK,WAAW,QAAQ,IAAI,eAAe,cAAc;AAAA,MACvE;AAAA,MACA;AAAA,IACF;AACA,SAAK,YAAY,KAAK,aAAa;AAAA,EACrC;AAAA,EAEA,YAAqB;AACnB,WAAO,OAAO,KAAK,WAAW,YAAY,KAAK,OAAO,SAAS;AAAA,EACjE;AAAA,EAEA,MAAM,QACJ,QACA,MACA,UAA6G,CAAC,GAClG;AACZ,UAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAI,QAAQ,CAAC,KAAK,UAAU,GAAG;AAC7B,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,MAAM,IAAI,IAAI,KAAK,WAAW,GAAG,IAAI,OAAO,IAAI,IAAI,IAAI,KAAK,OAAO;AAC1E,QAAI,QAAQ,OAAO;AACjB,iBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,QAAQ,KAAK,GAAG;AAClD,YAAI,MAAM,UAAa,MAAM,KAAM,KAAI,aAAa,IAAI,GAAG,OAAO,CAAC,CAAC;AAAA,MACtE;AAAA,IACF;AAEA,UAAM,UAAkC;AAAA,MACtC,QAAQ;AAAA,MACR,cAAc,KAAK;AAAA,IACrB;AACA,QAAI,QAAQ,KAAK,OAAQ,SAAQ,WAAW,IAAI,KAAK;AACrD,QAAI,QAAQ,SAAS,OAAW,SAAQ,cAAc,IAAI;AAE1D,UAAM,MAAM,MAAM,MAAM,KAAK;AAAA,MAC3B;AAAA,MACA;AAAA,MACA,MAAM,QAAQ,SAAS,SAAY,KAAK,UAAU,QAAQ,IAAI,IAAI;AAAA,IACpE,CAAC;AAED,UAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,QAAI,SAAkB;AACtB,QAAI,KAAK,SAAS,GAAG;AACnB,UAAI;AACF,iBAAS,KAAK,MAAM,IAAI;AAAA,MAC1B,QAAQ;AACN,iBAAS;AAAA,MACX;AAAA,IACF;AAEA,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI;AAAA,QACR,uBAAuB,MAAM,IAAI,IAAI,aAAa,IAAI,MAAM;AAAA,QAC5D,IAAI;AAAA,QACJ;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;","names":[]}
|