← Commit history
Publish 0.7.0
6 files changed
+379−7
bin/nb-dashboardadded+265@@ -0,0 +1,265 @@+#!/usr/bin/env node+// nb-dashboard — the Dock Bar dashboard for nb (Adom Native Browser). adom/nb-bridge#30.+//+// THE POINT (John, verbatim intent): "indicate cards for layer 1 and layer 2 of nb so as to+// encourage and advertise to the user to install the adom nbe (native browser extension) cuz if+// they do their experience using adom will be greatly improved." The Layer 2 card IS the product+// here; the rest of the dashboard exists to give it a stage.+//+// Dock publisher contract (adom/hydrogen docs/dockbar.md), the three rules that break every+// first attempt, all honored here:+// 1. serve SELF-BACKGROUNDS: prints the URL, forks a detached child, exits 0.+// 2. PATH is the manifest's job (dockbar.json steps set it); this script assumes nothing.+// 3. serve is IDEMPOTENT and VERSION-AWARE: an instance already up with the same version is+// reused (print URL, exit 0); a version-mismatched instance is told to shut down and replaced.+// Plus the two riding contracts: prints the DIRECT http://127.0.0.1:<port>/ URL (never the proxy+// form, never the name localhost), and takes ownership of $ADOM_DOCK_WEBVIEW once serving.+'use strict';+const http = require('http');+const { execFile, spawn } = require('child_process');++const VERSION = '0.7.0'; // keep in lockstep with package.json (pkg version)+const PORT = 8954;+const URL_SELF = `http://127.0.0.1:${PORT}/`;++// ---------- probes (run in the background; /api/status answers from cache in ms) ----------+const cache = {+ at: 0,+ facets: [], // [{label, state, detail, _hint}]+ state: 'unknown',+ targets: [], // [{name, readiness}]+ version: VERSION,+};++function cli(args, timeoutMs = 8000) {+ return new Promise((resolve) => {+ execFile('adom-bridge', args, { timeout: timeoutMs, maxBuffer: 8 * 1024 * 1024 }, (err, stdout) => {+ const out = String(stdout || '');+ try {+ let d = JSON.parse(out);+ if (typeof d.output === 'string' && d.output.trim().startsWith('{')) { try { d = { ...d, ...JSON.parse(d.output) }; } catch {} }+ resolve(d);+ } catch { resolve(err ? { _err: String((err && err.message) || 'cli failed').slice(0, 200) } : { _raw: out.slice(0, 200) }); }+ });+ });+}++async function refresh() {+ const facets = [];+ const targetRows = [];+ // Facet 1: Bridge (ab) — is the relay reachable and is any desktop connected?+ const tg = await cli(['targets']);+ const targets = (tg && tg.targets) || [];+ if (tg && tg._err && !targets.length) {+ facets.push({ label: 'Adom Bridge', state: 'fail', detail: 'adom-bridge CLI failed or is not installed', _hint: 'Install/repair the ab CLI: adom-wiki pkg install adom/adom-bridge, then adom-bridge targets.' });+ } else if (!targets.length) {+ facets.push({ label: 'Adom Bridge', state: 'fail', detail: 'no desktop connected to the relay', _hint: 'Start Adom Bridge on the user\'s desktop (it connects to the relay automatically).' });+ } else {+ 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;+ 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;+ }+ 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.' });+ // 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.state = l1ok ? (l2ready ? 'ok' : 'warn') : (targets.length ? 'warn' : 'fail');+ cache.at = Date.now();+}++// ---------- the page ----------+function esc(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])); }+function page() {+ return `<!doctype html><html lang="en"><head><meta charset="utf-8">+<meta name="viewport" content="width=device-width, initial-scale=1">+<title>NB Dashboard</title>+<style>+ :root { --bg:#0b1117; --card:#121a23; --edge:#1e2a36; --ink:#e8eef4; --dim:#8aa0b4; --teal:#00cec2; --amber:#f3b23e; --red:#ef5b5b; }+ * { box-sizing:border-box; margin:0; }+ body { background:var(--bg); color:var(--ink); font:15px/1.5 system-ui,-apple-system,'Segoe UI',sans-serif; min-height:100vh; }+ header { display:flex; align-items:center; gap:12px; padding:0 20px; height:44px; border-bottom:1px solid var(--edge); background:#0d141c; }+ .mark { font-weight:800; font-size:19px; letter-spacing:-.5px; background:linear-gradient(100deg,#00e6dc,#39b8ff 60%,#8c6bf7); -webkit-background-clip:text; background-clip:text; color:transparent; }+ header h1 { font-size:15px; font-weight:600; }+ header .ver { color:var(--dim); font-size:12px; margin-left:auto; }+ .led { width:10px; height:10px; border-radius:50%; display:inline-block; flex:none; }+ .led.ok { background:var(--teal); box-shadow:0 0 8px rgba(0,206,194,.7); }+ .led.warn { background:var(--amber); box-shadow:0 0 8px rgba(243,178,62,.6); }+ .led.fail { background:var(--red); box-shadow:0 0 8px rgba(239,91,91,.6); }+ .led.unknown { background:#3a4a5a; }+ main { max-width:980px; margin:0 auto; padding:24px 20px 40px; }+ .cards { display:grid; grid-template-columns:1fr 1fr; gap:16px; }+ @media (max-width:760px) { .cards { grid-template-columns:1fr; } }+ .card { background:var(--card); border:1px solid var(--edge); border-radius:12px; padding:18px 18px 16px; }+ .card h2 { display:flex; align-items:center; gap:10px; font-size:16px; margin-bottom:2px; }+ .layer { color:var(--dim); font-size:11px; text-transform:uppercase; letter-spacing:.12em; margin-bottom:10px; }+ .card p { color:var(--dim); font-size:13.5px; margin:6px 0; }+ .card p strong { color:var(--ink); font-weight:600; }+ .gain { display:flex; gap:8px; align-items:baseline; font-size:13.5px; color:var(--dim); margin:5px 0; }+ .gain::before { content:'+'; color:var(--teal); font-weight:700; }+ .upsell { border-color:rgba(0,206,194,.45); box-shadow:0 0 24px rgba(0,206,194,.06) inset; }+ .prompt { display:flex; gap:8px; margin-top:12px; }+ .prompt code { flex:1; background:#0a0f14; border:1px solid var(--edge); border-radius:8px; padding:9px 12px; font:12.5px/1.4 ui-monospace,Consolas,monospace; color:var(--teal); overflow-x:auto; white-space:nowrap; }+ .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; }+ 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); }+ .facets { margin-top:20px; display:flex; flex-direction:column; gap:8px; }+ .facet { display:flex; align-items:center; gap:10px; background:var(--card); border:1px solid var(--edge); border-radius:10px; padding:10px 14px; font-size:13.5px; }+ .facet .d { color:var(--dim); margin-left:auto; text-align:right; font-size:12.5px; }+ footer { text-align:center; color:var(--dim); font-size:12px; padding:18px; }+ footer a { color:var(--teal); text-decoration:none; }+ .age { font-size:11px; color:var(--dim); }+</style></head><body>+<header><span class="mark">nb</span><h1>Native Browser</h1><span class="led unknown" id="topled"></span><span class="ver">v${VERSION} · <span class="age" id="age"></span></span></header>+<main>+ <div class="cards">+ <div class="card" id="card-l1">+ <div class="layer">Layer 1 · works out of the box</div>+ <h2><span class="led unknown" id="led-l1"></span>Native Browser</h2>+ <p>Your AI opens pages in <strong>your real, signed-in browser</strong> - nothing to install. Real Chrome and Edge windows, your profiles, your logins.</p>+ <div class="gain">open a URL in the right profile (work vs personal)</div>+ <div class="gain">background by default: your focus is never stolen</div>+ <div class="gain">perfect for OAuth consent: you just click Approve</div>+ <p class="hintline" id="l1-detail"></p>+ </div>+ <div class="card upsell" id="card-l2">+ <div class="layer">Layer 2 · the good stuff</div>+ <h2><span class="led unknown" id="led-l2"></span>nbe — the Native Browser Extension</h2>+ <div id="l2-body"></div>+ </div>+ </div>+ <div class="facets" id="facets"></div>+ <div id="profiles"></div>+</main>+<footer><a href="https://wiki.adom.inc/adom/nb-bridge" target="_blank" rel="noopener">adom/nb-bridge on the wiki</a></footer>+<script>+const UPSELL = '<p><strong>Not installed yet</strong> - and it is the single best upgrade to your Adom experience. With the nbe your AI can:</p>'+ + '<div class="gain">log into sites with your own saved passwords (pup never can)</div>'+ + '<div class="gain">click and type with trusted, human-grade input - on a background window</div>'+ + '<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>'+ + '<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>';+function copyPrompt(){ try { navigator.clipboard.writeText(document.getElementById('pr').textContent); } catch(e){} }+function led(el, s){ el.className = 'led ' + (s||'unknown'); }+async function tick(){+ try {+ const r = await fetch('/api/status'); const d = await r.json();+ led(document.getElementById('topled'), d.state);+ document.getElementById('age').textContent = d.cacheAgeMs < 60000 ? 'live' : 'stale';+ const f = {}; for (const x of d.facets) f[x.label] = x;+ const l1 = f['Layer 1 - Native Browser'] || {}; const l2 = f['Layer 2 - nbe (extension)'] || {};+ led(document.getElementById('led-l1'), l1.state); led(document.getElementById('led-l2'), l2.state);+ document.getElementById('l1-detail').textContent = l1.detail || '';+ const body = document.getElementById('l2-body');+ if (l2.state === 'ok') {+ 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; }+ 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'); }+}+tick(); setInterval(tick, 5000);+</script>+</body></html>`;+}++// ---------- server ----------+function startServer() {+ const srv = http.createServer((req, res) => {+ 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 }));+ return;+ }+ if (u.startsWith('/api/shutdown') && req.method === 'POST') {+ res.writeHead(200, { 'Content-Type': 'application/json' });+ res.end(JSON.stringify({ ok: true, bye: VERSION }));+ setTimeout(() => process.exit(0), 150);+ return;+ }+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' });+ res.end(page());+ });+ srv.listen(PORT, '127.0.0.1', () => {+ // WEBVIEW OWNERSHIP: the dock told us which tab it pre-opened; take it over. Skip when unset.+ const tab = process.env.ADOM_DOCK_WEBVIEW;+ if (tab) {+ execFile('adom-cli', ['hydrogen', 'webview', 'open-or-refresh', '--name', tab, '--url', URL_SELF], { timeout: 15000 }, () => {});+ }+ refresh().catch(() => {});+ setInterval(() => refresh().catch(() => {}), 12000);+ });+ srv.on('error', (e) => { process.stderr.write(`nb-dashboard: ${e.message}\n`); process.exit(1); });+}++function probeExisting() {+ return new Promise((resolve) => {+ const req = http.get({ host: '127.0.0.1', port: PORT, path: '/api/status', timeout: 1500 }, (res) => {+ let b = ''; res.on('data', (c) => (b += c)); res.on('end', () => { try { resolve(JSON.parse(b)); } catch { resolve(null); } });+ });+ req.on('error', () => resolve(null));+ req.on('timeout', () => { req.destroy(); resolve(null); });+ });+}++async function serve(printUrl) {+ const existing = await probeExisting();+ if (existing && existing.version === VERSION) {+ if (printUrl) process.stdout.write(URL_SELF + '\n');+ process.exit(0);+ }+ if (existing) {+ // Version-blind reuse once pinned a bridge's users to a stale server - restart instead.+ await new Promise((resolve) => {+ const req = http.request({ host: '127.0.0.1', port: PORT, path: '/api/shutdown', method: 'POST', timeout: 2000 }, () => resolve());+ req.on('error', () => resolve()); req.on('timeout', () => { req.destroy(); resolve(); }); req.end();+ });+ await new Promise((r) => setTimeout(r, 500));+ }+ // Self-background: fork a detached child that runs the real server, print the URL, exit 0.+ const child = spawn(process.execPath, [__filename, '__run'], { detached: true, stdio: 'ignore', env: process.env });+ child.unref();+ // Wait for it to answer so the dock's readiness probe cannot race a slow start.+ for (let i = 0; i < 40; i++) {+ await new Promise((r) => setTimeout(r, 250));+ const up = await probeExisting();+ if (up && up.version === VERSION) { if (printUrl) process.stdout.write(URL_SELF + '\n'); process.exit(0); }+ }+ process.stderr.write('nb-dashboard: server did not come up\n');+ process.exit(1);+}++const cmd = process.argv[2] || 'serve';+if (cmd === '__run') startServer();+else if (cmd === 'serve') serve(process.argv.includes('--print-url'));+else if (cmd === 'status') probeExisting().then((d) => { console.log(JSON.stringify(d || { down: true }, null, 2)); process.exit(d ? 0 : 1); });+else if (cmd === 'stop') {+ const req = http.request({ host: '127.0.0.1', port: PORT, path: '/api/shutdown', method: 'POST' }, () => { console.log('stopped'); process.exit(0); });+ req.on('error', () => { console.log('not running'); process.exit(0); }); req.end();+} else if (cmd === '--version' || cmd === 'version') { console.log(VERSION); process.exit(0); }+else { console.log('usage: nb-dashboard serve [--print-url] | status | stop | version'); process.exit(1); }
dockbar.jsonadded+38@@ -0,0 +1,38 @@+{+ "dock_manifest": "1.2.0",+ "webview": {+ "title": "NB Dashboard",+ "favicon": "extension/icons/icon128.png"+ },+ "health": {+ "url": "/api/status",+ "poll_seconds": 15,+ "timeout_ms": 2000+ },+ "launch": {+ "mode": "container_command",+ "console": "show",+ "steps": [+ {+ "label": "Installing nb (Native Browser)",+ "run": "sh -c 'PATH=\"$HOME/.local/bin:$PATH\"; adom-wiki pkg install adom/nb-bridge; command -v nb-dashboard >/dev/null 2>&1 || { adom-wiki pkg uninstall adom/nb-bridge >/dev/null 2>&1; adom-wiki pkg install adom/nb-bridge; }'",+ "skip_if": "sh -c 'PATH=\"$HOME/.local/bin:$PATH\"; command -v nb-dashboard'",+ "timeout_seconds": 180+ },+ {+ "label": "Starting the NB Dashboard",+ "run": "sh -c 'PATH=\"$HOME/.local/bin:$PATH\"; exec nb-dashboard serve --print-url'",+ "await": "url",+ "timeout_seconds": 60+ }+ ],+ "on_ready": "navigate_placeholder",+ "ai": {+ "prompt": "The user just opened the NB Dashboard from the dock. nb (Adom Native Browser) drives their REAL signed-in Chrome/Edge in two layers: Layer 1 works out of the box (open URLs in their real profiles, background by default), Layer 2 is the nbe (Native Browser Extension) which unlocks the good stuff - logins with their own saved passwords, trusted clicks and typing on background windows, form filling, downloads behind auth, background screenshots. Check the dashboard's /api/status: if the Layer 2 LED is not green, the single most valuable thing you can do is OFFER to install the nbe FOR them (the installing-the-extension skill runs it end to end in about a minute; they approve one folder picker). Never make them install it themselves, never foreground their windows, and never drive windows they opened.",+ "thread": "existing",+ "thread_fallback": "new",+ "submit": "staged",+ "typed": false+ }+ }+}
install.sh+6@@ -17,4 +17,10 @@ for HOME_DIR in "${HOME}/.claude/skills" "${HOME}/.codex/skills"; do done echo "installed ${SKILLS// /, } -> $HOME_DIR" done+# The Dock Bar dashboard CLI (adom/nb-bridge#30): the dock's container_command steps expect+# `nb-dashboard` on ~/.local/bin.+mkdir -p "${HOME}/.local/bin"+chmod +x "$(pwd)/bin/nb-dashboard"+ln -sfn "$(pwd)/bin/nb-dashboard" "${HOME}/.local/bin/nb-dashboard"+echo "linked nb-dashboard -> ${HOME}/.local/bin/nb-dashboard" echo "Skills installed to Claude + Codex homes. Nothing else lands here: the bridge runtime auto-installs on your desktop via Bridge, and your AI can install the browser extension into Chrome/Edge for you - just ask it."
package.json+3−1@@ -1,6 +1,6 @@ { "slug": "nb-bridge",- "version": "0.6.3",+ "version": "0.7.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.",@@ -19,6 +19,8 @@ "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"
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.7.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]"+ }+}
uninstall.sh+3@@ -10,3 +10,6 @@ for HOME_DIR in "${HOME}/.claude/skills" "${HOME}/.codex/skills"; do rm -rf "$HOME_DIR/$name" && echo "removed $name from $HOME_DIR" done done+# remove the Dock Bar dashboard CLI + stop a running instance+"${HOME}/.local/bin/nb-dashboard" stop >/dev/null 2>&1 || true+rm -f "${HOME}/.local/bin/nb-dashboard" && echo "removed nb-dashboard"