@cereworker/browser 26.324.4 → 26.324.6

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/tools.js CHANGED
@@ -1,7 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { navigateTo, getPageText, screenshot, clickElement, typeText, evaluateJs, waitForSelector, getPageUrl, closeBrowser, setMode, getMode, } from './puppeteer.js';
3
- import { connectCdp, disconnectCdp } from './cdp.js';
4
- function wrapBrowserTool(fn) {
2
+ function wrap(fn) {
5
3
  return async (args) => {
6
4
  try {
7
5
  return await fn(args);
@@ -11,99 +9,106 @@ function wrapBrowserTool(fn) {
11
9
  }
12
10
  };
13
11
  }
14
- export const browserToolDefinitions = {
15
- browserNavigate: {
16
- description: 'Navigate the browser to a URL. Requires a running Chrome browser — prefer httpFetch for API calls.',
17
- parameters: z.object({
18
- url: z.string().describe('The URL to navigate to'),
19
- }),
20
- execute: wrapBrowserTool((args) => navigateTo(args.url)),
21
- },
22
- browserGetText: {
23
- description: 'Get the visible text content of the current page',
24
- parameters: z.object({}),
25
- execute: wrapBrowserTool(async () => getPageText()),
26
- },
27
- browserScreenshot: {
28
- description: 'Take a screenshot of the current page',
29
- parameters: z.object({
30
- path: z.string().optional().describe('File path to save screenshot'),
31
- }),
32
- execute: wrapBrowserTool(async (args) => screenshot(args.path)),
33
- },
34
- browserClick: {
35
- description: 'Click an element on the page by CSS selector',
36
- parameters: z.object({
37
- selector: z.string().describe('CSS selector of the element to click'),
38
- }),
39
- execute: wrapBrowserTool(async (args) => clickElement(args.selector)),
40
- },
41
- browserType: {
42
- description: 'Type text into an input element on the page',
43
- parameters: z.object({
44
- selector: z.string().describe('CSS selector of the input element'),
45
- text: z.string().describe('Text to type'),
46
- }),
47
- execute: wrapBrowserTool(async (args) => typeText(args.selector, args.text)),
48
- },
49
- browserEval: {
50
- description: 'Execute JavaScript code in the browser page context',
51
- parameters: z.object({
52
- code: z.string().describe('JavaScript code to evaluate'),
53
- }),
54
- execute: wrapBrowserTool(async (args) => evaluateJs(args.code)),
55
- },
56
- browserWait: {
57
- description: 'Wait for a CSS selector to appear on the page',
58
- parameters: z.object({
59
- selector: z.string().describe('CSS selector to wait for'),
60
- timeout: z.number().optional().default(5000).describe('Timeout in milliseconds'),
61
- }),
62
- execute: wrapBrowserTool(async (args) => waitForSelector(args.selector, args.timeout)),
63
- },
64
- browserGetUrl: {
65
- description: 'Get the current URL of the browser page',
66
- parameters: z.object({}),
67
- execute: wrapBrowserTool(async () => getPageUrl()),
68
- },
69
- browserConnect: {
70
- description: 'Connect to an existing Chrome browser via CDP (Chrome DevTools Protocol). Chrome must be running with --remote-debugging-port.',
71
- parameters: z.object({
72
- port: z.number().optional().default(9222).describe('Chrome remote debugging port'),
73
- wsEndpoint: z.string().optional().describe('Direct WebSocket endpoint URL (overrides port)'),
74
- }),
75
- execute: async (args) => {
76
- try {
77
- setMode('connect', { port: args.port, wsEndpoint: args.wsEndpoint });
78
- const s = await connectCdp({ port: args.port, wsEndpoint: args.wsEndpoint });
79
- const url = s.page.url();
80
- return `Connected to Chrome via CDP. Current page: ${url}`;
81
- }
82
- catch (err) {
83
- setMode('launch');
84
- return `Failed to connect: ${err instanceof Error ? err.message : String(err)}`;
85
- }
12
+ export function createBrowserTools(backend) {
13
+ return {
14
+ browserNavigate: {
15
+ description: 'Navigate the browser to a URL. Prefer httpFetch for API calls.',
16
+ parameters: z.object({
17
+ url: z.string().describe('The URL to navigate to'),
18
+ }),
19
+ execute: wrap((args) => backend.navigate(args.url)),
86
20
  },
87
- },
88
- browserDisconnect: {
89
- description: 'Disconnect from Chrome without closing it (CDP mode only)',
90
- parameters: z.object({}),
91
- execute: async () => {
92
- if (getMode() !== 'connect') {
93
- return 'Not in CDP mode. Use browserClose to close a launched browser.';
94
- }
95
- await disconnectCdp();
96
- setMode('launch');
97
- return 'Disconnected from Chrome';
21
+ browserGetText: {
22
+ description: 'Get the visible text content of the current page',
23
+ parameters: z.object({}),
24
+ execute: wrap(async () => backend.getPageText()),
98
25
  },
99
- },
100
- browserClose: {
101
- description: 'Close the browser (launch mode) or disconnect (CDP mode)',
102
- parameters: z.object({}),
103
- execute: async () => {
104
- await closeBrowser();
105
- return getMode() === 'connect' ? 'Disconnected from Chrome' : 'Browser closed';
26
+ browserScreenshot: {
27
+ description: 'Take a screenshot of the current page',
28
+ parameters: z.object({
29
+ path: z.string().optional().describe('File path to save screenshot'),
30
+ }),
31
+ execute: wrap(async (args) => backend.screenshot(args.path)),
106
32
  },
107
- },
108
- };
33
+ browserClick: {
34
+ description: 'Click an element on the page by CSS selector',
35
+ parameters: z.object({
36
+ selector: z.string().describe('CSS selector of the element to click'),
37
+ }),
38
+ execute: wrap(async (args) => backend.click(args.selector)),
39
+ },
40
+ browserType: {
41
+ description: 'Type text into an input element on the page',
42
+ parameters: z.object({
43
+ selector: z.string().describe('CSS selector of the input element'),
44
+ text: z.string().describe('Text to type'),
45
+ }),
46
+ execute: wrap(async (args) => backend.type(args.selector, args.text)),
47
+ },
48
+ browserEval: {
49
+ description: 'Execute JavaScript code in the browser page context',
50
+ parameters: z.object({
51
+ code: z.string().describe('JavaScript code to evaluate'),
52
+ }),
53
+ execute: wrap(async (args) => backend.evaluate(args.code)),
54
+ },
55
+ browserWait: {
56
+ description: 'Wait for a CSS selector to appear on the page',
57
+ parameters: z.object({
58
+ selector: z.string().describe('CSS selector to wait for'),
59
+ timeout: z.number().optional().default(5000).describe('Timeout in milliseconds'),
60
+ }),
61
+ execute: wrap(async (args) => backend.waitForSelector(args.selector, args.timeout)),
62
+ },
63
+ browserGetUrl: {
64
+ description: 'Get the current URL of the browser page',
65
+ parameters: z.object({}),
66
+ execute: wrap(async () => backend.getPageUrl()),
67
+ },
68
+ browserListTabs: {
69
+ description: 'List all open browser tabs with their URLs',
70
+ parameters: z.object({}),
71
+ execute: wrap(async () => {
72
+ const tabs = await backend.listTabs();
73
+ if (tabs.length === 0)
74
+ return 'No tabs open.';
75
+ return tabs.map((t) => `${t.active ? '→ ' : ' '}[${t.id}] ${t.url}${t.title ? ` - ${t.title}` : ''}`).join('\n');
76
+ }),
77
+ },
78
+ browserSwitchTab: {
79
+ description: 'Switch to a different browser tab by its ID',
80
+ parameters: z.object({
81
+ tabId: z.string().describe('Tab ID from browserListTabs'),
82
+ }),
83
+ execute: wrap(async (args) => backend.switchTab(args.tabId)),
84
+ },
85
+ browserNewTab: {
86
+ description: 'Open a new browser tab, optionally navigating to a URL',
87
+ parameters: z.object({
88
+ url: z.string().optional().describe('URL to navigate to in the new tab'),
89
+ }),
90
+ execute: wrap(async (args) => backend.newTab(args.url)),
91
+ },
92
+ browserCloseTab: {
93
+ description: 'Close a browser tab by ID, or close the current tab',
94
+ parameters: z.object({
95
+ tabId: z.string().optional().describe('Tab ID to close (current tab if omitted)'),
96
+ }),
97
+ execute: wrap(async (args) => backend.closeTab(args.tabId)),
98
+ },
99
+ browserConnect: {
100
+ description: 'Connect to the browser backend. In extension mode, waits for the Chrome extension.',
101
+ parameters: z.object({}),
102
+ execute: wrap(async () => backend.connect()),
103
+ },
104
+ browserDisconnect: {
105
+ description: 'Disconnect from the browser without closing it',
106
+ parameters: z.object({}),
107
+ execute: wrap(async () => {
108
+ await backend.disconnect();
109
+ return 'Disconnected from browser';
110
+ }),
111
+ },
112
+ };
113
+ }
109
114
  //# sourceMappingURL=tools.js.map
package/dist/tools.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,339 @@
1
+ // CereWorker Browser Bridge — Service Worker
2
+ // Connects to the CereWorker relay server via WebSocket and executes commands
3
+ // using Chrome extension APIs (tabs, scripting).
4
+
5
+ let ws = null;
6
+ let connected = false;
7
+ let reconnectDelay = 1000;
8
+ const MAX_RECONNECT_DELAY = 30000;
9
+
10
+ // Default config
11
+ let config = { port: 18900, token: '' };
12
+
13
+ // Load config from storage
14
+ chrome.storage.local.get(['relayPort', 'relayToken'], (result) => {
15
+ if (result.relayPort) config.port = result.relayPort;
16
+ if (result.relayToken) config.token = result.relayToken;
17
+ });
18
+
19
+ // Listen for config changes
20
+ chrome.storage.onChanged.addListener((changes) => {
21
+ if (changes.relayPort) config.port = changes.relayPort.newValue;
22
+ if (changes.relayToken) config.token = changes.relayToken.newValue;
23
+ // Reconnect with new config
24
+ if (ws) {
25
+ ws.close();
26
+ }
27
+ });
28
+
29
+ // Keep service worker alive with alarms
30
+ chrome.alarms.create('keepalive', { periodInMinutes: 0.4 });
31
+ chrome.alarms.onAlarm.addListener((alarm) => {
32
+ if (alarm.name === 'keepalive' && connected) {
33
+ // Ping is handled by WebSocket protocol; alarm just keeps SW alive
34
+ }
35
+ });
36
+
37
+ // Toggle connection on icon click
38
+ chrome.action.onClicked.addListener(() => {
39
+ if (connected) {
40
+ disconnect();
41
+ } else {
42
+ connectToRelay();
43
+ }
44
+ });
45
+
46
+ function setBadge(text, color) {
47
+ chrome.action.setBadgeText({ text });
48
+ chrome.action.setBadgeBackgroundColor({ color });
49
+ }
50
+
51
+ function disconnect() {
52
+ if (ws) {
53
+ ws.close();
54
+ ws = null;
55
+ }
56
+ connected = false;
57
+ setBadge('', '#888');
58
+ }
59
+
60
+ function connectToRelay() {
61
+ if (ws && ws.readyState <= 1) return; // Already open or connecting
62
+
63
+ const tokenParam = config.token ? `?token=${encodeURIComponent(config.token)}` : '';
64
+ const url = `ws://127.0.0.1:${config.port}/extension${tokenParam}`;
65
+
66
+ setBadge('...', '#F59E0B');
67
+
68
+ try {
69
+ ws = new WebSocket(url);
70
+ } catch (err) {
71
+ setBadge('!', '#EF4444');
72
+ scheduleReconnect();
73
+ return;
74
+ }
75
+
76
+ ws.onopen = () => {
77
+ connected = true;
78
+ reconnectDelay = 1000;
79
+ setBadge('ON', '#22C55E');
80
+ // Send hello
81
+ ws.send(JSON.stringify({ type: 'hello', version: '1.0.0' }));
82
+ };
83
+
84
+ ws.onclose = () => {
85
+ connected = false;
86
+ setBadge('', '#888');
87
+ ws = null;
88
+ scheduleReconnect();
89
+ };
90
+
91
+ ws.onerror = () => {
92
+ setBadge('!', '#EF4444');
93
+ };
94
+
95
+ ws.onmessage = async (event) => {
96
+ let msg;
97
+ try {
98
+ msg = JSON.parse(event.data);
99
+ } catch {
100
+ return;
101
+ }
102
+
103
+ if (msg.type !== 'command' || !msg.id || !msg.method) return;
104
+
105
+ try {
106
+ const result = await handleCommand(msg.method, msg.params || {});
107
+ sendResult(msg.id, result);
108
+ } catch (err) {
109
+ sendResult(msg.id, null, err.message || String(err));
110
+ }
111
+ };
112
+ }
113
+
114
+ function sendResult(id, result, error) {
115
+ if (!ws || ws.readyState !== 1) return;
116
+ const msg = { type: 'result', id };
117
+ if (error) {
118
+ msg.error = error;
119
+ } else {
120
+ msg.result = result;
121
+ }
122
+ ws.send(JSON.stringify(msg));
123
+ }
124
+
125
+ function scheduleReconnect() {
126
+ setTimeout(() => {
127
+ if (!connected) connectToRelay();
128
+ }, reconnectDelay);
129
+ reconnectDelay = Math.min(reconnectDelay * 2, MAX_RECONNECT_DELAY);
130
+ }
131
+
132
+ // --- Command handlers ---
133
+
134
+ async function handleCommand(method, params) {
135
+ switch (method) {
136
+ case 'navigate': return cmdNavigate(params);
137
+ case 'getPageText': return cmdGetPageText();
138
+ case 'screenshot': return cmdScreenshot();
139
+ case 'click': return cmdClick(params);
140
+ case 'type': return cmdType(params);
141
+ case 'evaluate': return cmdEvaluate(params);
142
+ case 'waitForSelector': return cmdWaitForSelector(params);
143
+ case 'getPageUrl': return cmdGetPageUrl();
144
+ case 'listTabs': return cmdListTabs();
145
+ case 'switchTab': return cmdSwitchTab(params);
146
+ case 'newTab': return cmdNewTab(params);
147
+ case 'closeTab': return cmdCloseTab(params);
148
+ default: throw new Error(`Unknown method: ${method}`);
149
+ }
150
+ }
151
+
152
+ async function getActiveTab() {
153
+ const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
154
+ if (!tab) throw new Error('No active tab');
155
+ return tab;
156
+ }
157
+
158
+ async function cmdNavigate({ url }) {
159
+ if (!url) throw new Error('url is required');
160
+ const tab = await getActiveTab();
161
+ await chrome.tabs.update(tab.id, { url });
162
+ // Wait for load
163
+ await new Promise((resolve) => {
164
+ const listener = (tabId, info) => {
165
+ if (tabId === tab.id && info.status === 'complete') {
166
+ chrome.tabs.onUpdated.removeListener(listener);
167
+ resolve();
168
+ }
169
+ };
170
+ chrome.tabs.onUpdated.addListener(listener);
171
+ // Timeout after 30s
172
+ setTimeout(() => {
173
+ chrome.tabs.onUpdated.removeListener(listener);
174
+ resolve();
175
+ }, 30000);
176
+ });
177
+ const updated = await chrome.tabs.get(tab.id);
178
+ return `Navigated to ${updated.url} - Title: ${updated.title || ''}`;
179
+ }
180
+
181
+ async function cmdGetPageText() {
182
+ const tab = await getActiveTab();
183
+ const [result] = await chrome.scripting.executeScript({
184
+ target: { tabId: tab.id },
185
+ func: () => {
186
+ const text = document.body.innerText;
187
+ return text.length > 10000 ? text.slice(0, 10000) + '\n... (truncated)' : text;
188
+ },
189
+ });
190
+ return result?.result ?? '';
191
+ }
192
+
193
+ async function cmdScreenshot() {
194
+ const dataUrl = await chrome.tabs.captureVisibleTab(null, { format: 'png' });
195
+ return dataUrl; // Base64 data URL
196
+ }
197
+
198
+ async function cmdClick({ selector }) {
199
+ if (!selector) throw new Error('selector is required');
200
+ const tab = await getActiveTab();
201
+ const [result] = await chrome.scripting.executeScript({
202
+ target: { tabId: tab.id },
203
+ func: (sel) => {
204
+ const el = document.querySelector(sel);
205
+ if (!el) return `Element not found: ${sel}`;
206
+ el.click();
207
+ return `Clicked: ${sel}`;
208
+ },
209
+ args: [selector],
210
+ });
211
+ return result?.result ?? `Element not found: ${selector}`;
212
+ }
213
+
214
+ async function cmdType({ selector, text }) {
215
+ if (!selector || text === undefined) throw new Error('selector and text are required');
216
+ const tab = await getActiveTab();
217
+ const [result] = await chrome.scripting.executeScript({
218
+ target: { tabId: tab.id },
219
+ func: (sel, txt) => {
220
+ const el = document.querySelector(sel);
221
+ if (!el) return `Element not found: ${sel}`;
222
+ el.focus();
223
+ // Set value and dispatch input event
224
+ el.value = txt;
225
+ el.dispatchEvent(new Event('input', { bubbles: true }));
226
+ el.dispatchEvent(new Event('change', { bubbles: true }));
227
+ return `Typed into: ${sel}`;
228
+ },
229
+ args: [selector, text],
230
+ });
231
+ return result?.result ?? `Element not found: ${selector}`;
232
+ }
233
+
234
+ async function cmdEvaluate({ code }) {
235
+ if (!code) throw new Error('code is required');
236
+ const tab = await getActiveTab();
237
+ const [result] = await chrome.scripting.executeScript({
238
+ target: { tabId: tab.id },
239
+ func: (c) => {
240
+ try {
241
+ const r = eval(c);
242
+ return String(r ?? '(no result)');
243
+ } catch (err) {
244
+ return `Error: ${err.message || String(err)}`;
245
+ }
246
+ },
247
+ args: [code],
248
+ });
249
+ return result?.result ?? '(no result)';
250
+ }
251
+
252
+ async function cmdWaitForSelector({ selector, timeout = 5000 }) {
253
+ if (!selector) throw new Error('selector is required');
254
+ const tab = await getActiveTab();
255
+ const [result] = await chrome.scripting.executeScript({
256
+ target: { tabId: tab.id },
257
+ func: (sel, ms) => {
258
+ return new Promise((resolve) => {
259
+ if (document.querySelector(sel)) {
260
+ resolve(`Found: ${sel}`);
261
+ return;
262
+ }
263
+ const observer = new MutationObserver(() => {
264
+ if (document.querySelector(sel)) {
265
+ observer.disconnect();
266
+ resolve(`Found: ${sel}`);
267
+ }
268
+ });
269
+ observer.observe(document.body, { childList: true, subtree: true });
270
+ setTimeout(() => {
271
+ observer.disconnect();
272
+ resolve(`Timeout waiting for: ${sel}`);
273
+ }, ms);
274
+ });
275
+ },
276
+ args: [selector, timeout],
277
+ });
278
+ return result?.result ?? `Timeout waiting for: ${selector}`;
279
+ }
280
+
281
+ async function cmdGetPageUrl() {
282
+ const tab = await getActiveTab();
283
+ return tab.url || '';
284
+ }
285
+
286
+ async function cmdListTabs() {
287
+ const tabs = await chrome.tabs.query({ currentWindow: true });
288
+ const result = tabs.map((t) => ({
289
+ id: String(t.id),
290
+ title: t.title || '',
291
+ url: t.url || '',
292
+ active: t.active,
293
+ }));
294
+ return JSON.stringify(result);
295
+ }
296
+
297
+ async function cmdSwitchTab({ tabId }) {
298
+ if (!tabId) throw new Error('tabId is required');
299
+ const id = parseInt(tabId, 10);
300
+ await chrome.tabs.update(id, { active: true });
301
+ const tab = await chrome.tabs.get(id);
302
+ return `Switched to tab ${id}: ${tab.url}`;
303
+ }
304
+
305
+ async function cmdNewTab({ url }) {
306
+ const tab = await chrome.tabs.create({ url: url || undefined });
307
+ if (url) {
308
+ // Wait for load
309
+ await new Promise((resolve) => {
310
+ const listener = (tabId, info) => {
311
+ if (tabId === tab.id && info.status === 'complete') {
312
+ chrome.tabs.onUpdated.removeListener(listener);
313
+ resolve();
314
+ }
315
+ };
316
+ chrome.tabs.onUpdated.addListener(listener);
317
+ setTimeout(() => {
318
+ chrome.tabs.onUpdated.removeListener(listener);
319
+ resolve();
320
+ }, 30000);
321
+ });
322
+ }
323
+ const updated = await chrome.tabs.get(tab.id);
324
+ return `Opened new tab: ${updated.url || 'about:blank'}`;
325
+ }
326
+
327
+ async function cmdCloseTab({ tabId }) {
328
+ if (tabId) {
329
+ const id = parseInt(tabId, 10);
330
+ await chrome.tabs.remove(id);
331
+ return `Closed tab ${id}`;
332
+ }
333
+ const tab = await getActiveTab();
334
+ await chrome.tabs.remove(tab.id);
335
+ return 'Closed current tab';
336
+ }
337
+
338
+ // Auto-connect on startup
339
+ connectToRelay();
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,37 @@
1
+ {
2
+ "manifest_version": 3,
3
+ "name": "CereWorker Browser Bridge",
4
+ "version": "1.0.0",
5
+ "description": "Connects CereWorker agent to your browser for automation",
6
+ "permissions": [
7
+ "tabs",
8
+ "scripting",
9
+ "activeTab",
10
+ "storage",
11
+ "alarms"
12
+ ],
13
+ "host_permissions": [
14
+ "http://127.0.0.1/*",
15
+ "http://localhost/*",
16
+ "<all_urls>"
17
+ ],
18
+ "background": {
19
+ "service_worker": "background.js"
20
+ },
21
+ "options_page": "options.html",
22
+ "action": {
23
+ "default_title": "CereWorker Bridge",
24
+ "default_icon": {
25
+ "16": "icons/icon16.png",
26
+ "32": "icons/icon32.png",
27
+ "48": "icons/icon48.png",
28
+ "128": "icons/icon128.png"
29
+ }
30
+ },
31
+ "icons": {
32
+ "16": "icons/icon16.png",
33
+ "32": "icons/icon32.png",
34
+ "48": "icons/icon48.png",
35
+ "128": "icons/icon128.png"
36
+ }
37
+ }
@@ -0,0 +1,34 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>CereWorker Browser Bridge - Options</title>
6
+ <style>
7
+ body { font-family: system-ui, sans-serif; padding: 24px; max-width: 400px; }
8
+ h1 { font-size: 18px; margin-bottom: 16px; }
9
+ label { display: block; margin-top: 12px; font-weight: 500; font-size: 14px; }
10
+ input { display: block; width: 100%; padding: 8px; margin-top: 4px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }
11
+ .status { margin-top: 16px; padding: 8px 12px; border-radius: 4px; font-size: 13px; }
12
+ .status.ok { background: #dcfce7; color: #166534; }
13
+ .status.error { background: #fee2e2; color: #991b1b; }
14
+ .status.checking { background: #fef9c3; color: #854d0e; }
15
+ button { margin-top: 16px; padding: 8px 16px; background: #2563eb; color: white; border: none; border-radius: 4px; cursor: pointer; }
16
+ button:hover { background: #1d4ed8; }
17
+ </style>
18
+ </head>
19
+ <body>
20
+ <h1>CereWorker Browser Bridge</h1>
21
+
22
+ <label for="port">Relay Port</label>
23
+ <input type="number" id="port" value="18900" min="1024" max="65535">
24
+
25
+ <label for="token">Token (optional)</label>
26
+ <input type="text" id="token" placeholder="Leave empty if no token configured">
27
+
28
+ <button id="save">Save & Test Connection</button>
29
+
30
+ <div id="status" class="status" style="display:none"></div>
31
+
32
+ <script src="options.js"></script>
33
+ </body>
34
+ </html>