From 9c02ea90985e7734c181f580f516aaad7e418f2f Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 15 Jan 2026 05:10:57 +0000 Subject: [PATCH] feat: polish chrome extension UX --- assets/chrome-extension/background.js | 41 ++++- assets/chrome-extension/icons/icon128.png | Bin 0 -> 614 bytes assets/chrome-extension/icons/icon16.png | Bin 0 -> 265 bytes assets/chrome-extension/icons/icon32.png | Bin 0 -> 318 bytes assets/chrome-extension/icons/icon48.png | Bin 0 -> 370 bytes assets/chrome-extension/manifest.json | 17 +- assets/chrome-extension/options.html | 194 +++++++++++++++++----- assets/chrome-extension/options.js | 9 +- 8 files changed, 214 insertions(+), 47 deletions(-) create mode 100644 assets/chrome-extension/icons/icon128.png create mode 100644 assets/chrome-extension/icons/icon16.png create mode 100644 assets/chrome-extension/icons/icon32.png create mode 100644 assets/chrome-extension/icons/icon48.png diff --git a/assets/chrome-extension/background.js b/assets/chrome-extension/background.js index 519a30a81..268fade73 100644 --- a/assets/chrome-extension/background.js +++ b/assets/chrome-extension/background.js @@ -1,9 +1,9 @@ const DEFAULT_PORT = 18792 const BADGE = { - on: { text: 'ON', color: '#0B6E4F' }, + on: { text: 'ON', color: '#FF5A36' }, off: { text: '', color: '#000000' }, - connecting: { text: '…', color: '#B45309' }, + connecting: { text: '…', color: '#F59E0B' }, error: { text: '!', color: '#B91C1C' }, } @@ -46,6 +46,7 @@ function setBadge(tabId, kind) { const cfg = BADGE[kind] void chrome.action.setBadgeText({ tabId, text: cfg.text }) void chrome.action.setBadgeBackgroundColor({ tabId, color: cfg.color }) + void chrome.action.setBadgeTextColor({ tabId, color: '#FFFFFF' }).catch(() => {}) } async function ensureRelayConnection() { @@ -111,6 +112,10 @@ function onRelayClosed(reason) { for (const tabId of tabs.keys()) { void chrome.debugger.detach({ tabId }).catch(() => {}) setBadge(tabId, 'connecting') + void chrome.action.setTitle({ + tabId, + title: 'Clawdbot Browser Relay: disconnected (click to re-attach)', + }) } tabs.clear() tabBySession.clear() @@ -125,6 +130,17 @@ function sendToRelay(payload) { ws.send(JSON.stringify(payload)) } +async function maybeOpenHelpOnce() { + try { + const stored = await chrome.storage.local.get(['helpOnErrorShown']) + if (stored.helpOnErrorShown === true) return + await chrome.storage.local.set({ helpOnErrorShown: true }) + await chrome.runtime.openOptionsPage() + } catch { + // ignore + } +} + function requestFromRelay(command) { const id = command.id return new Promise((resolve, reject) => { @@ -207,6 +223,10 @@ async function attachTab(tabId, opts = {}) { tabs.set(tabId, { state: 'connected', sessionId, targetId, attachOrder }) tabBySession.set(sessionId, tabId) + void chrome.action.setTitle({ + tabId, + title: 'Clawdbot Browser Relay: attached (click to detach)', + }) if (!opts.skipAttachedEvent) { sendToRelay({ @@ -256,6 +276,10 @@ async function detachTab(tabId, reason) { } setBadge(tabId, 'off') + void chrome.action.setTitle({ + tabId, + title: 'Clawdbot Browser Relay (click to attach/detach)', + }) } async function connectOrToggleForActiveTab() { @@ -271,6 +295,10 @@ async function connectOrToggleForActiveTab() { tabs.set(tabId, { state: 'connecting' }) setBadge(tabId, 'connecting') + void chrome.action.setTitle({ + tabId, + title: 'Clawdbot Browser Relay: connecting to local relay…', + }) try { await ensureRelayConnection() @@ -278,10 +306,13 @@ async function connectOrToggleForActiveTab() { } catch (err) { tabs.delete(tabId) setBadge(tabId, 'error') - const message = err instanceof Error ? err.message : String(err) - // Service worker: best-effort surface via title. - void chrome.action.setTitle({ tabId, title: `Clawdbot: ${message}` }) + void chrome.action.setTitle({ + tabId, + title: 'Clawdbot Browser Relay: relay not running (open options for setup)', + }) + void maybeOpenHelpOnce() // Extra breadcrumbs in chrome://extensions service worker logs. + const message = err instanceof Error ? err.message : String(err) console.warn('attach failed', message, nowStack()) } } diff --git a/assets/chrome-extension/icons/icon128.png b/assets/chrome-extension/icons/icon128.png new file mode 100644 index 0000000000000000000000000000000000000000..533cc812de79e24f30da7c024d5d23ee8be2622c GIT binary patch literal 614 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSY)RhkE)4%caKYZ?lNlJ8q&;06 zLn`LHy=&Vg;>gf&QPQyRpz(~Zl{2MR96Z)EqnauCe8L;fGp+_^0nFycqQU1C);z3W z+8xoht?FB~{@SJ6%bzV?ko7x#J2TLD5XhVL_lrAKskwd@uaQzAp05@4mk^eW`36!VYtg6u*06 z`s_{VzpKB`cRxWBa@N(K2@RzY_~_BCw=c{T?)f|-Sr4=w$9 zd)D8AUW0G-_3i(U@2gF7DN_~p`F!z?0$a-LR@bAF8#ey`ZT-i7mNd(EEoEkA=EMI# zRdgJa=KokJ&uH>QG455&i4#W-95|3*TPa&8HIc!G-^PH0Re@&#yUNjx4U8O1nR%Q& z4OwQNjR<}e7@oXlTjYB??-;L?goFeKe)eO%>=GBR1{~!UVP@cR%POhi659au34^Dr KpUXO@geCxOlV+Cy literal 0 HcmV?d00001 diff --git a/assets/chrome-extension/icons/icon32.png b/assets/chrome-extension/icons/icon32.png new file mode 100644 index 0000000000000000000000000000000000000000..f4c1be8a6a0faf269d0d4998c57cc7ebd09db913 GIT binary patch literal 318 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzwj^(N7l!{JxM1({$v~0Mo-U3d z5v|Dy60FLy20}pzN00uVFT~wf`@82lpPCoXa<&66t!A80wEOY?y+e?PL7vTljYgr+ zAsLOy2@;-OuD^EVVJ?n3QS$KzXm~m&Ib5@bmCqtmd1|i)O zfn_G<9EydH6&;%`_~V*4@4uZraV;Bb#)-o-uT8#w@aBn=nJ11PnDCv!OXyd;T>suA zB_+;_EDEZMDF<%sEHL5Zl{(OL#_`-sjinb97*6Clvq5ZKaZPN|tY){{PR=|%-%hYe z@l^5$nCQ8@lHk)lFrmLu@jYYLB!;bRlA4}f5Dgkyt$n+sf{zMb+kO7f&Bco^JM#uy z5p!Z&vaNf :root { color-scheme: light dark; - font-family: ui-monospace, Menlo, Monaco, Consolas, "SF Mono", monospace; + --accent: #ff5a36; + --panel: color-mix(in oklab, canvas 92%, canvasText 8%); + --border: color-mix(in oklab, canvasText 18%, transparent); + --muted: color-mix(in oklab, canvasText 70%, transparent); + --shadow: 0 10px 30px color-mix(in oklab, canvasText 18%, transparent); + font-family: ui-rounded, system-ui, -apple-system, BlinkMacSystemFont, "SF Pro Rounded", + "SF Pro Display", "Segoe UI", sans-serif; line-height: 1.4; } body { - margin: 24px; - max-width: 720px; + margin: 0; + min-height: 100vh; + background: + radial-gradient(1000px 500px at 10% 0%, color-mix(in oklab, var(--accent) 30%, transparent), transparent 70%), + radial-gradient(900px 450px at 90% 0%, color-mix(in oklab, var(--accent) 18%, transparent), transparent 75%), + canvas; + color: canvasText; + } + .wrap { + max-width: 820px; + margin: 36px auto; + padding: 0 24px 48px 24px; + } + header { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 18px; + } + .logo { + width: 44px; + height: 44px; + border-radius: 14px; + background: color-mix(in oklab, var(--accent) 18%, transparent); + border: 1px solid color-mix(in oklab, var(--accent) 35%, transparent); + box-shadow: var(--shadow); + display: grid; + place-items: center; + } + .logo img { + width: 28px; + height: 28px; + image-rendering: pixelated; } h1 { - font-size: 18px; - margin: 0 0 16px 0; + font-size: 20px; + margin: 0; + letter-spacing: -0.01em; } - label { - display: block; - font-size: 12px; - opacity: 0.9; - margin-bottom: 6px; + .subtitle { + margin: 2px 0 0 0; + color: var(--muted); + font-size: 13px; } - input { - width: 140px; - padding: 8px 10px; - border-radius: 10px; - border: 1px solid color-mix(in oklab, currentColor 20%, transparent); - background: color-mix(in oklab, currentColor 3%, transparent); + .grid { + display: grid; + grid-template-columns: 1fr; + gap: 14px; } - button { - margin-left: 8px; - padding: 8px 12px; - border-radius: 10px; - border: 1px solid color-mix(in oklab, currentColor 20%, transparent); - background: color-mix(in oklab, currentColor 10%, transparent); - cursor: pointer; + .card { + background: var(--panel); + border: 1px solid var(--border); + border-radius: 16px; + padding: 16px; + box-shadow: var(--shadow); + } + .card h2 { + margin: 0 0 10px 0; + font-size: 14px; + letter-spacing: 0.01em; + } + .card p { + margin: 8px 0 0 0; + color: var(--muted); + font-size: 13px; } .row { display: flex; align-items: center; gap: 8px; + flex-wrap: wrap; + } + label { + display: block; + font-size: 12px; + color: var(--muted); + margin-bottom: 6px; + } + input { + width: 160px; + padding: 10px 12px; + border-radius: 12px; + border: 1px solid var(--border); + background: color-mix(in oklab, canvas 92%, canvasText 8%); + color: canvasText; + outline: none; + } + input:focus { + border-color: color-mix(in oklab, var(--accent) 70%, transparent); + box-shadow: 0 0 0 4px color-mix(in oklab, var(--accent) 20%, transparent); + } + button { + padding: 10px 14px; + border-radius: 12px; + border: 1px solid color-mix(in oklab, var(--accent) 55%, transparent); + background: linear-gradient( + 180deg, + color-mix(in oklab, var(--accent) 80%, white 20%), + var(--accent) + ); + color: white; + font-weight: 650; + letter-spacing: 0.01em; + cursor: pointer; + } + button:active { + transform: translateY(1px); } .hint { - margin-top: 12px; + margin-top: 10px; font-size: 12px; - opacity: 0.8; + color: var(--muted); } code { - font-family: inherit; + font-family: ui-monospace, Menlo, Monaco, Consolas, "SF Mono", monospace; + font-size: 12px; + } + a { + color: color-mix(in oklab, var(--accent) 85%, canvasText 15%); + } + .status { + margin-top: 10px; + font-size: 12px; + color: color-mix(in oklab, var(--accent) 70%, canvasText 30%); + min-height: 16px; } -

Clawdbot Browser Relay

+
+
+ +
+

Clawdbot Browser Relay

+

Click the toolbar button on a tab to attach / detach.

+
+
-
- -
- - +
+
+

Getting started

+

+ If you see a red ! badge on the extension icon, the relay server is not reachable. + Start Clawdbot’s browser relay on this machine (Gateway or clawdbot browser serve), + then click the toolbar button again. +

+

+ Full guide (install, remote Gateway, security): docs.clawd.bot/tools/chrome-extension +

+
+ +
+

Relay port

+ +
+ + +
+
+ Default: 18792. Extension connects to: http://127.0.0.1:<port>/. + Only change this if your Clawdbot profile uses a different cdpUrl port. +
+
+
-
- Default: 18792. Relay base URL: http://127.0.0.1:<port>/. -
-
+ +
- - - diff --git a/assets/chrome-extension/options.js b/assets/chrome-extension/options.js index 826c251d9..dfe9f08f2 100644 --- a/assets/chrome-extension/options.js +++ b/assets/chrome-extension/options.js @@ -7,10 +7,17 @@ function clampPort(value) { return n } +function updateRelayUrl(port) { + const el = document.getElementById('relay-url') + if (!el) return + el.textContent = `http://127.0.0.1:${port}/` +} + async function load() { const stored = await chrome.storage.local.get(['relayPort']) const port = clampPort(stored.relayPort) document.getElementById('port').value = String(port) + updateRelayUrl(port) } async function save() { @@ -18,6 +25,7 @@ async function save() { const port = clampPort(input.value) await chrome.storage.local.set({ relayPort: port }) input.value = String(port) + updateRelayUrl(port) const status = document.getElementById('status') status.textContent = `Saved. Using http://127.0.0.1:${port}/` setTimeout(() => { @@ -27,4 +35,3 @@ async function save() { document.getElementById('save').addEventListener('click', () => void save()) void load() -