[object Object]

← back to Marketing Command Center

snapshot before restart: preserve in-flight work

19f79988016c4765e4c1357535a2a46e36de23b4 · 2026-09-04 22:38:51 -0700 · Steve

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gbmuMHgCLDEPM4TpfAr4G

Files touched

Diff

commit 19f79988016c4765e4c1357535a2a46e36de23b4
Author: Steve <steve@designerwallcoverings.com>
Date:   Fri Sep 4 22:38:51 2026 -0700

    snapshot before restart: preserve in-flight work
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_011gbmuMHgCLDEPM4TpfAr4G
---
 drafts/glitter-sequin-exclusive/connie-create.sh   | 27 +++++++++++++++
 drafts/glitter-sequin-exclusive/connie-create2.sh  | 31 +++++++++++++++++
 drafts/glitter-sequin-exclusive/kamatera-create.sh | 39 ++++++++++++++++++++++
 public/panels/assets.html                          |  7 ++++
 public/panels/assets.js                            | 38 +++++++++++++++++++++
 5 files changed, 142 insertions(+)

diff --git a/drafts/glitter-sequin-exclusive/connie-create.sh b/drafts/glitter-sequin-exclusive/connie-create.sh
new file mode 100644
index 0000000..1a1986d
--- /dev/null
+++ b/drafts/glitter-sequin-exclusive/connie-create.sh
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+# Glitter & Sequin — create the REAL draft by posting DIRECTLY to Connie
+# (cc-agent, /root/DW-Agents/cc-agent) at 127.0.0.1:9820, which holds the live
+# Constant Contact OAuth token. Reads the route schema, then attempts create.
+# READ + DRAFT only. Does NOT send/schedule.
+set +e
+PAYLOAD=/tmp/gs-draft.json
+PASS="${CC_PASS:-DWSecure2024!}"     # Connie auth (verified 200)
+CONNIE=http://127.0.0.1:9820
+SRC=/root/DW-Agents/cc-agent
+line(){ printf '\n== %s ==\n' "$1"; }
+
+line "connie /api/campaigns/draft handler (expected body fields)"
+grep -rnE "campaigns/draft" "$SRC" --include=*.js 2>/dev/null | grep -iE "post|router|app" | head
+# show ~35 lines after the route definition so we see req.body destructuring
+F=$(grep -rlE "campaigns/draft" "$SRC" --include=*.js 2>/dev/null | head -1)
+if [ -n "$F" ]; then
+  echo "-- source: $F --"
+  awk '/campaigns\/draft/{p=1} p{print; n++} n>38{exit}' "$F"
+fi
+
+line "attempt create: POST $CONNIE/api/campaigns/draft (our {name,subject,from,html})"
+curl -s -w '\nHTTP:%{http_code}\n' --max-time 25 -u admin:"$PASS" \
+  -H 'Content-Type: application/json' -X POST \
+  "$CONNIE/api/campaigns/draft" --data @"$PAYLOAD"
+
+line "done (nothing sent)"
diff --git a/drafts/glitter-sequin-exclusive/connie-create2.sh b/drafts/glitter-sequin-exclusive/connie-create2.sh
new file mode 100644
index 0000000..60efe4d
--- /dev/null
+++ b/drafts/glitter-sequin-exclusive/connie-create2.sh
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+# Glitter & Sequin — REAL draft create on Connie, with fields remapped to its
+# /api/campaigns/draft schema (html_content / from_email / preheader).
+# Connie server-side adds the CAN-SPAM address + [[trackingImage]]. DRAFT only.
+set +e
+PASS="${CC_PASS:-DWSecure2024!}"
+CONNIE=http://127.0.0.1:9820
+
+node -e '
+const fs=require("fs");
+const s=JSON.parse(fs.readFileSync("/tmp/gs-draft.json","utf8"));
+const body={
+  name: s.name,
+  subject: s.subject,
+  preheader: s.preheader || "A private release of shimmer, sequin, and metallic, reserved for our trade list first.",
+  html_content: s.html_content || s.html,
+  from_email: s.from_email || s.from || "info@designerwallcoverings.com",
+  from_name: s.from_name || "Designer Wallcoverings"
+};
+fs.writeFileSync("/tmp/gs-connie.json", JSON.stringify(body));
+console.log("remapped -> bytes:", JSON.stringify(body).length,
+            "| name:", body.name,
+            "| html_content present:", !!body.html_content);
+'
+
+printf '\n== POST %s/api/campaigns/draft ==\n' "$CONNIE"
+curl -s -w '\nHTTP:%{http_code}\n' --max-time 30 -u admin:"$PASS" \
+  -H 'Content-Type: application/json' -X POST \
+  "$CONNIE/api/campaigns/draft" --data @/tmp/gs-connie.json
+
+printf '\n== done — DRAFT only, nothing sent ==\n'
diff --git a/drafts/glitter-sequin-exclusive/kamatera-create.sh b/drafts/glitter-sequin-exclusive/kamatera-create.sh
new file mode 100644
index 0000000..d3a4bf9
--- /dev/null
+++ b/drafts/glitter-sequin-exclusive/kamatera-create.sh
@@ -0,0 +1,39 @@
+#!/usr/bin/env bash
+# Glitter & Sequin — create the REAL Constant Contact draft on Kamatera.
+# Robust, single-run: verifies payload + auth, POSTs to the MCC, and if the MCC
+# is in mock mode it discovers Connie's own create routes so we can finish.
+# READ + DRAFT only. Does NOT send/schedule anything.
+set +e
+PAYLOAD=/tmp/gs-draft.json
+PASS="${CC_PASS:-DWSecure2024!}"        # override: CC_PASS=... bash kamatera-create.sh
+MCC=http://127.0.0.1:9661
+CONNIE=http://127.0.0.1:9820
+
+line(){ printf '\n== %s ==\n' "$1"; }
+
+line "payload"
+if [ -s "$PAYLOAD" ]; then
+  echo "bytes: $(wc -c <"$PAYLOAD")"
+  head -c 180 "$PAYLOAD"; echo " ..."
+else
+  echo "MISSING or EMPTY: $PAYLOAD  (re-scp the payload before continuing)"
+fi
+
+line "auth check (pass=${PASS:0:3}***)"
+echo "MCC    /api/constant-contact/campaigns -> $(curl -s -o /dev/null -w '%{http_code}' --max-time 6 -u admin:"$PASS" "$MCC/api/constant-contact/campaigns")"
+echo "Connie /api/campaigns                  -> $(curl -s -o /dev/null -w '%{http_code}' --max-time 6 -u admin:"$PASS" "$CONNIE/api/campaigns")"
+
+line "create draft via MCC (POST /api/constant-contact/draft)"
+curl -s -w '\nHTTP:%{http_code}\n' --max-time 20 -u admin:"$PASS" \
+  -H 'Content-Type: application/json' -X POST \
+  "$MCC/api/constant-contact/draft" --data @"$PAYLOAD"
+
+line "Connie cc-agent location + create routes (for direct-post fallback)"
+CWD=$(pm2 jlist 2>/dev/null | node -e 'let d="";process.stdin.on("data",c=>d+=c).on("end",()=>{try{const a=JSON.parse(d);const p=a.find(x=>/connie|cc-?agent/i.test((x.name||"")+" "+((x.pm2_env||{}).pm_cwd||"")));process.stdout.write(p?((p.pm2_env||{}).pm_cwd||""):"")}catch(e){}})' 2>/dev/null)
+echo "connie cwd: ${CWD:-<not found via pm2>}"
+if [ -n "$CWD" ]; then
+  grep -rhoE "(app|router)\.(get|post|put)\(['\"][^'\"]+" "$CWD" --include=*.js 2>/dev/null \
+    | sed -E "s/.*\((['\"])//" \
+    | grep -iE 'camp|draft|generate|email|create|product' | sort -u | head -30
+fi
+line "done (nothing sent)"
diff --git a/public/panels/assets.html b/public/panels/assets.html
index ade6cbf..c786e5b 100644
--- a/public/panels/assets.html
+++ b/public/panels/assets.html
@@ -19,6 +19,7 @@
       <button class="as-mode btn ghost" data-mode="harvest" style="border:0;border-radius:0;border-bottom:2px solid transparent;">✂ Harvest</button>
       <button class="as-mode btn ghost" data-mode="instagram" style="border:0;border-radius:0;border-bottom:2px solid transparent;">📸 Instagram</button>
       <button class="as-mode btn ghost" data-mode="cc" style="border:0;border-radius:0;border-bottom:2px solid transparent;">📧 CC Library</button>
+      <button class="as-mode btn ghost" data-mode="flipbooks" style="border:0;border-radius:0;border-bottom:2px solid transparent;">📖 Flip Books</button>
     </div>
 
     <!-- upload pane -->
@@ -114,6 +115,12 @@
       <button id="as-cc-go" class="btn gold">Import from Constant Contact</button>
     </div>
 
+    <!-- flip books pane — the DW collection flip-book marketing assets -->
+    <div class="as-pane" data-pane="flipbooks" style="display:none;">
+      <div class="muted" style="font-size:12px;margin-bottom:10px;">The DW collection flip books — narrated page-turn lookbooks. Grab the shareable page link, the poster image, or the video. Hosted on marketing.designerwallcoverings.com.</div>
+      <div id="as-flip-grid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:14px;"></div>
+    </div>
+
     <div id="as-add-status" style="font-size:12px;color:var(--mut);margin-top:10px;min-height:16px;"></div>
   </div>
 
diff --git a/public/panels/assets.js b/public/panels/assets.js
index fec52f2..861a4aa 100644
--- a/public/panels/assets.js
+++ b/public/panels/assets.js
@@ -466,7 +466,45 @@ window.MCC_PANELS['assets'] = {
       } catch (e) { rp('#rp-msg').textContent = '✗ ' + e.message; rp('#rp-msg').style.color = '#c0563f'; }
     };
 
+    // ── flip books — DW collection flip-book marketing assets ───────────────────
+    const FB_BASE = 'https://marketing.designerwallcoverings.com';
+    const FLIPBOOKS = [
+      { name: 'The Graduate Collection', poster: '/public/collections/graduate-poster.jpg', page: 'https://www.designerwallcoverings.com/collections/the-graduate-collection', video: '/reels/graduate-collection-flipbook.mp4', created: '2026-07-30T11:34:31' },
+      { name: 'Commercial Collection', poster: '/public/collections/commercial-collection-poster.jpg', page: FB_BASE + '/collections/commercial-collection', video: '/reels/commercial-collection-flipbook.mp4', created: '2026-08-12T12:29:27' },
+      { name: 'Franquemont — London', poster: '/public/collections/franquemont-london-poster.jpg', page: FB_BASE + '/collections/franquemont-london', video: '/reels/franquemont-london-flipbook.mp4', created: '2026-08-04T15:49:02' },
+      { name: 'Glitter Walls', poster: '/public/collections/glitter-walls-poster.jpg', page: FB_BASE + '/collections/glitter-walls', video: '/reels/glitter-walls-flipbook.mp4', created: '2026-08-10T13:25:06' },
+      { name: 'Innovations USA', poster: '/public/collections/innovations-usa-poster.jpg', page: FB_BASE + '/collections/innovations-usa', video: '/reels/innovations-usa-flipbook.mp4', created: '2026-08-04T15:02:05' },
+      { name: 'Majilite Metallics', poster: '/public/collections/majilite-metallics-poster.jpg', page: FB_BASE + '/collections/majilite-metallics', video: '/reels/majilite-metallics-flipbook.mp4', created: '2026-08-10T12:28:41' },
+      { name: 'Maya Romanoff', poster: '/public/collections/maya-romanoff-poster.jpg', page: FB_BASE + '/collections/maya-romanoff', video: '/reels/maya-romanoff-flipbook.mp4', created: '2026-08-10T12:47:43' },
+    ];
+    function renderFlipbooks() {
+      const grid = $('#as-flip-grid'); if (!grid) return;
+      grid.innerHTML = FLIPBOOKS.map(fb => {
+        const poster = FB_BASE + fb.poster, video = fb.video ? FB_BASE + fb.video : '';
+        return `<div class="card" style="padding:0;overflow:hidden;margin:0;display:flex;flex-direction:column;">
+          <a href="${esc(fb.page)}" target="_blank" rel="noopener" style="display:block;aspect-ratio:16/10;background:#e8e3da;overflow:hidden;">
+            <img src="${esc(poster)}" alt="${esc(fb.name)} flip book" loading="lazy" style="width:100%;height:100%;object-fit:cover;display:block;" onerror="this.style.opacity=.25;this.alt='⚠ poster failed'">
+          </a>
+          <div style="padding:9px 11px 11px;display:flex;flex-direction:column;gap:6px;">
+            <div style="font-size:12.5px;font-weight:600;line-height:1.25;" title="${esc(fb.name)}">${esc(fb.name)}</div>
+            <div style="display:flex;align-items:center;gap:6px;flex-wrap:wrap;">
+              <span class="pill" style="font-size:9.5px;">Flip Book</span>
+              <span class="when" title="${esc(fb.created)}" style="font-size:10.5px;color:var(--mut);">🕓 ${esc(fmtWhen(fb.created))}</span>
+            </div>
+            <div style="display:flex;gap:6px;flex-wrap:wrap;">
+              <a class="btn ghost" style="font-size:11px;padding:4px 9px;" href="${esc(fb.page)}" target="_blank" rel="noopener">Open ↗</a>
+              ${video ? `<a class="btn ghost" style="font-size:11px;padding:4px 9px;" href="${esc(video)}" target="_blank" rel="noopener">▶ Video</a>` : ''}
+              <button class="btn ghost" style="font-size:11px;padding:4px 9px;" data-fbcopy="${esc(fb.page)}">Copy link</button>
+              <button class="btn ghost" style="font-size:11px;padding:4px 9px;" data-fbcopy="${esc(poster)}">Copy image</button>
+            </div>
+          </div>
+        </div>`;
+      }).join('');
+      grid.querySelectorAll('[data-fbcopy]').forEach(b => b.addEventListener('click', () => copyUrl(b.dataset.fbcopy)));
+    }
+
     // ── boot ───────────────────────────────────────────────────────────────────
     loadLibrary();
+    renderFlipbooks();
   },
 };

← 3643590 auto-data-snapshot: 2026-09-04T19:59:19 (1 data files) — pub  ·  back to Marketing Command Center  ·  auto-data-snapshot: 2026-09-05T08:08:29 (1 data files) — dat 56d15e8 →