[object Object]

← back to Norma

IG poster: --product <handle> pulls a real DW product (live storefront) and auto-composes image+caption; verified dry-run on Shakespeare Mural

8a9959fdd0f7b4f9b76602e695b493b80c40ff0c · 2026-08-10 12:20:40 -0700 · Steve

Files touched

Diff

commit 8a9959fdd0f7b4f9b76602e695b493b80c40ff0c
Author: Steve <steve@designerwallcoverings.com>
Date:   Mon Aug 10 12:20:40 2026 -0700

    IG poster: --product <handle> pulls a real DW product (live storefront) and auto-composes image+caption; verified dry-run on Shakespeare Mural
---
 agents/instagram-agent/POSTING.md |  1 +
 agents/instagram-agent/content.js | 46 +++++++++++++++++++++++++++++++++++++++
 agents/instagram-agent/post-to.js | 13 ++++++++++-
 3 files changed, 59 insertions(+), 1 deletion(-)

diff --git a/agents/instagram-agent/POSTING.md b/agents/instagram-agent/POSTING.md
index 5dd1361..6bd1704 100644
--- a/agents/instagram-agent/POSTING.md
+++ b/agents/instagram-agent/POSTING.md
@@ -14,6 +14,7 @@ node post-to.js verify <account>     # read-only: prove the token reaches it
 node post-to.js verify --verify-all  # health-sweep every account
 
 # POST (dry-run by default — add --confirm to actually publish)
+node post-to.js <account> --product <handle>     --confirm                   # real DW product (auto image+caption)
 node post-to.js <account> --image  <public_url> --caption "…" --confirm     # single image
 node post-to.js <account> --images "u1,u2,u3"    --caption "…" --confirm     # carousel (2–10)
 node post-to.js <account> --reel   <public_mp4>  --caption "…" --confirm     # reel
diff --git a/agents/instagram-agent/content.js b/agents/instagram-agent/content.js
new file mode 100644
index 0000000..fb5456f
--- /dev/null
+++ b/agents/instagram-agent/content.js
@@ -0,0 +1,46 @@
+/**
+ * content.js — resolve a real DW product into post-ready {image_url, caption}.
+ *
+ * Pulls from the LIVE public storefront (products/<handle>.json) so there's no
+ * catalog DB coupling or credentials. Lets the poster publish actual products
+ * across the 35-account fleet instead of hand-pasted image URLs.
+ *
+ *   node post-to.js <account> --product <handle-or-url> --confirm
+ *
+ * A caller-supplied --caption always wins over the auto-composed one.
+ */
+
+const DEFAULT_STORE = process.env.DW_STORE || 'https://designerwallcoverings.com';
+
+/** Accept a bare handle, a /products/<handle> path, or a full product URL. */
+function handleFrom(input) {
+  const s = String(input || '').trim();
+  const m = s.match(/\/products\/([^/?#]+)/);
+  if (m) return { handle: m[1], store: (s.match(/^https?:\/\/[^/]+/) || [DEFAULT_STORE])[0] };
+  return { handle: s.replace(/^@/, ''), store: DEFAULT_STORE };
+}
+
+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}` : ''}`;
+}
+
+/** Resolve a product → { image_url, caption, title, url }. Throws if not found/imageless. */
+async function resolveProduct(input) {
+  const { handle, store } = handleFrom(input);
+  const url = `${store}/products/${handle}.json`;
+  const r = await fetch(url);
+  if (!r.ok) throw new Error(`product "${handle}" not found (${r.status}) at ${store}`);
+  const { product } = await r.json();
+  if (!product) throw new Error(`no product payload for "${handle}"`);
+  const img = (product.images && product.images[0] && product.images[0].src) || product.image?.src;
+  if (!img) throw new Error(`product "${handle}" has no image`);
+  const productUrl = `${store}/products/${handle}`;
+  return { image_url: img, caption: autoCaption(product, productUrl), title: product.title, url: productUrl };
+}
+
+module.exports = { resolveProduct, handleFrom };
diff --git a/agents/instagram-agent/post-to.js b/agents/instagram-agent/post-to.js
index 84f3824..f36def7 100644
--- a/agents/instagram-agent/post-to.js
+++ b/agents/instagram-agent/post-to.js
@@ -23,6 +23,7 @@
  */
 
 const accounts = require('./accounts');
+const content = require('./content');
 
 function parseArgs(argv) {
   const a = { _: [] };
@@ -194,8 +195,18 @@ async function main() {
     return;
   }
 
+  // --product <handle-or-url>: pull a real DW product → fills --image + --caption
+  if (args.product) {
+    try {
+      const prod = await content.resolveProduct(args.product);
+      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`);
+    } catch (e) { console.error(`--product failed: ${e.message}`); process.exit(1); }
+  }
+
   if (!args.image && !args.images && !args.reel && !args.story) {
-    console.error('Nothing to post. Provide --image <url> | --images "u1,u2,..." | --reel <mp4Url> | --story <url>.');
+    console.error('Nothing to post. Provide --product <handle> | --image <url> | --images "u1,u2,..." | --reel <mp4Url> | --story <url>.');
     console.error('Run `node post-to.js list` to see accounts, or `verify <account>` to health-check.');
     process.exit(1);
   }

← 5dd450b IG poster hardening: carousel (2-10 img) posts, verify/healt  ·  back to Norma  ·  chore: lint (0 fixes) + refactor (STORY_TTL_MS) + v1.1.0 (se 02b2ffa →