← Commit history
bin/nb-dashboard+73−16@@ -18,7 +18,7 @@ const http = require('http'); const { execFile, spawn } = require('child_process'); -const VERSION = '0.7.2'; // keep in lockstep with package.json (pkg version)+const VERSION = '0.8.0'; // keep in lockstep with package.json (pkg version) const PORT = 8954; const URL_SELF = `http://127.0.0.1:${PORT}/`; @@ -58,26 +58,45 @@ async function refresh() { facets.push({ label: 'Adom Bridge', state: 'ok', detail: `${targets.length} desktop(s): ${targets.map((t) => t.name).join(', ')}` }); } // Facets 2+3: layer 1 / layer 2, judged across every connected desktop (any-ok wins).- let l1ok = false; let l2ready = false; let l2installable = false;+ let l1ok = false; let l2ready = false; let anyMissing = false; let anyDisabled = false;+ const matrix = []; // one row per machine x browser-profile, in every state incl. deliberately-disabled for (const t of targets.slice(0, 4)) { const r = await cli(['--target', t.name, '--ai-thread', 'nb-dashboard', 'nbrowser_readiness', '{}']); targetRows.push({ name: t.name, state: r.state || null, profiles: r.profiles || [], extensionVersions: r.extensionVersions || {}, chromeInstalled: r.chromeInstalled }); if (r.state) l1ok = true; // the nb bridge answered = layer 1 verbs route if (r.ready === true) l2ready = true;- if (r.state === 'needs-extension' || r.state === 'resolving') l2installable = true;+ const pr = await cli(['--target', t.name, '--ai-thread', 'nb-dashboard', 'nbrowser_profiles', '{}']);+ for (const p of (pr.profiles || [])) {+ if (/pending-\d+/.test(String(p.profile || '')) || p.unresolved) continue; // mid-identification, not a real row+ // Four states. 'disabled' is a USER CHOICE (they installed it, then switched it off - the+ // browser-native way of saying "nb may not drive this browser"). It is respected: grey, never+ // counted against health, never upsold. John does exactly this on Edge, on purpose.+ const st = p.live ? 'connected'+ : (p.extensionInstalled && p.extensionEnabled === false) ? 'disabled'+ : p.extensionInstalled ? 'asleep'+ : 'missing';+ if (st === 'missing') anyMissing = true;+ if (st === 'disabled') anyDisabled = true;+ matrix.push({ machine: t.name, browser: p.browser, profile: p.profile, email: p.email || '', state: st,+ version: (r.extensionVersions || {})[p.profile] || null });+ } }+ const l2installable = anyMissing; facets.push(l1ok ? { label: 'Layer 1 - Native Browser', state: 'ok', detail: 'extension-free opens into the user\'s real browser work now', _hint: 'nbrowser_open_os_window / nbrowser_profiles are live.' } : { label: 'Layer 1 - Native Browser', state: targets.length ? 'warn' : 'fail', detail: targets.length ? 'nb bridge not answering on the connected desktop(s)' : 'needs a connected desktop', _hint: 'adom-bridge refresh_bridges {"name":"native-browser"} on the desktop, then re-check nbrowser_readiness.' }); facets.push(l2ready- ? { label: 'Layer 2 - nbe (extension)', state: 'ok', detail: 'nbe connected - full driving is live', _hint: 'Drive with nbrowser_open_window; background screenshots, trusted clicks, logins all work.' }- : l2installable- ? { label: 'Layer 2 - nbe (extension)', state: 'warn', detail: 'nbe is NOT installed/connected - the user is missing the best part', _hint: 'OFFER the install (installing-the-extension skill runs it FOR the user in about a minute). This is the upsell the dashboard exists for.' }- : { label: 'Layer 2 - nbe (extension)', state: 'fail', detail: 'no desktop to check', _hint: 'Connect Adom Bridge first.' });+ ? { label: 'Layer 2 - nbe (extension)', state: 'ok', detail: anyMissing ? 'nbe live (some browsers still without it)' : 'nbe connected - full driving is live', _hint: 'Drive with nbrowser_open_window. Rows marked DISABLED are the user\'s explicit choice - treat like nbrowser_profile_block: never nag, never try to enable, never drive.' }+ : anyMissing+ ? { label: 'Layer 2 - nbe (extension)', state: 'warn', detail: 'nbe is NOT installed - the user is missing the best part', _hint: 'OFFER the install (installing-the-extension skill runs it FOR the user in about a minute). But a browser where the extension is INSTALLED-AND-DISABLED is the user saying no - respect it like nbrowser_profile_block.' }+ : anyDisabled+ ? { label: 'Layer 2 - nbe (extension)', state: 'ok', detail: 'disabled everywhere by the user - their choice, respected', _hint: 'The user deliberately disabled nbe. Do NOT upsell, enable, or work around it - offer layer 1 only.' }+ : { label: 'Layer 2 - nbe (extension)', state: 'fail', detail: 'no desktop to check', _hint: 'Connect Adom Bridge first.' }); // Rollup is OURS: the dashboard is healthy when layer 1 works; a missing nbe is a warn (that is // the upsell state, not an outage). cache.facets = facets; cache.targets = targetRows;+ cache.matrix = matrix; cache.state = l1ok ? (l2ready ? 'ok' : 'warn') : (targets.length ? 'warn' : 'fail'); cache.at = Date.now(); }@@ -117,6 +136,10 @@ function page() { .prompt button { background:var(--teal); color:#04211f; border:0; border-radius:8px; padding:0 14px; font-weight:700; font-size:13px; cursor:pointer; } .prompt button:active { transform:translateY(1px); } .hintline { font-size:12px; color:var(--dim); margin-top:6px; }+ .mini { background:transparent; border:1px solid var(--teal); color:var(--teal); border-radius:6px; padding:2px 10px; font-size:12px; cursor:pointer; }+ .mini:hover { background:rgba(0,206,194,.12); }+ tr.off td { color:var(--dim); opacity:.75; }+ .explain { margin-top:12px; border:1px solid rgba(0,206,194,.35); border-radius:10px; padding:12px 14px; background:rgba(0,206,194,.05); } table { width:100%; border-collapse:collapse; margin-top:10px; font-size:13px; } th { text-align:left; color:var(--dim); font-weight:500; font-size:11.5px; text-transform:uppercase; letter-spacing:.08em; padding:4px 8px; border-bottom:1px solid var(--edge); } td { padding:6px 8px; border-bottom:1px solid rgba(30,42,54,.5); }@@ -156,10 +179,26 @@ const UPSELL = '<p><strong>Not installed yet</strong> - and it is the single bes + '<div class="gain">fill forms, download behind logins, and clean up after itself</div>' + '<div class="gain">screenshot pages in the background while you keep working</div>' + '<div class="gain">show a gliding cursor that narrates what it is doing</div>'- + '<p>The install takes about a minute and <strong>your AI runs it for you</strong>:</p>'+ + '<p><strong>Installing takes your AI, not a download.</strong> There is no store listing yet, so the nbe installs in developer mode - and your AI drives that whole flow: loads the extension, wires the secure native host, verifies the connection. About a minute; you approve one folder picker.</p>' + '<div class="prompt"><code id="pr">install the adom native browser extension in my chrome</code><button onclick="copyPrompt()">Copy</button></div>'- + '<p class="hintline">Paste that to your AI. It drives the whole install; you approve the one folder picker.</p>';+ + '<p class="hintline">Paste that into any Adom AI thread to start.</p>'; function copyPrompt(){ try { navigator.clipboard.writeText(document.getElementById('pr').textContent); } catch(e){} }+function installPrompt(i){+ const r = (window.__matrix||[])[i]; if (!r) return;+ const p = 'install the adom native browser extension in my ' + r.browser + ' on ' + r.machine;+ const box = document.getElementById('install-explain');+ box.innerHTML = '<div class="explain">'+ + '<p><strong>Installing the nbe takes your AI, not a download.</strong> There is no store listing yet '+ + '(the Chrome/Edge review queue takes a while for an extension with these permissions), so it installs '+ + 'in developer mode - and your AI drives that whole flow for you: it loads the extension into <strong>'+ + r.browser + ' on ' + r.machine + '</strong>, wires the secure native-messaging host, and verifies the '+ + 'connection. Takes about a minute; you approve one folder picker.</p>'+ + '<p>Paste this to any Adom AI thread:</p>'+ + '<div class="prompt"><code id="pr2">' + p + '</code>'+ + '<button onclick="try{navigator.clipboard.writeText(document.getElementById(\'pr2\').textContent);this.textContent=\'Copied!\';}catch(e){}">Copy</button></div>'+ + '</div>';+ box.scrollIntoView({ behavior: 'smooth', block: 'nearest' });+} function led(el, s){ el.className = 'led ' + (s||'unknown'); } async function tick(){ try {@@ -177,13 +216,31 @@ async function tick(){ if (preview) { led(document.getElementById('led-l2'), 'warn'); body.innerHTML = '<p style="color:var(--amber);font-size:11px;text-transform:uppercase;letter-spacing:.1em">Preview mode - not the live state</p>' + UPSELL;- } else if (l2.state === 'ok') {+ } else {+ const m = d.matrix || []; let rows = '';- for (const t of (d.targets||[])) for (const [p,v] of Object.entries(t.extensionVersions||{}))- rows += '<tr><td>'+t.name+'</td><td>'+p+'</td><td>v'+v+'</td><td><span class="led ok"></span></td></tr>';- body.innerHTML = '<p><strong>Connected.</strong> Full driving is live: trusted clicks, logins, background capture, downloads behind auth.</p>'- + (rows ? '<table><tr><th>Desktop</th><th>Profile</th><th>nbe</th><th></th></tr>'+rows+'</table>' : '');- } else { body.innerHTML = UPSELL; }+ for (let i = 0; i < m.length; i++) {+ const r = m[i];+ const who = r.email || r.profile;+ if (r.state === 'connected')+ rows += '<tr><td>'+r.machine+'</td><td>'+r.browser+'</td><td>'+who+'</td><td><span class="led ok"></span> live'+(r.version?' v'+r.version:'')+'</td></tr>';+ else if (r.state === 'asleep')+ rows += '<tr><td>'+r.machine+'</td><td>'+r.browser+'</td><td>'+who+'</td><td><span class="led ok" style="opacity:.45"></span> installed, wakes on demand</td></tr>';+ else if (r.state === 'disabled')+ rows += '<tr class="off"><td>'+r.machine+'</td><td>'+r.browser+'</td><td>'+who+'</td><td><span class="led unknown"></span> disabled by you - nb will not drive this browser</td></tr>';+ else+ rows += '<tr><td>'+r.machine+'</td><td>'+r.browser+'</td><td>'+who+'</td><td><button class="mini" onclick="installPrompt('+i+')">Install…</button></td></tr>';+ }+ const anyLive = m.some(r => r.state === 'connected');+ const anyMissing = m.some(r => r.state === 'missing');+ body.innerHTML =+ (anyLive ? '<p><strong>Connected.</strong> Full driving is live: trusted clicks, logins, background capture, downloads behind auth.</p>'+ : anyMissing ? UPSELL : '<p>Per-browser status:</p>')+ + (rows ? '<table><tr><th>Machine</th><th>Browser</th><th>Profile</th><th>nbe</th></tr>'+rows+'</table>' : '')+ + (anyLive && anyMissing ? '<p class="hintline">Browsers without nbe have an Install button - it explains the AI-driven install and hands you the prompt.</p>' : '')+ + '<div id="install-explain"></div>';+ window.__matrix = m;+ } document.getElementById('facets').innerHTML = d.facets.map(x => '<div class="facet"><span class="led '+(x.state||'unknown')+'"></span>'+x.label+'<span class="d">'+(x.detail||'')+'</span></div>').join(''); } catch(e) { led(document.getElementById('topled'), 'unknown'); }@@ -199,7 +256,7 @@ function startServer() { const u = String(req.url || '/'); if (u.startsWith('/api/status')) { res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });- res.end(JSON.stringify({ state: cache.state, version: VERSION, facets: cache.facets, targets: cache.targets, cacheAgeMs: Date.now() - cache.at }));+ res.end(JSON.stringify({ state: cache.state, version: VERSION, facets: cache.facets, targets: cache.targets, matrix: cache.matrix || [], cacheAgeMs: Date.now() - cache.at })); return; } if (u.startsWith('/api/shutdown') && req.method === 'POST') {
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "nb-bridge",- "version": "0.7.2",+ "version": "0.8.0", "type": "app", "title": "NB - Native Browser Bridge", "description": "This package installs the SKILLS into your container so your AI knows how to drive your real signed-in Chrome and Edge; Bridge loads the bridge runtime itself from the release zip.",
page.json+64−6@@ -16,10 +16,10 @@ "chrome", "edge", "nbrowser",- "ai-agent",- "developer-tools",- "vibe-coding",- "native-messaging"+ "pup",+ "native-messaging",+ "credentials",+ "chip-fetcher" ], "status": "live", "discovery_triggers": [@@ -70,5 +70,63 @@ }, "visibility": "public", "brief": "Your real browser, driven by AI.",- "discovery_pitch": "nbe (the Adom Native Browser Extension, layer 2 of nb) drives the user's REAL signed-in Chrome/Edge via nbrowser_* verbs: use it for LOGGING IN, filling forms, and downloading behind auth with their OWN saved passwords - pup cannot (it is a fresh Chrome-for-Testing with no logins). Next moves: run 'adom-bridge bridge_list' to check it is installed; if not, OFFER to install it (see the installing-the-extension skill) - never make the user do it. If this container lacks the skills, 'adom-wiki pkg install adom/nb-bridge'. Check readiness with 'adom-bridge nbrowser_readiness' (state:needs-extension means offer the install)."-}+ "discovery_pitch": "nbe (the Adom Native Browser Extension, layer 2 of nb) drives the user's REAL signed-in Chrome/Edge via nbrowser_* verbs: use it for LOGGING IN, filling forms, and downloading behind auth with their OWN saved passwords - pup cannot (it is a fresh Chrome-for-Testing with no logins). Next moves: run 'adom-bridge bridge_list' to check it is installed; if not, OFFER to install it (see the installing-the-extension skill) - never make the user do it. If this container lacks the skills, 'adom-wiki pkg install adom/nb-bridge'. Check readiness with 'adom-bridge nbrowser_readiness' (state:needs-extension means offer the install).",+ "version": "0.8.0",+ "type": "app",+ "description": "This package installs the SKILLS into your container so your AI knows how to drive your real signed-in Chrome and Edge; Bridge loads the bridge runtime itself from the release zip.",+ "install": {+ "binary_name": "nb-bridge"+ },+ "files": [+ "screenshots/hero.png",+ "skills/adom-native-browser/SKILL.md",+ "skills/driving-sites/SKILL.md",+ "skills/driving-the-desktop/SKILL.md",+ "skills/driving-the-extension/SKILL.md",+ "skills/driving-the-extension-demo/SKILL.md",+ "skills/installing-the-extension/SKILL.md",+ "skills/native-browser-recording/SKILL.md",+ "skills/native-browser-safety/SKILL.md",+ "skills/reading-google-messages/SKILL.md",+ "skills/uninstalling-the-extension/SKILL.md",+ "bin/nb-dashboard",+ "dockbar.json",+ "install.sh",+ "uninstall.sh",+ "package.json"+ ],+ "scripts": {+ "install": "./install.sh",+ "uninstall": "./uninstall.sh"+ },+ "dependencies": {+ "adom/adom-bridge": "^2.0.3"+ },+ "sample_prompts": [+ {+ "label": "Open in my browser",+ "prompt": "open the wiki in my real chrome work profile"+ },+ {+ "label": "Log in for me",+ "prompt": "log into digikey for me using my saved password"+ },+ {+ "label": "Form behind a login",+ "prompt": "fill out this form on the site I'm already signed into"+ },+ {+ "label": "Download behind auth",+ "prompt": "download that datasheet, it's behind a login"+ },+ {+ "label": "Install the extension",+ "prompt": "install the adom native browser extension in my chrome"+ }+ ],+ "org": "adom",+ "author": {+ "name": "John Lauer",+ "email": "[email protected]"+ }+}