@ericmhalvorsen/aperture 0.2.1
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 +148 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +64 -0
- package/dist/client/handlers.d.ts +2 -0
- package/dist/client/handlers.js +171 -0
- package/dist/client/patches.d.ts +18 -0
- package/dist/client/patches.js +56 -0
- package/dist/client/storage.d.ts +5 -0
- package/dist/client/storage.js +31 -0
- package/dist/client/ui.d.ts +26 -0
- package/dist/client/ui.js +600 -0
- package/dist/client.d.ts +60 -0
- package/dist/client.js +435 -0
- package/dist/frameworks/next.d.ts +3 -0
- package/dist/frameworks/next.js +12 -0
- package/dist/frameworks/shared.d.ts +1 -0
- package/dist/frameworks/shared.js +61 -0
- package/dist/frameworks/vite.d.ts +10 -0
- package/dist/frameworks/vite.js +22 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +5 -0
- package/dist/mcp-server.d.ts +39 -0
- package/dist/mcp-server.js +237 -0
- package/dist/react.d.ts +8 -0
- package/dist/react.js +20 -0
- package/dist/register.d.ts +1 -0
- package/dist/register.js +2 -0
- package/dist/server.d.ts +25 -0
- package/dist/server.js +315 -0
- package/dist/tools.d.ts +165 -0
- package/dist/tools.js +161 -0
- package/dist/transports.d.ts +29 -0
- package/dist/transports.js +86 -0
- package/dist/types.d.ts +50 -0
- package/dist/types.js +1 -0
- package/dist-browser/client.js +457 -0
- package/package.json +123 -0
package/README.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# @ericmhalvorsen/aperture
|
|
2
|
+
|
|
3
|
+
> Let Agent see browser. Agent build better stuff. Perchance
|
|
4
|
+
|
|
5
|
+
No extensions. No CORS hacks. Auto-connect your local dev browser to Claude Code, Cursor, OpenCode, or any other MCP-capable agent.
|
|
6
|
+
|
|
7
|
+
## 5-Minute Quickstart
|
|
8
|
+
|
|
9
|
+
### 1. Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -D @ericmhalvorsen/aperture
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### 2. Add to your app
|
|
16
|
+
|
|
17
|
+
**Next.js:**
|
|
18
|
+
```ts
|
|
19
|
+
// next.config.ts
|
|
20
|
+
import { withAperture } from "@ericmhalvorsen/aperture/next";
|
|
21
|
+
|
|
22
|
+
export default withAperture({
|
|
23
|
+
// your existing config
|
|
24
|
+
});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Vite:**
|
|
28
|
+
```ts
|
|
29
|
+
// vite.config.ts
|
|
30
|
+
import { aperture } from "@ericmhalvorsen/aperture/vite";
|
|
31
|
+
|
|
32
|
+
export default {
|
|
33
|
+
plugins: [aperture()],
|
|
34
|
+
};
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Other frameworks:**
|
|
38
|
+
```bash
|
|
39
|
+
# Run in a separate terminal
|
|
40
|
+
npx @ericmhalvorsen/aperture
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 3. Add the client
|
|
44
|
+
|
|
45
|
+
**React/Next.js:**
|
|
46
|
+
```tsx
|
|
47
|
+
import { Aperture } from "@ericmhalvorsen/aperture/react";
|
|
48
|
+
|
|
49
|
+
export default function RootLayout({ children }) {
|
|
50
|
+
return (
|
|
51
|
+
<html>
|
|
52
|
+
<body>
|
|
53
|
+
{children}
|
|
54
|
+
{process.env.NODE_ENV === "development" && <Aperture />}
|
|
55
|
+
</body>
|
|
56
|
+
</html>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Vite/Vanilla:**
|
|
62
|
+
```typescript
|
|
63
|
+
// In your main entry file
|
|
64
|
+
import "@ericmhalvorsen/aperture/register";
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**No bundler:**
|
|
68
|
+
```html
|
|
69
|
+
<script src="http://localhost:3456/aperture.js"></script>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 4. Start your dev server
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npm run dev
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
You should see:
|
|
79
|
+
- A green dot badge in the bottom-right of your page
|
|
80
|
+
- Console output: `[Aperture] Server running on port 3456`
|
|
81
|
+
|
|
82
|
+
### 5. Connect your agent
|
|
83
|
+
|
|
84
|
+
Add to your agent's MCP config:
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"mcpServers": {
|
|
89
|
+
"aperture": {
|
|
90
|
+
"url": "http://localhost:3456/mcp"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
When the agent makes its first request, you'll see an approval dialog in your browser. Click "Allow" to grant access.
|
|
97
|
+
|
|
98
|
+
### More
|
|
99
|
+
|
|
100
|
+
- [How It Works](./docs/how-it-works.md)
|
|
101
|
+
- [MCP Capabilities](./docs/capabilities.md)
|
|
102
|
+
- [Security Model](./docs/security.md)
|
|
103
|
+
- [Architecture](./docs/architecture.md)
|
|
104
|
+
- [Debugging](./docs/debugging.md)
|
|
105
|
+
- [Chrome Extension Plan](./docs/plans/EXTENSION.md)
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## AI Development Policy
|
|
110
|
+
|
|
111
|
+
This project is developed in part using AI. When submitting pull requests, you may use AI but please review your submission carefully. The pull request will be treated as if it was written by you.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Local Development
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# Install
|
|
119
|
+
pnpm install
|
|
120
|
+
|
|
121
|
+
# Watch compilation
|
|
122
|
+
pnpm dev
|
|
123
|
+
|
|
124
|
+
# Build
|
|
125
|
+
pnpm build
|
|
126
|
+
|
|
127
|
+
# Lint & format
|
|
128
|
+
pnpm run check
|
|
129
|
+
pnpm run format
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Link for local testing
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
# In the aperture directory:
|
|
136
|
+
pnpm link --global
|
|
137
|
+
|
|
138
|
+
# In your test project:
|
|
139
|
+
pnpm link --global @ericmhalvorsen/aperture
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
After editing Aperture source, run `pnpm build` in the aperture directory. The consuming project (linked) will pick up the changes on restart.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## License
|
|
147
|
+
|
|
148
|
+
MIT
|
package/dist/bin.d.ts
ADDED
package/dist/bin.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { parseArgs } from "node:util";
|
|
4
|
+
import pc from "picocolors";
|
|
5
|
+
import { ApertureServer } from "./server.js";
|
|
6
|
+
const { values, positionals } = parseArgs({
|
|
7
|
+
options: {
|
|
8
|
+
port: { type: "string", short: "p" },
|
|
9
|
+
verbose: { type: "boolean", short: "v" },
|
|
10
|
+
help: { type: "boolean", short: "h" },
|
|
11
|
+
},
|
|
12
|
+
allowPositionals: true,
|
|
13
|
+
strict: false,
|
|
14
|
+
});
|
|
15
|
+
if (values.help) {
|
|
16
|
+
console.log(`
|
|
17
|
+
${pc.cyan(pc.bold("Aperture MCP Server"))}
|
|
18
|
+
|
|
19
|
+
${pc.bold("Usage:")}
|
|
20
|
+
$ aperture [options] [command...]
|
|
21
|
+
|
|
22
|
+
${pc.bold("Options:")}
|
|
23
|
+
-p, --port <port> Port to run the MCP server on (default: 3456)
|
|
24
|
+
-v, --verbose Enable verbose logging of MCP traffic
|
|
25
|
+
-h, --help Show this help message
|
|
26
|
+
|
|
27
|
+
${pc.bold("Examples:")}
|
|
28
|
+
$ aperture # Run standalone MCP server
|
|
29
|
+
$ aperture stdin # Run standalone MCP server via stdin/stdout
|
|
30
|
+
$ aperture -p 4000 # Run on port 4000
|
|
31
|
+
$ aperture next dev # Run MCP server and spawn "next dev"
|
|
32
|
+
`);
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
const port = Number(values.port || process.env.APERTURE_PORT) || 3456;
|
|
36
|
+
const verbose = Boolean(values.verbose);
|
|
37
|
+
const isStdioMode = positionals[0] === "stdin";
|
|
38
|
+
if (isStdioMode) {
|
|
39
|
+
new ApertureServer(port, { verbose, silentStartup: true, stdio: true });
|
|
40
|
+
console.error(pc.cyan(`\n● Aperture MCP Server initialized (stdio mode)`));
|
|
41
|
+
console.error(`${pc.dim("├")} MCP Endpoint: ${pc.green(`ws://localhost:${port}/mcp`)}`);
|
|
42
|
+
console.error(`${pc.dim("└")} Browser Script: ${pc.blue(`http://localhost:${port}/aperture.js`)}\n`);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
new ApertureServer(port, { verbose, silentStartup: true });
|
|
46
|
+
console.log(pc.cyan(`\n● Aperture MCP Server initialized`));
|
|
47
|
+
console.log(`${pc.dim("├")} MCP Endpoint: ${pc.green(`ws://localhost:${port}/mcp`)}`);
|
|
48
|
+
console.log(`${pc.dim("└")} Browser Script: ${pc.blue(`http://localhost:${port}/aperture.js`)}\n`);
|
|
49
|
+
if (positionals.length > 0) {
|
|
50
|
+
const [command, ...args] = positionals;
|
|
51
|
+
console.log(pc.dim(`> Spawning wrapped command: ${command} ${args.join(" ")}\n`));
|
|
52
|
+
const child = spawn(command, args, {
|
|
53
|
+
stdio: "inherit",
|
|
54
|
+
shell: true,
|
|
55
|
+
env: { ...process.env, APERTURE_PORT: port.toString() },
|
|
56
|
+
});
|
|
57
|
+
child.on("exit", (code) => {
|
|
58
|
+
process.exit(code ?? 0);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
console.log(pc.dim("Waiting for connections... (Press Ctrl+C to stop)"));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { getConsoleBuffer, getNetworkBuffer } from "./patches.js";
|
|
2
|
+
export const TOOL_HANDLERS = {
|
|
3
|
+
browser_dom_query: (_client, { selector, includeHtml = false }) => {
|
|
4
|
+
const elements = Array.from(document.querySelectorAll(String(selector)));
|
|
5
|
+
return elements.map((el) => ({
|
|
6
|
+
tag: el.tagName.toLowerCase(),
|
|
7
|
+
text: el.textContent?.slice(0, 200) || "",
|
|
8
|
+
visible: !!el.offsetParent,
|
|
9
|
+
attributes: Object.fromEntries(Array.from(el.attributes).map((a) => [a.name, a.value])),
|
|
10
|
+
html: includeHtml ? el.outerHTML.slice(0, 500) : undefined,
|
|
11
|
+
}));
|
|
12
|
+
},
|
|
13
|
+
browser_network_requests: (_client, { limit = 20 }) => {
|
|
14
|
+
return getNetworkBuffer().slice(-Number(limit));
|
|
15
|
+
},
|
|
16
|
+
browser_page_info: (_client, { logLimit = 20, logLevel = "all" }) => {
|
|
17
|
+
let logs = getConsoleBuffer();
|
|
18
|
+
if (logLevel !== "all") {
|
|
19
|
+
logs = logs.filter((e) => e.level === logLevel);
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
url: window.location.href,
|
|
23
|
+
title: document.title,
|
|
24
|
+
width: window.innerWidth,
|
|
25
|
+
height: window.innerHeight,
|
|
26
|
+
scrollX: window.scrollX,
|
|
27
|
+
scrollY: window.scrollY,
|
|
28
|
+
userAgent: navigator.userAgent,
|
|
29
|
+
logs: logs.slice(-Number(logLimit)),
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
browser_storage_get: (_client, { type, key, prefix, name }) => {
|
|
33
|
+
if (type === "localStorage") {
|
|
34
|
+
const result = {};
|
|
35
|
+
if (key) {
|
|
36
|
+
result[String(key)] = localStorage.getItem(String(key));
|
|
37
|
+
}
|
|
38
|
+
if (prefix) {
|
|
39
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
40
|
+
const k = localStorage.key(i);
|
|
41
|
+
if (k?.startsWith(String(prefix))) {
|
|
42
|
+
result[k] = localStorage.getItem(k);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
if (type === "cookie") {
|
|
49
|
+
const cookies = {};
|
|
50
|
+
const docCookies = document.cookie;
|
|
51
|
+
if (docCookies) {
|
|
52
|
+
for (const cookie of docCookies.split(";")) {
|
|
53
|
+
const [k, v] = cookie.split("=").map((s) => s.trim());
|
|
54
|
+
if (k)
|
|
55
|
+
cookies[k] = decodeURIComponent(v || "");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (name) {
|
|
59
|
+
const targetName = String(name);
|
|
60
|
+
return { [targetName]: cookies[targetName] || null };
|
|
61
|
+
}
|
|
62
|
+
return cookies;
|
|
63
|
+
}
|
|
64
|
+
return { error: `Unknown type: ${type}. Use 'localStorage' or 'cookie'.` };
|
|
65
|
+
},
|
|
66
|
+
browser_screenshot: async (client) => {
|
|
67
|
+
try {
|
|
68
|
+
const dataUrl = await client.captureScreenshotFromStream();
|
|
69
|
+
return { base64: dataUrl.split(",")[1], format: "png" };
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
return { error: e instanceof Error ? e.message : String(e) };
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
browser_evaluate: (_client, { expression }) => {
|
|
76
|
+
// biome-ignore lint/security/noGlobalEval: developer-only bridge, gated by approval
|
|
77
|
+
const result = window.eval(String(expression));
|
|
78
|
+
return {
|
|
79
|
+
result: typeof result === "object" ? JSON.stringify(result) : String(result),
|
|
80
|
+
};
|
|
81
|
+
},
|
|
82
|
+
browser_click: (_client, { selector }) => {
|
|
83
|
+
const element = document.querySelector(String(selector));
|
|
84
|
+
if (!element) {
|
|
85
|
+
return { error: `Element not found matching selector: ${selector}` };
|
|
86
|
+
}
|
|
87
|
+
element.focus();
|
|
88
|
+
const events = [
|
|
89
|
+
"pointerdown",
|
|
90
|
+
"mousedown",
|
|
91
|
+
"pointerup",
|
|
92
|
+
"mouseup",
|
|
93
|
+
"click",
|
|
94
|
+
];
|
|
95
|
+
for (const name of events) {
|
|
96
|
+
const ev = new MouseEvent(name, {
|
|
97
|
+
bubbles: true,
|
|
98
|
+
cancelable: true,
|
|
99
|
+
});
|
|
100
|
+
element.dispatchEvent(ev);
|
|
101
|
+
}
|
|
102
|
+
return { success: true };
|
|
103
|
+
},
|
|
104
|
+
browser_type: (_client, { selector, text }) => {
|
|
105
|
+
const element = document.querySelector(String(selector));
|
|
106
|
+
if (!element) {
|
|
107
|
+
return { error: `Element not found matching selector: ${selector}` };
|
|
108
|
+
}
|
|
109
|
+
const isInput = element instanceof HTMLInputElement;
|
|
110
|
+
const isTextArea = element instanceof HTMLTextAreaElement;
|
|
111
|
+
if (isInput || isTextArea) {
|
|
112
|
+
const proto = isInput
|
|
113
|
+
? HTMLInputElement.prototype
|
|
114
|
+
: HTMLTextAreaElement.prototype;
|
|
115
|
+
const setter = Object.getOwnPropertyDescriptor(proto, "value")?.set;
|
|
116
|
+
if (setter) {
|
|
117
|
+
setter.call(element, String(text));
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
element.value =
|
|
121
|
+
String(text);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
else if (element.isContentEditable) {
|
|
125
|
+
element.textContent = String(text);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
return {
|
|
129
|
+
error: `Element is not an input, textarea, or contenteditable.`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
element.dispatchEvent(new Event("input", { bubbles: true }));
|
|
133
|
+
element.dispatchEvent(new Event("change", { bubbles: true }));
|
|
134
|
+
return { success: true };
|
|
135
|
+
},
|
|
136
|
+
browser_scroll: (_client, { selector, x, y, scrollIntoView }) => {
|
|
137
|
+
if (selector) {
|
|
138
|
+
const element = document.querySelector(String(selector));
|
|
139
|
+
if (!element) {
|
|
140
|
+
return { error: `Element not found matching selector: ${selector}` };
|
|
141
|
+
}
|
|
142
|
+
if (scrollIntoView) {
|
|
143
|
+
element.scrollIntoView({
|
|
144
|
+
behavior: "smooth",
|
|
145
|
+
block: "center",
|
|
146
|
+
inline: "nearest",
|
|
147
|
+
});
|
|
148
|
+
return { success: true };
|
|
149
|
+
}
|
|
150
|
+
if (x !== undefined)
|
|
151
|
+
element.scrollLeft = Number(x);
|
|
152
|
+
if (y !== undefined)
|
|
153
|
+
element.scrollTop = Number(y);
|
|
154
|
+
return {
|
|
155
|
+
success: true,
|
|
156
|
+
scrollLeft: element.scrollLeft,
|
|
157
|
+
scrollTop: element.scrollTop,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
const posX = x !== undefined ? Number(x) : window.scrollX;
|
|
162
|
+
const posY = y !== undefined ? Number(y) : window.scrollY;
|
|
163
|
+
window.scrollTo({ left: posX, top: posY, behavior: "smooth" });
|
|
164
|
+
return {
|
|
165
|
+
success: true,
|
|
166
|
+
scrollX: window.scrollX,
|
|
167
|
+
scrollY: window.scrollY,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface ConsoleEntry {
|
|
2
|
+
level: string;
|
|
3
|
+
message: string;
|
|
4
|
+
timestamp: number;
|
|
5
|
+
}
|
|
6
|
+
export interface NetworkEntry {
|
|
7
|
+
url: string;
|
|
8
|
+
method: string;
|
|
9
|
+
start: number;
|
|
10
|
+
end: number;
|
|
11
|
+
status: number;
|
|
12
|
+
responseText: string;
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function getConsoleBuffer(): ConsoleEntry[];
|
|
16
|
+
export declare function getNetworkBuffer(): NetworkEntry[];
|
|
17
|
+
export declare function patchConsole(): void;
|
|
18
|
+
export declare function patchFetch(): void;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const consoleBuffer = [];
|
|
2
|
+
const networkBuffer = [];
|
|
3
|
+
export function getConsoleBuffer() {
|
|
4
|
+
return consoleBuffer;
|
|
5
|
+
}
|
|
6
|
+
export function getNetworkBuffer() {
|
|
7
|
+
return networkBuffer;
|
|
8
|
+
}
|
|
9
|
+
export function patchConsole() {
|
|
10
|
+
const levels = ["log", "warn", "error", "info", "debug"];
|
|
11
|
+
for (const level of levels) {
|
|
12
|
+
const orig = console[level];
|
|
13
|
+
console[level] = (...args) => {
|
|
14
|
+
const message = args
|
|
15
|
+
.map((a) => (typeof a === "string" ? a : JSON.stringify(a)))
|
|
16
|
+
.join(" ");
|
|
17
|
+
consoleBuffer.push({ level, message, timestamp: Date.now() });
|
|
18
|
+
if (consoleBuffer.length > 500)
|
|
19
|
+
consoleBuffer.shift();
|
|
20
|
+
orig(...args);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function patchFetch() {
|
|
25
|
+
const origFetch = window.fetch;
|
|
26
|
+
window.fetch = async (...args) => {
|
|
27
|
+
const entry = {
|
|
28
|
+
url: String(args[0]),
|
|
29
|
+
method: "GET",
|
|
30
|
+
start: Date.now(),
|
|
31
|
+
end: 0,
|
|
32
|
+
status: 0,
|
|
33
|
+
responseText: "",
|
|
34
|
+
};
|
|
35
|
+
if (args[1]) {
|
|
36
|
+
entry.method = args[1].method || "GET";
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const res = await origFetch(...args);
|
|
40
|
+
entry.status = res.status;
|
|
41
|
+
entry.end = Date.now();
|
|
42
|
+
networkBuffer.push(entry);
|
|
43
|
+
if (networkBuffer.length > 100)
|
|
44
|
+
networkBuffer.shift();
|
|
45
|
+
return res;
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
entry.end = Date.now();
|
|
49
|
+
entry.error = String(err);
|
|
50
|
+
networkBuffer.push(entry);
|
|
51
|
+
if (networkBuffer.length > 100)
|
|
52
|
+
networkBuffer.shift();
|
|
53
|
+
throw err;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export const storage = {
|
|
2
|
+
get(key) {
|
|
3
|
+
if (typeof window === "undefined" || typeof localStorage === "undefined") {
|
|
4
|
+
return null;
|
|
5
|
+
}
|
|
6
|
+
try {
|
|
7
|
+
return localStorage.getItem(key);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
set(key, value) {
|
|
14
|
+
if (typeof window === "undefined" || typeof localStorage === "undefined") {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
localStorage.setItem(key, value);
|
|
19
|
+
}
|
|
20
|
+
catch { }
|
|
21
|
+
},
|
|
22
|
+
remove(key) {
|
|
23
|
+
if (typeof window === "undefined" || typeof localStorage === "undefined") {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
localStorage.removeItem(key);
|
|
28
|
+
}
|
|
29
|
+
catch { }
|
|
30
|
+
},
|
|
31
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare function injectStyles(): void;
|
|
2
|
+
export declare function requestDisplayMedia(): Promise<MediaStream | null>;
|
|
3
|
+
export declare function showApprovalDialog(agentName: string, onApprovalStateChange: (state: {
|
|
4
|
+
stream?: MediaStream | null;
|
|
5
|
+
capabilities?: string[];
|
|
6
|
+
}) => void): Promise<{
|
|
7
|
+
approved: boolean;
|
|
8
|
+
capabilities: string[];
|
|
9
|
+
ttlMs?: number;
|
|
10
|
+
dismissed?: boolean;
|
|
11
|
+
}>;
|
|
12
|
+
export declare function showStatusDialog(options: {
|
|
13
|
+
wsReadyState: number;
|
|
14
|
+
approved: boolean;
|
|
15
|
+
denied: boolean;
|
|
16
|
+
capabilities: string[];
|
|
17
|
+
isBadgeHidden: () => boolean;
|
|
18
|
+
showBadge: () => void;
|
|
19
|
+
hideBadgeFor24h: () => void;
|
|
20
|
+
revokeApproval: () => void;
|
|
21
|
+
onApprovalStateChange: (state: {
|
|
22
|
+
approved: boolean;
|
|
23
|
+
capabilities: string[];
|
|
24
|
+
stream?: MediaStream | null;
|
|
25
|
+
}) => void;
|
|
26
|
+
}): void;
|