← back to Dw Slideshow Gen
README.md
87 lines
# dw-slideshow-gen
Designer Wallcoverings marketing **slideshow generator** — turn a marketing topic
or product into a 6-slide, 1080×1920 TikTok/Instagram photo-carousel as PNGs.
**Zero image-gen cost**: slides are drawn headless with `@napi-rs/canvas` (no
browser, no OpenAI), deterministic and on-brand.
## Why this shape
- **6 slides:** slide 1 = hook, 2–5 = substantive body, 6 = CTA — the proven
TikTok carousel structure (slide 1 is the whole ballgame).
- **Brand from `DESIGN.md`:** fonts (Didot display / Avenir body), warm-charcoal
ground, brass accent, cream type, hashtags — one source of truth.
- **`renderSpec()` is server-callable:** the same function a CLI uses here can be
called from a marketing endpoint later to serve PNGs. No rewrite.
## Use
```bash
# straight from the live catalog (queries dw_unified, downloads product photos, renders):
node index.js catalog --limit 6 # 6 newest -> one carousel each
node index.js catalog --vendor "Anna French" --mode roundup # "New Anna French arrivals"
node index.js catalog --type Wallcovering --since 2026-06-01
# from a hand-authored queue:
node index.js all samples/queue.json # plan + render (template copy)
node index.js plan samples/queue.json # -> output/plan-<date>.json
node index.js render output/plan-<date>.json # -> output/renders/<id>/slide_N.png
# assemble the postable draft package (fail-closed — never posts without creds+--confirm):
node index.js post # all render dirs -> zip + caption + instructions
```
Flags:
- `--ai` — generate slide copy via headless Claude Code (`claude -p`, reuses the
Max-plan login, **no metered API key**). Falls back to the template generator
on any failure.
- `--bg DIR` — use real product photography as slide backgrounds
(`slide_N.jpg` per slide or one `bg.jpg`) with an automatic legibility scrim.
- `--design FILE` — point at a different `DESIGN.md`.
## Queue shape
```json
{ "items": [ {
"id": "grasscloth-moody-dining",
"topic": "Grasscloth wallcoverings",
"angle": "a moody dining room",
"style": "Organic Modern", "colorway": "Charcoal & Flax", "room": "dining room",
"facts": ["Title: one substantive sentence.", "..."], // one per body slide (≤4)
"hashtags": ["#grasscloth", "..."] // optional, ≤5
} ] }
```
If `facts` is omitted the generator writes on-brand body slides from the topic.
## Posting (Postiz) — verified API, fail-closed
Client (`lib/postiz.js`) is built to the verified Postiz public API
(`https://api.postiz.com/public/v1`, `Authorization: <key>`; shapes lifted from the
official SDK + DTOs). `post` **always** builds the local zip package ($0). With
`--postiz` it stages into Postiz:
```bash
node index.js integrations # list connected channels (needs key)
node index.js post --postiz # DRY-RUN: prints the exact request body, no network
node index.js post --postiz --confirm # stage as a Postiz DRAFT (reversible)
node index.js post --postiz --channel tiktok --schedule 2026-07-01T15:00:00Z --confirm
```
**Why `draft`, not direct-to-TikTok:** we stage as Postiz `type:'draft'`. It needs no
per-provider settings, lands in the Postiz dashboard for you to review + publish (you
add the trending sound there), and is reversible via `DELETE /posts/{id}`. This
deliberately sidesteps Postiz issue **#1338** (still OPEN) — TikTok `UPLOAD`+`SELF_ONLY`
posts get stuck in QUEUE because `privacy_level` is dropped upstream. `--schedule`
uses the schedule path with full TikTok settings if you'd rather queue directly.
**Gate:** network calls fire only with `POSTIZ_API_KEY` **and** `--confirm`. Without
`--confirm` it's a dry-run. Going live is outward-facing → Steve-gated.
**Setup:** Postiz → connect TikTok → Settings → Developers → Public API → generate key
→ paste it (routed via `/secrets` as `POSTIZ_API_KEY`). No `INTEGRATION_ID` needed —
the engine lists channels and picks TikTok automatically (override with `--channel`).
**Analytics:** Postiz's analytics are **dashboard-only** (platform.postiz.com/analytics) —
there is no public-API analytics endpoint, so per-post stats can't be pulled
programmatically. The engine integrates posting + channels; analytics stays in the UI.
## Cost
Render: **$0 (local)** — no image-gen API. Catalog photo download: **$0**.
Optional `--ai` copy uses the existing Claude Code login (no separate billing).
`output/` is gitignored.