← Commit history

Publish 2.0.302

John Lauer ·4aeab38e32 ·2mo ago ·parent 9d68074
3 files changed +13−5
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "pup-bridge",-  "version": "2.0.295",+  "version": "2.0.302",   "type": "app",   "description": "The pup SKILL PACK \u2014 the AI skills that teach a cloud/container AI how to drive the Adom Desktop Puppeteer (pup) bridge (browser_* verbs): the everyday `pup` skill plus focused sub-skills for windows/sessions/tabs, screenshots+recording, browsers+Chrome install, and driving the Adom wiki. Installs into ~/.claude/skills + ~/.codex/skills. DOCS ONLY \u2014 this is NOT the bridge runtime. The bridge itself (the Node program that drives the browser) runs on the DESKTOP: Adom Desktop bundles it and auto-installs/updates it, and it's published separately on the Releases tab as adom-bridge-puppeteer-v<ver>.zip. The pkg version is kept in lockstep with the Release version so the page's max version row always carries BOTH a pkg tarball (skills) and a runtime zip (bridge) \u2014 a skills-only or runtime-only bump on the top row breaks constraint installs. TL;DR: pkg install = the skills for your AI; the bridge = the Release zip, which Adom Desktop manages for you.",   "keywords": [
page.json+1−1
@@ -4,7 +4,7 @@   "slug": "pup-bridge",   "title": "Pup - Puppeteer Bridge",   "brief": "pup is the AI's own browser: a real, full Chrome on the user's desktop that the AI fully controls (a sandbox, not the user's signed-in browser). Rides Bridge; pup_* verbs open windows and tabs, navigate, screenshot, and eval JS.",-  "version": "2.0.295",+  "version": "2.0.302",   "tags": [     "skills",     "pup",
tools/pup-dashboard+11−3
@@ -277,17 +277,25 @@ async function serve() {   }    // child: bind, publish the real port, and live on.+  // Dock contract (ah build thread, 2026-08-23): the dock's health probe is a CROSS-ORIGIN fetch+  // from the Hydrogen shell page, so without CORS the browser discards a perfectly good 200 and the+  // card's LED reads red "not answering". Every /api/* and /health answer carries+  // Access-Control-Allow-Origin: * (same as kicad 1.0.45 / fusion-dashboard), and OPTIONS preflight+  // is answered for the POST endpoints.+  const CORS = { 'access-control-allow-origin': '*', 'access-control-allow-methods': 'GET, POST, OPTIONS', 'access-control-allow-headers': 'content-type' };   const server = http.createServer(async (req, res) => {     const u = (req.url || '/').split('?')[0].replace(/\/+$/, '') || '/';-    if (u.endsWith('/api/status')) {+    const isApi = u.includes('/api/') || u.endsWith('/health');+    if (isApi && req.method === 'OPTIONS') { res.writeHead(204, CORS); return res.end(); }+    if (u.endsWith('/api/status') || u.endsWith('/health')) {       let body; try { body = await buildStatus(); }       catch (e) { body = { state: 'fail', label: 'pup', detail: 'status build failed: ' + e.message, leds: [] }; }-      res.writeHead(200, { 'content-type': 'application/json', 'cache-control': 'no-store' });+      res.writeHead(200, { 'content-type': 'application/json', 'cache-control': 'no-store', ...CORS });       return res.end(JSON.stringify(body));     }     if (u.endsWith('/api/demo') && req.method === 'POST') {       let lines; try { lines = await runDemo(); } catch (e) { lines = ['demo failed: ' + e.message]; }-      res.writeHead(200, { 'content-type': 'application/json' });+      res.writeHead(200, { 'content-type': 'application/json', ...CORS });       return res.end(JSON.stringify({ lines }));     }     res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });