← 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
A drafts/glitter-sequin-exclusive/connie-create.shA drafts/glitter-sequin-exclusive/connie-create2.shA drafts/glitter-sequin-exclusive/kamatera-create.shM public/panels/assets.htmlM public/panels/assets.js
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 →