@agenticmail/enterprise 0.5.236 → 0.5.238

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.
@@ -0,0 +1,114 @@
1
+ import "./chunk-KFQGP6VL.js";
2
+
3
+ // src/cli-serve.ts
4
+ import { existsSync, readFileSync } from "fs";
5
+ import { join } from "path";
6
+ import { homedir } from "os";
7
+ function loadEnvFile() {
8
+ const candidates = [
9
+ join(process.cwd(), ".env"),
10
+ join(homedir(), ".agenticmail", ".env")
11
+ ];
12
+ for (const envPath of candidates) {
13
+ if (!existsSync(envPath)) continue;
14
+ try {
15
+ const content = readFileSync(envPath, "utf8");
16
+ for (const line of content.split("\n")) {
17
+ const trimmed = line.trim();
18
+ if (!trimmed || trimmed.startsWith("#")) continue;
19
+ const eq = trimmed.indexOf("=");
20
+ if (eq < 0) continue;
21
+ const key = trimmed.slice(0, eq).trim();
22
+ let val = trimmed.slice(eq + 1).trim();
23
+ if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
24
+ val = val.slice(1, -1);
25
+ }
26
+ if (!process.env[key]) process.env[key] = val;
27
+ }
28
+ console.log(`Loaded config from ${envPath}`);
29
+ return;
30
+ } catch {
31
+ }
32
+ }
33
+ }
34
+ async function ensureSecrets() {
35
+ const { randomUUID } = await import("crypto");
36
+ const envDir = join(homedir(), ".agenticmail");
37
+ const envPath = join(envDir, ".env");
38
+ let dirty = false;
39
+ if (!process.env.JWT_SECRET) {
40
+ process.env.JWT_SECRET = randomUUID() + randomUUID();
41
+ dirty = true;
42
+ console.log("[startup] Generated new JWT_SECRET (existing sessions will need to re-login)");
43
+ }
44
+ if (!process.env.AGENTICMAIL_VAULT_KEY) {
45
+ process.env.AGENTICMAIL_VAULT_KEY = randomUUID() + randomUUID();
46
+ dirty = true;
47
+ console.log("[startup] Generated new AGENTICMAIL_VAULT_KEY");
48
+ console.log("[startup] \u26A0\uFE0F Previously encrypted credentials will need to be re-entered in the dashboard");
49
+ }
50
+ if (dirty) {
51
+ try {
52
+ if (!existsSync(envDir)) {
53
+ const { mkdirSync } = await import("fs");
54
+ mkdirSync(envDir, { recursive: true });
55
+ }
56
+ const { appendFileSync } = await import("fs");
57
+ const lines = [];
58
+ let existing = "";
59
+ if (existsSync(envPath)) {
60
+ existing = readFileSync(envPath, "utf8");
61
+ }
62
+ if (!existing.includes("JWT_SECRET=")) {
63
+ lines.push(`JWT_SECRET=${process.env.JWT_SECRET}`);
64
+ }
65
+ if (!existing.includes("AGENTICMAIL_VAULT_KEY=")) {
66
+ lines.push(`AGENTICMAIL_VAULT_KEY=${process.env.AGENTICMAIL_VAULT_KEY}`);
67
+ }
68
+ if (lines.length) {
69
+ appendFileSync(envPath, "\n" + lines.join("\n") + "\n", { mode: 384 });
70
+ console.log(`[startup] Saved secrets to ${envPath}`);
71
+ }
72
+ } catch (e) {
73
+ console.warn(`[startup] Could not save secrets to ${envPath}: ${e.message}`);
74
+ }
75
+ }
76
+ }
77
+ async function runServe(_args) {
78
+ loadEnvFile();
79
+ const DATABASE_URL = process.env.DATABASE_URL;
80
+ const PORT = parseInt(process.env.PORT || "8080", 10);
81
+ await ensureSecrets();
82
+ const JWT_SECRET = process.env.JWT_SECRET;
83
+ const VAULT_KEY = process.env.AGENTICMAIL_VAULT_KEY;
84
+ if (!DATABASE_URL) {
85
+ console.error("ERROR: DATABASE_URL is required.");
86
+ console.error("");
87
+ console.error("Set it via environment variable or .env file:");
88
+ console.error(" DATABASE_URL=postgresql://user:pass@host:5432/db npx @agenticmail/enterprise start");
89
+ console.error("");
90
+ console.error("Or create a .env file (in cwd or ~/.agenticmail/.env):");
91
+ console.error(" DATABASE_URL=postgresql://user:pass@host:5432/db");
92
+ console.error(" JWT_SECRET=your-secret-here");
93
+ console.error(" PORT=3200");
94
+ process.exit(1);
95
+ }
96
+ const { createAdapter } = await import("./factory-672W7A5B.js");
97
+ const { createServer } = await import("./server-VXVRSEFL.js");
98
+ const db = await createAdapter({
99
+ type: DATABASE_URL.startsWith("postgres") ? "postgres" : "sqlite",
100
+ connectionString: DATABASE_URL
101
+ });
102
+ await db.migrate();
103
+ const server = createServer({
104
+ port: PORT,
105
+ db,
106
+ jwtSecret: JWT_SECRET,
107
+ corsOrigins: ["*"]
108
+ });
109
+ await server.start();
110
+ console.log(`AgenticMail Enterprise server running on :${PORT}`);
111
+ }
112
+ export {
113
+ runServe
114
+ };
package/dist/cli.js CHANGED
@@ -53,14 +53,14 @@ Skill Development:
53
53
  break;
54
54
  case "serve":
55
55
  case "start":
56
- import("./cli-serve-WHWBI3NH.js").then((m) => m.runServe(args.slice(1))).catch(fatal);
56
+ import("./cli-serve-OHDYAC3L.js").then((m) => m.runServe(args.slice(1))).catch(fatal);
57
57
  break;
58
58
  case "agent":
59
- import("./cli-agent-M3YGU3IG.js").then((m) => m.runAgent(args.slice(1))).catch(fatal);
59
+ import("./cli-agent-HDEPHBZX.js").then((m) => m.runAgent(args.slice(1))).catch(fatal);
60
60
  break;
61
61
  case "setup":
62
62
  default:
63
- import("./setup-RHYG7BZO.js").then((m) => m.runSetupWizard()).catch(fatal);
63
+ import("./setup-OBCJB3R6.js").then((m) => m.runSetupWizard()).catch(fatal);
64
64
  break;
65
65
  }
66
66
  function fatal(err) {
@@ -0,0 +1,411 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Browser Provider Setup Guide — AgenticMail Enterprise</title>
7
+ <style>
8
+ :root { --bg: #0f172a; --card: #1e293b; --border: #334155; --text: #e2e8f0; --muted: #94a3b8; --accent: #3b82f6; --success: #15803d; --warning: #f59e0b; --danger: #ef4444; --radius: 10px; }
9
+ * { box-sizing: border-box; margin: 0; padding: 0; }
10
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: var(--bg); color: var(--text); line-height: 1.7; padding: 32px; max-width: 900px; margin: 0 auto; }
11
+ h1 { font-size: 28px; margin-bottom: 8px; }
12
+ h2 { font-size: 20px; margin: 32px 0 12px; padding-bottom: 8px; border-bottom: 1px solid var(--border); }
13
+ h3 { font-size: 16px; margin: 20px 0 8px; color: var(--accent); }
14
+ p { margin-bottom: 12px; }
15
+ code { background: #0f172a; border: 1px solid var(--border); padding: 2px 6px; border-radius: 4px; font-size: 13px; color: var(--accent); }
16
+ pre { background: #0f172a; border: 1px solid var(--border); padding: 16px; border-radius: var(--radius); overflow-x: auto; margin: 12px 0; font-size: 13px; line-height: 1.5; }
17
+ pre code { background: none; border: none; padding: 0; }
18
+ .card { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; margin: 16px 0; }
19
+ .tip { background: rgba(59,130,246,0.1); border: 1px solid rgba(59,130,246,0.3); padding: 12px 16px; border-radius: var(--radius); margin: 12px 0; font-size: 13px; }
20
+ .warning { background: rgba(245,158,11,0.1); border: 1px solid rgba(245,158,11,0.3); padding: 12px 16px; border-radius: var(--radius); margin: 12px 0; font-size: 13px; }
21
+ .danger { background: rgba(239,68,68,0.1); border: 1px solid rgba(239,68,68,0.3); padding: 12px 16px; border-radius: var(--radius); margin: 12px 0; font-size: 13px; }
22
+ table { width: 100%; border-collapse: collapse; margin: 12px 0; font-size: 13px; }
23
+ th, td { text-align: left; padding: 8px 12px; border: 1px solid var(--border); }
24
+ th { background: var(--card); font-weight: 600; }
25
+ ul, ol { padding-left: 24px; margin-bottom: 12px; }
26
+ li { margin-bottom: 6px; }
27
+ .badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; }
28
+ .badge-easy { background: rgba(21,128,61,0.2); color: #22c55e; }
29
+ .badge-medium { background: rgba(245,158,11,0.2); color: #f59e0b; }
30
+ .badge-advanced { background: rgba(239,68,68,0.2); color: #ef4444; }
31
+ a { color: var(--accent); }
32
+ .back { display: inline-block; margin-bottom: 20px; font-size: 13px; color: var(--muted); text-decoration: none; }
33
+ .back:hover { color: var(--text); }
34
+ </style>
35
+ </head>
36
+ <body>
37
+
38
+ <a class="back" href="/dashboard/agents">← Back to Dashboard</a>
39
+
40
+ <h1>Browser Provider Setup Guide</h1>
41
+ <p style="color: var(--muted); margin-bottom: 24px;">Complete reference for connecting your AI agents to browsers — local, remote, or cloud-hosted.</p>
42
+
43
+ <div class="card">
44
+ <h3 style="margin-top: 0;">Quick Comparison</h3>
45
+ <table>
46
+ <tr>
47
+ <th>Provider</th>
48
+ <th>Best For</th>
49
+ <th>Video Calls</th>
50
+ <th>Difficulty</th>
51
+ <th>Cost</th>
52
+ </tr>
53
+ <tr>
54
+ <td><strong>Local Chromium</strong></td>
55
+ <td>Web scraping, form filling, screenshots</td>
56
+ <td>No (headless) / Yes (headed, if display available)</td>
57
+ <td><span class="badge badge-easy">Easy</span></td>
58
+ <td>Free</td>
59
+ </tr>
60
+ <tr>
61
+ <td><strong>Remote CDP</strong></td>
62
+ <td>Video calls, persistent sessions, full browser control</td>
63
+ <td>Yes</td>
64
+ <td><span class="badge badge-medium">Medium</span></td>
65
+ <td>VM cost only</td>
66
+ </tr>
67
+ <tr>
68
+ <td><strong>Browserless.io</strong></td>
69
+ <td>Scalable scraping, stealth browsing, managed infra</td>
70
+ <td>No</td>
71
+ <td><span class="badge badge-easy">Easy</span></td>
72
+ <td>From $0/mo (free tier)</td>
73
+ </tr>
74
+ <tr>
75
+ <td><strong>Browserbase</strong></td>
76
+ <td>AI agent automation, session replay, anti-detection</td>
77
+ <td>No</td>
78
+ <td><span class="badge badge-easy">Easy</span></td>
79
+ <td>From $0/mo (free tier)</td>
80
+ </tr>
81
+ <tr>
82
+ <td><strong>Steel.dev</strong></td>
83
+ <td>Self-hostable, AI-native, session management</td>
84
+ <td>No</td>
85
+ <td><span class="badge badge-medium">Medium</span></td>
86
+ <td>Free (self-hosted) / Paid (cloud)</td>
87
+ </tr>
88
+ <tr>
89
+ <td><strong>ScrapingBee</strong></td>
90
+ <td>Web scraping with proxy rotation, CAPTCHA solving</td>
91
+ <td>No</td>
92
+ <td><span class="badge badge-easy">Easy</span></td>
93
+ <td>From $0/mo (free tier)</td>
94
+ </tr>
95
+ </table>
96
+ </div>
97
+
98
+ <!-- ═══════════════════════════════════════════ -->
99
+ <h2 id="local">1. Local Chromium (Default)</h2>
100
+
101
+ <p>The simplest option. A headless Chromium browser runs on the same machine as AgenticMail. No external services needed.</p>
102
+
103
+ <h3>When to use</h3>
104
+ <ul>
105
+ <li>Web scraping and data extraction</li>
106
+ <li>Taking screenshots of web pages</li>
107
+ <li>Filling out forms and automating websites</li>
108
+ <li>Any task that doesn't need a camera, microphone, or visible display</li>
109
+ </ul>
110
+
111
+ <h3>Setup</h3>
112
+ <p>No setup needed — it's the default. Chromium is bundled with Playwright and installed automatically when the agent first starts.</p>
113
+
114
+ <div class="tip"><strong>Headed mode:</strong> If your server has a display (desktop, not headless server), you can switch to "Headed" mode in the dashboard to see the browser window. This is useful for debugging.</div>
115
+
116
+ <h3>Troubleshooting</h3>
117
+ <table>
118
+ <tr><th>Issue</th><th>Solution</th></tr>
119
+ <tr><td>Chromium not found</td><td>The agent installs it automatically on first run. If it fails, check disk space (needs ~300MB).</td></tr>
120
+ <tr><td>Missing dependencies on Linux</td><td>Install: <code>apt-get install -y libnss3 libatk1.0-0 libatk-bridge2.0-0 libdrm2 libxcomposite1 libxdamage1 libxrandr2 libgbm1 libpango-1.0-0 libasound2</code></td></tr>
121
+ <tr><td>"Display not available" in headed mode</td><td>Headed mode requires X11 or Wayland. Use headless mode on servers without a display, or install Xvfb: <code>apt-get install xvfb && Xvfb :99 &</code></td></tr>
122
+ <tr><td>Timeout on navigation</td><td>Increase timeout in browser settings. Some sites take longer to load with JavaScript rendering.</td></tr>
123
+ </table>
124
+
125
+ <!-- ═══════════════════════════════════════════ -->
126
+ <h2 id="remote-cdp">2. Remote Browser (CDP)</h2>
127
+
128
+ <p>Connect to a Chrome browser running on another machine. The agent controls it remotely via the Chrome DevTools Protocol. This is the <strong>only option that supports video calls</strong> (Google Meet, Teams, Zoom) because it can access a real camera, microphone, and display on the remote machine.</p>
129
+
130
+ <h3>When to use</h3>
131
+ <ul>
132
+ <li>Video calls and meetings (Google Meet, Microsoft Teams, Zoom)</li>
133
+ <li>Tasks requiring a persistent browser session (stay logged in across agent restarts)</li>
134
+ <li>When you need a real display, camera, or microphone</li>
135
+ <li>Running the agent on a headless server but needing browser GUI features</li>
136
+ </ul>
137
+
138
+ <h3>What you need</h3>
139
+ <ol>
140
+ <li>A machine with Google Chrome installed (any OS — Mac, Windows, Linux)</li>
141
+ <li>Chrome configured to accept remote connections</li>
142
+ <li>Network access between your AgenticMail server and the Chrome machine</li>
143
+ </ol>
144
+
145
+ <h3>Setup: macOS</h3>
146
+ <div class="card">
147
+ <p><strong>Step 1:</strong> Open Terminal on the Mac where you want Chrome to run.</p>
148
+ <p><strong>Step 2:</strong> Launch Chrome with remote debugging enabled:</p>
149
+ <pre><code>/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
150
+ --remote-debugging-port=9222 \
151
+ --remote-debugging-address=0.0.0.0 \
152
+ --user-data-dir=/tmp/chrome-remote \
153
+ --no-first-run</code></pre>
154
+ <p><strong>Step 3:</strong> In the AgenticMail dashboard, enter the address: <code>192.168.x.x:9222</code> (replace with the Mac's IP address)</p>
155
+ <p><strong>Step 4:</strong> Click "Test Connection" — you should see the Chrome version.</p>
156
+ </div>
157
+
158
+ <div class="tip"><strong>To run Chrome at startup on macOS:</strong> Create a Launch Agent plist at <code>~/Library/LaunchAgents/com.chrome.remote.plist</code> — or simply add the command to Login Items in System Settings.</div>
159
+
160
+ <h3>Setup: Linux (Ubuntu/Debian)</h3>
161
+ <div class="card">
162
+ <p><strong>Step 1:</strong> Install Chrome if not already installed:</p>
163
+ <pre><code>wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo gpg --dearmor -o /usr/share/keyrings/google-chrome.gpg
164
+ echo "deb [arch=amd64 signed-by=/usr/share/keyrings/google-chrome.gpg] http://dl.google.com/linux/chrome/deb/ stable main" | sudo tee /etc/apt/sources.list.d/google-chrome.list
165
+ sudo apt-get update && sudo apt-get install -y google-chrome-stable</code></pre>
166
+
167
+ <p><strong>Step 2:</strong> For headless servers (no display), install a virtual display:</p>
168
+ <pre><code>sudo apt-get install -y xvfb pulseaudio
169
+ # Start virtual display and audio
170
+ Xvfb :99 -screen 0 1920x1080x24 &
171
+ export DISPLAY=:99
172
+ pulseaudio --start</code></pre>
173
+
174
+ <p><strong>Step 3:</strong> Launch Chrome:</p>
175
+ <pre><code>google-chrome-stable \
176
+ --remote-debugging-port=9222 \
177
+ --remote-debugging-address=0.0.0.0 \
178
+ --no-sandbox \
179
+ --disable-gpu \
180
+ --user-data-dir=/tmp/chrome-remote \
181
+ --no-first-run &</code></pre>
182
+
183
+ <p><strong>Step 4:</strong> Enter the server IP and port in the dashboard: <code>your-server-ip:9222</code></p>
184
+ </div>
185
+
186
+ <div class="tip"><strong>For video calls on Linux:</strong> Install virtual camera/audio: <code>sudo apt install v4l2loopback-dkms</code> for virtual camera, and PulseAudio virtual sinks for audio routing.</div>
187
+
188
+ <h3>Setup: Windows</h3>
189
+ <div class="card">
190
+ <p><strong>Step 1:</strong> Open PowerShell and run:</p>
191
+ <pre><code>& "C:\Program Files\Google\Chrome\Application\chrome.exe" `
192
+ --remote-debugging-port=9222 `
193
+ --remote-debugging-address=0.0.0.0 `
194
+ --user-data-dir="$env:TEMP\chrome-remote" `
195
+ --no-first-run</code></pre>
196
+
197
+ <p><strong>Step 2:</strong> Allow Chrome through Windows Firewall when prompted (or add a rule for port 9222).</p>
198
+ <p><strong>Step 3:</strong> Enter the Windows machine's IP and port in the dashboard.</p>
199
+ </div>
200
+
201
+ <h3>Setup: Cloud VMs</h3>
202
+ <div class="card">
203
+ <table>
204
+ <tr><th>Provider</th><th>Best Option</th><th>Notes</th></tr>
205
+ <tr><td>AWS</td><td>EC2 with Ubuntu Desktop AMI, or WorkSpaces</td><td>Use t3.medium+. Open port 9222 in security group (restrict to your AgenticMail IP).</td></tr>
206
+ <tr><td>Azure</td><td>Azure Virtual Desktop or VM with Desktop</td><td>Use B2s or larger. Open port 9222 in NSG.</td></tr>
207
+ <tr><td>Google Cloud</td><td>Compute Engine with Desktop</td><td>Use e2-medium+. Open port 9222 in firewall rules.</td></tr>
208
+ <tr><td>Hetzner</td><td>Cloud Server with Ubuntu Desktop</td><td>Very cost-effective (~€4/mo). Open port 9222 in firewall.</td></tr>
209
+ <tr><td>DigitalOcean</td><td>Droplet with Desktop</td><td>Use 2GB+ RAM. Open port 9222 in firewall.</td></tr>
210
+ </table>
211
+ <p style="margin-top: 8px;">For all cloud VMs: follow the Linux setup above, then configure the firewall to only allow connections from your AgenticMail server's IP address.</p>
212
+ </div>
213
+
214
+ <h3>Securing the Connection</h3>
215
+ <div class="warning"><strong>Security note:</strong> Chrome's remote debugging port has NO authentication by default. Anyone who can reach port 9222 has full browser control. Always restrict access.</div>
216
+
217
+ <p><strong>Option A: Firewall rules (simplest)</strong></p>
218
+ <p>Only allow your AgenticMail server's IP to connect to port 9222:</p>
219
+ <pre><code># Linux (ufw)
220
+ sudo ufw allow from YOUR_AGENTICMAIL_IP to any port 9222
221
+
222
+ # Linux (iptables)
223
+ sudo iptables -A INPUT -p tcp --dport 9222 -s YOUR_AGENTICMAIL_IP -j ACCEPT
224
+ sudo iptables -A INPUT -p tcp --dport 9222 -j DROP</code></pre>
225
+
226
+ <p><strong>Option B: SSH Tunnel (most secure)</strong></p>
227
+ <p>Don't expose port 9222 at all. Instead, use the SSH Tunnel field in the dashboard:</p>
228
+ <ol>
229
+ <li>Ensure SSH access to the remote machine is configured (SSH key recommended)</li>
230
+ <li>In the dashboard, set the Remote Browser Address to <code>localhost:9222</code></li>
231
+ <li>In the SSH Tunnel field, enter: <code>user@remote-host</code></li>
232
+ <li>We automatically create the tunnel (<code>ssh -L 9222:localhost:9222 user@remote-host</code>) before connecting</li>
233
+ </ol>
234
+
235
+ <div class="tip"><strong>SSH key setup:</strong> On your AgenticMail server, run <code>ssh-keygen</code> if you haven't already, then copy your public key to the remote machine: <code>ssh-copy-id user@remote-host</code></div>
236
+
237
+ <h3>Troubleshooting</h3>
238
+ <table>
239
+ <tr><th>Issue</th><th>Solution</th></tr>
240
+ <tr><td>"Cannot connect to CDP"</td><td>Check: (1) Chrome is running with --remote-debugging-port, (2) firewall allows the connection, (3) IP/port are correct</td></tr>
241
+ <tr><td>"Connection refused"</td><td>Chrome may not be listening on all interfaces. Ensure <code>--remote-debugging-address=0.0.0.0</code> is set (or use SSH tunnel with localhost)</td></tr>
242
+ <tr><td>"WebSocket handshake failed"</td><td>The WebSocket URL changes each time Chrome starts. Use the host:port format (e.g., <code>192.168.1.100:9222</code>) and we auto-discover the current URL</td></tr>
243
+ <tr><td>"No cameras found" in video call</td><td>The remote machine needs a camera (physical or virtual). On Linux: <code>sudo modprobe v4l2loopback</code></td></tr>
244
+ <tr><td>"No audio" in video call</td><td>Install PulseAudio on Linux. On macOS/Windows, audio should work automatically.</td></tr>
245
+ <tr><td>SSH tunnel fails</td><td>Check: (1) SSH key is configured (no password prompt), (2) remote host is reachable, (3) user has login access</td></tr>
246
+ </table>
247
+
248
+ <!-- ═══════════════════════════════════════════ -->
249
+ <h2 id="browserless">3. Browserless.io</h2>
250
+
251
+ <p>A managed cloud browser service. Browserless runs Chrome instances in the cloud and you connect via API token. Great for scalable scraping and automation without managing infrastructure.</p>
252
+
253
+ <h3>When to use</h3>
254
+ <ul>
255
+ <li>High-volume web scraping that needs to scale</li>
256
+ <li>Anti-detection / stealth browsing (bypassing bot detection)</li>
257
+ <li>When you don't want to manage browser infrastructure</li>
258
+ <li>Concurrent browsing sessions across multiple agents</li>
259
+ </ul>
260
+
261
+ <h3>Setup</h3>
262
+ <ol>
263
+ <li>Sign up at <a href="https://www.browserless.io" target="_blank">browserless.io</a></li>
264
+ <li>Go to your <a href="https://www.browserless.io/dashboard" target="_blank">dashboard</a> and copy your API token</li>
265
+ <li>Paste the token in the AgenticMail dashboard under Browser → Browserless → API Token</li>
266
+ <li>Click "Test Connection" to verify</li>
267
+ </ol>
268
+
269
+ <div class="tip"><strong>Free tier:</strong> Browserless offers a free tier with limited sessions per month — plenty for testing.</div>
270
+
271
+ <h3>Self-hosted Browserless</h3>
272
+ <p>You can run Browserless on your own server using Docker:</p>
273
+ <pre><code>docker run -d --name browserless \
274
+ -p 3000:3000 \
275
+ -e TOKEN=your-secret-token \
276
+ ghcr.io/browserless/chromium</code></pre>
277
+ <p>Then set the endpoint to <code>ws://your-server:3000</code> in the dashboard.</p>
278
+
279
+ <div class="warning"><strong>Note:</strong> Browserless does not support video calls (no camera/mic access). Use Remote CDP for meetings.</div>
280
+
281
+ <!-- ═══════════════════════════════════════════ -->
282
+ <h2 id="browserbase">4. Browserbase</h2>
283
+
284
+ <p>AI-native cloud browser built specifically for agent automation. Offers session replay, anti-detection, and managed infrastructure with a developer-friendly API.</p>
285
+
286
+ <h3>When to use</h3>
287
+ <ul>
288
+ <li>AI agent workflows that need persistent browser sessions</li>
289
+ <li>Session recording and replay for debugging agent actions</li>
290
+ <li>Sites with aggressive anti-bot detection</li>
291
+ <li>When you want the most agent-friendly browser service</li>
292
+ </ul>
293
+
294
+ <h3>Setup</h3>
295
+ <ol>
296
+ <li>Sign up at <a href="https://www.browserbase.com" target="_blank">browserbase.com</a></li>
297
+ <li>Go to <a href="https://www.browserbase.com/settings" target="_blank">Settings</a> and copy your API key</li>
298
+ <li>Copy your Project ID from the dashboard</li>
299
+ <li>Enter both in the AgenticMail dashboard under Browser → Browserbase</li>
300
+ <li>Click "Test Connection" to verify</li>
301
+ </ol>
302
+
303
+ <div class="tip"><strong>Session replay:</strong> Browserbase records every session by default. You can watch exactly what your agent did at <a href="https://www.browserbase.com" target="_blank">browserbase.com</a>.</div>
304
+
305
+ <!-- ═══════════════════════════════════════════ -->
306
+ <h2 id="steel">5. Steel.dev</h2>
307
+
308
+ <p>Open-source browser API designed for AI agents. Can be self-hosted for free or used as a managed cloud service. Built-in session management and stealth capabilities.</p>
309
+
310
+ <h3>When to use</h3>
311
+ <ul>
312
+ <li>Self-hosted browser infrastructure (full control, no vendor lock-in)</li>
313
+ <li>AI agent workflows that need session persistence</li>
314
+ <li>When you want open-source and the ability to customize</li>
315
+ </ul>
316
+
317
+ <h3>Setup: Cloud (managed)</h3>
318
+ <ol>
319
+ <li>Sign up at <a href="https://app.steel.dev" target="_blank">app.steel.dev</a></li>
320
+ <li>Copy your API key from the dashboard</li>
321
+ <li>Enter it in AgenticMail under Browser → Steel</li>
322
+ <li>Click "Test Connection" to verify</li>
323
+ </ol>
324
+
325
+ <h3>Setup: Self-hosted (Docker)</h3>
326
+ <pre><code>docker run -d --name steel \
327
+ -p 3000:3000 \
328
+ ghcr.io/nichochar/steel-browser:latest</code></pre>
329
+ <p>Set the endpoint to <code>http://your-server:3000</code> and leave the API key empty for local use.</p>
330
+
331
+ <!-- ═══════════════════════════════════════════ -->
332
+ <h2 id="scrapingbee">6. ScrapingBee</h2>
333
+
334
+ <p>Web scraping API with built-in browser rendering, proxy rotation, and CAPTCHA solving. Works differently from other providers — it routes your browser traffic through ScrapingBee's proxy network for anti-detection.</p>
335
+
336
+ <h3>When to use</h3>
337
+ <ul>
338
+ <li>Scraping websites that block bots aggressively</li>
339
+ <li>When you need residential proxy IPs</li>
340
+ <li>CAPTCHA-heavy websites</li>
341
+ <li>Geo-targeted browsing (browse as if from a specific country)</li>
342
+ </ul>
343
+
344
+ <h3>Setup</h3>
345
+ <ol>
346
+ <li>Sign up at <a href="https://www.scrapingbee.com" target="_blank">scrapingbee.com</a></li>
347
+ <li>Copy your API key from the <a href="https://www.scrapingbee.com/dashboard" target="_blank">dashboard</a></li>
348
+ <li>Enter it in AgenticMail under Browser → ScrapingBee</li>
349
+ <li>Click "Test Connection" — you'll see your remaining API credits</li>
350
+ </ol>
351
+
352
+ <div class="tip"><strong>Free tier:</strong> ScrapingBee offers 1,000 free API credits — enough for testing.</div>
353
+
354
+ <div class="warning"><strong>Note:</strong> ScrapingBee works as a proxy, not a direct browser connection. Some advanced Playwright features (like file uploads or complex interactions) may not work through the proxy. For those cases, use Browserless or Browserbase.</div>
355
+
356
+ <!-- ═══════════════════════════════════════════ -->
357
+ <h2 id="security">Security Best Practices</h2>
358
+
359
+ <div class="card">
360
+ <h3 style="margin-top: 0;">For all providers</h3>
361
+ <ul>
362
+ <li><strong>Use domain allowlists:</strong> Restrict which websites agents can visit in Security & Limits</li>
363
+ <li><strong>Set page limits:</strong> Limit concurrent pages to prevent runaway browsing</li>
364
+ <li><strong>Enable SSRF protection:</strong> Blocks navigation to internal/private IPs (enabled by default)</li>
365
+ <li><strong>Rotate API keys:</strong> Change cloud provider API keys periodically</li>
366
+ </ul>
367
+
368
+ <h3>For Remote CDP specifically</h3>
369
+ <ul>
370
+ <li><strong>Never expose port 9222 to the public internet</strong> without a firewall or SSH tunnel</li>
371
+ <li><strong>Use SSH tunnels</strong> for the most secure connection (all traffic encrypted)</li>
372
+ <li><strong>Use a dedicated Chrome profile</strong> (--user-data-dir) — don't use your personal browser</li>
373
+ <li><strong>Run Chrome as a non-root user</strong> on Linux</li>
374
+ <li><strong>Keep Chrome updated</strong> — remote debugging inherits all Chrome vulnerabilities</li>
375
+ </ul>
376
+
377
+ <h3>For cloud providers</h3>
378
+ <ul>
379
+ <li><strong>API keys are encrypted:</strong> All credentials are stored encrypted in the AgenticMail vault</li>
380
+ <li><strong>Use provider IP allowlists:</strong> Most cloud browser providers let you restrict API access by IP</li>
381
+ <li><strong>Monitor usage:</strong> Set up alerts on your cloud provider dashboard for unusual usage</li>
382
+ </ul>
383
+ </div>
384
+
385
+ <!-- ═══════════════════════════════════════════ -->
386
+ <h2 id="faq">Frequently Asked Questions</h2>
387
+
388
+ <div class="card">
389
+ <h3 style="margin-top: 0;">Can I use video calls with cloud browser providers?</h3>
390
+ <p>No. Cloud providers (Browserless, Browserbase, Steel, ScrapingBee) don't have camera or microphone access. For video calls, use <strong>Remote CDP</strong> connected to a machine with a camera and microphone (physical or virtual).</p>
391
+
392
+ <h3>Which provider is fastest?</h3>
393
+ <p>Local Chromium is fastest (no network latency). For cloud providers, Browserless and Steel are typically fastest for page loads. Browserbase adds session management overhead but is the most reliable for complex automations.</p>
394
+
395
+ <h3>Can I switch providers without losing anything?</h3>
396
+ <p>Yes. Browser sessions are independent of each other. Switching providers in the dashboard takes effect on the next browser action. Note: cookies and login sessions from one provider don't transfer to another.</p>
397
+
398
+ <h3>What if my cloud provider goes down?</h3>
399
+ <p>The agent will fall back to local Chromium automatically if the cloud provider connection fails (configurable in Security & Limits).</p>
400
+
401
+ <h3>How do I use multiple browsers for different tasks?</h3>
402
+ <p>Each agent has its own browser configuration. Create different agents for different purposes — one with Remote CDP for meetings, another with Browserless for scraping.</p>
403
+ </div>
404
+
405
+ <p style="margin-top: 32px; color: var(--muted); font-size: 12px; text-align: center;">
406
+ AgenticMail Enterprise — Browser Provider Setup Guide<br>
407
+ Need help? Contact support or check the <a href="https://docs.agenticmail.io" style="color: var(--muted);">full documentation</a>.
408
+ </p>
409
+
410
+ </body>
411
+ </html>
@@ -442,47 +442,60 @@ export function BrowserConfigCard(props) {
442
442
  sectionTitle(I.globe(), 'Remote Browser Connection'),
443
443
  h('div', { style: { padding: '10px 14px', background: 'var(--info-soft)', borderRadius: 'var(--radius)', marginBottom: 12, fontSize: 12, lineHeight: 1.5 } },
444
444
  h('strong', null, 'How it works: '),
445
- 'The agent connects to a Chrome/Chromium browser running on another machine via the Chrome DevTools Protocol (CDP). ',
446
- 'This is required for video calls (Google Meet, Teams, Zoom) where the browser needs a camera, microphone, and display. ',
445
+ 'The agent connects to a Chrome browser running on another machine (a VM, cloud desktop, or remote server). ',
446
+ 'This is ideal for video calls (Google Meet, Teams, Zoom) where the browser needs a camera, microphone, and display.',
447
447
  h('br', null), h('br', null),
448
- h('strong', null, 'Setup options:'), h('br', null),
449
- '\u2022 Run Chrome with --remote-debugging-port=9222 on a VM/desktop', h('br', null),
450
- '\u2022 Use a cloud desktop (AWS WorkSpaces, Azure Virtual Desktop, Hetzner)', h('br', null),
451
- '\u2022 Set up a dedicated browser VM with virtual camera/audio for meetings', h('br', null),
452
- '\u2022 Use SSH tunneling to expose Chrome DevTools securely'
448
+ h('strong', null, 'What you need: '), 'A machine with Chrome installed and remote debugging enabled. ',
449
+ 'Just enter the connection URL below we handle everything else automatically, including SSH tunneling if needed.',
450
+ h('br', null), h('br', null),
451
+ h('strong', null, 'Connection format: '), 'Enter either:',
452
+ h('br', null),
453
+ '\u2022 A hostname and port (e.g., ', h('code', { style: { fontSize: 11, color: 'var(--accent)' } }, '192.168.1.100:9222'), ') — we auto-discover the WebSocket URL',
454
+ h('br', null),
455
+ '\u2022 A full WebSocket URL (e.g., ', h('code', { style: { fontSize: 11, color: 'var(--accent)' } }, 'ws://192.168.1.100:9222/devtools/browser/...'), ')',
456
+ h('br', null), h('br', null),
457
+ h('strong', null, 'Don\'t have a remote browser yet? '), h('a', { href: '/docs/browser-providers', target: '_blank', style: { color: 'var(--accent)' } }, 'Read our setup guide'),
458
+ ' for step-by-step instructions on setting up Chrome for remote access on any machine (Mac, Linux, Windows, cloud VMs).'
453
459
  ),
454
460
  h('div', { style: { display: 'grid', gap: 12 } },
455
461
  h('div', { className: 'form-group' },
456
- h('label', { style: labelStyle }, 'CDP WebSocket URL *'),
457
- h('input', { className: 'input', placeholder: 'ws://192.168.1.100:9222/devtools/browser/...',
462
+ h('label', { style: labelStyle }, 'Remote Browser Address *'),
463
+ h('input', { className: 'input', placeholder: '192.168.1.100:9222 or ws://host:9222/devtools/browser/...',
458
464
  value: cfg.cdpUrl || '',
459
465
  onChange: function(e) { update('cdpUrl', e.target.value); }
460
466
  }),
461
- h('div', { style: helpStyle }, 'WebSocket URL from chrome://inspect or --remote-debugging-port output. Format: ws://host:port/devtools/browser/<id>')
467
+ h('div', { style: helpStyle }, 'IP address and port of the remote Chrome browser. We auto-detect the connection details.')
462
468
  ),
463
469
  h('div', { style: { display: 'grid', gap: 12, gridTemplateColumns: '1fr 1fr' } },
464
470
  h('div', { className: 'form-group' },
465
471
  h('label', { style: labelStyle }, 'Auth Token'),
466
- h('input', { className: 'input', type: 'password', placeholder: 'Optional — for authenticated CDP endpoints',
472
+ h('input', { className: 'input', type: 'password', placeholder: 'Optional — for authenticated endpoints',
467
473
  value: cfg.cdpAuthToken || '',
468
474
  onChange: function(e) { update('cdpAuthToken', e.target.value || undefined); }
469
- })
475
+ }),
476
+ h('div', { style: helpStyle }, 'Only needed if the remote browser requires authentication.')
470
477
  ),
471
478
  h('div', { className: 'form-group' },
472
- h('label', { style: labelStyle }, 'Connection Timeout (ms)'),
473
- h('input', { className: 'input', type: 'number', min: 5000, max: 60000,
474
- value: cfg.cdpTimeout || 30000,
475
- onChange: function(e) { update('cdpTimeout', parseInt(e.target.value) || 30000); }
476
- })
479
+ h('label', { style: labelStyle }, 'Connection Timeout'),
480
+ h('select', { className: 'input', value: String(cfg.cdpTimeout || 30000),
481
+ onChange: function(e) { update('cdpTimeout', parseInt(e.target.value)); }
482
+ },
483
+ h('option', { value: '10000' }, '10 seconds'),
484
+ h('option', { value: '30000' }, '30 seconds (recommended)'),
485
+ h('option', { value: '60000' }, '60 seconds (slow networks)')
486
+ )
477
487
  )
478
488
  ),
479
489
  h('div', { className: 'form-group' },
480
- h('label', { style: labelStyle }, 'SSH Tunnel (auto-connect)'),
481
- h('input', { className: 'input', placeholder: 'ssh -L 9222:localhost:9222 user@remote-host (optional)',
482
- value: cfg.sshTunnel || '',
483
- onChange: function(e) { update('sshTunnel', e.target.value || undefined); }
484
- }),
485
- h('div', { style: helpStyle }, 'SSH command to establish tunnel before connecting. Agent will run this automatically.')
490
+ h('label', { style: labelStyle }, 'SSH Tunnel'),
491
+ h('div', { style: { display: 'grid', gap: 8, gridTemplateColumns: '1fr auto' } },
492
+ h('input', { className: 'input', placeholder: 'user@remote-host (we handle the rest automatically)',
493
+ value: cfg.sshTunnel || '',
494
+ onChange: function(e) { update('sshTunnel', e.target.value || undefined); }
495
+ }),
496
+ cfg.sshTunnel && h('span', { style: { display: 'flex', alignItems: 'center', fontSize: 11, color: 'var(--success)', whiteSpace: 'nowrap' } }, I.check(), ' Auto-connect enabled')
497
+ ),
498
+ h('div', { style: helpStyle }, 'Optional. If the remote browser is behind a firewall, enter the SSH user@host and we\'ll automatically create a secure tunnel before connecting. The SSH key must be configured on this server.')
486
499
  )
487
500
  )
488
501
  ),