← back to Ventura Claw
Snapshot: ElevenLabs + Shopify connectors + coming-soon page
45dc5f63535d247b8ab49dd6eba95721cb9275f3 · 2026-05-06 12:53:12 -0700 · Steve Abrams
server/connectors/elevenlabs.js — full impl that was missed in the
earlier commit (file was untracked)
server/connectors/shopify.js — DW prod store connector (read shop info,
products, orders; write order.fulfill, order.refund, product.update)
server/connectors/index.js — register both with proper write/read action
sets; Shopify writes are sensitive (gated)
server/public/coming-soon.html — placeholder
server/server.js — adjacent updates
Files touched
A server/connectors/elevenlabs.jsM server/connectors/index.jsA server/connectors/shopify.jsA server/public/coming-soon.htmlM server/server.js
Diff
commit 45dc5f63535d247b8ab49dd6eba95721cb9275f3
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed May 6 12:53:12 2026 -0700
Snapshot: ElevenLabs + Shopify connectors + coming-soon page
server/connectors/elevenlabs.js — full impl that was missed in the
earlier commit (file was untracked)
server/connectors/shopify.js — DW prod store connector (read shop info,
products, orders; write order.fulfill, order.refund, product.update)
server/connectors/index.js — register both with proper write/read action
sets; Shopify writes are sensitive (gated)
server/public/coming-soon.html — placeholder
server/server.js — adjacent updates
---
server/connectors/elevenlabs.js | 87 +++++++++++
server/connectors/index.js | 5 +-
server/connectors/shopify.js | 78 ++++++++++
server/public/coming-soon.html | 325 ++++++++++++++++++++++++++++++++++++++++
server/server.js | 2 +
5 files changed, 496 insertions(+), 1 deletion(-)
diff --git a/server/connectors/elevenlabs.js b/server/connectors/elevenlabs.js
new file mode 100644
index 0000000..0fe949b
--- /dev/null
+++ b/server/connectors/elevenlabs.js
@@ -0,0 +1,87 @@
+// ElevenLabs — REAL. Text-to-speech generation + voice library + user account.
+// Free tier: 10K characters/month. Voice cloning available on paid tiers.
+function key(creds) {
+ const k = creds?.ELEVENLABS_API_KEY || process.env.ELEVENLABS_API_KEY;
+ if (!k) throw new Error("ELEVENLABS_API_KEY not set");
+ return k;
+}
+
+async function call(method, path, body, creds, accept = "application/json") {
+ const r = await fetch("https://api.elevenlabs.io/v1" + path, {
+ method,
+ headers: {
+ "xi-api-key": key(creds),
+ "Content-Type": "application/json",
+ "Accept": accept,
+ },
+ body: body ? JSON.stringify(body) : undefined,
+ });
+ if (accept === "audio/mpeg") {
+ if (!r.ok) {
+ const txt = await r.text();
+ throw new Error(`elevenlabs ${path} ${r.status}: ${txt.slice(0, 200)}`);
+ }
+ const buf = Buffer.from(await r.arrayBuffer());
+ return { audio_mp3_base64: buf.toString("base64"), bytes: buf.length };
+ }
+ const d = r.status === 204 ? {} : await r.json();
+ if (!r.ok) throw new Error(`elevenlabs ${path} ${r.status}: ${d.detail?.message || d.message || JSON.stringify(d).slice(0, 200)}`);
+ return d;
+}
+
+module.exports = {
+ meta: {
+ id: "elevenlabs",
+ name: "ElevenLabs",
+ category: "ai-voice",
+ docsUrl: "https://elevenlabs.io/docs/api-reference/introduction",
+ auth: "api_key",
+ realImpl: true,
+ },
+ fields: [
+ { key: "ELEVENLABS_API_KEY", label: "API Key", type: "password", required: true, hint: "elevenlabs.io → Profile → API Keys (free tier: 10K chars/month)" },
+ ],
+ configured(c) { return !!(c?.ELEVENLABS_API_KEY || process.env.ELEVENLABS_API_KEY); },
+
+ async health(c) {
+ if (!this.configured(c)) return { ok: false, reason: "no_token" };
+ try {
+ const u = await call("GET", "/user", null, c);
+ return {
+ ok: true,
+ subscription: u.subscription?.tier || "free",
+ chars_used: u.subscription?.character_count || 0,
+ chars_limit: u.subscription?.character_limit || 0,
+ };
+ } catch (e) { return { ok: false, reason: e.message }; }
+ },
+
+ // Read actions
+ async voicesList(c) {
+ const d = await call("GET", "/voices", null, c);
+ return { voices: (d.voices || []).map(v => ({
+ id: v.voice_id, name: v.name, category: v.category,
+ labels: v.labels || {}, description: v.description || "",
+ preview_url: v.preview_url || "",
+ })) };
+ },
+
+ async user(c) { return call("GET", "/user", null, c); },
+
+ async modelsList(c) { return call("GET", "/models", null, c); },
+
+ // Write actions (TTS generation counts against free-tier quota)
+ async textToSpeech(c, args) {
+ const { voice_id, text, model_id, stability, similarity_boost } = args || {};
+ if (!voice_id) throw new Error("voice_id required");
+ if (!text) throw new Error("text required");
+ return call("POST", `/text-to-speech/${voice_id}`, {
+ text: String(text).slice(0, 5000),
+ model_id: model_id || "eleven_turbo_v2_5",
+ voice_settings: {
+ stability: Number.isFinite(stability) ? stability : 0.5,
+ similarity_boost: Number.isFinite(similarity_boost) ? similarity_boost : 0.75,
+ },
+ }, c, "audio/mpeg");
+ },
+};
diff --git a/server/connectors/index.js b/server/connectors/index.js
index a013593..2a36560 100644
--- a/server/connectors/index.js
+++ b/server/connectors/index.js
@@ -14,8 +14,9 @@ const gmail = require("./gmail");
const purelymail = require("./purelymail");
const archive = require("./archive");
const elevenlabs = require("./elevenlabs");
+const shopify = require("./shopify");
-const REAL = { slack, stripe, cloudflare, mailchimp, hubspot, notion, airtable, twilio, figma, canva, etsy, gmail, purelymail, archive, elevenlabs };
+const REAL = { slack, stripe, cloudflare, mailchimp, hubspot, notion, airtable, twilio, figma, canva, etsy, gmail, purelymail, archive, elevenlabs, shopify };
// Phase 8 — central sensitivity registry. Source of truth for "this action mutates state."
// Anything in WRITE_ACTIONS[id] requires the approval gate. Anything NOT listed is treated as read-only.
@@ -36,6 +37,7 @@ const WRITE_ACTIONS = {
stripe: new Set(["charge.create", "customer.create", "payment_link.create", "refund.create", "subscription.cancel"]),
twilio: new Set(["sms.send"]),
elevenlabs: new Set(["textToSpeech"]),
+ shopify: new Set(["order.fulfill", "order.refund", "product.update"]),
};
// Read actions enumerated per connector — explicit allowlist for fail-safe behavior.
@@ -55,6 +57,7 @@ const READ_ACTIONS = {
stripe: new Set(["balance.get", "charges.list"]),
twilio: new Set(["messages.list", "phone_numbers.list"]),
elevenlabs: new Set(["voicesList", "user", "modelsList"]),
+ shopify: new Set(["shop.info", "products.list", "products.count", "orders.list", "orders.recent", "collections.list"]),
};
function isWrite(id, action) {
diff --git a/server/connectors/shopify.js b/server/connectors/shopify.js
new file mode 100644
index 0000000..49f9384
--- /dev/null
+++ b/server/connectors/shopify.js
@@ -0,0 +1,78 @@
+// Shopify (DW prod store) — REAL.
+// Reads SHOPIFY_ADMIN_TOKEN + SHOPIFY_STORE from env. Routed via secrets-manager.
+// Docs: https://shopify.dev/docs/api/admin-rest
+
+function token(c) {
+ const t = c?.SHOPIFY_ADMIN_TOKEN || process.env.SHOPIFY_ADMIN_TOKEN || process.env.SHOPIFY_ACCESS_TOKEN;
+ if (!t) throw new Error("SHOPIFY_ADMIN_TOKEN not set");
+ return t;
+}
+function store(c) {
+ return c?.SHOPIFY_STORE || process.env.SHOPIFY_STORE || "designer-laboratory-sandbox.myshopify.com";
+}
+async function call(method, path, body, c) {
+ const url = `https://${store(c)}/admin/api/2024-10${path}`;
+ const r = await fetch(url, {
+ method,
+ headers: { "X-Shopify-Access-Token": token(c), "Content-Type": "application/json" },
+ body: body ? JSON.stringify(body) : undefined,
+ signal: AbortSignal.timeout(20_000)
+ });
+ const text = await r.text();
+ if (!r.ok) throw new Error(`shopify ${method} ${path} ${r.status}: ${text.slice(0, 200)}`);
+ return text ? JSON.parse(text) : {};
+}
+
+module.exports = {
+ meta: { id: "shopify", name: "Shopify", category: "commerce", docsUrl: "https://shopify.dev/docs/api/admin-rest", auth: "api_key", realImpl: true },
+ fields: [
+ { key: "SHOPIFY_ADMIN_TOKEN", label: "Admin Access Token (shpat_...)", type: "password", required: true, hint: "Settings → Apps → Develop apps → reveal Admin API access token" },
+ { key: "SHOPIFY_STORE", label: "Store domain", type: "text", required: false, hint: "e.g. mystore.myshopify.com (optional, defaults to env)" },
+ ],
+ configured(c) { return !!(c?.SHOPIFY_ADMIN_TOKEN || process.env.SHOPIFY_ADMIN_TOKEN || process.env.SHOPIFY_ACCESS_TOKEN); },
+ async health(c) {
+ if (!this.configured(c)) return { ok: false, reason: "no_token" };
+ try {
+ const d = await call("GET", "/shop.json", null, c);
+ return { ok: true, store: d.shop?.name, domain: d.shop?.myshopify_domain, plan: d.shop?.plan_name };
+ } catch (e) { return { ok: false, reason: e.message }; }
+ },
+ actions: {
+ async "shop.info"(_, c) { return call("GET", "/shop.json", null, c); },
+ async "products.list"({ limit }, c) { return call("GET", `/products.json?limit=${Math.min(limit||10, 250)}`, null, c); },
+ async "products.count"(_, c) { return call("GET", "/products/count.json", null, c); },
+ async "orders.list"({ status, limit }, c) {
+ const s = status || "any";
+ return call("GET", `/orders.json?status=${s}&limit=${Math.min(limit||10, 250)}`, null, c);
+ },
+ async "orders.recent"(_, c) {
+ return call("GET", `/orders.json?status=any&limit=20&order=created_at desc`, null, c);
+ },
+ async "order.fulfill"({ order_id, tracking_number, tracking_company }, c) {
+ if (!order_id) throw new Error("order_id required");
+ return call("POST", `/orders/${order_id}/fulfillments.json`, {
+ fulfillment: { tracking_number, tracking_company, notify_customer: true }
+ }, c);
+ },
+ async "order.refund"({ order_id, amount, reason }, c) {
+ if (!order_id) throw new Error("order_id required");
+ return call("POST", `/orders/${order_id}/refunds.json`, {
+ refund: { note: reason || "automated refund", shipping: { full_refund: true }, transactions: amount ? [{ amount, kind: "refund" }] : undefined }
+ }, c);
+ },
+ async "product.update"({ product_id, title, tags, body_html, status }, c) {
+ if (!product_id) throw new Error("product_id required");
+ const fields = { id: product_id };
+ if (title !== undefined) fields.title = title;
+ if (tags !== undefined) fields.tags = tags;
+ if (body_html !== undefined) fields.body_html = body_html;
+ if (status !== undefined) fields.status = status;
+ return call("PUT", `/products/${product_id}.json`, { product: fields }, c);
+ },
+ async "collections.list"(_, c) {
+ const cust = await call("GET", "/custom_collections.json?limit=250", null, c);
+ const smart = await call("GET", "/smart_collections.json?limit=250", null, c);
+ return { custom: cust.custom_collections || [], smart: smart.smart_collections || [] };
+ },
+ }
+};
diff --git a/server/public/coming-soon.html b/server/public/coming-soon.html
new file mode 100644
index 0000000..4bb9473
--- /dev/null
+++ b/server/public/coming-soon.html
@@ -0,0 +1,325 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8" />
+<meta name="viewport" content="width=device-width,initial-scale=1" />
+<meta name="theme-color" content="#0e0e10" />
+<title>VenturaClaw — Coming soon · One AI command for 56 SaaS tools</title>
+<meta name="description" content="VenturaClaw is launching soon. Type one sentence — the AI picks the right SaaS tool from 56 wired-in connectors and runs the action. Drop your email for first-access." />
+<meta name="robots" content="index,follow" />
+<link rel="canonical" href="https://venturaclaw.com/" />
+<meta property="og:type" content="website" />
+<meta property="og:title" content="VenturaClaw — Coming soon" />
+<meta property="og:description" content="One AI command, 56 connectors, zero workflow building. Launching soon — get on the early-access list." />
+<meta property="og:url" content="https://venturaclaw.com/" />
+<meta property="og:site_name" content="VenturaClaw" />
+<meta name="twitter:card" content="summary_large_image" />
+<style>
+ :root {
+ --bg: #0e0e10;
+ --bg-2: #15151a;
+ --rule: #2a2a32;
+ --ink: #f4f1ea;
+ --ink-soft: #d8d2c5;
+ --ink-mute: #8b857a;
+ --gold: #d4a04a;
+ --gold-glow: #e6b96a;
+ --good: #6ec081;
+ --serif: "Cormorant Garamond", Georgia, serif;
+ --mono: "JetBrains Mono", "SFMono-Regular", Menlo, Consolas, monospace;
+ --sans: -apple-system, "SF Pro Text", "Inter", system-ui, sans-serif;
+ --max: 1100px;
+ }
+ *, *::before, *::after { box-sizing: border-box; }
+ html, body { margin: 0; background: var(--bg); color: var(--ink); font-family: var(--sans); font-weight: 300; }
+ a { color: var(--gold); }
+ ::selection { background: var(--gold); color: var(--bg); }
+
+ /* topbar */
+ .topbar {
+ max-width: var(--max);
+ margin: 0 auto;
+ padding: 22px 28px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
+ .brand { display: flex; gap: 12px; align-items: center; }
+ .logo-dot {
+ width: 14px; height: 14px; border-radius: 50%;
+ background: radial-gradient(circle at 30% 30%, var(--gold-glow), var(--gold) 60%, #7a5a26);
+ box-shadow: 0 0 18px rgba(212, 160, 74, 0.45);
+ }
+ .brand-name { font-family: var(--serif); font-size: 22px; font-weight: 500; letter-spacing: -0.005em; }
+ .brand-name em { font-style: italic; color: var(--gold); }
+ .brand-sub { font-family: var(--mono); font-size: 9px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink-mute); margin-top: 2px; }
+ .status-pill {
+ font-family: var(--mono); font-size: 10px; letter-spacing: 0.18em; text-transform: uppercase;
+ color: var(--gold); border: 1px solid var(--gold); padding: 8px 14px; border-radius: 999px;
+ display: inline-flex; align-items: center; gap: 8px;
+ }
+ .status-pill::before {
+ content: ""; display: inline-block; width: 6px; height: 6px; border-radius: 50%;
+ background: var(--gold-glow); box-shadow: 0 0 10px var(--gold-glow);
+ animation: pulse 1.8s infinite;
+ }
+ @keyframes pulse {
+ 0%, 100% { opacity: 1; transform: scale(1); }
+ 50% { opacity: 0.55; transform: scale(0.85); }
+ }
+
+ /* hero */
+ .hero { max-width: var(--max); margin: 56px auto 0; padding: 0 28px; }
+ .eyebrow {
+ font-family: var(--mono); font-size: 10px; letter-spacing: 0.22em; text-transform: uppercase;
+ color: var(--ink-mute); margin-bottom: 22px; display: flex; align-items: center; gap: 10px;
+ }
+ .eyebrow .dot {
+ display: inline-block; width: 6px; height: 6px; border-radius: 50%;
+ background: var(--good); box-shadow: 0 0 10px var(--good);
+ }
+ .headline {
+ font-family: var(--serif); font-weight: 400; line-height: 1.05;
+ font-size: clamp(46px, 7.5vw, 92px); margin: 0 0 24px; letter-spacing: -0.012em;
+ }
+ .headline em { font-style: italic; color: var(--gold); }
+ .subhead {
+ font-size: 19px; line-height: 1.6; color: var(--ink-soft);
+ max-width: 720px; margin: 0 0 40px;
+ }
+
+ /* soon card */
+ .soon-card {
+ background: linear-gradient(180deg, var(--bg-2), rgba(21, 21, 26, 0.4));
+ border: 1px solid var(--rule);
+ border-left: 3px solid var(--gold);
+ padding: 32px 36px;
+ max-width: 640px;
+ margin: 0 0 56px;
+ }
+ .soon-card h2 {
+ font-family: var(--serif); font-weight: 400; font-size: 28px; margin: 0 0 14px; letter-spacing: -0.005em;
+ }
+ .soon-card p { font-size: 15px; line-height: 1.65; color: var(--ink-soft); margin: 0 0 20px; }
+ .soon-card .ml { margin-bottom: 8px; }
+ .soon-card form { display: flex; gap: 8px; flex-wrap: wrap; }
+ .soon-card input[type=email] {
+ flex: 1; min-width: 240px;
+ padding: 14px 18px;
+ background: var(--bg);
+ border: 1px solid var(--rule);
+ color: var(--ink);
+ font-family: var(--sans); font-size: 14px;
+ outline: none;
+ transition: border-color 200ms ease;
+ }
+ .soon-card input[type=email]:focus { border-color: var(--gold); box-shadow: 0 0 0 3px rgba(212, 160, 74, 0.12); }
+ .soon-card button {
+ background: var(--gold); color: var(--bg);
+ border: 0; padding: 14px 22px;
+ font-family: var(--mono); font-size: 11px; letter-spacing: 0.22em; text-transform: uppercase; font-weight: 600;
+ cursor: pointer;
+ transition: background 200ms ease;
+ }
+ .soon-card button:hover { background: var(--gold-glow); }
+ .soon-card .ack {
+ display: none;
+ margin-top: 14px; padding: 12px 14px;
+ background: rgba(110, 192, 129, 0.08); border: 1px solid var(--good);
+ color: var(--good); font-family: var(--mono); font-size: 11px; letter-spacing: 0.05em;
+ }
+ .soon-card .ack.show { display: block; }
+ .soon-card .alt {
+ margin-top: 18px; font-family: var(--mono); font-size: 11px; color: var(--ink-mute); letter-spacing: 0.04em;
+ }
+
+ /* connectors strip — purely visual, no backend */
+ .connectors {
+ max-width: var(--max); margin: 0 auto 80px; padding: 0 28px;
+ }
+ .connectors-label {
+ font-family: var(--mono); font-size: 10px; letter-spacing: 0.18em; text-transform: uppercase;
+ color: var(--ink-mute); margin-bottom: 18px;
+ }
+ .connectors-label b { color: var(--gold); font-family: var(--serif); font-style: italic; font-weight: 400; font-size: 14px; letter-spacing: 0; text-transform: none; margin-right: 6px; }
+ .connector-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
+ gap: 1px;
+ background: var(--rule);
+ border: 1px solid var(--rule);
+ }
+ .connector {
+ background: var(--bg);
+ padding: 14px 16px;
+ font-family: var(--mono); font-size: 11px; letter-spacing: 0.05em;
+ color: var(--ink-soft);
+ transition: background 220ms ease, color 220ms ease;
+ }
+ .connector b { display: block; font-family: var(--serif); font-weight: 400; font-size: 14px; color: var(--ink); letter-spacing: -0.005em; margin-bottom: 2px; }
+ .connector .cat { font-size: 9px; letter-spacing: 0.18em; text-transform: uppercase; color: var(--ink-mute); }
+ .connector:hover { background: var(--bg-2); color: var(--gold); }
+
+ /* moats — keep three only */
+ .moats { max-width: var(--max); margin: 0 auto 80px; padding: 0 28px; }
+ .moats h2 { font-family: var(--serif); font-weight: 400; font-size: 38px; margin: 0 0 36px; letter-spacing: -0.005em; }
+ .moats h2 em { font-style: italic; color: var(--gold); }
+ .moat-grid {
+ display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 24px;
+ }
+ .moat { background: var(--bg-2); border: 1px solid var(--rule); padding: 28px 26px; }
+ .moat-num { font-family: var(--mono); font-size: 11px; color: var(--gold); letter-spacing: 0.18em; margin-bottom: 14px; }
+ .moat-title { font-family: var(--serif); font-weight: 500; font-size: 20px; line-height: 1.3; margin-bottom: 12px; letter-spacing: -0.005em; }
+ .moat-body { font-size: 14px; line-height: 1.6; color: var(--ink-soft); }
+
+ /* footer */
+ footer {
+ max-width: var(--max); margin: 0 auto;
+ padding: 40px 28px 60px;
+ border-top: 1px solid var(--rule);
+ font-family: var(--mono); font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase;
+ color: var(--ink-mute);
+ display: flex; justify-content: space-between; flex-wrap: wrap; gap: 14px;
+ }
+ footer a { color: var(--ink-mute); text-decoration: none; }
+ footer a:hover { color: var(--gold); }
+</style>
+</head>
+<body>
+
+<header class="topbar">
+ <div class="brand">
+ <div class="logo-dot"></div>
+ <div>
+ <div class="brand-name">Ventura<em>Claw</em></div>
+ <div class="brand-sub">Connected Operations</div>
+ </div>
+ </div>
+ <div class="status-pill">Coming soon</div>
+</header>
+
+<main>
+ <section class="hero">
+ <div class="eyebrow"><span class="dot"></span><span>Public launch in development · early-access list open</span></div>
+ <h1 class="headline">Don't build the workflow.<br>Just <em>ask for the result.</em></h1>
+ <p class="subhead">
+ Every other automation tool sells the canvas — the boxes, the arrows, the if/then. VenturaClaw sells the outcome. Type what you want — <em>"refund order 1234 on Shopify," "post in #launch on Slack," "purge the homepage cache"</em> — 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>
+
+ <div class="soon-card">
+ <h2>Drop your email — we'll ping you on launch.</h2>
+ <p class="ml">Limited early-access spots. No spam, no newsletter, one email when the door opens.</p>
+ <form id="ws" action="mailto:info@venturaclaw.com" method="GET" enctype="text/plain">
+ <input type="email" name="subject" placeholder="you@example.com" required>
+ <button type="submit">Get notified →</button>
+ </form>
+ <div class="ack" id="ack" role="status" aria-live="polite">Thanks — we'll be in touch.</div>
+ <p class="alt">Or email <a href="mailto:info@venturaclaw.com">info@venturaclaw.com</a> with "early access" in the subject.</p>
+ </div>
+ </section>
+
+ <section class="connectors">
+ <div class="connectors-label"><b>56</b> connectors pre-wired · ready on day one</div>
+ <div class="connector-grid">
+ <div class="connector"><b>Stripe</b><span class="cat">Payments</span></div>
+ <div class="connector"><b>Shopify</b><span class="cat">Commerce</span></div>
+ <div class="connector"><b>Slack</b><span class="cat">Messaging</span></div>
+ <div class="connector"><b>Cloudflare</b><span class="cat">Infra</span></div>
+ <div class="connector"><b>HubSpot</b><span class="cat">CRM</span></div>
+ <div class="connector"><b>Notion</b><span class="cat">Docs</span></div>
+ <div class="connector"><b>Discord</b><span class="cat">Community</span></div>
+ <div class="connector"><b>Google Workspace</b><span class="cat">Productivity</span></div>
+ <div class="connector"><b>Mailchimp</b><span class="cat">Email</span></div>
+ <div class="connector"><b>SendGrid</b><span class="cat">Email</span></div>
+ <div class="connector"><b>Twilio</b><span class="cat">SMS / voice</span></div>
+ <div class="connector"><b>Zendesk</b><span class="cat">Support</span></div>
+ <div class="connector"><b>Intercom</b><span class="cat">Support</span></div>
+ <div class="connector"><b>Linear</b><span class="cat">Project mgmt</span></div>
+ <div class="connector"><b>Jira</b><span class="cat">Project mgmt</span></div>
+ <div class="connector"><b>GitHub</b><span class="cat">Code</span></div>
+ <div class="connector"><b>GitLab</b><span class="cat">Code</span></div>
+ <div class="connector"><b>Vercel</b><span class="cat">Deploy</span></div>
+ <div class="connector"><b>Netlify</b><span class="cat">Deploy</span></div>
+ <div class="connector"><b>AWS</b><span class="cat">Infra</span></div>
+ <div class="connector"><b>Google Cloud</b><span class="cat">Infra</span></div>
+ <div class="connector"><b>Azure</b><span class="cat">Infra</span></div>
+ <div class="connector"><b>DigitalOcean</b><span class="cat">Infra</span></div>
+ <div class="connector"><b>Datadog</b><span class="cat">Observability</span></div>
+ <div class="connector"><b>Sentry</b><span class="cat">Errors</span></div>
+ <div class="connector"><b>PagerDuty</b><span class="cat">On-call</span></div>
+ <div class="connector"><b>Asana</b><span class="cat">Project mgmt</span></div>
+ <div class="connector"><b>Trello</b><span class="cat">Project mgmt</span></div>
+ <div class="connector"><b>Monday</b><span class="cat">Project mgmt</span></div>
+ <div class="connector"><b>Salesforce</b><span class="cat">CRM</span></div>
+ <div class="connector"><b>Pipedrive</b><span class="cat">CRM</span></div>
+ <div class="connector"><b>Zoho CRM</b><span class="cat">CRM</span></div>
+ <div class="connector"><b>QuickBooks</b><span class="cat">Accounting</span></div>
+ <div class="connector"><b>Xero</b><span class="cat">Accounting</span></div>
+ <div class="connector"><b>FreshBooks</b><span class="cat">Accounting</span></div>
+ <div class="connector"><b>Square</b><span class="cat">Payments</span></div>
+ <div class="connector"><b>PayPal</b><span class="cat">Payments</span></div>
+ <div class="connector"><b>Plaid</b><span class="cat">Banking</span></div>
+ <div class="connector"><b>Airtable</b><span class="cat">Database</span></div>
+ <div class="connector"><b>Coda</b><span class="cat">Docs</span></div>
+ <div class="connector"><b>Calendly</b><span class="cat">Scheduling</span></div>
+ <div class="connector"><b>Zoom</b><span class="cat">Meetings</span></div>
+ <div class="connector"><b>Google Meet</b><span class="cat">Meetings</span></div>
+ <div class="connector"><b>Microsoft Teams</b><span class="cat">Meetings</span></div>
+ <div class="connector"><b>Dropbox</b><span class="cat">Files</span></div>
+ <div class="connector"><b>Box</b><span class="cat">Files</span></div>
+ <div class="connector"><b>OneDrive</b><span class="cat">Files</span></div>
+ <div class="connector"><b>Figma</b><span class="cat">Design</span></div>
+ <div class="connector"><b>Webflow</b><span class="cat">Web</span></div>
+ <div class="connector"><b>WordPress</b><span class="cat">CMS</span></div>
+ <div class="connector"><b>Ghost</b><span class="cat">Publishing</span></div>
+ <div class="connector"><b>Substack</b><span class="cat">Newsletter</span></div>
+ <div class="connector"><b>Buffer</b><span class="cat">Social</span></div>
+ <div class="connector"><b>Hootsuite</b><span class="cat">Social</span></div>
+ <div class="connector"><b>ElevenLabs</b><span class="cat">Voice AI</span></div>
+ <div class="connector"><b>OpenAI</b><span class="cat">LLM</span></div>
+ <div class="connector"><b>Anthropic</b><span class="cat">LLM</span></div>
+ </div>
+ </section>
+
+ <section class="moats">
+ <h2>What makes it <em>different.</em></h2>
+ <div class="moat-grid">
+ <div class="moat">
+ <div class="moat-num">01</div>
+ <div class="moat-title">Encrypted at rest, by default.</div>
+ <div class="moat-body">Every API key and OAuth token is wrapped in AES-256-GCM with a versioned key envelope. Rotate the master key and old data still decrypts via dual-read until the next save sweep. Plaintext never touches disk.</div>
+ </div>
+ <div class="moat">
+ <div class="moat-num">02</div>
+ <div class="moat-title">Sensitive actions wait for approval.</div>
+ <div class="moat-body">Refunds over a threshold, mass deletes, DNS edits — these don't fire until a second human signs off in the approval queue. The AI proposes; a person disposes. Audit-logged either way.</div>
+ </div>
+ <div class="moat">
+ <div class="moat-num">03</div>
+ <div class="moat-title">Audit trail on every fired action.</div>
+ <div class="moat-body">Who asked, what model routed it, which connector ran, what arguments fired, what came back. Append-only. Exportable. The "tell me everything that happened on Stripe last week" dashboard is the same query the auditor runs.</div>
+ </div>
+ </div>
+ </section>
+</main>
+
+<footer>
+ <span>© 2026 VenturaClaw · Steve Abrams</span>
+ <span>
+ <a href="mailto:info@venturaclaw.com">info@venturaclaw.com</a>
+ </span>
+</footer>
+
+<script>
+ // Soft mailto fallback — if the user hits "Get notified" on a device with
+ // no mail client, the form action still does *something*. The ack box only
+ // shows if mailto returns control to the page (i.e. handler exists).
+ document.getElementById('ws').addEventListener('submit', function() {
+ setTimeout(function(){
+ var ack = document.getElementById('ack');
+ if (ack) ack.classList.add('show');
+ }, 200);
+ });
+</script>
+</body>
+</html>
diff --git a/server/server.js b/server/server.js
index 9f78c1f..29d3e43 100644
--- a/server/server.js
+++ b/server/server.js
@@ -23,6 +23,8 @@ const rateLimit = require("express-rate-limit");
"FIGMA_TOKEN","CANVA_TOKEN","ETSY_KEYSTRING","ETSY_ACCESS_TOKEN","ETSY_SHOP_ID",
"GEORGE_URL","GEORGE_USER","GEORGE_PASS","GEORGE_BASIC_AUTH",
"PURELYMAIL_API_TOKEN","BROWSERBASE_API_KEY","BROWSERBASE_PROJECT_ID",
+ "SHOPIFY_ADMIN_TOKEN","SHOPIFY_ACCESS_TOKEN","SHOPIFY_STORE",
+ "ANTHROPIC_API_KEY","ELEVENLABS_API_KEY",
// LLM
"OLLAMA_URL","OLLAMA_MODEL","OLLAMA_FALLBACK_URL","OLLAMA_FALLBACK_MODEL","CC_KEY_ID"
]);
← 5b582d2 chat: surface Admin link in topbar for admin users
·
back to Ventura Claw
·
competitor matrix: 8 platforms crawled, 10 differentiation v b15fb3e →