← back to Norma
IG captions: strip price + raw vendor (was leaking $4.25 memo price); quality + DW-service messaging, per-product variation (anti-spam)
37895ae7e640abb7a67664130ead3533adf89737 · 2026-08-11 08:28:50 -0700 · Steve Abrams
Files touched
M agents/instagram-agent/content.jsA agents/instagram-agent/gen-ig-activity.jsM agents/instagram-agent/post-to.jsA agents/instagram-agent/public/ig-activity.json
Diff
commit 37895ae7e640abb7a67664130ead3533adf89737
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Aug 11 08:28:50 2026 -0700
IG captions: strip price + raw vendor (was leaking $4.25 memo price); quality + DW-service messaging, per-product variation (anti-spam)
---
agents/instagram-agent/content.js | 29 +++++--
agents/instagram-agent/gen-ig-activity.js | 47 +++++++++++
agents/instagram-agent/post-to.js | 22 +++++
agents/instagram-agent/public/ig-activity.json | 106 +++++++++++++++++++++++++
4 files changed, 198 insertions(+), 6 deletions(-)
diff --git a/agents/instagram-agent/content.js b/agents/instagram-agent/content.js
index fb5456f..b5f1b0e 100644
--- a/agents/instagram-agent/content.js
+++ b/agents/instagram-agent/content.js
@@ -20,13 +20,30 @@ function handleFrom(input) {
return { handle: s.replace(/^@/, ''), store: DEFAULT_STORE };
}
+// QUALITY + DW SERVICE only. NEVER a price (the $-variant is the memo sample), and
+// NEVER the raw vendor (may be a private label that must not be named). Captions vary
+// per product so identical text across accounts doesn't trip Instagram's spam filter.
+const SERVICE_LINES = [
+ 'Crafted to an exacting standard and specified for the trade — Designer Wallcoverings offers studio sampling, expert specification, and service designers rely on.',
+ 'Quality you can feel, made for real interiors. Designer Wallcoverings supports the trade with samples to your studio, specification help, and white-glove service.',
+ 'Considered materials, faithful color, lasting quality. Designer Wallcoverings — to-the-trade sampling and specification, handled with care.',
+ 'Designer-grade quality, ready to specify. Samples to your studio and dedicated service from the Designer Wallcoverings trade team.',
+ 'Beautifully made and built to last. Designer Wallcoverings brings the sampling, specification, and service serious projects demand.',
+];
function autoCaption(p, url) {
- const title = p.title || '';
- const vendor = p.vendor ? ` by ${p.vendor}` : '';
- const price = (p.variants && p.variants[0] && p.variants[0].price)
- ? `\n$${Number(p.variants[0].price).toFixed(2)}` : '';
- const tag = (p.product_type || 'wallcovering').toLowerCase().replace(/[^a-z0-9]/g, '');
- return `${title}${vendor}.${price}\n\nShop: ${url}\n#wallpaper #interiordesign #wallcovering${tag ? ` #${tag}` : ''}`;
+ const title = String(p.title || 'Designer Wallcovering').split('|')[0].trim();
+ const type = (p.product_type || 'wallcovering').toLowerCase();
+ const tag = type.replace(/[^a-z0-9]/g, '');
+ const tagLine = tag && tag !== 'wallcovering' ? ` #${tag}` : '';
+ const idx = [...title].reduce((a, c) => a + c.charCodeAt(0), 0) % SERVICE_LINES.length;
+ return [
+ title,
+ '',
+ SERVICE_LINES[idx],
+ '',
+ `Explore: ${url}`,
+ `#wallpaper #interiordesign #wallcovering${tagLine} #tothetrade #designerwallcoverings`,
+ ].join('\n');
}
/** Resolve a product → { image_url, caption, title, url }. Throws if not found/imageless. */
diff --git a/agents/instagram-agent/gen-ig-activity.js b/agents/instagram-agent/gen-ig-activity.js
new file mode 100644
index 0000000..f1936a5
--- /dev/null
+++ b/agents/instagram-agent/gen-ig-activity.js
@@ -0,0 +1,47 @@
+#!/usr/bin/env node
+/**
+ * gen-ig-activity.js — compile the post ledger into a static feed for the
+ * marketing.designerwallcoverings.com "IG Network Activity" panel.
+ * Reads data/post-ledger.jsonl → writes ig-activity.json (newest first, deduped)
+ * to (a) this agent's data/ and public/, and (b) the MCC public dir if present.
+ * No server coupling — the panel is a static page that fetches this JSON.
+ */
+const fs = require('fs');
+const path = require('path');
+
+const HERE = __dirname;
+const LEDGER = path.join(HERE, 'data', 'post-ledger.jsonl');
+const reg = (() => { try { return JSON.parse(fs.readFileSync(path.join(HERE, 'accounts.json'), 'utf8')).accounts || {}; } catch { return {}; } })();
+
+const rows = fs.existsSync(LEDGER)
+ ? fs.readFileSync(LEDGER, 'utf8').split('\n').filter(Boolean).map((l) => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean)
+ : [];
+
+// dedupe by permalink (keep first-seen), newest first
+const seen = new Set();
+const posts = rows.filter((r) => { const k = r.permalink || r.media_id || JSON.stringify(r); if (seen.has(k)) return false; seen.add(k); return true; })
+ .map((r) => ({ ...r, page_name: r.page_name || reg[r.handle]?.page_name || null }))
+ .sort((a, b) => new Date(b.ts) - new Date(a.ts));
+
+const byAccount = {};
+for (const p of posts) byAccount[p.handle] = (byAccount[p.handle] || 0) + 1;
+
+const out = {
+ generated_at: new Date().toISOString(),
+ total_posts: posts.length,
+ accounts_touched: Object.keys(byAccount).length,
+ last_post_at: posts[0]?.ts || null,
+ by_account: byAccount,
+ posts,
+};
+
+const targets = [
+ path.join(HERE, 'data', 'ig-activity.json'),
+ path.join(HERE, 'public', 'ig-activity.json'),
+ path.join(process.env.HOME, 'Projects', 'marketing-command-center', 'public', 'ig-activity.json'),
+];
+for (const t of targets) {
+ try { fs.mkdirSync(path.dirname(t), { recursive: true }); fs.writeFileSync(t, JSON.stringify(out, null, 2)); console.log('wrote', t); }
+ catch (e) { /* MCC may not exist on every host */ }
+}
+console.log(`ig-activity: ${out.total_posts} posts across ${out.accounts_touched} accounts`);
diff --git a/agents/instagram-agent/post-to.js b/agents/instagram-agent/post-to.js
index f36def7..9d3554d 100644
--- a/agents/instagram-agent/post-to.js
+++ b/agents/instagram-agent/post-to.js
@@ -202,6 +202,9 @@ async function main() {
args.image = args.image || prod.image_url;
if (!args.caption) args.caption = prod.caption;
console.log(`Product: ${prod.title}\n image: ${prod.image_url}\n`);
+ args._productTitle = prod.title;
+ args._productHandle = String(args.product).replace(/^@/, '');
+ args._imageUrl = prod.image_url;
} catch (e) { console.error(`--product failed: ${e.message}`); process.exit(1); }
}
@@ -236,6 +239,19 @@ async function main() {
try {
const r = await publishOne(t, args);
results.push(r);
+ if (!dry) {
+ try {
+ const fs = require('fs'), path = require('path');
+ const dir = path.join(__dirname, 'data');
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
+ fs.appendFileSync(path.join(dir, 'post-ledger.jsonl'), JSON.stringify({
+ ts: new Date().toISOString(), handle: t.handle, page_name: t.page_name || null,
+ product_handle: args._productHandle || null, product_title: args._productTitle || null,
+ image_url: args._imageUrl || args.image || null,
+ permalink: r.permalink || null, media_id: r.media_id || null, kind: r.kind || null,
+ }) + '\n');
+ } catch (_) { /* ledger is best-effort; never block a post */ }
+ }
console.log(dry
? ` ✓ @${t.handle} — dry-run OK (${r.kind})`
: ` ✓ @${t.handle} — posted ${r.kind} ${r.permalink || r.media_id}`);
@@ -250,6 +266,12 @@ async function main() {
const ok = results.filter((r) => !r.error).length;
console.log(`\n${ok}/${results.length} ${dry ? 'validated' : 'posted'}.`);
if (dry) console.log('Add --confirm to publish for real.');
+ // Refresh the marketing.dw "IG Network Activity" feed after any live post.
+ if (!dry && ok) {
+ try {
+ require('child_process').execFileSync(process.execPath, [require('path').join(__dirname, 'gen-ig-activity.js')], { stdio: 'ignore' });
+ } catch (_) { /* feed refresh is best-effort */ }
+ }
}
main().catch((e) => { console.error('FAILED:', e.message); process.exit(1); });
diff --git a/agents/instagram-agent/public/ig-activity.json b/agents/instagram-agent/public/ig-activity.json
new file mode 100644
index 0000000..92f8dbd
--- /dev/null
+++ b/agents/instagram-agent/public/ig-activity.json
@@ -0,0 +1,106 @@
+{
+ "generated_at": "2026-08-11T15:18:04.900Z",
+ "total_posts": 8,
+ "accounts_touched": 8,
+ "last_post_at": "2026-08-11T08:13:00-07:00",
+ "by_account": {
+ "screenprintedwallpaper": 1,
+ "metallicwallpaper": 1,
+ "goldleafwallpaper": 1,
+ "linenwallpaper": 1,
+ "grassclothwallpaper": 1,
+ "velvetwallpaper": 1,
+ "suedewallpaper": 1,
+ "textilewallpaper": 1
+ },
+ "posts": [
+ {
+ "ts": "2026-08-11T08:13:00-07:00",
+ "handle": "screenprintedwallpaper",
+ "page_name": "Screen Printed Wallpaper",
+ "product_handle": "regal-lattice-screen-printed-wallpaper-tre-12907",
+ "product_title": "Regal Lattice - Screen Printed Wallcovering",
+ "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/3547fe79068d43fd86725b501b7c7f01.jpg",
+ "permalink": "https://www.instagram.com/p/Db54ExfmxI5/",
+ "media_id": null,
+ "kind": "IMAGE"
+ },
+ {
+ "ts": "2026-08-11T08:12:00-07:00",
+ "handle": "metallicwallpaper",
+ "page_name": "Metallic Wallpaper",
+ "product_handle": "showgirls-wallpaper",
+ "product_title": "Showgirls Metallic Gold & Black Wallcovering | Graduate Collection",
+ "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/ScreenShot2023-09-11at1.53.19PM.png",
+ "permalink": "https://www.instagram.com/p/Db54BKHG8Sv/",
+ "media_id": null,
+ "kind": "IMAGE"
+ },
+ {
+ "ts": "2026-08-11T08:12:00-07:00",
+ "handle": "goldleafwallpaper",
+ "page_name": "Gold Leaf Wallpaper",
+ "product_handle": "dwtt-80525-designer-wallcoverings-los-angeles",
+ "product_title": "Metal Leaf Metallic Gold - Gold Wallcovering | Thibaut",
+ "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/T41011_e0cd5049-136c-456e-b07d-a26bbb77e65b.jpg",
+ "permalink": "https://www.instagram.com/p/Db54C7Cm-G_/",
+ "media_id": null,
+ "kind": "IMAGE"
+ },
+ {
+ "ts": "2026-08-11T07:56:00-07:00",
+ "handle": "linenwallpaper",
+ "page_name": "Linen Wallpaper",
+ "product_handle": "pure-elements-sisal-linen",
+ "product_title": "Pure Elements Sisal Linen Wallcoverings",
+ "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/0075624_sisal_650.jpg",
+ "permalink": "https://www.instagram.com/p/Db50HyfG93k/",
+ "media_id": null,
+ "kind": "IMAGE"
+ },
+ {
+ "ts": "2026-08-11T07:56:00-07:00",
+ "handle": "grassclothwallpaper",
+ "page_name": "Wallpaper Weekly",
+ "product_handle": "papalo-paper-grasscloth-prg-71096",
+ "product_title": "Papalo Paper Grasscloth | Phillipe Romano",
+ "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/cf484d7a1d1bd1eeba8b00c589a2453d.jpg",
+ "permalink": "https://www.instagram.com/p/Db50Jmnm-7m/",
+ "media_id": null,
+ "kind": "IMAGE"
+ },
+ {
+ "ts": "2026-08-11T07:55:00-07:00",
+ "handle": "velvetwallpaper",
+ "page_name": "Velvet Wallpaper",
+ "product_handle": "veronicas-madison-flocked-velvet-wallpaper-4",
+ "product_title": "Veronica's Madison Flocked Velvet Wallcovering",
+ "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/VV218_madison_MAROON_1056x_8d36f6e3-f1df-410f-a0a6-f8c80c8ac535.webp",
+ "permalink": "https://www.instagram.com/p/Db50Endm9JZ/",
+ "media_id": null,
+ "kind": "IMAGE"
+ },
+ {
+ "ts": "2026-08-11T07:55:00-07:00",
+ "handle": "suedewallpaper",
+ "page_name": "Suede Wallpaper",
+ "product_handle": "prw-920205-cts-07",
+ "product_title": "Ciudad Suede Natural | Phillipe Romano",
+ "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/prw-920205-cts-07.jpg",
+ "permalink": "https://www.instagram.com/p/Db50GKmG_k8/",
+ "media_id": null,
+ "kind": "IMAGE"
+ },
+ {
+ "ts": "2026-08-11T07:52:00-07:00",
+ "handle": "textilewallpaper",
+ "page_name": "Textile Wallpaper",
+ "product_handle": "wick-submerge-maharam",
+ "product_title": "Wick Submerge | Maharam",
+ "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/MH-WICK-021_0.jpg",
+ "permalink": "https://www.instagram.com/p/Db5zlrhG1xF/",
+ "media_id": null,
+ "kind": "IMAGE"
+ }
+ ]
+}
\ No newline at end of file
← 85c7619 auto-data-snapshot: 2026-08-10T13:03:09 (1 data files) — age
·
back to Norma
·
ig-activity: best-effort scp to live marketing.dw after each 2d8bec8 →