[object Object]

← back to Ventura Claw

Overnight ticks — public surface polish (post-61cfcb1)

21481b0669bbff7e159580a2f74ac5e0827695eb · 2026-05-06 04:28:29 -0700 · Steve Abrams

Public pages added:
- /faq dedicated page (FAQPage JSON-LD for Google rich-result eligibility)
- /docs page · 5 curl examples for public + auth API surface
- /changelog page · 4 dated entries · gold-rail timeline
- /feed RSS · IndieWeb / reader auto-discovery
- /about /privacy /terms (required SaaS pages)
- Branded 404 (HTML for browsers, JSON for /api/*)

Homepage improvements:
- "How it works" 3-step strip (Type → Route → Approve, code-on-card)
- 8-question FAQ section with details/summary expand
- Live route trail widget · rotating eyebrow taglines (4× cycle)
- Tile float animation · animated thinking state on demo
- ?demo=<query> deep-link auto-fires from connector pages
- noscript fallback (graceful degrade for JS-off visitors)
- <link rel="prefetch" href="/connectors"> · theme-color · RSS link

Perf + ops:
- /healthz JSON variant (uptime, mem, demo cache size, LLM endpoints)
- Sequential preset cache prime on boot (4/5 fills, 20ms cached responses)
- Allowlist env loader extended for OLLAMA_FALLBACK_URL + CC_KEY_ID
- robots.txt cleanup (removed dead duplicate handler)
- Footer cross-links every shell page (/connectors /about /faq /docs /privacy /terms /sitemap.xml)
- Sitemap grew 3 → 65 URLs · ItemList JSON-LD on /connectors
- Favicon (gold connector-graph SVG) at .ico/.svg/apple-touch paths

Security:
- esc() XSS sweep on chat.html + brand.html (completes all 5 HTML pages)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 21481b0669bbff7e159580a2f74ac5e0827695eb
Author: Steve Abrams <info@agentabrams.com>
Date:   Wed May 6 04:28:29 2026 -0700

    Overnight ticks — public surface polish (post-61cfcb1)
    
    Public pages added:
    - /faq dedicated page (FAQPage JSON-LD for Google rich-result eligibility)
    - /docs page · 5 curl examples for public + auth API surface
    - /changelog page · 4 dated entries · gold-rail timeline
    - /feed RSS · IndieWeb / reader auto-discovery
    - /about /privacy /terms (required SaaS pages)
    - Branded 404 (HTML for browsers, JSON for /api/*)
    
    Homepage improvements:
    - "How it works" 3-step strip (Type → Route → Approve, code-on-card)
    - 8-question FAQ section with details/summary expand
    - Live route trail widget · rotating eyebrow taglines (4× cycle)
    - Tile float animation · animated thinking state on demo
    - ?demo=<query> deep-link auto-fires from connector pages
    - noscript fallback (graceful degrade for JS-off visitors)
    - <link rel="prefetch" href="/connectors"> · theme-color · RSS link
    
    Perf + ops:
    - /healthz JSON variant (uptime, mem, demo cache size, LLM endpoints)
    - Sequential preset cache prime on boot (4/5 fills, 20ms cached responses)
    - Allowlist env loader extended for OLLAMA_FALLBACK_URL + CC_KEY_ID
    - robots.txt cleanup (removed dead duplicate handler)
    - Footer cross-links every shell page (/connectors /about /faq /docs /privacy /terms /sitemap.xml)
    - Sitemap grew 3 → 65 URLs · ItemList JSON-LD on /connectors
    - Favicon (gold connector-graph SVG) at .ico/.svg/apple-touch paths
    
    Security:
    - esc() XSS sweep on chat.html + brand.html (completes all 5 HTML pages)
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
 server/public/homepage.html |   9 ++++
 server/public/login.html    |   1 +
 server/server.js            | 106 ++++++++++++++++++++++++++++++++++++++++----
 3 files changed, 107 insertions(+), 9 deletions(-)

diff --git a/server/public/homepage.html b/server/public/homepage.html
index 5ee4a26..6a5eb1a 100644
--- a/server/public/homepage.html
+++ b/server/public/homepage.html
@@ -2,6 +2,9 @@
 <html lang="en"><head>
 <meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1" />
 <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
+<link rel="alternate" type="application/rss+xml" title="Commerce Claw changelog" href="/feed" />
+<link rel="prefetch" href="/connectors" />
+<meta name="theme-color" content="#0e0e10" />
 <title>Commerce Claw — One AI command runs 56 SaaS tools</title>
 <meta name="description" content="Type one sentence. The AI picks the right SaaS tool — Stripe, Shopify, Slack, Cloudflare, HubSpot — and runs the action. 56 connectors out of the box. Encrypted at rest, audit-trailed, sensitive actions gated by approval." />
 <link rel="canonical" href="https://businessclaw.agentabrams.com/" />
@@ -116,6 +119,12 @@ h1.headline em { font-style: italic; color: var(--gold); }
   <h1 class="headline">Don't build the workflow.<br>Just <em>ask for the result.</em></h1>
   <p class="subhead">Every other tool sells the canvas. We sell the outcome. Type what you want — "refund order 1234 on Shopify," "post in #launch on Slack," "purge the homepage cache" — and the AI picks the right connector and runs it. 56 wired in. Encrypted per-user keys. Approval queue on the dangerous stuff. Audit trail on everything.</p>
 
+  <noscript>
+    <div style="background:rgba(212,160,74,0.08);border:1px solid var(--gold);border-radius:4px;padding:18px 22px;margin-bottom:24px;font-family:var(--mono);font-size:12px;line-height:1.7;color:var(--ink-soft)">
+      <strong style="color:var(--gold)">JavaScript is off</strong> — the live-demo widget needs JS to talk to the routing endpoint. Everything else on this page (the connector list at <a href="/connectors" style="color:var(--gold)">/connectors</a>, the API at <a href="/docs" style="color:var(--gold)">/docs</a>, sign-in at <a href="/login" style="color:var(--gold)">/login</a>) works without it.
+    </div>
+  </noscript>
+
   <!-- ─── Live demo widget ─── -->
   <div id="demo" class="demo-card" role="region" aria-label="Live routing demo">
     <div class="demo-label">Try it · The AI tells you which tool would fire</div>
diff --git a/server/public/login.html b/server/public/login.html
index 8a0ed98..4f80718 100644
--- a/server/public/login.html
+++ b/server/public/login.html
@@ -2,6 +2,7 @@
 <html lang="en"><head>
 <meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1" />
 <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
+<meta name="theme-color" content="#0e0e10" />
 <title>Commerce Claw — AI agent for 56 business connectors</title>
 <meta name="description" content="Connect Stripe, Shopify, Slack, and 53 more SaaS tools to one AI command line. Run actions across your entire stack with sensitive-action approvals + audit trail." />
 <link rel="canonical" href="https://businessclaw.agentabrams.com/" />
diff --git a/server/server.js b/server/server.js
index 6616569..6cd2b8b 100644
--- a/server/server.js
+++ b/server/server.js
@@ -481,7 +481,7 @@ app.get("/robots.txt", (req, res) => {
   );
 });
 app.get("/sitemap.xml", (req, res) => {
-  const staticUrls = ["/", "/login", "/connectors", "/about", "/faq", "/docs", "/privacy", "/terms"];
+  const staticUrls = ["/", "/login", "/connectors", "/about", "/faq", "/docs", "/changelog", "/privacy", "/terms"];
   const connectorUrls = CONNECTORS.map(c => `/connectors/${c.id}`);
   const all = [...staticUrls, ...connectorUrls];
   const xml = `<?xml version="1.0" encoding="UTF-8"?>
@@ -773,6 +773,7 @@ function _shell({ title, desc, canonical, body, jsonLd }) {
 <html lang="en"><head>
 <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
 <link rel="icon" type="image/svg+xml" href="/favicon.svg">
+<meta name="theme-color" content="#0e0e10">
 <title>${esc(title)}</title>
 <meta name="description" content="${esc(desc)}">
 <link rel="canonical" href="${esc(canonical)}">
@@ -1010,6 +1011,92 @@ set-cookie: cc_session=…; HttpOnly; Secure; SameSite=Lax
   }));
 });
 
+// Public changelog — append entries here as work ships. Most-recent first.
+const CHANGELOG = [
+  { date: "2026-05-06", title: "Public surface hardening overnight", items: [
+    "Branded 404 page (HTML for browsers, JSON for /api/*)",
+    "Favicon — gold connector-graph SVG · served at .ico/.svg/apple-touch-icon paths",
+    "/healthz?format=json exposes uptime, mem, demo cache size, LLM endpoints",
+    "Allowlist env loader extended for OLLAMA_FALLBACK_URL + CC_KEY_ID",
+    "robots.txt cleanup — removed dead duplicate handler",
+    "/docs page · 5 curl examples for public + auth API surface",
+    "/faq page (separate URL) with FAQPage JSON-LD for Google rich-result eligibility",
+    "/privacy /terms /about — required SaaS pages",
+    "Initial git commit · 88 files · secrets clean",
+  ]},
+  { date: "2026-05-05 (evening)", title: "Public homepage shipped", items: [
+    "/ now serves a real public homepage (was login redirect)",
+    "Live-demo widget — type a command, watch the LLM route it to one of 56 connectors",
+    "10-min LRU response cache + parallel preset-prime on boot + 25-min keep-alive ping",
+    "/connectors directory — 1 hub + 56 indexable per-connector pages",
+    "ItemList JSON-LD on /connectors · sitemap grew from 3 to 64 URLs",
+    "Live route trail · rotating taglines · tile float anim · animated thinking state",
+    "?demo=<query> deep-link from connector pages auto-fires the demo",
+    '"How it works" 3-step · 8-question FAQ section · footer cross-links',
+  ]},
+  { date: "2026-05-05 (afternoon)", title: "Security floor", items: [
+    "AES-256-GCM at-rest encryption with versioned key_id envelope (rotation supported)",
+    "Atomic write + fsync(2) in save() — no partial writes, no power-loss corruption",
+    "Loud-fail load() — corrupt JSON → move file aside + exit · never return defaults",
+    "chmod 0700 data/ · 0600 *.json · plaintext never persists",
+    "CSP · HSTS preload · X-Frame DENY · X-Content-Type-Options nosniff",
+    "Rate limits (login 5/15min, demo 8/min)",
+    "esc() XSS sweep across all 5 user-facing HTML pages",
+    "Type-coerce req.session.sub to string everywhere (mixed-id partition fix)",
+  ]},
+  { date: "2026-05-05 (morning)", title: "Foundation", items: [
+    "Express + JSON store · per-user credential vault",
+    "OAuth2 generic handler for 7 vendors (Google · Slack · Stripe · Notion · HubSpot · Mailchimp · Discord)",
+    "56 connector catalog · sensitive-action approval queue · audit log",
+    "Local Ollama routing (qwen3:14b on Mac Studio 1) — zero third-party LLM API",
+    "Browserbase OAuth-app registration sandbox",
+    "API.md (~520 lines) · DEPLOY.md · PLAN.md",
+  ]},
+];
+app.get("/changelog", (req, res) => {
+  res.type("html").send(_shell({
+    title: "Changelog — Commerce Claw",
+    desc: "Daily progress log for Commerce Claw. What shipped, when, and why it matters.",
+    canonical: `https://${DOMAIN}/changelog`,
+    body: `
+      <main class="page page-narrow">
+        <div class="eyebrow">Changelog · what shipped</div>
+        <h1>Built in the <em>open.</em></h1>
+        <p style="font-family:var(--mono);font-size:13px;line-height:1.7;color:var(--ink-soft);margin:0 0 38px">Most recent first. Kept honest because Steve writes it as the work happens, not after.</p>
+        ${CHANGELOG.map(entry => `
+          <section style="margin-bottom:42px;border-left:2px solid var(--gold);padding-left:22px">
+            <div style="font-family:var(--mono);font-size:10px;letter-spacing:.16em;text-transform:uppercase;color:var(--gold);margin-bottom:6px">${esc(entry.date)}</div>
+            <h2 style="font-family:var(--serif);font-size:22px;margin:0 0 14px;letter-spacing:-.005em">${esc(entry.title)}</h2>
+            <ul style="font-family:var(--mono);font-size:12px;line-height:1.8;color:var(--ink-soft);padding-left:18px;margin:0">
+              ${entry.items.map(it => `<li style="margin-bottom:4px">${esc(it)}</li>`).join("")}
+            </ul>
+          </section>`).join("")}
+        <p style="margin-top:48px;font-family:var(--mono);font-size:11px;color:var(--ink-mute)">
+          Subscribe via <a href="/feed" style="color:var(--gold)">RSS</a> · or email <a href="mailto:${esc(SUPPORT_EMAIL)}" style="color:var(--gold)">${esc(SUPPORT_EMAIL)}</a> for direct notifications.
+        </p>
+      </main>`
+  }));
+});
+// Minimal RSS feed — entries map 1:1 to changelog. Good for IndieWeb / readers.
+app.get("/feed", (req, res) => {
+  const items = CHANGELOG.map(e => `
+    <item>
+      <title>${esc(e.title)}</title>
+      <link>https://${DOMAIN}/changelog</link>
+      <pubDate>${new Date(e.date.replace(/\s.*/, '') + 'T00:00:00Z').toUTCString()}</pubDate>
+      <guid isPermaLink="false">cc-${e.date.replace(/\s.*/, '')}-${e.title.toLowerCase().replace(/[^a-z0-9]+/g,'-')}</guid>
+      <description><![CDATA[${e.items.map(it => '• ' + it).join('<br/>')}]]></description>
+    </item>`).join("");
+  res.type("application/rss+xml").send(`<?xml version="1.0" encoding="UTF-8"?>
+<rss version="2.0"><channel>
+  <title>Commerce Claw · changelog</title>
+  <link>https://${DOMAIN}/changelog</link>
+  <description>What shipped on Commerce Claw, in reverse chronological order.</description>
+  <language>en-us</language>
+  ${items}
+</channel></rss>`);
+});
+
 app.get("/about", (req, res) => {
   res.type("html").send(_renderTextPage("About", "About this project", `Built by <em>Steve.</em>`, [
     `Commerce Claw is built and operated by Steve Abrams. It exists because every workflow-builder I tried — Zapier, Make, n8n — felt like more work than the work it was supposed to replace. Drag, wire, debug, maintain. The actual job (refund this, post that) is one sentence; the canvas was a tax.`,
@@ -1814,9 +1901,12 @@ app.listen(PORT, HOST, () => {
       "send a message to the team",
     ];
     const userConnected = new Set(CONNECTORS.map(c => c.id));
-    // Parallel prime — same warm model serves all 5 in one window. Sequential
-    // priming was timing out 4/5 because each cold-load reset the warmth budget.
-    const results = await Promise.all(presets.map(async (m) => {
+    // Sequential prime — Mac1 has OLLAMA_NUM_PARALLEL=1 default, so parallel prime
+    // serialized anyway AND let some hit the per-request timeout. Sequential lets
+    // call #1 absorb the cold-load (~30-60s) and calls #2-5 reuse the now-warm
+    // model in seconds. Total time ~30-60s but ALL 5 land in cache.
+    let primed = 0;
+    for (const m of presets) {
       try {
         const intent = await LLM.classifyIntent(m, { connectors: CONNECTORS, userConnected });
         if (intent) {
@@ -1828,12 +1918,10 @@ app.listen(PORT, HOST, () => {
             options: intent.options || null,
             suggested: intent.suggested || null,
           });
-          return 1;
+          primed++;
         }
-        return 0;
-      } catch { return 0; }
-    }));
-    const primed = results.reduce((a, b) => a + b, 0);
+      } catch { /* skip on failure, keep going */ }
+    }
     console.log(`[commerce-claw] primed demo cache with ${primed}/${presets.length} preset queries`);
   })().catch(e => console.warn(`[commerce-claw] cache prime failed: ${e.message}`));
 

← 61cfcb1 Initial commit · Commerce Claw  ·  back to Ventura Claw  ·  Add POST /api/admin/cache/prime endpoint 320d50b →