@hover-dev/core 0.6.0 → 0.7.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/dist/agents/claude.d.ts.map +1 -1
- package/dist/agents/claude.js +23 -0
- package/dist/agents/types.d.ts +5 -0
- package/dist/agents/types.d.ts.map +1 -1
- package/dist/playwright/launchChrome.d.ts +12 -0
- package/dist/playwright/launchChrome.d.ts.map +1 -1
- package/dist/playwright/launchChrome.js +4 -1
- package/dist/playwright/raiseWindow.js +20 -5
- package/dist/playwright/resolveMcpConfig.d.ts +17 -0
- package/dist/playwright/resolveMcpConfig.d.ts.map +1 -1
- package/dist/playwright/resolveMcpConfig.js +17 -27
- package/dist/plugin-api.d.ts +161 -0
- package/dist/plugin-api.d.ts.map +1 -0
- package/dist/plugin-api.js +52 -0
- package/dist/service/cdpHandlers.d.ts +12 -3
- package/dist/service/cdpHandlers.d.ts.map +1 -1
- package/dist/service/cdpHandlers.js +24 -9
- package/dist/service/saveHandlers.d.ts.map +1 -1
- package/dist/service/saveHandlers.js +15 -4
- package/dist/service/types.d.ts +13 -1
- package/dist/service/types.d.ts.map +1 -1
- package/dist/service/types.js +12 -0
- package/dist/service.d.ts +7 -0
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +389 -50
- package/package.json +5 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../src/agents/claude.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAA2C,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../src/agents/claude.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAA2C,MAAM,YAAY,CAAC;AA6G3F,eAAO,MAAM,WAAW,EAAE,eA0HzB,CAAC"}
|
package/dist/agents/claude.js
CHANGED
|
@@ -39,12 +39,35 @@ function claudeState(state) {
|
|
|
39
39
|
}
|
|
40
40
|
return state;
|
|
41
41
|
}
|
|
42
|
+
/** Every built-in claude-code tool that has nothing to do with driving a
|
|
43
|
+
* browser. Combined with `--strict-mcp-config` + an allow-list of mcp__*
|
|
44
|
+
* ids, this leaves Claude with only the Playwright MCP (plus any
|
|
45
|
+
* plugin-contributed MCPs) as a usable tool surface. */
|
|
46
|
+
const CLAUDE_DEFAULT_DISALLOWED_TOOLS = [
|
|
47
|
+
// file / shell / data access — never appropriate for browser driving
|
|
48
|
+
'Bash', 'BashOutput', 'KillBash',
|
|
49
|
+
'Edit', 'MultiEdit', 'Write', 'Read', 'NotebookEdit',
|
|
50
|
+
'Grep', 'Glob', 'Task', 'TodoWrite',
|
|
51
|
+
'WebFetch', 'WebSearch',
|
|
52
|
+
// plan / worktree / cron / notification — irrelevant in -p mode
|
|
53
|
+
'EnterPlanMode', 'ExitPlanMode',
|
|
54
|
+
'EnterWorktree', 'ExitWorktree',
|
|
55
|
+
'CronCreate', 'CronDelete', 'CronList',
|
|
56
|
+
'PushNotification', 'RemoteTrigger',
|
|
57
|
+
// task & tool introspection added in claude 2.1.x — let through and
|
|
58
|
+
// the agent will burn turns exploring instead of executing
|
|
59
|
+
'ToolSearch',
|
|
60
|
+
'Monitor', 'TaskOutput', 'TaskStop',
|
|
61
|
+
'AskUserQuestion',
|
|
62
|
+
'ShareOnboardingGuide',
|
|
63
|
+
];
|
|
42
64
|
export const claudeAgent = {
|
|
43
65
|
id: 'claude',
|
|
44
66
|
binName: 'claude',
|
|
45
67
|
protocol: 'argv',
|
|
46
68
|
streamFormat: 'stream-json',
|
|
47
69
|
sandboxStrength: 'hard',
|
|
70
|
+
defaultDisallowedTools: CLAUDE_DEFAULT_DISALLOWED_TOOLS,
|
|
48
71
|
display: {
|
|
49
72
|
label: 'Claude Code',
|
|
50
73
|
tagline: 'Anthropic — best-in-class browser driving, hard tool sandbox',
|
package/dist/agents/types.d.ts
CHANGED
|
@@ -146,6 +146,11 @@ export interface AgentDescriptor {
|
|
|
146
146
|
streamFormat: StreamFormat;
|
|
147
147
|
sandboxStrength: SandboxStrength;
|
|
148
148
|
display: AgentDisplay;
|
|
149
|
+
/** Hard-sandbox agents pass this list to `disallowedTools` when the
|
|
150
|
+
* service-level allow/deny config isn't explicitly overridden. Lets the
|
|
151
|
+
* per-CLI deny list live alongside its descriptor instead of as a magic
|
|
152
|
+
* array in the service. Soft-sandbox agents leave this undefined. */
|
|
153
|
+
defaultDisallowedTools?: readonly string[];
|
|
149
154
|
buildArgs(opts: InvokeOptions): string[];
|
|
150
155
|
/**
|
|
151
156
|
* Parse a single line of agent stdout into normalised InvokeEvents.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/agents/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/agents/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,6 +9,18 @@ export interface LaunchOptions {
|
|
|
9
9
|
readyTimeoutMs?: number;
|
|
10
10
|
/** Poll interval while waiting (default 300ms). */
|
|
11
11
|
pollMs?: number;
|
|
12
|
+
/** Security-mode proxy config. When provided, Chrome is launched with
|
|
13
|
+
* `--proxy-server=127.0.0.1:<proxyPort>` and the given SPKI pinned via
|
|
14
|
+
* `--ignore-certificate-errors-spki-list` so HTTPS certs from the
|
|
15
|
+
* Hover MITM CA validate without polluting the OS trust store. The
|
|
16
|
+
* caller is expected to pick a different `userDataDir` and `port` from
|
|
17
|
+
* the normal-mode launch so the two profiles don't share state. */
|
|
18
|
+
proxy?: {
|
|
19
|
+
/** Local mockttp port the proxy is listening on. */
|
|
20
|
+
port: number;
|
|
21
|
+
/** Base64 SHA-256 of the MITM CA's SubjectPublicKeyInfo. */
|
|
22
|
+
spki: string;
|
|
23
|
+
};
|
|
12
24
|
}
|
|
13
25
|
export type LaunchResult = {
|
|
14
26
|
ok: true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"launchChrome.d.ts","sourceRoot":"","sources":["../../src/playwright/launchChrome.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,aAAa;IAC5B,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2CAA2C;IAC3C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"launchChrome.d.ts","sourceRoot":"","sources":["../../src/playwright/launchChrome.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,aAAa;IAC5B,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2CAA2C;IAC3C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;wEAKoE;IACpE,KAAK,CAAC,EAAE;QACN,oDAAoD;QACpD,IAAI,EAAE,MAAM,CAAC;QACb,4DAA4D;QAC5D,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AAED,MAAM,MAAM,YAAY,GACpB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,cAAc,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACxE;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAMlC,wBAAgB,gBAAgB,IAAI,MAAM,GAAG,IAAI,CA0ChD;AAiCD;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CA8DvF"}
|
|
@@ -111,8 +111,11 @@ export async function launchDebugChrome(opts = {}) {
|
|
|
111
111
|
`--user-data-dir=${userDataDir}`,
|
|
112
112
|
'--no-first-run',
|
|
113
113
|
'--no-default-browser-check',
|
|
114
|
-
url,
|
|
115
114
|
];
|
|
115
|
+
if (opts.proxy) {
|
|
116
|
+
args.push(`--proxy-server=127.0.0.1:${opts.proxy.port}`, `--ignore-certificate-errors-spki-list=${opts.proxy.spki}`);
|
|
117
|
+
}
|
|
118
|
+
args.push(url);
|
|
116
119
|
const child = spawn(chrome, args, {
|
|
117
120
|
detached: true,
|
|
118
121
|
stdio: 'ignore',
|
|
@@ -103,22 +103,37 @@ export async function raiseChromeWindow(pid) {
|
|
|
103
103
|
function runCapture(cmd, args) {
|
|
104
104
|
return new Promise(resolve => {
|
|
105
105
|
let out = '';
|
|
106
|
+
let settled = false;
|
|
107
|
+
const finish = (v) => {
|
|
108
|
+
if (settled)
|
|
109
|
+
return;
|
|
110
|
+
settled = true;
|
|
111
|
+
resolve(v);
|
|
112
|
+
};
|
|
106
113
|
const child = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
107
114
|
child.stdout.on('data', chunk => {
|
|
108
115
|
out += chunk.toString();
|
|
109
116
|
});
|
|
110
|
-
child.on('error', () =>
|
|
111
|
-
child.on('close', code =>
|
|
117
|
+
child.on('error', () => finish(null));
|
|
118
|
+
child.on('close', code => finish(code === 0 ? out : null));
|
|
112
119
|
});
|
|
113
120
|
}
|
|
114
121
|
function runDetached(cmd, args) {
|
|
115
122
|
return new Promise((resolve, reject) => {
|
|
123
|
+
let settled = false;
|
|
116
124
|
const child = spawn(cmd, args, { stdio: 'ignore' });
|
|
117
|
-
child.on('error',
|
|
118
|
-
|
|
125
|
+
child.on('error', err => {
|
|
126
|
+
if (settled)
|
|
127
|
+
return;
|
|
128
|
+
settled = true;
|
|
129
|
+
reject(err);
|
|
130
|
+
});
|
|
131
|
+
child.on('close', () => {
|
|
132
|
+
if (settled)
|
|
133
|
+
return;
|
|
134
|
+
settled = true;
|
|
119
135
|
// Don't treat non-zero as fatal — caller already wraps in try/catch.
|
|
120
136
|
resolve();
|
|
121
|
-
void code;
|
|
122
137
|
});
|
|
123
138
|
});
|
|
124
139
|
}
|
|
@@ -18,10 +18,27 @@
|
|
|
18
18
|
* which lets multiple Hover services (one per example app) coexist
|
|
19
19
|
* without stepping on each other's CDP endpoint.
|
|
20
20
|
*/
|
|
21
|
+
export interface ExtraMcpServer {
|
|
22
|
+
/** Stable id of the server. Becomes the JSON key under mcpServers; also
|
|
23
|
+
* the prefix Claude exposes its tools under (`mcp__<id>__<tool>`). */
|
|
24
|
+
id: string;
|
|
25
|
+
command: string;
|
|
26
|
+
args?: string[];
|
|
27
|
+
env?: Record<string, string>;
|
|
28
|
+
}
|
|
21
29
|
export declare function resolveMcpConfig(opts: {
|
|
22
30
|
/** CDP URL passed to the MCP server's `--cdp-endpoint` flag. */
|
|
23
31
|
cdpUrl: string;
|
|
24
32
|
/** Service port — used to namespace the temp config file. */
|
|
25
33
|
port: number;
|
|
34
|
+
/** Additional MCP servers contributed by active plugins. Each becomes
|
|
35
|
+
* a key under the mcpServers object. The id is also used to name the
|
|
36
|
+
* tool prefix Claude exposes (e.g. `mcp__hover_security__list_flows`),
|
|
37
|
+
* but Claude sanitises non-alphanumeric chars to underscores, so the
|
|
38
|
+
* caller does NOT need to do that. */
|
|
39
|
+
extra?: ExtraMcpServer[];
|
|
40
|
+
/** Suffix for the output filename so multiple parallel configs from
|
|
41
|
+
* the same service (e.g. mode toggle round-trips) don't share state. */
|
|
42
|
+
suffix?: string;
|
|
26
43
|
}): string;
|
|
27
44
|
//# sourceMappingURL=resolveMcpConfig.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolveMcpConfig.d.ts","sourceRoot":"","sources":["../../src/playwright/resolveMcpConfig.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"resolveMcpConfig.d.ts","sourceRoot":"","sources":["../../src/playwright/resolveMcpConfig.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,cAAc;IAC7B;2EACuE;IACvE,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb;;;;2CAIuC;IACvC,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC;IACzB;6EACyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,MAAM,CAgDT"}
|
|
@@ -3,26 +3,6 @@ import { mkdirSync, writeFileSync } from 'node:fs';
|
|
|
3
3
|
import { dirname, resolve } from 'node:path';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import process from 'node:process';
|
|
6
|
-
/**
|
|
7
|
-
* Resolve a ready-to-use MCP config file path that points at the local
|
|
8
|
-
* `@playwright/mcp` package via an absolute Node-resolved path.
|
|
9
|
-
*
|
|
10
|
-
* Why this exists: Hover originally shipped a static `mcp.config.json`
|
|
11
|
-
* with `"command": "npx", "args": ["-y", "@playwright/mcp@latest", …]`.
|
|
12
|
-
* That meant every `claude -p` invocation kicked off a registry lookup
|
|
13
|
-
* for `@latest` plus a tarball metadata round-trip before the MCP server
|
|
14
|
-
* even started — adding 300 ms - 2 s of dead air to first-token latency
|
|
15
|
-
* on every command (verified via `time npx -y @playwright/mcp@latest`).
|
|
16
|
-
*
|
|
17
|
-
* The fix is to (a) declare `@playwright/mcp` as a real dependency of
|
|
18
|
-
* `@hover-dev/core` so npm resolves it locally at install time, and
|
|
19
|
-
* (b) write a synthetic config file pointing `node <abs-path>/cli.js`
|
|
20
|
-
* at the resolved location. No registry hit on the hot path.
|
|
21
|
-
*
|
|
22
|
-
* The config file is written to `<tmpdir>/hover/mcp-config-<port>.json`,
|
|
23
|
-
* which lets multiple Hover services (one per example app) coexist
|
|
24
|
-
* without stepping on each other's CDP endpoint.
|
|
25
|
-
*/
|
|
26
6
|
export function resolveMcpConfig(opts) {
|
|
27
7
|
// Resolve the package's main file, then walk back to its package root.
|
|
28
8
|
// Using `package.json` as the resolution target is the documented
|
|
@@ -46,17 +26,27 @@ export function resolveMcpConfig(opts) {
|
|
|
46
26
|
// pin to that file directly via Node so the user doesn't need the
|
|
47
27
|
// bin shim on PATH and we skip yet another resolution layer.
|
|
48
28
|
const cliPath = resolve(pkgRoot, 'cli.js');
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
args: [cliPath, '--cdp-endpoint', opts.cdpUrl],
|
|
54
|
-
},
|
|
29
|
+
const mcpServers = {
|
|
30
|
+
playwright: {
|
|
31
|
+
command: process.execPath, // current Node binary
|
|
32
|
+
args: [cliPath, '--cdp-endpoint', opts.cdpUrl],
|
|
55
33
|
},
|
|
56
34
|
};
|
|
35
|
+
for (const extra of opts.extra ?? []) {
|
|
36
|
+
// Claude sanitises the key for tool naming; we keep the raw id here
|
|
37
|
+
// because mcp-config consumers (claude / codex) accept arbitrary
|
|
38
|
+
// strings and do their own normalisation.
|
|
39
|
+
mcpServers[extra.id] = {
|
|
40
|
+
command: extra.command,
|
|
41
|
+
args: extra.args,
|
|
42
|
+
env: extra.env,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const config = { mcpServers };
|
|
57
46
|
const outDir = resolve(tmpdir(), 'hover');
|
|
58
47
|
mkdirSync(outDir, { recursive: true });
|
|
59
|
-
const
|
|
48
|
+
const suffix = opts.suffix ? `-${opts.suffix}` : '';
|
|
49
|
+
const outPath = resolve(outDir, `mcp-config-${opts.port}${suffix}.json`);
|
|
60
50
|
writeFileSync(outPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
61
51
|
return outPath;
|
|
62
52
|
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hover plugin API — the public contract third-party packages target.
|
|
3
|
+
*
|
|
4
|
+
* Plugins are *mostly declarative*: they ship a manifest describing what
|
|
5
|
+
* resources they contribute (a mode, MCP servers, Chrome flags, agent
|
|
6
|
+
* prompt fragments, widget event schemas). For genuinely time-bound work
|
|
7
|
+
* — booting a sidecar like mockttp when a mode activates, tearing it down
|
|
8
|
+
* when the mode deactivates or the service shuts down — they register
|
|
9
|
+
* namespaced lifecycle hooks.
|
|
10
|
+
*
|
|
11
|
+
* Patterned after Astro Integrations (declarative manifest + namespaced
|
|
12
|
+
* hooks: `astro:config:setup` etc). The `apiVersion` literal lets us
|
|
13
|
+
* evolve the manifest and reject mismatched plugins at load time with a
|
|
14
|
+
* clear error rather than silent breakage.
|
|
15
|
+
*
|
|
16
|
+
* Stability:
|
|
17
|
+
* - `apiVersion: 1` is what this file declares; breaking changes bump.
|
|
18
|
+
* - Adding new optional fields or new hook names is non-breaking.
|
|
19
|
+
* - Plugin authors should import only from this module; deep imports
|
|
20
|
+
* into `@hover-dev/core` internals are not part of the contract.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* The Hover plugin API version this build of @hover-dev/core understands.
|
|
24
|
+
* Plugins declare which version they target via their manifest; mismatches
|
|
25
|
+
* are rejected at load time.
|
|
26
|
+
*/
|
|
27
|
+
export type HoverApiVersion = 1;
|
|
28
|
+
export declare const CURRENT_API_VERSION: HoverApiVersion;
|
|
29
|
+
export interface HoverPluginMode {
|
|
30
|
+
/** Globally unique id (across all loaded plugins). Lowercase kebab. */
|
|
31
|
+
id: string;
|
|
32
|
+
/** Human-readable label shown in the widget mode-picker. */
|
|
33
|
+
label: string;
|
|
34
|
+
/** One-liner help text shown in the dropdown. */
|
|
35
|
+
description?: string;
|
|
36
|
+
/** Mode ids this mode cannot be active alongside. Two plugins both
|
|
37
|
+
* needing an exclusive proxy would set each other here. */
|
|
38
|
+
conflictsWith?: string[];
|
|
39
|
+
}
|
|
40
|
+
export interface HoverPluginMcpServer {
|
|
41
|
+
/** Stable, namespaced id (`@hover-dev/security:flows`). Host enforces
|
|
42
|
+
* uniqueness across all loaded plugins. */
|
|
43
|
+
id: string;
|
|
44
|
+
command: string;
|
|
45
|
+
args?: string[];
|
|
46
|
+
env?: Record<string, string>;
|
|
47
|
+
/** Modes in which this MCP is exposed to the agent. Default: only the
|
|
48
|
+
* plugin's own mode. Use `['*']` to mean "always on". */
|
|
49
|
+
activeInModes?: string[];
|
|
50
|
+
}
|
|
51
|
+
export interface HoverPluginChromeFlags {
|
|
52
|
+
/** Extra args appended to the Chrome launch argv. */
|
|
53
|
+
args?: string[];
|
|
54
|
+
/** Custom user-data-dir for this mode. Strongly recommended when proxy
|
|
55
|
+
* is set, so the secured profile doesn't share cookies with normal mode. */
|
|
56
|
+
userDataDir?: string;
|
|
57
|
+
/** Custom CDP port for this mode. Strongly recommended for the same
|
|
58
|
+
* reason — keeps the two modes' Chromes addressable independently. */
|
|
59
|
+
cdpPort?: number;
|
|
60
|
+
/** When present, Chrome is launched with --proxy-server + the
|
|
61
|
+
* --ignore-certificate-errors-spki-list pin so the proxy's MITM CA is
|
|
62
|
+
* accepted without polluting the OS trust store. */
|
|
63
|
+
proxy?: {
|
|
64
|
+
port: number;
|
|
65
|
+
spki: string;
|
|
66
|
+
};
|
|
67
|
+
/** Modes in which these flags apply. Default: only the plugin's own mode. */
|
|
68
|
+
activeInModes?: string[];
|
|
69
|
+
}
|
|
70
|
+
export interface HoverPluginSystemPromptAddition {
|
|
71
|
+
text: string;
|
|
72
|
+
/** Modes in which this paragraph is included in the agent's system
|
|
73
|
+
* prompt. Default: only the plugin's own mode. */
|
|
74
|
+
activeInModes?: string[];
|
|
75
|
+
}
|
|
76
|
+
export interface HoverBroadcast {
|
|
77
|
+
/** Push a JSON event to every WebSocket-connected widget. Event `type`
|
|
78
|
+
* should be namespaced by the plugin (`security:flow:added`). */
|
|
79
|
+
(event: {
|
|
80
|
+
type: string;
|
|
81
|
+
payload?: unknown;
|
|
82
|
+
}): void;
|
|
83
|
+
}
|
|
84
|
+
export interface HoverHookCtxBase {
|
|
85
|
+
/** Absolute path of the user's project root (Vite's `server.config.root`,
|
|
86
|
+
* Astro's project dir, etc.). Use this for persisting CA material, not
|
|
87
|
+
* process.cwd(). */
|
|
88
|
+
devRoot: string;
|
|
89
|
+
/** Push a custom event to every connected widget. */
|
|
90
|
+
broadcast: HoverBroadcast;
|
|
91
|
+
}
|
|
92
|
+
/** Fired when this plugin's mode becomes active. The plugin may boot
|
|
93
|
+
* sidecars (mockttp, profilers, …) here and return any settings that
|
|
94
|
+
* affect downstream subsystems (Chrome relaunch, MCP server env vars). */
|
|
95
|
+
export interface ModeActivateCtx extends HoverHookCtxBase {
|
|
96
|
+
modeId: string;
|
|
97
|
+
/** Tell the host "Chrome should be relaunched with these proxy settings"
|
|
98
|
+
* for the duration of this mode. Pass null to clear. */
|
|
99
|
+
setChromeProxy(proxy: {
|
|
100
|
+
port: number;
|
|
101
|
+
spki: string;
|
|
102
|
+
} | null): void;
|
|
103
|
+
/** Set additional env vars on one of this plugin's declared MCP servers.
|
|
104
|
+
* The MCP server isn't actually spawned until the agent runs a command,
|
|
105
|
+
* so plugins use this in activate() to pass runtime data (port numbers,
|
|
106
|
+
* auth tokens) that didn't exist at manifest-construction time.
|
|
107
|
+
* Merged on top of any env declared in the manifest; subsequent calls
|
|
108
|
+
* for the same id replace previous overrides. */
|
|
109
|
+
setMcpServerEnv(id: string, env: Record<string, string>): void;
|
|
110
|
+
}
|
|
111
|
+
/** Fired when this plugin's mode is being deactivated. The plugin
|
|
112
|
+
* MUST stop any sidecar it started in activate. */
|
|
113
|
+
export interface ModeDeactivateCtx extends HoverHookCtxBase {
|
|
114
|
+
modeId: string;
|
|
115
|
+
}
|
|
116
|
+
/** Fired exactly once when the host service is shutting down for any
|
|
117
|
+
* reason. Hooks must release subprocesses and file handles. */
|
|
118
|
+
export type ShutdownCtx = HoverHookCtxBase;
|
|
119
|
+
export interface HoverHooks {
|
|
120
|
+
'hover:mode:activate'?: (ctx: ModeActivateCtx) => void | Promise<void>;
|
|
121
|
+
'hover:mode:deactivate'?: (ctx: ModeDeactivateCtx) => void | Promise<void>;
|
|
122
|
+
'hover:service:shutdown'?: (ctx: ShutdownCtx) => void | Promise<void>;
|
|
123
|
+
}
|
|
124
|
+
export interface HoverPluginManifest {
|
|
125
|
+
/** Always 1 in this build. Future versions may add 2, 3, … */
|
|
126
|
+
apiVersion: HoverApiVersion;
|
|
127
|
+
/** Globally unique plugin name. Use the npm package name. */
|
|
128
|
+
name: string;
|
|
129
|
+
/** Optional widget mode contributed by this plugin. */
|
|
130
|
+
mode?: HoverPluginMode;
|
|
131
|
+
/** Extra MCP servers exposed to the agent in the indicated modes. */
|
|
132
|
+
mcpServers?: HoverPluginMcpServer[];
|
|
133
|
+
/** Chrome launch overrides for the indicated modes. */
|
|
134
|
+
chromeFlags?: HoverPluginChromeFlags;
|
|
135
|
+
/** System-prompt paragraphs concatenated into the agent's prompt in
|
|
136
|
+
* the indicated modes. */
|
|
137
|
+
systemPromptAdditions?: HoverPluginSystemPromptAddition[];
|
|
138
|
+
/** Names of custom event types this plugin broadcasts. Documented
|
|
139
|
+
* here so the widget side can be tree-shaken to skip handlers for
|
|
140
|
+
* events that no loaded plugin will ever produce. */
|
|
141
|
+
widgetEventTypes?: string[];
|
|
142
|
+
hooks?: HoverHooks;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Branded factory that wraps a plugin manifest factory. The wrapper
|
|
146
|
+
* - asserts `apiVersion` matches this core's version at construction time
|
|
147
|
+
* (catches authors who copy-pasted from a tutorial for a different core),
|
|
148
|
+
* - returns a `(opts) => manifest` so call sites read `securityMode()` /
|
|
149
|
+
* `perfMode({ sampleHz: 100 })` uniformly.
|
|
150
|
+
*
|
|
151
|
+
* Use:
|
|
152
|
+
*
|
|
153
|
+
* export default defineHoverPlugin<MyOpts>((opts) => ({
|
|
154
|
+
* apiVersion: 1,
|
|
155
|
+
* name: '@hover-dev/security',
|
|
156
|
+
* mode: { id: 'security', label: 'Security testing' },
|
|
157
|
+
* ...
|
|
158
|
+
* }));
|
|
159
|
+
*/
|
|
160
|
+
export declare function defineHoverPlugin<TOpts = void>(factory: (opts: TOpts) => HoverPluginManifest): (opts: TOpts) => HoverPluginManifest;
|
|
161
|
+
//# sourceMappingURL=plugin-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-api.d.ts","sourceRoot":"","sources":["../src/plugin-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hover plugin API — the public contract third-party packages target.
|
|
3
|
+
*
|
|
4
|
+
* Plugins are *mostly declarative*: they ship a manifest describing what
|
|
5
|
+
* resources they contribute (a mode, MCP servers, Chrome flags, agent
|
|
6
|
+
* prompt fragments, widget event schemas). For genuinely time-bound work
|
|
7
|
+
* — booting a sidecar like mockttp when a mode activates, tearing it down
|
|
8
|
+
* when the mode deactivates or the service shuts down — they register
|
|
9
|
+
* namespaced lifecycle hooks.
|
|
10
|
+
*
|
|
11
|
+
* Patterned after Astro Integrations (declarative manifest + namespaced
|
|
12
|
+
* hooks: `astro:config:setup` etc). The `apiVersion` literal lets us
|
|
13
|
+
* evolve the manifest and reject mismatched plugins at load time with a
|
|
14
|
+
* clear error rather than silent breakage.
|
|
15
|
+
*
|
|
16
|
+
* Stability:
|
|
17
|
+
* - `apiVersion: 1` is what this file declares; breaking changes bump.
|
|
18
|
+
* - Adding new optional fields or new hook names is non-breaking.
|
|
19
|
+
* - Plugin authors should import only from this module; deep imports
|
|
20
|
+
* into `@hover-dev/core` internals are not part of the contract.
|
|
21
|
+
*/
|
|
22
|
+
export const CURRENT_API_VERSION = 1;
|
|
23
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
24
|
+
// Author helper
|
|
25
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
26
|
+
/**
|
|
27
|
+
* Branded factory that wraps a plugin manifest factory. The wrapper
|
|
28
|
+
* - asserts `apiVersion` matches this core's version at construction time
|
|
29
|
+
* (catches authors who copy-pasted from a tutorial for a different core),
|
|
30
|
+
* - returns a `(opts) => manifest` so call sites read `securityMode()` /
|
|
31
|
+
* `perfMode({ sampleHz: 100 })` uniformly.
|
|
32
|
+
*
|
|
33
|
+
* Use:
|
|
34
|
+
*
|
|
35
|
+
* export default defineHoverPlugin<MyOpts>((opts) => ({
|
|
36
|
+
* apiVersion: 1,
|
|
37
|
+
* name: '@hover-dev/security',
|
|
38
|
+
* mode: { id: 'security', label: 'Security testing' },
|
|
39
|
+
* ...
|
|
40
|
+
* }));
|
|
41
|
+
*/
|
|
42
|
+
export function defineHoverPlugin(factory) {
|
|
43
|
+
return (opts) => {
|
|
44
|
+
const manifest = factory(opts);
|
|
45
|
+
if (manifest.apiVersion !== CURRENT_API_VERSION) {
|
|
46
|
+
throw new Error(`[hover] plugin "${manifest.name}" targets apiVersion ` +
|
|
47
|
+
`${String(manifest.apiVersion)} but this Hover supports ` +
|
|
48
|
+
`${CURRENT_API_VERSION}. Update either the plugin or @hover-dev/core.`);
|
|
49
|
+
}
|
|
50
|
+
return manifest;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -11,7 +11,16 @@
|
|
|
11
11
|
* file can be a thin orchestrator.
|
|
12
12
|
*/
|
|
13
13
|
import type { WebSocket } from 'ws';
|
|
14
|
+
import { type LaunchOptions } from '../playwright/launchChrome.js';
|
|
14
15
|
import { type ClientMessage } from './types.js';
|
|
16
|
+
/** Extra launch options surfaced from the active mode (security plugin
|
|
17
|
+
* needs proxy + spki + separate profile + non-default CDP port). When
|
|
18
|
+
* none are set, behaviour is identical to pre-v0.7 normal-mode launch. */
|
|
19
|
+
export type LaunchExtras = Pick<LaunchOptions, 'userDataDir' | 'proxy'> & {
|
|
20
|
+
/** Override CDP port (mode-specific, e.g. 9333 for security). When set,
|
|
21
|
+
* this also wins over the `port` parsed from cdpUrl. */
|
|
22
|
+
cdpPort?: number;
|
|
23
|
+
};
|
|
15
24
|
/**
|
|
16
25
|
* "Is this widget running inside the debug Chrome?" The widget asks this on
|
|
17
26
|
* connect (and after every status-changing event) so it can render itself as
|
|
@@ -20,14 +29,14 @@ import { type ClientMessage } from './types.js';
|
|
|
20
29
|
* - wrong-window → disabled, with a "use the other window" notice
|
|
21
30
|
* - no-cdp → enabled but click triggers launch-chrome instead
|
|
22
31
|
*/
|
|
23
|
-
export declare function handleCheckCdp(ws: WebSocket, msg: ClientMessage, cdpUrl: string): Promise<void>;
|
|
32
|
+
export declare function handleCheckCdp(ws: WebSocket, msg: ClientMessage, cdpUrl: string, extras?: LaunchExtras): Promise<void>;
|
|
24
33
|
/**
|
|
25
34
|
* Launch a debug Chrome navigated to `pageUrl`, then re-check status. The
|
|
26
35
|
* re-check usually returns 'wrong-window' (because the widget asking is in
|
|
27
36
|
* the user's regular Chrome, not the freshly-launched one) — the widget then
|
|
28
37
|
* displays the "use the other window" state.
|
|
29
38
|
*/
|
|
30
|
-
export declare function handleLaunchChrome(ws: WebSocket, msg: ClientMessage, cdpUrl: string): Promise<void>;
|
|
39
|
+
export declare function handleLaunchChrome(ws: WebSocket, msg: ClientMessage, cdpUrl: string, extras?: LaunchExtras): Promise<void>;
|
|
31
40
|
/**
|
|
32
41
|
* bringToFront the debug-Chrome tab matching `pageUrl`'s origin (or open one
|
|
33
42
|
* if none exists). Used by the wrong-window UI's "switch to debug Chrome"
|
|
@@ -35,5 +44,5 @@ export declare function handleLaunchChrome(ws: WebSocket, msg: ClientMessage, cd
|
|
|
35
44
|
* the widget cares about, and the widget the user is about to focus is a
|
|
36
45
|
* different page (and will run its own check-cdp on its own ws connection).
|
|
37
46
|
*/
|
|
38
|
-
export declare function handleFocusDebug(ws: WebSocket, msg: ClientMessage, cdpUrl: string): Promise<void>;
|
|
47
|
+
export declare function handleFocusDebug(ws: WebSocket, msg: ClientMessage, cdpUrl: string, extras?: LaunchExtras): Promise<void>;
|
|
39
48
|
//# sourceMappingURL=cdpHandlers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cdpHandlers.d.ts","sourceRoot":"","sources":["../../src/service/cdpHandlers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"cdpHandlers.d.ts","sourceRoot":"","sources":["../../src/service/cdpHandlers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAEpC,OAAO,EAAqB,KAAK,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACtF,OAAO,EAAQ,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAEtD;;2EAE2E;AAC3E,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,EAAE,aAAa,GAAG,OAAO,CAAC,GAAG;IACxE;6DACyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAClC,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,aAAa,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,YAAY,GACpB,OAAO,CAAC,IAAI,CAAC,CAWf;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,aAAa,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,YAAY,GACpB,OAAO,CAAC,IAAI,CAAC,CAkCf;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,aAAa,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,YAAY,GACpB,OAAO,CAAC,IAAI,CAAC,CAaf"}
|
|
@@ -21,13 +21,16 @@ import { send } from './types.js';
|
|
|
21
21
|
* - wrong-window → disabled, with a "use the other window" notice
|
|
22
22
|
* - no-cdp → enabled but click triggers launch-chrome instead
|
|
23
23
|
*/
|
|
24
|
-
export async function handleCheckCdp(ws, msg, cdpUrl) {
|
|
24
|
+
export async function handleCheckCdp(ws, msg, cdpUrl, extras) {
|
|
25
25
|
const pageUrl = msg.payload?.pageUrl;
|
|
26
26
|
if (typeof pageUrl !== 'string' || !pageUrl) {
|
|
27
27
|
send(ws, { type: 'error', payload: { message: 'check-cdp: pageUrl is required' } });
|
|
28
28
|
return;
|
|
29
29
|
}
|
|
30
|
-
const
|
|
30
|
+
const effectiveCdpUrl = extras?.cdpPort
|
|
31
|
+
? `http://localhost:${extras.cdpPort}`
|
|
32
|
+
: cdpUrl;
|
|
33
|
+
const status = await checkCdpStatus(effectiveCdpUrl, pageUrl);
|
|
31
34
|
send(ws, { type: 'cdp-status', payload: status });
|
|
32
35
|
}
|
|
33
36
|
/**
|
|
@@ -36,7 +39,7 @@ export async function handleCheckCdp(ws, msg, cdpUrl) {
|
|
|
36
39
|
* the user's regular Chrome, not the freshly-launched one) — the widget then
|
|
37
40
|
* displays the "use the other window" state.
|
|
38
41
|
*/
|
|
39
|
-
export async function handleLaunchChrome(ws, msg, cdpUrl) {
|
|
42
|
+
export async function handleLaunchChrome(ws, msg, cdpUrl, extras) {
|
|
40
43
|
const pageUrl = msg.payload?.pageUrl;
|
|
41
44
|
if (typeof pageUrl !== 'string' || !pageUrl) {
|
|
42
45
|
send(ws, { type: 'error', payload: { message: 'launch-chrome: pageUrl is required' } });
|
|
@@ -45,7 +48,7 @@ export async function handleLaunchChrome(ws, msg, cdpUrl) {
|
|
|
45
48
|
// Tell the widget we're launching so it can render a spinner immediately —
|
|
46
49
|
// findChromeBinary + spawn + ready-poll can take a few seconds.
|
|
47
50
|
send(ws, { type: 'cdp-status', payload: { state: 'no-cdp', launching: true } });
|
|
48
|
-
const port = (() => {
|
|
51
|
+
const port = extras?.cdpPort ?? (() => {
|
|
49
52
|
try {
|
|
50
53
|
return Number(new URL(cdpUrl).port) || 9222;
|
|
51
54
|
}
|
|
@@ -53,13 +56,22 @@ export async function handleLaunchChrome(ws, msg, cdpUrl) {
|
|
|
53
56
|
return 9222;
|
|
54
57
|
}
|
|
55
58
|
})();
|
|
56
|
-
const result = await launchDebugChrome({
|
|
59
|
+
const result = await launchDebugChrome({
|
|
60
|
+
url: pageUrl,
|
|
61
|
+
port,
|
|
62
|
+
userDataDir: extras?.userDataDir,
|
|
63
|
+
proxy: extras?.proxy,
|
|
64
|
+
});
|
|
57
65
|
if (!result.ok) {
|
|
58
66
|
send(ws, { type: 'cdp-status', payload: { state: 'no-cdp', reason: result.reason } });
|
|
59
67
|
return;
|
|
60
68
|
}
|
|
61
|
-
// Re-check
|
|
62
|
-
|
|
69
|
+
// Re-check status against the port we actually launched on, so a mode-
|
|
70
|
+
// specific port (9333 for security) doesn't get probed at 9222.
|
|
71
|
+
const effectiveCdpUrl = extras?.cdpPort
|
|
72
|
+
? `http://localhost:${extras.cdpPort}`
|
|
73
|
+
: cdpUrl;
|
|
74
|
+
const status = await checkCdpStatus(effectiveCdpUrl, pageUrl);
|
|
63
75
|
send(ws, { type: 'cdp-status', payload: status });
|
|
64
76
|
}
|
|
65
77
|
/**
|
|
@@ -69,13 +81,16 @@ export async function handleLaunchChrome(ws, msg, cdpUrl) {
|
|
|
69
81
|
* the widget cares about, and the widget the user is about to focus is a
|
|
70
82
|
* different page (and will run its own check-cdp on its own ws connection).
|
|
71
83
|
*/
|
|
72
|
-
export async function handleFocusDebug(ws, msg, cdpUrl) {
|
|
84
|
+
export async function handleFocusDebug(ws, msg, cdpUrl, extras) {
|
|
73
85
|
const pageUrl = msg.payload?.pageUrl;
|
|
74
86
|
if (typeof pageUrl !== 'string' || !pageUrl) {
|
|
75
87
|
send(ws, { type: 'error', payload: { message: 'focus-debug: pageUrl is required' } });
|
|
76
88
|
return;
|
|
77
89
|
}
|
|
78
|
-
const
|
|
90
|
+
const effectiveCdpUrl = extras?.cdpPort
|
|
91
|
+
? `http://localhost:${extras.cdpPort}`
|
|
92
|
+
: cdpUrl;
|
|
93
|
+
const result = await focusDebugTab(effectiveCdpUrl, pageUrl);
|
|
79
94
|
if (!result.ok) {
|
|
80
95
|
send(ws, { type: 'error', payload: { message: `focus-debug: ${result.reason}` } });
|
|
81
96
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"saveHandlers.d.ts","sourceRoot":"","sources":["../../src/service/saveHandlers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,EAAE,UAAU,EAAgC,KAAK,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACnG,OAAO,EAAE,SAAS,EAAmB,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACvF,OAAO,EAAE,YAAY,EAAsB,MAAM,0BAA0B,CAAC;AAC5E,OAAO,EAAQ,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAEtD,UAAU,kBAAkB,CAAC,YAAY,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE;IAC9E,qEAAqE;IACrE,WAAW,EAAE,MAAM,CAAC;IACpB,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,UAAU,EAAE,MAAM,CAAC;IACnB;wDACoD;IACpD,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,0EAA0E;IAC1E,WAAW,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,KAAK,CAAC;IAC9E,wEAAwE;IACxE,KAAK,EAAE,CAAC,IAAI,EAAE;QACZ,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,SAAS,EAAE,CAAC;QACnB,UAAU,EAAE,aAAa,EAAE,CAAC;QAC5B,OAAO,EAAE,WAAW,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC;QAC/C,SAAS,EAAE,OAAO,CAAC;KACpB,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;CAC7B;AAED,wBAAsB,kBAAkB,CAAC,YAAY,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAC1F,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,kBAAkB,CAAC,YAAY,CAAC,GACpC,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"saveHandlers.d.ts","sourceRoot":"","sources":["../../src/service/saveHandlers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,EAAE,UAAU,EAAgC,KAAK,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACnG,OAAO,EAAE,SAAS,EAAmB,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACvF,OAAO,EAAE,YAAY,EAAsB,MAAM,0BAA0B,CAAC;AAC5E,OAAO,EAAQ,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAEtD,UAAU,kBAAkB,CAAC,YAAY,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE;IAC9E,qEAAqE;IACrE,WAAW,EAAE,MAAM,CAAC;IACpB,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,UAAU,EAAE,MAAM,CAAC;IACnB;wDACoD;IACpD,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,0EAA0E;IAC1E,WAAW,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,KAAK,CAAC;IAC9E,wEAAwE;IACxE,KAAK,EAAE,CAAC,IAAI,EAAE;QACZ,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,SAAS,EAAE,CAAC;QACnB,UAAU,EAAE,aAAa,EAAE,CAAC;QAC5B,OAAO,EAAE,WAAW,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC;QAC/C,SAAS,EAAE,OAAO,CAAC;KACpB,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;CAC7B;AAED,wBAAsB,kBAAkB,CAAC,YAAY,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAC1F,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,kBAAkB,CAAC,YAAY,CAAC,GACpC,OAAO,CAAC,IAAI,CAAC,CA0Cf;AAED,eAAO,MAAM,YAAY,EAAE,kBAAkB,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC,CAanF,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,kBAAkB,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC,CAOjF,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,kBAAkB,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAYxF,CAAC"}
|