← back to Consulting Designerwallcoverings Com
scripts/seed-buckets.mjs
154 lines
#!/usr/bin/env node
// Seed the growth command-center buckets with REAL DW state.
// - socials: live channel status from the snapshot
// - competitors + ads: connie registry + a $0 pixel scan of each competitor's
// homepage (advertising-signals pattern, inline light version)
// - content-calendar: DRAFT-ONLY posts aligned to the activation drip
// - suggestions: impact/effort moves from the analysis brief
// Repeatable; overwrites bucket files (they remain editable in /admin).
import { readFileSync, writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
const HERE = dirname(fileURLToPath(import.meta.url));
const DATA = join(HERE, '..', 'data');
const readJ = (f, d) => { try { return JSON.parse(readFileSync(join(DATA, f), 'utf8')); } catch { return d; } };
const writeJ = (f, v) => writeFileSync(join(DATA, f), JSON.stringify(v, null, 2));
const SNAP = readJ('dw-analysis.json', { channels: [], competitors: [] });
/* ---- socials: live connection state --------------------------------------- */
const socialUrls = readJ('socials.json', {});
const socials = {
_status: 'Live from the marketing command center, ' + (SNAP.collected_at || ''),
platforms: (SNAP.channels || []).map(c => ({
key: c.key, label: c.label || c.key, connected: !!c.connected,
accounts: c.accounts || 0,
url: socialUrls[c.key] || '',
})),
};
/* ---- best times: interiors-category cadence (analyst) ---------------------- */
const bestTimes = {
_basis: 'Analyst cadence for the interiors/design vertical — refine against GA4 + per-post engagement once attribution lands',
instagram: ['Tue 11:00', 'Thu 19:00', 'Sat 10:00'],
facebook: ['Wed 13:00', 'Sun 12:00'],
tiktok: ['Tue 18:00', 'Fri 20:00'],
pinterest_note: 'Pinterest is the missing high-fit channel for wallcoverings — evenings + weekends dominate saves',
youtube: ['Sat 11:00'],
threads: ['Mon 12:00', 'Thu 12:00'],
bluesky: ['Wed 10:00'],
linkedin: ['Tue 09:00', 'Thu 09:00'],
};
/* ---- competitors + $0 pixel scan ------------------------------------------- */
const PIXELS = [
['google_ads', /googleadservices|AW-\d{9}|gtag\('config',\s*'AW-/i],
['meta', /connect\.facebook\.net|fbq\(/i],
['tiktok', /analytics\.tiktok\.com|ttq\./i],
['pinterest', /pintrk\(|ct\.pinterest\.com/i],
['microsoft_bing', /bat\.bing\.com/i],
['ga4', /gtag\('config',\s*'G-|googletagmanager\.com\/gtag/i],
];
async function scanDomain(domain) {
try {
const r = await fetch('https://' + domain, {
headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36' },
signal: AbortSignal.timeout(12000), redirect: 'follow',
});
const html = (await r.text()).slice(0, 400000);
const hits = PIXELS.filter(([, re]) => re.test(html)).map(([k]) => k);
return { ok: true, pixels: hits };
} catch (e) { return { ok: false, pixels: [], error: String(e.message || e).slice(0, 80) }; }
}
// Extract paid-ad pixels (everything except analytics-only 'ga4').
const adPixels = scan => scan.pixels.filter(p => p !== 'ga4');
const comps = [];
const ads = [];
for (const c of (SNAP.competitors || [])) {
const scan = await scanDomain(c.domain);
const paid = adPixels(scan);
comps.push({
id: 'comp-' + c.domain, name: c.name, domain: c.domain,
watch: c.enabled ? 'active' : 'paused', products_found: c.products_found,
notes: c.notes || '', pixels: scan.pixels, scanned: scan.ok,
});
ads.push({
domain: c.domain, name: c.name,
ad_pixels: paid,
analytics: scan.pixels.includes('ga4'),
read: scan.ok
? (paid.length ? 'Paying to advertise (' + paid.join(', ') + ')' : 'No paid-ad pixels detected on homepage')
: 'Homepage unreachable to scanner (' + (scan.error || '') + ')',
scanned_at: new Date().toISOString(),
method: '$0 homepage pixel scan (advertising-signals pattern)',
});
console.log(c.domain, '→', scan.ok ? (scan.pixels.join(',') || 'no pixels') : 'unreachable');
}
// DW itself, same lens
const self = await scanDomain('www.designerwallcoverings.com');
const selfPaid = adPixels(self);
ads.unshift({
domain: 'designerwallcoverings.com', name: 'Designer Wallcoverings (you)',
ad_pixels: selfPaid, analytics: self.pixels.includes('ga4'),
read: selfPaid.length ? 'Running: ' + selfPaid.join(', ') : 'No paid-ad pixels detected on homepage',
scanned_at: new Date().toISOString(), method: '$0 homepage pixel scan',
});
/* ---- content calendar: DRAFT-ONLY, drip-aligned ----------------------------- */
const cal = [];
const themes = [
['New-arrivals story', 'Daily drop from the activation drip — 3 patterns, one mood', ['instagram', 'facebook', 'threads']],
['Texture Tuesday', 'One material macro (grasscloth / cork / silk) + care note', ['instagram', 'tiktok', 'bluesky']],
['Designer pick', 'One pattern, one room render, one designer quote', ['instagram', 'pinterest', 'facebook']],
['Before / after wall', 'Client or render transformation, sample CTA', ['tiktok', 'instagram', 'youtube']],
['Trade corner', 'Spec tip for designers (widths, repeats, Type II)', ['linkedin', 'threads']],
['Sample Saturday', '$4.25 memo program explainer + bundle prompt', ['instagram', 'facebook', 'tiktok']],
['Pattern history', 'The story of one motif (damask / toile / chinoiserie)', ['facebook', 'bluesky', 'threads']],
];
const start = new Date();
for (let d = 0; d < 14; d++) {
const day = new Date(start.getTime() + d * 86400000);
const t = themes[d % themes.length];
cal.push({
date: day.toISOString().slice(0, 10),
title: t[0], brief: t[1], channels: t[2],
status: 'DRAFT', rail: 'Social posting is DRAFT-ONLY until Steve enables it',
});
}
/* ---- media + directories + suggestions -------------------------------------- */
const media = [
{ kind: 'reels-gallery', label: 'DW brand films & reels showcase', url: 'https://marketing.designerwallcoverings.com/showcase', note: 'Existing produced library — the reels feed for the calendar above' },
{ kind: 'nightly-films', label: 'Nightly recap films', url: 'https://builds.agentabrams.com/nightly', note: 'Internal — proof of the video pipeline capacity' },
];
const directories = [
{ name: 'Google Business Profile', status: 'verify', note: 'Confirm ownership + categories + photo freshness for the LA showroom' },
{ name: 'Yelp', status: 'verify', note: 'Claim/refresh; interiors buyers cross-check Yelp before showroom visits' },
{ name: 'Apple Maps', status: 'todo', note: 'Apple Business Connect listing' },
{ name: 'Bing Places', status: 'todo', note: 'Free; feeds Copilot answers' },
{ name: 'Houzz', status: 'verify', note: 'Category-native directory — pro profile + project photos' },
{ name: 'Pinterest business account', status: 'todo', note: 'The missing high-fit channel; rich pins off the product feed' },
];
const suggestions = [
{ id: 'sug-1', title: 'Sellable-variant integrity sprint (22.9k backlog)', category: 'Revenue', impact: 5, effort: 3, rationale: 'A third of live catalog is browseable-not-buyable; top 8 vendor lines cover ~17k. Data work, not spend.' },
{ id: 'sug-2', title: 'Per-channel UTM + coupon attribution', category: 'Measurement', impact: 4, effort: 1, rationale: 'Reach exists (80 FB pages, 34 IG); nothing proves revenue per channel. Prerequisite to scaling any spend.' },
{ id: 'sug-3', title: 'Sampled-but-didn’t-buy email sequence', category: 'Conversion', impact: 4, effort: 2, rationale: 'The $4.25 memo program produces daily purchase-intent signals; follow-up converts existing intent at near-zero cost.' },
{ id: 'sug-4', title: 'Sync social cadence to the activation drip', category: 'Content', impact: 3, effort: 1, rationale: '~500 new products/day = native daily story; calendar above is pre-drafted for it.' },
{ id: 'sug-5', title: 'Pinterest launch (business account + rich pins)', category: 'Channel', impact: 4, effort: 2, rationale: 'Highest-fit missing channel for wallcoverings discovery; product feed already exists.' },
{ id: 'sug-6', title: 'Designer-net trade portal', category: 'Trade', impact: 4, effort: 3, rationale: 'Highest-LTV segment currently buys through retail flow; formalize trade pricing behind a login.' },
{ id: 'sug-7', title: 'AEO blocks fleet-wide', category: 'Search', impact: 3, effort: 2, rationale: 'Flagship AEO block exists; extend to the 287-site fleet for AI-answer citations.' },
{ id: 'sug-8', title: 'Hospitality / contract quote desk', category: 'Projects', impact: 3, effort: 3, rationale: 'Type II lines in catalog; one hotel corridor = 400 rolls. Spec-sheet + quote flow.' },
];
writeJ('socials.json', socials);
writeJ('best-times.json', bestTimes);
writeJ('competitors.json', comps);
writeJ('ads.json', ads);
writeJ('content-calendar.json', cal);
writeJ('media.json', media);
writeJ('directories.json', directories);
writeJ('suggestions.json', suggestions);
console.log('Buckets seeded:', { competitors: comps.length, ads: ads.length, calendar: cal.length, suggestions: suggestions.length });