Skip to content

pradhankukiran/atelier-slack-bot

Repository files navigation

Atelier

Slack-native AI studio for ecommerce ad agencies — one bot that runs the daily creative work (ad copy, advertorial drafts, future ad-platform digests) for multiple client brands.

The architectural idea is a shared brand brain: describe a client brand once via /brand setup, and every module reads from it. Ad copy, listicles, and future modules inherit voice, audience, do-not-say rules, and the running winners log without rework. Adding a new module is one folder under src/modules/, one Slack command, zero schema changes.

Architecture

                       Slack workspace
                              |
                              v
                   +----------+-----------+
                   |   @slack/bolt App    |
                   |  (socket | http)     |
                   +----------+-----------+
                              |
       +----------------------+----------------------+
       |                      |                      |
       v                      v                      v
  slash commands         action handlers          modals
  /ping /brand           creative approve         brand setup
  /creative /listicle    listicle approve
       |                      |
       +----------+-----------+
                  |
                  v
         +--------+---------+
         |     modules      |
         |  creative/       |
         |  listicle/       |
         +--------+---------+
                  |
                  v
         +--------+---------+
         |   shared core    |
         | llm  -> claude   |  prompt cache on brand prefix
         |       -> groq    |  fallback / dev
         | retry            |  exp backoff + jitter
         | tavily           |  research
         | brand winners    |  shared brain
         | generations      |
         | db (sqlite/WAL)  |
         +------------------+

Modules

Module Status Slack surface Description
Brand setup shipped /brand setup, /brand show, /brand list Modal-based editor for the per-brand context block every other module reads.
Creative engine shipped /creative brand=... goal=... Multi-pass pipeline: analyze prior winners, generate angles, pick hooks, write Meta-style copy variants. Approve a variant to feed it back as a future winner.
Listicle engine shipped /listicle brand=... topic=... count=N Research via Tavily, outline, per-item drafting, framing pass. Returns Slack preview plus an HTML attachment.

Tech stack

  • TypeScript (Node 22+), ESM with NodeNext resolution
  • Slack: @slack/bolt — Socket Mode for dev, ExpressReceiver/HTTP for prod
  • LLM: @anthropic-ai/sdk with prompt caching on the brand-context prefix, plus groq-sdk as a free dev alternative
  • Research: @tavily/core for web search + extraction
  • Storage: SQLite via better-sqlite3 (WAL mode, foreign keys on)
  • Validation: zod v4
  • Logging: pino with pino-pretty in dev
  • Retry: shared withRetry helper with full-jitter exponential backoff, used by both LLM providers

LLM provider toggle

The LLM_PROVIDER env var switches between anthropic (default — Claude with prompt caching, used in production) and groq (Llama via groq-sdk, free for dev testing but without caching and with less reliable structured JSON). Set GROQ_API_KEY when using Groq, and verify the wiring with npm run smoke:llm.

Run npm run smoke:claude to verify Anthropic prompt caching is working end-to-end — the script makes two identical calls and asserts the second one hits the cache (cache_read_input_tokens > 0).

Both providers go through the same shared retry layer: 429s, 529s, and 5xx responses are retried up to three times with full-jitter exponential backoff (base 500ms, cap 8s). Retry attempts log at warn level with provider, attempt, delayMs, status, and message fields so you can grep for them.

Local development

  1. cp .env.example .env and fill in the values (at minimum: Slack bot token, signing secret, app token for socket mode, ANTHROPIC_API_KEY, TAVILY_API_KEY).
  2. npm install
  3. npm run dev — starts the Bolt app under tsx watch in Socket Mode.
  4. In Slack, run /ping to confirm the bot is alive.

Slack app setup

  1. Create a new app at https://api.slack.com/apps from a manifest or scratch.
  2. Under OAuth & Permissions, add these bot scopes: commands, chat:write, files:write, im:write.
  3. Under Socket Mode, enable Socket Mode for dev. Generate an app-level token (xapp-...) with scope connections:write and put it in SLACK_APP_TOKEN.
  4. Under Slash Commands, register four commands: /ping, /brand, /creative, /listicle. Point each at the same Request URL (Socket Mode uses internal dispatch, so the URL is only checked when running HTTP mode).
  5. Under Interactivity & Shortcuts, enable Interactivity and set the Request URL to the same /slack/events path used by your slash commands. Slack uses this endpoint for button clicks (e.g. the Approve buttons) and modal submissions (e.g. /brand setup). For Socket Mode dev the URL field is not actually called, but Slack may still require a value — use https://example.com/slack/events as a placeholder.
  6. Under Basic Information, copy the Signing Secret into SLACK_SIGNING_SECRET.
  7. Install the app into your workspace and copy the Bot Token (xoxb-...) into SLACK_BOT_TOKEN.

Demo

After completing local development setup:

npm run seed:demo
npm run dev
# in Slack:
/creative brand=Drift goal="cold prospect, women 35-55, sleep issues"
# approve one of the variants by clicking its button
/listicle brand=Drift topic="best magnesium supplements for sleep" count=7

seed:demo upserts a fully-formed Drift brand (a sleep-supplement brand built on magnesium glycinate plus L-theanine) and seeds three in-voice creative winners so the creative pipeline has a "winners pattern memo" to learn from on the first call. The script is idempotent — re-running it skips winner insertion if the brand already has them.

Deploying to Railway

Atelier ships with a railway.json that selects the NIXPACKS builder, runs npm run start, and registers /health as the healthcheck endpoint. The HTTP transport is required in production; Socket Mode does not bind a port and Railway needs one.

  1. Create a new Railway project from this repo (Connect GitHub, pick branch).
  2. Under Variables, set the following:
    • SLACK_TRANSPORT=http
    • SLACK_BOT_TOKEN=xoxb-...
    • SLACK_SIGNING_SECRET=...
    • ANTHROPIC_API_KEY=... (or GROQ_API_KEY=... if testing on the free provider)
    • LLM_PROVIDER=anthropic (or groq)
    • TAVILY_API_KEY=...
    • NODE_ENV=production
    • DATABASE_PATH=/data/atelier.db
    • PORT is set automatically by Railway; the app will respect it.
  3. Under Settings → Volumes, create a volume and mount it at /data so the SQLite file persists across deploys.
  4. Under Settings → Networking, generate a public domain (or attach a custom one).
  5. In the Slack app dashboard, switch to event-driven mode: disable Socket Mode, then under Slash Commands set each command's Request URL to https://<your-railway-domain>/slack/events (the default ExpressReceiver path). Reinstall the app if Slack prompts for it.
  6. Under Interactivity & Shortcuts, enable Interactivity and set the Request URL to https://<your-railway-domain>/slack/events. Without this, button clicks (Approve) and modal submissions (/brand setup) will silently fail in production.
  7. Trigger a redeploy if env vars changed after the first build, then run /ping from your workspace to confirm.

Future modules

The shared-brain pattern makes the next modules incremental work, not greenfield work. Three high-value adds from the original scope:

  • Google Ads digest — a /google-ads command that ingests a CSV export (or live API pull) and produces a Slack-native performance read: top movers, audience-segment commentary, suggested next-week budget shifts. Reads brand context (voice, do-not-say) to write the commentary in the same register as the rest of the agency's deliverables; reads winners to flag which ad concepts are tied to active campaigns.
  • Meta Ads digest — symmetric module for Meta. Same shape: ingest, analyze, narrate. Crucially, the creative-engine output and the Meta digest input live in the same brand record, so the agency can close the loop on which approved variants actually performed and feed that signal back into the winner pool.
  • Slack-native team management — a /team command surface for assigning ownership of a brand to specific Slack users, scoping who can approve winners, and producing weekly summaries of agency activity per brand. This reuses brands, winners, and generations directly; no schema changes required.

Each of those modules adds one folder under src/modules/, registers one Slack command, and inherits the entire brand brain — voice, audience, do-not-say, competitors, product notes, and the running winners log — without rework.

License

Proprietary. Portfolio / demo project.

About

Slack-native AI studio for ecommerce ad agencies. Multi-pass LLM pipelines for ad creative and advertorial listicles, sharing one brand brain.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors