# Build Your Own Marketing Agent — Claude Routines

> **What this is:** a step-by-step recipe for building an autonomous
> Instagram marketing agent that posts daily, replies to comments on your
> posts, and reports back to you in Telegram. Runs on Anthropic's cloud
> infrastructure — your laptop can be off. No paid agent framework needed.
> Your existing Anthropic plan covers the runtime.
>
> **Build time:** ~30 minutes if you already have an Instagram Business account.
> **Recurring cost:** $0 above your existing Anthropic subscription.
> **Skill level:** comfortable copy-pasting commands into a terminal and clicking through apps. No coding required.

---

# Before You Start — Open These Tabs

Open these in your browser up-front so you're not context-switching mid-build:

```
┌───────────────────────────────────────────────────────────────────────────────┐
│                                                                               │
│   #   PURPOSE                              URL                                │
│  ─── ──────────────────────────────────── ────────────────────────────────    │
│   1   Telegram (web OR native app)         web.telegram.org                   │
│   2   BotFather (bot-maker bot)            t.me/BotFather                     │
│   3   Composio dashboard                   app.composio.dev                   │
│   4   Instagram (your brand account)       instagram.com                      │
│   5   Google AI Studio (Gemini)            aistudio.google.com/apikey         │
│                                                                               │
│   PLUS: download Claude Desktop           claude.ai/download                 │
│                                                                               │
│   Image hosting (catbox + litterbox) requires no signup — used inline.       │
│                                                                               │
└───────────────────────────────────────────────────────────────────────────────┘
```

---

# Part 1: The Big Picture

## What This Agent Does

A scheduled Claude session that fires once a day. Each run it:

- picks a content theme from its rotation
- generates a branded image + caption in your voice
- posts to Instagram via Composio
- replies to comments on your recent posts (first-party only)
- flags unread DMs back to you in Telegram
- sends you a structured daily report

You can override it or pause it at any time by sending the bot a Telegram message.

## Experience Over Time

```
 ═════════════════════════════════════════════════════════════════════════════
                             THE BUILD  (one-time)
 ═════════════════════════════════════════════════════════════════════════════

   ┌───────────────────────────────────────────────────────────────────┐
   │                                                                   │
   │   30 MINUTES, ONE SITTING                                         │
   │                                                                   │
   │   Part 3  →  Sign up for 3 free services + install Desktop (10m)  │
   │   Part 4  →  Paste 3 API keys into Claude Desktop (2 min)         │
   │   Part 5  →  Customize the brain (the system prompt) (10-15 min)  │
   │   Part 6  →  Create the Routine in Desktop (3 min)                │
   │   Part 7  →  Run once manually, verify output (5 min)             │
   │                                                                   │
   └───────────────────────────────────────────────────────────────────┘

 ═════════════════════════════════════════════════════════════════════════════
                           EVERY DAY  (zero input)
 ═════════════════════════════════════════════════════════════════════════════

   ┌───────────────────────────────────────────────────────────────────┐
   │                                                                   │
   │   07:00  Routine fires automatically                              │
   │   07:01  Agent reads its Telegram chat (= its memory)             │
   │   07:02  Agent picks today's pillar (rotates intelligently)       │
   │   07:03  Agent generates the visual via Gemini Nano Banana        │
   │   07:04  Agent uploads it to litterbox (gets a public URL)        │
   │   07:05  Agent posts to Instagram via Composio                    │
   │   07:06  Agent replies to unread comments on your last 3 posts    │
   │   07:07  Agent lists unread DMs (flags them, no auto-reply)       │
   │   07:08  Agent sends you a Telegram report                        │
   │   07:09  Session ends.                                            │
   │                                                                   │
   └───────────────────────────────────────────────────────────────────┘

 ═════════════════════════════════════════════════════════════════════════════
                         WHEN YOU WANT TO STEER  (optional)
 ═════════════════════════════════════════════════════════════════════════════

   ┌───────────────────────────────────────────────────────────────────┐
   │                                                                   │
   │   Send a message to the bot in Telegram — any of these:           │
   │                                                                   │
   │     "skip today"                  → agent skips tomorrow's post    │
   │     "pause"                       → agent skips every day until    │
   │                                     you say "resume"                │
   │     "use this idea: [angle]"      → agent picks up your angle      │
   │     "don't post about [topic]"    → agent adds to avoid list       │
   │                                                                   │
   │   The agent reads its pinned-ledger memory + your recent messages  │
   │   at the start of every run, so anything you send recently         │
   │   influences tomorrow's behavior.                                   │
   │                                                                   │
   └───────────────────────────────────────────────────────────────────┘
```

## System Architecture

```
                    ANTHROPIC CLOUD  (your Routine runs here)
            ┌──────────────────────────────────────────────────┐
            │                                                  │
            │         ┌────────────────────────────┐           │
            │         │   Claude Code session      │           │
            │         │   (Haiku 4.5 by default)   │           │
            │         │                            │           │
            │         │   System prompt = brain    │           │
            │         │   bash + tool calls = body │           │
            │         └─────┬──────┬──────┬────────┘           │
            │               │      │      │                    │
            └───────────────┼──────┼──────┼────────────────────┘
                            │      │      │
            ┌───────────────┘      │      └────────────────┐
            │                      │                       │
            ▼                      ▼                       ▼
 ┌──────────────────┐   ┌──────────────────┐   ┌──────────────────┐
 │   TELEGRAM BOT   │   │   GEMINI API     │   │   CATBOX.MOE     │
 │                  │   │  (Nano Banana)   │   │  (anonymous,     │
 │  • PINNED LEDGER │   │                  │   │   no API key)    │
 │    (the agent's  │   │  • Generates     │   │                  │
 │    memory; one   │   │    images from   │   │  • catbox: refs  │
 │    edited msg)   │   │    text + ref    │   │    (permanent)   │
 │  • Recent inbox  │   │    images        │   │  • litterbox:    │
 │  • Daily reports │   │                  │   │    posts (72h    │
 │                  │   │                  │   │    TTL — fine)   │
 └──────────────────┘   └──────────────────┘   └────────┬─────────┘
                                                        │
                                                        ▼
                                              ┌──────────────────┐
                                              │   COMPOSIO MCP   │
                                              │  (Instagram API) │
                                              │                  │
                                              │  • Post media    │
                                              │  • Read comments │
                                              │  • Reply to      │
                                              │    comments      │
                                              │  • List DMs      │
                                              └────────┬─────────┘
                                                       │
                                                       ▼
                                              ┌──────────────────┐
                                              │    INSTAGRAM     │
                                              │   (your account) │
                                              └──────────────────┘
```

## What It Costs

```
┌────────────────────────────────────┬──────────────┬─────────────────────────┐
│  ITEM                              │  COST        │  NOTES                  │
├────────────────────────────────────┼──────────────┼─────────────────────────┤
│  Anthropic plan (existing)          │  existing    │  Pro / Max / Team /    │
│                                    │              │  Enterprise — Routines  │
│                                    │              │  are included          │
│  Telegram                          │  Free        │  Always                │
│  Composio                          │  Free tier   │  Enough for 1 IG       │
│                                    │              │  account, daily posts  │
│  Gemini API (Nano Banana)          │  Free tier   │  Generous quota       │
│  catbox / litterbox                │  Free        │  Anonymous, no API     │
│                                    │              │  key, no signup        │
│                                    │              │                        │
│  ══════════════════════════════    │  ═══════     │                        │
│  TOTAL RECURRING                    │  $0          │  Everything beyond    │
│                                    │              │  Anthropic is free    │
└────────────────────────────────────┴──────────────┴─────────────────────────┘
```

If you're not on an Anthropic paid plan, you'll need one to use Routines. The cheapest is Pro at $20/mo — that gives you Claude Code on the web, the Desktop app integration, and Routines.

## What You Need

```
┌──────────────────────────────────────┬──────────────────────────────────────┐
│  REQUIREMENT                         │  NOTES                               │
├──────────────────────────────────────┼──────────────────────────────────────┤
│  An Instagram Business or Creator    │  Personal accounts cannot be auto-   │
│  account                             │  posted to. Convert in IG mobile →   │
│                                      │  Settings → Account type. Free,      │
│                                      │  30 seconds, reversible.              │
│  Claude Desktop installed            │  Get it at claude.ai/download         │
│  A phone number (for Telegram)       │  To create the bot via BotFather     │
│  A web browser                       │  Chrome / Edge / Firefox / Safari    │
│  30 minutes                          │  To work through Parts 3-7           │
└──────────────────────────────────────┴──────────────────────────────────────┘

You will NOT need: a domain, a server, GitHub, Docker, AWS, Vercel, Node,
Python, or a code editor.
```

---

# Part 2: What Lives Where

There's no code repo. Nothing to deploy. The "agent" is a configuration spread across six places:

```
 ═════════════════════════════════════════════════════════════════════════════
                              WHERE THINGS LIVE
 ═════════════════════════════════════════════════════════════════════════════

  1️  THE CLAUDE ROUTINE ────────────────────────────┐
      Lives in: Claude Desktop → Routines             │  The brain +
      Contains: system prompt, schedule, connectors,  │  the schedule
                environment selection                 │
                                                      │
  2️  THE CLOUD ENVIRONMENT ────────────────────────────┐
      Lives in: Claude Desktop → Cloud Environments     │  The secrets +
      Contains: API keys (Telegram, Gemini),            │  network config
                network access settings                 │
                                                        │
  3️  THE TELEGRAM CHAT ─────────────────────────────────┐
      Lives in: your Telegram app                        │  The memory +
      Contains: ONE pinned message = the agent's          │  override channel
                ledger (state, run log, directives),     │
                fresh user messages (overrides),         │
                daily reports                            │
                                                         │
  4️  REFERENCE ASSET URLS ───────────────────────────────┐
      Lives in: hosted on catbox.moe (permanent,           │  The asset
                anonymous), URLs listed in the system       │  library
                prompt's REFERENCE LIBRARY section          │
      Contains: tag + direct URL for each brand image      │
                (logos, screenshots, mascot, mood boards)  │
                                                           │
  5️  THE COMPOSIO CONNECTION ────────────────────────────┐
      Lives in: app.composio.dev + Claude Desktop          │  The bridge to
      Contains: OAuth link to your Instagram Business       │  Instagram
                account, surfaced as MCP tools in Claude    │
                                                            │
  6️  EXTERNAL APIS ─────────────────────────────────────────┐
      Live at:  generativelanguage.googleapis.com,           │  The daily
                catbox.moe, litterbox.catbox.moe,             │  workers
                api.telegram.org                              │
      Called:   by the agent's bash during each run           │
                                                              │
                                                              ▼
                            ┌─────────────────────────────────────────┐
                            │  NO DATABASE                             │
                            │  NO REPO                                 │
                            │  NO DEPLOYED SERVER                      │
                            │                                          │
                            │  Telegram pinned msg = memory.           │
                            │  catbox.moe hosts the asset library.    │
                            └─────────────────────────────────────────┘
```

---

# Part 3: The Services You Need

## Overview — What You're Signing Up For

```
 ╔═══════════════════════════════════════════════════════════════════════════╗
 ║  3 EXTERNAL SERVICES + CLAUDE DESKTOP                                      ║
 ║  Each gives you one piece of data. Paste all into Claude in Part 4.        ║
 ╠═══════════════════════════════════════════════════════════════════════════╣
 ║                                                                            ║
 ║    SERVICE          SIGN-UP TIME    WHAT YOU SAVE                          ║
 ║    ═══════          ═════════════   ══════════════════════════             ║
 ║                                                                            ║
 ║    Telegram Bot     2 min           TELEGRAM_BOT_TOKEN                    ║
 ║                                     TELEGRAM_CHAT_ID                       ║
 ║                                                                            ║
 ║    Composio + IG    5 min           (OAuth — no key to save)              ║
 ║                                                                            ║
 ║    Gemini API       1 min           GEMINI_API_KEY                         ║
 ║                                                                            ║
 ║    Claude Desktop   3 min install   (no key — just the app)               ║
 ║                                                                            ║
 ║    Image hosting    0 min           catbox.moe + litterbox.catbox.moe      ║
 ║    (catbox.moe)                     are anonymous — no signup, no key.     ║
 ║                                     Used inline in Part 5.                 ║
 ║                                                                            ║
 ╚═══════════════════════════════════════════════════════════════════════════╝
```

## 3.1 — Telegram Bot

**What you're making:** a bot account that acts as the agent's memory + message channel. The agent maintains its memory as a single pinned message in your chat; you and the agent both message normally beyond that.

```
                        STEP-BY-STEP FLOW
                        ═════════════════

   ┌─────────────────────────────────────────────────────────────────┐
   │  1  Open Telegram. Search @BotFather. Send /newbot               │
   └─────────────────────────────────────────────────────────────────┘
                                 │
                                 ▼
   ┌─────────────────────────────────────────────────────────────────┐
   │  2  BotFather asks for a display name                            │
   │     → type anything readable: "Marketing Agent"                  │
   └─────────────────────────────────────────────────────────────────┘
                                 │
                                 ▼
   ┌─────────────────────────────────────────────────────────────────┐
   │  3  BotFather asks for a username (must end in "bot")            │
   │     → e.g. "your_marketing_agent_bot"                            │
   └─────────────────────────────────────────────────────────────────┘
                                 │
                                 ▼
   ┌─────────────────────────────────────────────────────────────────┐
   │  4  BotFather replies with a TOKEN                               │
   │     → looks like:  1234567890:AAEsomelongstring_with_chars       │
   │                                                                  │
   │     SAVE THE TOKEN EXACTLY AS GIVEN — no `bot` prefix added.     │
   │     The system prompt's URLs prepend `bot` themselves; if you    │
   │     save `bot1234567890:AAE...` instead, every Telegram call     │
   │     hits a 404 silently.                                         │
   │                                                                  │
   │        ┌───────────────────────────────────────────────┐         │
   │        │   SAVE THIS ────────────►  TELEGRAM_BOT_TOKEN │         │
   │        └───────────────────────────────────────────────┘         │
   └─────────────────────────────────────────────────────────────────┘
                                 │
                                 ▼
   ┌─────────────────────────────────────────────────────────────────┐
   │  5  Open a chat with your new bot (BotFather links it)           │
   │     Send /start, then send any plain message like "hi"           │
   │                                                                  │
   │     (The plain message is what creates your chat record in       │
   │     getUpdates — /start alone sometimes doesn't.)                │
   └─────────────────────────────────────────────────────────────────┘
                                 │
                                 ▼
   ┌─────────────────────────────────────────────────────────────────┐
   │  6  In a browser, paste this URL (replace <TOKEN>):              │
   │                                                                  │
   │        https://api.telegram.org/bot<TOKEN>/getUpdates             │
   │                                                                  │
   │     In the JSON response, find:   "chat":{"id":<NUMBER>}         │
   │                                                                  │
   │        ┌───────────────────────────────────────────────┐         │
   │        │   SAVE THIS ────────────►  TELEGRAM_CHAT_ID   │         │
   │        └───────────────────────────────────────────────┘         │
   └─────────────────────────────────────────────────────────────────┘
```

**Verify it works.** Two checks — do both:

1. **Token format check** (`getMe` — confirms your token works AND is in the right format):

```bash
curl -s "https://api.telegram.org/bot<TOKEN>/getMe"
```

Should return JSON with `"ok":true` and your bot's info. If you see `"ok":false` or `"description":"Not Found"`, your token is wrong or has an extra `bot` prefix in it — fix the saved value.

2. **Send-message check** (confirms your chat_id is right):

```bash
curl -X POST "https://api.telegram.org/bot<TOKEN>/sendMessage" -H "Content-Type: application/json" -d '{"chat_id":<CHAT_ID>,"text":"Hello from the agent build."}'
```

Your phone should buzz with a message from the bot. If it doesn't, your chat_id is wrong — redo step 6.

## 3.2 — Composio + Instagram

**What you're making:** a secure bridge between Claude and your Instagram account. Composio handles all the Meta Graph API complexity (OAuth, token rotation, Business verification) and exposes IG as tools Claude can use.

> **No API key for Composio in this build.** The OAuth connection through Claude Desktop's Connectors panel is all you need — Composio's tools propagate from Desktop to your Routine automatically. (Some advanced production setups use a Composio API key with per-run MCP bootstrap; that's not what this tutorial teaches and you don't need it.)

```
                        STEP-BY-STEP FLOW
                        ═════════════════

    COMPOSIO SIDE                            CLAUDE DESKTOP SIDE
    ─────────────                            ────────────────────

   ┌──────────────────────┐
   │  1  Go to             │
   │     app.composio.dev  │
   │     Sign up (free)    │
   └──────────┬───────────┘
              │
              ▼
   ┌──────────────────────┐
   │  2  Search for         │
   │     "Instagram" in    │
   │     Composio. Click   │
   │     Connect           │
   └──────────┬───────────┘
              │
              ▼
   ┌──────────────────────────────────┐
   │  3  OAuth into your IG BUSINESS   │
   │     or CREATOR account.           │
   │     (If your IG is personal:      │
   │      IG mobile → Settings →       │
   │      Account type → Business.     │
   │      30 sec, reversible.)         │
   └──────────┬───────────────────────┘
              │
              ▼
   ┌──────────────────────┐
   │  4  Grant permissions:│
   │     posting, comments,│
   │     DMs                │
   └──────────┬───────────┘
              │
              ▼
   ┌──────────────────────┐
   │  5  Confirm the       │
   │     connection shows   │
   │     as ACTIVE in       │
   │     Composio dashboard │
   └──────────┬───────────┘
              │
              └─────────────────────────────────┐
                                                │
                                                ▼
                                ┌─────────────────────────────────────┐
                                │  6  Open Claude Desktop              │
                                │     → Settings → Connectors          │
                                │     → Add custom connector           │
                                │                                      │
                                │     Paste this URL:                  │
                                │     https://connect.composio.dev/mcp │
                                └───────────┬──────────────────────────┘
                                            │
                                            ▼
                                ┌─────────────────────────────────────┐
                                │  7  Claude opens an OAuth window.    │
                                │     Sign in with the same Composio   │
                                │     account from step 1. Authorize.  │
                                └───────────┬──────────────────────────┘
                                            │
                                            ▼
                                ┌─────────────────────────────────────┐
                                │  8  Composio now appears in your     │
                                │     Claude Desktop connectors list.  │
                                └─────────────────────────────────────┘
```

**Verify it works.** In any Claude Desktop chat, ask:

```
What Composio tools do you have for Instagram?
```

You should see tool names like `INSTAGRAM_POST_MEDIA`, `INSTAGRAM_LIST_COMMENTS`, `INSTAGRAM_REPLY_TO_COMMENT`, etc. If Claude says it doesn't see any Composio tools, step 6 didn't wire up — redo it.

There's no API key to save for Composio. The connection is OAuth-managed inside Claude.

## 3.3 — Gemini API Key

```
 ┌────────────────────────────────────────────────────────────────────────────┐
 │                                                                            │
 │   GEMINI                                                                   │
 │   ──────                                                                   │
 │   aistudio.google.com/apikey  →  sign in  →  Create API key  →  copy       │
 │                                                                            │
 │        SAVE ────►  GEMINI_API_KEY  (starts with "AIza...")                 │
 │                                                                            │
 └────────────────────────────────────────────────────────────────────────────┘
```

Image hosting is handled by **catbox.moe** (permanent, for your reference images) and **litterbox.catbox.moe** (72-hour temporary, for the agent's daily generated posts). Both are anonymous — no signup, no API key. You'll use them inline in Part 5; nothing to set up here.

> **Why two?** Instagram's Graph API requires a public HTTPS URL when posting media (it doesn't accept raw bytes). Some popular image hosts get rejected by Meta's fetcher; catbox + litterbox URLs work reliably. The 72-hour TTL on litterbox is fine — IG copies the asset to its own CDN at publish time, so the URL only needs to be live for the moment of publishing.

## 3.4 — Claude Desktop App

Download at **claude.ai/download**, install, sign in with your Anthropic account (Pro / Max / Team / Enterprise), and confirm **Claude Code on the web** is enabled in Settings. That's it — Desktop is the control surface for everything Claude-related from here.

---

# Part 4: Configure the Cloud Environment

A cloud environment is a named sandbox config that holds the agent's API keys and controls what the sandbox can reach. Create it once, attach it to the Routine in Part 6.

```
  Claude Desktop  →  Settings  →  Cloud Environments  →  [+ Add environment]
```

Fill in the form like this:

```
  ═══════════════════════════════════════════════════════════════════════════
  
    Name .................. Marketing Agent
  
    Network access ........ Full
  
                            — or — Custom with these four domains:
                                     api.telegram.org
                                     generativelanguage.googleapis.com
                                     catbox.moe
                                     litterbox.catbox.moe
  
    Environment vars ...... (paste block below — no quotes, one per line)
  
                            TELEGRAM_BOT_TOKEN=PASTE_YOUR_BOT_TOKEN
                            TELEGRAM_CHAT_ID=PASTE_YOUR_CHAT_ID
                            GEMINI_API_KEY=PASTE_YOUR_GEMINI_KEY
  
    Setup script .......... (leave empty)
  
  ═══════════════════════════════════════════════════════════════════════════
```

Click **Save**.

```
 ┌────────────────────────────────────────────────────────────────────────┐
 │  ⚠  Env vars are stored in plaintext and visible to anyone who can      │
 │     edit this environment. Don't add unrelated production keys here.   │
 │     If a key ever gets exposed, regenerate it at its source service.   │
 └────────────────────────────────────────────────────────────────────────┘
```

---

# Part 5: The System Prompt — The Brain

The system prompt is the agent's job description. It defines who the agent is, what it can do, what it can't, and how to decide between options during a run. It's the single most important artifact in this whole build — iterate on it until it produces work that feels like you.

## Prerequisite: Upload Your Reference Images to catbox.moe

The agent fetches reference images (logos, product screenshots, mascots, mood boards) from catbox.moe URLs listed inside the prompt itself. Upload them first, then paste the URL block into the REFERENCE LIBRARY section of the prompt.

Put all your reference images in one local folder — name each file with the tag you want the agent to use (lowercase, no spaces, use dashes):

```
~/Desktop/marketing-agent-refs/
  ├── hero.jpg
  ├── dashboard.jpg
  ├── mood-warm.png
  └── feature-q2.jpg
```

Accepted formats: `.jpg` `.jpeg` `.png` `.webp`. Each image under 200 MB (catbox cap is generous).

Open your terminal and paste the script for your OS. **No API key needed** — catbox is anonymous. Both scripts use only tools that ship with the OS plus `curl`.

**macOS / Linux:**

```bash
cd ~/Desktop/marketing-agent-refs
for f in *.jpg *.jpeg *.png *.webp; do
  [ -f "$f" ] || continue
  tmp="/tmp/_up_$f"
  cp "$f" "$tmp"
  head -c 8 /dev/urandom >> "$tmp"
  url=$(curl -s -F "reqtype=fileupload" -F "fileToUpload=@$tmp" https://catbox.moe/user/api.php)
  rm -f "$tmp"
  echo "${f%.*} | $url"
done
```

**Windows (PowerShell):**

```powershell
cd "$HOME\Desktop\marketing-agent-refs"
Get-ChildItem -File | Where-Object { $_.Extension -match '\.(jpg|jpeg|png|webp)$' } | ForEach-Object {
  $bytes = [System.IO.File]::ReadAllBytes($_.FullName)
  $suffix = New-Object byte[] 8
  (New-Object Random).NextBytes($suffix)
  $combined = New-Object byte[] ($bytes.Length + 8)
  [Array]::Copy($bytes, 0, $combined, 0, $bytes.Length)
  [Array]::Copy($suffix, 0, $combined, $bytes.Length, 8)
  $tmp = "$env:TEMP\_up_$($_.Name)"
  [System.IO.File]::WriteAllBytes($tmp, $combined)
  $url = & curl.exe -s -F "reqtype=fileupload" -F "fileToUpload=@$tmp" https://catbox.moe/user/api.php
  Remove-Item $tmp -Force -ErrorAction SilentlyContinue
  "$($_.BaseName) | $url"
}
```

> **Why the 8-byte random suffix?** catbox deduplicates by content hash, and some files hit silent content blocklists — the upload succeeds but the URL serves 0 bytes. Appending 8 random bytes to each file before upload guarantees a unique hash, sidesteps any blocklist collision. The trailing bytes sit after the PNG/JPEG end-of-image marker, so every decoder (including Gemini Nano Banana and Instagram's image handler) ignores them — your visual content is unchanged.

The script prints one line per image, ready to paste into the prompt:

```
hero       | https://files.catbox.moe/abc123.jpg
dashboard  | https://files.catbox.moe/def456.jpg
mood-warm  | https://files.catbox.moe/ghi789.png
feature-q2 | https://files.catbox.moe/jkl012.jpg
```

Catbox returns the URL as plain text in the response body — no JSON parsing needed, the script just captures the response directly.

Copy that whole block — you'll paste it into the **REFERENCE LIBRARY** section of the prompt below.

To add or remove a reference later: drop a new image in the folder + rerun the one-liner (or delete a line from the prompt). Described in detail in the final appendix.

## Anatomy of the Prompt

The prompt has 15 core sections. Most you'll customize for your brand; a few you leave exactly as-is (the API-call recipes). Here's the anatomy:

```
 ╔═══════════════════════════════════════════════════════════════════════════╗
 ║                         THE PROMPT — ANATOMY                               ║
 ╠═══════════════════════════════════════════════════════════════════════════╣
 ║                                                                            ║
 ║     [IDENTITY]                                           CUSTOMIZE         ║
 ║     Header paragraph: who you are representing          (brand, handle,    ║
 ║                                                          audience)         ║
 ║     ────────                                                               ║
 ║     YOUR JOB                                            CUSTOMIZE          ║
 ║     One-sentence optimization target                   (growth framing)    ║
 ║     ────────                                                               ║
 ║     DAILY BUDGET                                        LEAVE AS-IS        ║
 ║     Hard caps the agent can't exceed                                       ║
 ║     ────────                                                               ║
 ║     YOUR MEMORY (pinned-ledger model)                   LEAVE AS-IS        ║
 ║     Ledger read/write flow, format, edge cases                             ║
 ║     ────────                                                               ║
 ║     TOOLS                                               LEAVE AS-IS        ║
 ║     List of what the agent can call                                        ║
 ║     ────────                                                               ║
 ║     REFERENCE LIBRARY                                   CUSTOMIZE          ║
 ║     Paste your catbox `tag | url` block here           (your refs)         ║
 ║     ────────                                                               ║
 ║     PILLARS                                             CUSTOMIZE          ║
 ║     Content rotation categories                        (5 pillars +        ║
 ║                                                         percentages)       ║
 ║     ────────                                                               ║
 ║     VOICE                                               CUSTOMIZE          ║
 ║     Tone rules + say/don't-say examples                (your brand voice)  ║
 ║     ────────                                                               ║
 ║     VISUAL STRATEGY                                     CUSTOMIZE          ║
 ║     Per-pillar image branching                         (brand color,       ║
 ║                                                         vibe word)         ║
 ║     ────────                                                               ║
 ║     IMAGE GENERATION / HOSTING / POST                   LEAVE AS-IS        ║
 ║     Gemini + litterbox + Composio recipes                                  ║
 ║     ────────                                                               ║
 ║     ENGAGEMENT                                          LEAVE AS-IS        ║
 ║     Own-post reply + DM triage logic                                       ║
 ║     ────────                                                               ║
 ║     TELEGRAM REPORT FORMAT                              LEAVE AS-IS        ║
 ║     Run-end dashboard + ledger bookkeeping lines                           ║
 ║     ────────                                                               ║
 ║     HASHTAG SETS                                        CUSTOMIZE          ║
 ║     Rotating hashtag pool                              (your audience      ║
 ║                                                         hashtags)          ║
 ║     ────────                                                               ║
 ║     HARD GUARDRAILS                                     MOSTLY AS-IS       ║
 ║     Things the agent must never do                     (add brand-         ║
 ║                                                         specific NEVERs)   ║
 ║                                                                            ║
 ╚═══════════════════════════════════════════════════════════════════════════╝
```

## The Full Prompt

Copy everything between the two `=====` lines and paste into your Routine's Description field in Part 6.

```
==================================================================

You are the marketing agent for [YOUR BRAND HERE], running once a day from
an Anthropic cloud Routine. Your account is @[YOUR_INSTAGRAM_HANDLE] on
Instagram. [ONE-SENTENCE BRAND CONTEXT — what your product does, who it
serves, what's distinctive about it.]

## YOUR JOB

Grow @[YOUR_INSTAGRAM_HANDLE] into a brand [YOUR TARGET AUDIENCE] trust.
This is a job description, not a checklist — you have judgment within
bounds. When a rule and good judgment collide, pick the smaller mistake
and flag it in your report.

## DAILY BUDGET (hard caps)

- 1 Instagram post (or 0 if a "skip" pillar fires, or user said skip)
- Up to 5 comment replies on the user's last 3 IG posts
- DM triage only (flag interesting DMs to user — never auto-reply)
- Up to 3 Gemini API calls per run
- 1 Telegram report at the end

## YOUR MEMORY (pinned-ledger model)

A single pinned message in the Telegram chat is your persistent memory — you own it, you maintain it. `getUpdates` is no longer memory; it's only a recent-inbox check for new user messages since last run. (See TELEGRAM I/O below for the curl recipes referenced here.)

### Read at start

0. **Telegram precondition check** (10s timeout, must pass before any other run logic):
   `curl -s -m 10 "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/getMe"`
   Expected: response JSON with `ok: true`. If response has `ok: false` or the request 404s, the token is malformed (likely an extra `bot` prefix in the env var, or wrong token entirely). EXIT immediately — every Telegram call downstream would also fail, the report would never send, and the run would be invisible. Print the raw response in the session log so the user can debug. Do NOT proceed.
1. **Pinned ledger** (PRIMARY) — `getChat`, parse `result.pinned_message.text` and `result.pinned_message.message_id`. Bot-owned ledger = pinned-message text starts with `===[YOUR BRAND] AGENT LEDGER===`. Anything else → treat as a user directive (see Edge cases).
2. **Fresh inbox** (SECONDARY) — `getUpdates?limit=20`. New user messages since last run. Anything future-relevant MUST be mirrored into the ledger before run end or it's lost.

After parsing the ledger, **drift check**: compute time since `Last-updated`. If >30h → add `Need from you: Last run was <X>h ago — possible missed scheduled trigger.` to your report. If <20h → `Need from you: Two runs in <X>h — possible double-fire.` Catches both failure modes for free.

User's LATEST message overrides earlier directives. Skip / pause / stop → confirm, still update the ledger (record the skip), end without posting.

### Ledger format (parseable, ≤3800 chars total)

```
===[YOUR BRAND] AGENT LEDGER===
Last-updated: <ISO 8601 UTC, e.g., 2026-04-19T01:54:44Z>

---STANDING DIRECTIVES---
- <durable user guidance, deduplicated, one bullet each>

---RUN LOG (most recent first, ~30 entries max)---
<YYYY-MM-DD> | pillar:<name> | asset:<tag or generated-text-card> | trend:<≤12 words or "none">

===END LEDGER===
```

### Write at end (smart trim, not blind FIFO)

Keep today's run + last 7 days in full detail. Entries >14 days: summarize or drop if not informing rotation/avoidance. Standing directives: never silently drop, deduplicate, consolidate similar bullets. If directives section >1000 chars, ask the user in your run-end report to help consolidate.

Concrete summarization example: a week of Apr 3-7 entries can collapse to one line:
```
2026-04-03..07 | pillars:Magic×3,Pain×2,Tip×2 | assets:hero,dashboard,mood-warm,sheets,heatmap,tip1,tip2 | trends:OK
```

Build new ledger → try `editMessageText` to update the pin in place. If that fails (non-200, `ok:false`, or "can't be edited"): `sendMessage` new ledger → capture id → `pinChatMessage` new → `unpinChatMessage` old → `deleteMessage` old. (If `deleteMessage` itself fails, ignore — the unpinned old ledger is invisible from the chat header anyway.)

Include the current ledger `message_id` as the second-to-last line of your run-end report (immediately above `Need from you`).

### Edge cases

- **No pinned message** (first run): create fresh ledger with empty sections, send + pin, proceed. Report `LedgerStatus: bootstrap-created`.
- **Pinned message isn't your ledger** (e.g., user pinned their own as a directive): treat its text as a new directive, write your ledger merging it in, pin yours, unpin theirs. Report `LedgerStatus: migrated-from-user-pin`.
- **Ledger parse fails** (malformed/truncated): DO NOT overwrite. Add `Need from you: Ledger parse failed — please re-pin or clear.` to the report. Report `LedgerStatus: parse-failed`. **Also skip today's IG post** — rotation logic (asset avoidance, pillar balance) depends on a readable ledger; posting blind is worse than missing a day. Wait for acknowledgment.
- **`pinChatMessage` fails** (any 403 / "not enough rights" / similar): do NOT silently continue. Two sub-cases:
    - **First-run bootstrap:** skip today's IG post. Report `LedgerStatus: pin-failed-bootstrap`. Include `Need from you: Bot cannot pin in this chat — grant pin permission, then I'll rebuild the ledger on next run.`
    - **Edit-fallback path:** the OLD pinned ledger is still authoritative for next run. Report `LedgerStatus: pin-failed-fallback`. Include `Need from you: Ledger couldn't be re-pinned. New ledger message_id: <N>. Please pin it manually, or grant pin permission.`
    In BOTH cases, still send the daily report so the user sees what happened.

## TOOLS

- bash (curl for Telegram, Gemini, catbox/litterbox, plus base64 for payloads)
- Composio Instagram MCP (post media, list comments on own posts, reply to
  comments, list DMs)
- Telegram Bot API:
    - getMe — preflight check (Step 0 of run)
    - getChat — read pinned ledger (your memory)
    - getUpdates — read fresh user messages since last run
    - sendMessage — send your daily report + (fallback) new ledger
    - editMessageText — update the pinned ledger in place
    - pinChatMessage / unpinChatMessage / deleteMessage — fallback ledger flow
- Gemini Nano Banana — image generation (text-to-image OR image-to-image
  with one or more reference inputs)
- catbox.moe — anonymous image hosting. files.catbox.moe URLs are permanent
  (reference assets, listed in REFERENCE LIBRARY below). litterbox.catbox.moe
  URLs expire after 72h (the day's generated post — IG copies at publish, so
  72h is fine).

## REFERENCE LIBRARY

These are the brand reference images you can use as Nano Banana image inputs.
Fetch any URL via curl + base64-encode before passing to Gemini. Don't reuse
the same ref within 14 days (check `asset:` lines in the ledger run log).

[PASTE YOUR tag | url BLOCK HERE — one line per reference, from the bulk
upload script above]

Example format (replace with your actual refs):
  hero       | https://files.catbox.moe/abc123.jpg
  dashboard  | https://files.catbox.moe/def456.jpg
  mood-warm  | https://files.catbox.moe/ghi789.png

## PILLARS (soft rotation target — adjust to fit your brand)

Default mix:
- [PILLAR 1 NAME] (~35%) — [DESCRIPTION — typically: real product or
  feature in action]
- [PILLAR 2 NAME] (~30%) — [DESCRIPTION — typically: relatable audience
  pain or joy]
- [PILLAR 3 NAME] (~20%) — [DESCRIPTION — typically: a useful tip or
  insight, value-first, not product-tied]
- [PILLAR 4 NAME] (~10%) — [DESCRIPTION — typically: behind-the-scenes
  or founder content. If this requires filmed video, DON'T auto-post —
  send the user a script via Telegram and skip IG for the day.]
- [PILLAR 5 NAME] (~5%) — [DESCRIPTION — typically: announcements,
  launches, milestones. Skip if there's nothing to announce.]

Use judgment. If the last 3 posts were the same pillar, skip it. If a
specific pillar doesn't fit today (e.g., Sunday-themed pain on a Wednesday),
pick something else.

## VOICE (non-negotiable)

[CUSTOMIZE — these are the voice rules every caption must follow.
Examples below; replace with your brand's:]

- Warm, direct, [TARGET AUDIENCE]-literate. Use language they actually use.
- [PRIMARY VOICE QUALITY], not [WHAT TO AVOID].
- 1-2 emojis max per caption.
- First line = hook (this is what shows in IG preview).
- End with engagement question OR "link in bio".
- 3-5 hashtags from the sets below.
- Max 150 words.

Always avoid (regardless of brand — these break trust with any audience):
- Sycophancy ("[Audience], you're SUCH heroes!!!" — treat people like adults)
- Hyperbole ("This will BLOW YOUR MIND", "GAME-CHANGING", etc.)
- Emoji spam (more than 2 per caption, chains like 🔥🔥🔥)
- Generic AI-marketing register ("Unlock the power of...", "Revolutionary...")

Say vs don't-say (give your agent paired examples):
- "[ON-BRAND PHRASE 1]" NOT "[OFF-BRAND PHRASE 1]"
- "[ON-BRAND PHRASE 2]" NOT "[OFF-BRAND PHRASE 2]"
- "[ON-BRAND PHRASE 3]" NOT "[OFF-BRAND PHRASE 3]"

## VISUAL STRATEGY (per pillar)

Your full reference library is in the REFERENCE LIBRARY section above —
you can see every ref the user has uploaded. Pick one that works for
today, and lean toward variety: if a ref hasn't appeared in recent
`Asset:` lines from your past reports, prefer it. Novel pairings
(an unexpected ref for the pillar) often produce better posts than
always reaching for the obvious match. You don't need to force a
rotation — just don't keep picking the same 2-3 refs forever.

### Fetching the ref

Once you've picked a tag, read the matching URL from your own REFERENCE
LIBRARY above, then:

  IMG_B64=$(curl -s "<THE_URL>" | base64 -w 0)

Pass IMG_B64 as inlineData to Nano Banana (see IMAGE GENERATION CALL below).

### [PILLAR 1] — typically uses a reference asset

With your selected ref in hand:
1. Fetch the URL + base64-encode (see above)
2. Send to Nano Banana as image input with a restyle prompt:

   "Style this reference image for an Instagram post. Keep the
   recognizable subject of the image intact (do not redraw or replace
   it). Add: a soft [BRAND COLOR] gradient background, subtle drop
   shadow, bold 3-6 word hook overlay in the lower third reading
   '<HOOK>'. Aesthetic: clean, modern, [BRAND VIBE]. 1080x1080 square."

   Replace <HOOK> with a bumper-sticker phrase, 3-6 words max. Longer
   text gets mangled by Nano Banana — keep it short.

If no relevant ref exists, fall back to a text card (see PILLAR 2/3
visual strategy below).

### [PILLAR 2 / 3 / TREND-style] — text card, no product

Generate a minimalist text card. Prompt:

   "Create a 1080x1080 Instagram text card. Centered bold typography
   reading '<HOOK>'. Background: soft [BRAND COLOR] gradient with a small
   accent shape. Clean modern aesthetic, like a minimalist quote card.
   No illustrations, no characters, no other text — just typography and
   gradient."

### [PILLAR 4] — behind-the-scenes, no auto-post

Send the user a Telegram message with a 30-second Reel script and the
angle. Skip the IG post for the day. Your Telegram report still runs
(Pillar: BTS, Posted: Skipped (script sent)).

### [PILLAR 5] — announcements

Look through the last 14 days of Telegram messages for a user message
flagging a launch ("we shipped X", "new feature: Y"). If found and a
relevant ref image exists, use it. If no recent announcement signal, roll
to the next pillar.

## IMAGE GENERATION CALL

Endpoint:
  curl -s -X POST "https://generativelanguage.googleapis.com/v1beta/models/gemini-3.1-flash-image-preview:generateContent" \
    -H "Content-Type: application/json" \
    -H "X-goog-api-key: $GEMINI_API_KEY" \
    -d @payload.json

Payload for image-to-image (with a reference):
{
  "contents": [{"parts": [
    {"inlineData": {"mimeType": "image/jpeg", "data": "<BASE64_REF>"}},
    {"text": "<RESTYLE_PROMPT>"}
  ]}],
  "generationConfig": {"responseModalities": ["IMAGE", "TEXT"]}
}

Payload for text-card (no reference):
{
  "contents": [{"parts": [
    {"text": "<TEXT_CARD_PROMPT>"}
  ]}],
  "generationConfig": {"responseModalities": ["IMAGE", "TEXT"]}
}

Extract the base64 image from
candidates[0].content.parts[*].inlineData.data, decode to a temp file
(e.g. /tmp/output.png). If output is broken (empty, error, mangled text)
retry ONCE with a tweaked prompt. If still bad, skip the post and flag
in the report. Never exceed 3 Gemini calls total per run.

## IMAGE HOSTING (litterbox)

After generating the image, upload to litterbox to get a public HTTPS URL:

  curl -s -F "reqtype=fileupload" -F "time=72h" -F "fileToUpload=@/tmp/output.png" \
    https://litterbox.catbox.moe/resources/internals/api.php

The response body IS the URL (plain text, no JSON parse). Pass it directly to
Composio's IG post call. 72h TTL is fine — IG copies the asset to its CDN at
publish time, so the litterbox URL only needs to be live for the moment of
publishing.

If the upload fails, retry ONCE. If it fails again, skip today's post and flag
`Posted: Skipped (image host upload failed)` in the report.

## POST TO INSTAGRAM

Use Composio's Instagram MCP tool to post the media. Pass the litterbox URL
as the image source and your generated caption as the text. If Composio
returns an error, flag in the Telegram report — do NOT retry blindly.

## ENGAGEMENT (first-party only)

### Step 1 — comment replies on your own posts

Via Composio:
1. List your last 3 IG posts
2. For each, list comments
3. Skip any comment that already has a reply from you — Composio's comment
   data includes replies, so check natively at the API level (don't try to
   reconstruct from past reports — that's brittle).
4. For each unreplied comment from a real user (not spam, not single
   emoji, not generic "🔥🔥🔥"):
   - Read the comment, write a context-aware reply (1-2 sentences)
   - Reply via Composio
   - Log the reply in your Telegram report

Cap: max 5 replies per run. Quality > quantity.

### Step 2 — DM triage

Via Composio:
1. List unread DMs
2. For each, summarize one line: "@handle: [paraphrase of message]"
3. Send the bundle in your Telegram report
4. DO NOT auto-reply to DMs — flag for the user to handle personally
5. **If the DM endpoint returns an error or a response suggesting a permission issue** (e.g. "missing messaging scope", "no conversations found — possible reasons: missing instagram_manage_messages permission"), report `DMs: endpoint returned <brief reason> — may need permission check` rather than defaulting to "0 unread". The ambiguity matters — "truly zero DMs" and "can't read DMs" look the same at the API but need different follow-ups from the user.

## TELEGRAM REPORT FORMAT

Plain text (no parse_mode). Sent as a fresh message every run (separate from the
ledger — ledger is bot-facing memory; report is the user's dashboard).

  Posted: <brief title OR "Skipped (reason)">
  Pillar: <pillar name>
  IG caption: <full caption, OR "n/a">
  Image: <one-line description>
  Asset: <tag used, OR "generated-text-card", OR "generated">
  Replied: <N replies → @user1, @user2, @user3 (with one-line context for each)>
  DMs: <unread to triage:
    @handle1: paraphrase
    @handle2: paraphrase>
    (or "0 unread", or "endpoint returned <reason>" if Composio errors / permission issue)
  LedgerStatus: <edited | re-pinned | bootstrap-created | migrated-from-user-pin | parse-failed | pin-failed-bootstrap | pin-failed-fallback>
  LedgerMessageId: <numeric id of current pinned ledger>
  Need from you: <one task OR "Nothing today">

`Need from you:` is always the last line — never append anything after it.

## TELEGRAM I/O

> **Env var format**: `$TELEGRAM_BOT_TOKEN` is the raw token from BotFather (e.g. `1234567890:AAEsomelongstring`) — NO `bot` prefix in the saved value. Every URL below prepends `bot` itself: `https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/...`. If your env var already starts with `bot`, every call hits `botbot...` and 404s silently — the Step 0 getMe smoke test catches this.

Preflight (Step 0):       `curl -s -m 10 "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/getMe"` → expect `{"ok":true,...}`

Read pinned ledger (your primary memory):
  curl -s "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/getChat?chat_id=$TELEGRAM_CHAT_ID"
  → use result.pinned_message.text and result.pinned_message.message_id

Read recent inbox (fresh user messages since last run):
  curl -s "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/getUpdates?limit=20"

Send a message (your daily report or fallback new ledger):
  curl -s -X POST "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/sendMessage" \
    -H "Content-Type: application/json" \
    -d "{\"chat_id\":\"$TELEGRAM_CHAT_ID\",\"text\":\"YOUR_MESSAGE\"}"

Edit the pinned ledger in place:
  curl -s -X POST "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/editMessageText" \
    -H "Content-Type: application/json" \
    -d "{\"chat_id\":\"$TELEGRAM_CHAT_ID\",\"message_id\":<id>,\"text\":\"<new_text>\"}"

Pin a message (use disable_notification to avoid pinging the user):
  curl -s -X POST "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/pinChatMessage" \
    -H "Content-Type: application/json" \
    -d "{\"chat_id\":\"$TELEGRAM_CHAT_ID\",\"message_id\":<id>,\"disable_notification\":true}"

Unpin and delete (cleanup of old ledger after re-pinning a new one):
  curl -s -X POST "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/unpinChatMessage" \
    -H "Content-Type: application/json" \
    -d "{\"chat_id\":\"$TELEGRAM_CHAT_ID\",\"message_id\":<id>}"

  curl -s -X POST "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/deleteMessage" \
    -H "Content-Type: application/json" \
    -d "{\"chat_id\":\"$TELEGRAM_CHAT_ID\",\"message_id\":<id>}"

Plain text only (no parse_mode). Escape any double-quotes in message bodies.

## HASHTAG SETS (rotate)

[CUSTOMIZE — give 4-5 sets of hashtags grouped by audience segment.
Examples:]

- [Audience segment 1]: #tag1 #tag2 #tag3
- [Audience segment 2]: #tag1 #tag2 #tag3
- [General]: #tag1 #tag2 #tag3
- [Niche]: #tag1 #tag2 #tag3

Captions: 3-5 hashtags per post, mixed across sets. Rotate so you don't
hit the same hashtags every day.

## FIRST RUN / NO LEDGER

If no pinned message exists in the chat:
- Create a fresh ledger with empty STANDING DIRECTIVES + empty RUN LOG (just the header/section dividers).
- sendMessage the ledger → capture the message_id → pinChatMessage it (disable_notification=true).
- Proceed with the normal run flow (pick pillar, post, engage, etc.). Today's run becomes the first RUN LOG entry when you write the ledger at end-of-run.
- Introduce yourself in the run-end report: "Hi, this is [YOUR BRAND HERE]'s marketing agent, first live run. [normal report fields below.]"
- Report `LedgerStatus: bootstrap-created` so the user knows the pin was freshly established.
- If `pinChatMessage` fails on bootstrap, follow the pin-failure edge case in YOUR MEMORY → Edge cases.

## HARD GUARDRAILS

- NEVER exceed: 1 post/day, 5 comment replies/day, 3 Gemini calls/run
- NO posts if user's latest Telegram message says skip / pause / stop
- NO likes or comments on strangers' posts — own-surface only
- NO canned "Nice post!" / "Thanks!" / "Great content!" replies — every
  reply must reference the specific content of the comment it's replying to
- NO auto-replying to DMs — triage only
- NO content that reveals private user data, internal info, or
  competitive strategy
- NO political content, NO commentary on news events, NO controversies
- NEVER silently overwrite a ledger that fails to parse — always ask the user
- NEVER post if the ledger is in parse-failed state — rotation logic is blind without it
- NEVER silently ignore a `pinChatMessage` failure — always report and ask for permission fix
- NEVER continue past the Step 0 Telegram smoke test if `getMe` fails — exit with a clear session-log message instead

## WHEN IN DOUBT

If a tool fails twice, if a pillar doesn't fit today, if a generated
asset looks off — skip the post for today. Send the user a report
explaining what happened. Better to miss a day than post off-brand.

==================================================================
```

## Customization Checklist

Before pasting the prompt into your Routine, fill in every bracketed placeholder. Easy verification: do a `Cmd+F` / `Ctrl+F` for `[` — there should be **zero open brackets remaining** in your customized version. The Anatomy table above marks which sections need customization (CUSTOMIZE) vs leave-alone (LEAVE AS-IS).

---

# Part 6: Create the Routine

## Open the Routine Editor in Claude Desktop

```
  CLAUDE DESKTOP
  ══════════════
    │
    │   Sidebar ───► Routines  (or Schedule in some versions)
    │                   │
    │                   ▼
    │          ┌────────────────────────┐
    │          │  [+ New routine]       │
    │          └───────────┬────────────┘
    │                      │
    │                      ▼
    │          ┌────────────────────────┐
    └──────────┤  If prompted: pick     │
               │  "New remote task"     │
               │  (NOT "New local task")│
               └────────────┬───────────┘
                            │
                            ▼
               ┌────────────────────────┐
               │   ROUTINE FORM         │
               └────────────────────────┘
```

## Fill Out the Form

```
  ROUTINE FORM
  ════════════════════════════════════════════════════════════════════════════
  
    Name ............................ Marketing Agent
  
    Description (the brain) ......... (paste the customized prompt from
                                       Part 5 — every [BRACKET] filled in)
  
          ┌─ inside the Description's footer area ────────────────────┐
          │                                                            │
          │    Model selector  →  Haiku 4.5                            │
          │    (Opus works too but costs ~5× more for the same task)   │
          │                                                            │
          │    Repository      →  LEAVE EMPTY                          │
          │    (this agent has no code repo)                           │
          │                                                            │
          │    Environment     →  Marketing Agent                      │
          │    (the one you created in Part 4)                         │
          │                                                            │
          └────────────────────────────────────────────────────────────┘
  
    Trigger ......................... Schedule → Daily → <your time>
  
                                       Pick a time that catches your
                                       audience's attention window:
                                         • 7-9 AM local (morning scroll)
                                         • 4-6 PM local (post-work)
                                         • 8-10 PM local (evening wind-down)
                                       Avoid mid-afternoon — dead hours.
  
                                       Minimum interval: 1 hour.
  
    Connectors tab .................. KEEP: Composio (only)
                                       REMOVE: everything else
                                       (Slack, Linear, Drive, GitHub MCP, etc.)
  
    Permissions tab ................. Accept defaults for first run.
                                       (Tighten later once the agent works.)
  
  ════════════════════════════════════════════════════════════════════════════
```

## The Yellow Warning You'll See

```
 ┌────────────────────────────────────────────────────────────────────────┐
 │                                                                        │
 │  ⚠  Under Connectors, Claude shows a yellow warning saying:            │
 │                                                                        │
 │     "Claude can use all tools from these connectors — including        │
 │      writes — without asking for permission during runs."              │
 │                                                                        │
 │     This is LITERAL. Routines run autonomously with no permission      │
 │     prompts. Every connector you keep can be used silently.            │
 │                                                                        │
 │     → Remove every connector you don't need for this task              │
 │     → Keep only Composio                                               │
 │                                                                        │
 └────────────────────────────────────────────────────────────────────────┘
```

## Save + First Run

Click **Create** → the routine's detail page opens → click **Run now** for a one-shot run that ignores the schedule. Desktop gives you a live session URL; open it to watch every tool call, API hit, and post attempt in real time.

---

# Part 7: First Run + Iteration

## The Iteration Loop

Expect to refine the prompt over the first few runs. The loop:

```
     ┌──────────────────┐
     │   Click Run now   │
     └─────────┬────────┘
               │
               ▼
     ┌──────────────────┐
     │ Watch live session│
     │ + Telegram report │
     └─────────┬────────┘
               │
         ┌─────┴─────┐
         │           │
      Looks       Looks
      good?       off?
         │           │
         │           ▼
         │     ┌──────────────────────┐
         │     │ Pause Repeats on the  │
         │     │ routine detail page   │
         │     └──────────┬───────────┘
         │                │
         │                ▼
         │     ┌──────────────────────┐
         │     │ Edit the Description  │
         │     │ (the system prompt)   │
         │     │ — fix the specific    │
         │     │ thing that was off    │
         │     └──────────┬───────────┘
         │                │
         │                ▼
         │     ┌──────────────────────┐
         │     │ Click Run now again  │◄──── LOOP
         │     └──────────────────────┘
         │
         ▼
 ┌──────────────────────┐
 │ Re-enable Repeats    │
 │ Let it run daily     │
 └──────────────────────┘
```

## What to Watch on the First Run

```
 ┌────────────────────────────────────────────────────────────────────────────┐
 │                                                                            │
 │  LIVE SESSION (Claude Desktop)                                             │
 │  ═════════════════════════════                                             │
 │                                                                            │
 │    ☐  Agent reads Telegram chat (getUpdates call near the start)          │
 │    ☐  Agent picks a pillar and explains why in its scratchpad              │
 │    ☐  Agent calls Gemini 1-2 times (not 10)                                │
 │    ☐  Agent uploads to litterbox and gets a URL back                       │
 │    ☐  Agent calls Composio to post                                         │
 │    ☐  Agent lists comments on past posts                                  │
 │    ☐  Agent sends final Telegram report                                    │
 │                                                                            │
 │  ────────────────────────────────────────────────────────────────────     │
 │                                                                            │
 │  INSTAGRAM                                                                 │
 │  ═════════                                                                 │
 │                                                                            │
 │    ☐  A new post appears on your feed                                      │
 │    ☐  Image looks on-brand                                                │
 │    ☐  Caption reads in your voice                                          │
 │    ☐  3-5 hashtags, mixed across sets                                     │
 │                                                                            │
 │  ────────────────────────────────────────────────────────────────────     │
 │                                                                            │
 │  TELEGRAM (the bot chat)                                                   │
 │  ═══════════════════════                                                   │
 │                                                                            │
 │    Report includes these lines:                                            │
 │    ☐  Posted:     (brief title)                                           │
 │    ☐  Pillar:     (pillar name)                                           │
 │    ☐  IG caption: (full caption text)                                     │
 │    ☐  Image:      (one-line description)                                  │
 │    ☐  Asset:      (ref filename OR "generated-text-card")                 │
 │    ☐  Replied:    (lists any comment replies made)                        │
 │    ☐  DMs:        (lists unread, or "0 unread", or "endpoint               │
 │                    returned <reason>" if Composio errors)                  │
 │    ☐  Need from you: (one task OR "Nothing today")                        │
 │                                                                            │
 └────────────────────────────────────────────────────────────────────────────┘
```

## Reading the Daily Report

The report structure isn't decorative — future runs read their own past reports as memory. Every field has a purpose:

```
  FIELD            WHY IT'S THERE (how future-self uses it)
  ══════════      ══════════════════════════════════════════════════════
  Posted:          Lets future-self know what was published
  Pillar:          Tracks rotation across days
  IG caption:      Future runs check for hook/topic repetition
  Image:           Quick visual recap (describes the image)
  Asset:           Tracks which references have been used recently
                   (so they don't get reused within 14 days)
  Replied:         Tracks which comments have been replied to
                   (so the agent doesn't reply twice to the same one)
  DMs:             Lets you act on incoming DMs from your phone
  Need from you:   Calls out any blocker or question

  ⚠  If you rename a field in the prompt later, the agent loses its memory
     of past runs for that field. Add new fields freely, but don't rename
     the existing ones.
```

## Common Failures + Fixes

```
 ┌──────────────────────────────────────────────────────────────────────────────┐
 │  SYMPTOM                         LIKELY CAUSE              FIX               │
 │  ══════════                       ═════════════              ═══              │
 │                                                                              │
 │  "env var not set" error         Wrong environment         Double-check      │
 │                                   selected on routine       env is           │
 │                                                             "Marketing Agent"│
 │                                                                              │
 │  Telegram smoke test fails       Token has extra `bot`     Strip the `bot`  │
 │  on Step 0 (getMe 404)            prefix saved in env, OR   prefix from your │
 │                                   wrong token entirely      env var, OR      │
 │                                                             regenerate token │
 │                                                             via @BotFather   │
 │                                                                              │
 │  Telegram report never arrives    chat_id wrong, OR you    Re-run getUpdates │
 │  (smoke test passes)              never sent a plain msg    (Part 3.1 step 6)│
 │                                   to the bot                & re-save        │
 │                                                                              │
 │  Gemini call returns 403          API key wrong, or         Regenerate key   │
 │                                   generativelanguage API    in AI Studio;    │
 │                                   not enabled in project    check API is     │
 │                                                             enabled          │
 │                                                                              │
 │  litterbox upload fails           Network blip, or rare    Retry. If still   │
 │                                   transient catbox issue   failing, agent    │
 │                                                             skips today's    │
 │                                                             post automatic-  │
 │                                                             ally.            │
 │                                                                              │
 │  Catbox URL serves 0 bytes        Content hash hit          Re-upload with   │
 │                                   silent blocklist           the unique-hash │
 │                                                              suffix script    │
 │                                                              (Part 5)        │
 │                                                                              │
 │  Composio IG post fails           IG is personal (not       Convert to       │
 │                                   Business), or OAuth       Business in IG;  │
 │                                   token expired             reconnect in    │
 │                                                             Composio         │
 │                                                                              │
 │  Image looks generic / off-brand  No relevant ref in        Upload more refs │
 │                                   library, OR prompt too    to catbox & add  │
 │                                   generic                   to REFERENCE     │
 │                                                             LIBRARY; tighten │
 │                                                             visual prompt    │
 │                                                                              │
 │  Same pillar 3 days in a row      Agent not reading the     Verify ledger    │
 │                                   pinned ledger correctly   parses + the     │
 │                                                             pillar rotation  │
 │                                                             rule fires       │
 │                                                                              │
 │  Caption too long or too many     Voice rules too loose     Tighten VOICE    │
 │  emojis                                                     section with     │
 │                                                             stricter bounds  │
 │                                                                              │
 │  Canned "Nice post!" replies      Guardrail not strict      Add to HARD      │
 │                                   enough                    GUARDRAILS       │
 │                                                                              │
 └──────────────────────────────────────────────────────────────────────────────┘
```

---

# Part 8: Design Decisions & Gotchas

## Why It's Built This Way

```
 ┌────────────────────────────────────────────────────────────────────────────┐
 │                                                                            │
 │  PINNED-LEDGER MEMORY (no database)                                        │
 │  ──────────────────────────────────                                        │
 │  Memory is ONE pinned message in the bot's Telegram chat. Agent reads it   │
 │  via getChat at run start, edits it in place at run end.                   │
 │                                                                            │
 │  Why a pinned message instead of chat history? Telegram's getUpdates only  │
 │  retains messages for ~24 hours — chat history can't serve as durable     │
 │  memory. A pinned message persists forever and survives every getUpdates  │
 │  rotation.                                                                 │
 │                                                                            │
 │  Bonus: the ledger is editable from outside the agent. A Claude Desktop   │
 │  session can update it directly via editMessageText for urgent mid-week   │
 │  changes — no waiting for the next scheduled run.                          │
 │                                                                            │
 │  ─────────────────────────────────────────────────────────────────         │
 │                                                                            │
 │  CATBOX.MOE FOR IMAGE HOSTING (no GitHub, no signup, no API key)           │
 │  ───────────────────────────────────────────────────────────────────       │
 │  Two roles, one service family:                                            │
 │                                                                            │
 │  • catbox.moe (permanent URLs) hosts your reference images. They're         │
 │    pasted into the prompt's REFERENCE LIBRARY block as `tag | url` lines.  │
 │    Agent reads that block and curls a URL when it needs the bytes.         │
 │                                                                            │
 │  • litterbox.catbox.moe (72h TTL) hosts each generated post image. The     │
 │    72h TTL is fine because Instagram's Graph API copies the asset to its   │
 │    own CDN at publish time — the URL only needs to be live for the         │
 │    moment of the publish call.                                             │
 │                                                                            │
 │  Why catbox over alternatives like imgbb? Empirically, IG Graph API's      │
 │  fetcher rejects some popular image-host URLs with "media could not be     │
 │  fetched" errors. catbox.moe URLs work reliably. Bonus: anonymous, no      │
 │  signup, no API key, no env var to manage.                                 │
 │                                                                            │
 │  Why not Telegram's built-in file hosting? Its file URLs contain the bot   │
 │  token directly (https://api.telegram.org/file/bot<TOKEN>/<path>), which   │
 │  leaks a credential to Meta every post, and URLs are documented as valid   │
 │  only "at least 1 hour" — IG caching can break. catbox avoids both.        │
 │                                                                            │
 │  ─────────────────────────────────────────────────────────────────         │
 │                                                                            │
 │  COMPOSIO INSTEAD OF RAW META GRAPH API                                    │
 │  ──────────────────────────────────────                                    │
 │  Meta's Graph API is hostile to set up direct: Business verification,       │
 │  app review for production scopes, and OAuth dance with token rotation.    │
 │  Composio handles all of that and exposes IG as MCP tools to Claude.       │
 │  30 seconds of OAuth vs 3 hours of Meta dev portal.                        │
 │                                                                            │
 │  ─────────────────────────────────────────────────────────────────         │
 │                                                                            │
 │  FIRST-PARTY ENGAGEMENT ONLY (no spraying strangers)                       │
 │  ───────────────────────────────────────────────────                       │
 │  Meta blocks third-party LIKE endpoints for anti-spam reasons, and         │
 │  commenting on strangers' posts via API is restricted to your own posts.   │
 │  Even if you bypass via browser automation, safe volumes trigger shadow-   │
 │  bans and high volumes get the account locked. Real growth comes from     │
 │  quality posts + thoughtful replies on YOUR posts, not from spraying       │
 │  generic comments on strangers'. First-party-only is lower risk AND more  │
 │  useful.                                                                   │
 │                                                                            │
 │  ─────────────────────────────────────────────────────────────────         │
 │                                                                            │
 │  JOB-DESCRIPTION PROMPT (not a step-by-step checklist)                     │
 │  ───────────────────────────────────────────────────                       │
 │  An agent given "do X then Y then Z" runs as a brittle script. An agent   │
 │  given a goal + budget + bounds + decision framework exercises judgment.   │
 │  It picks a different pillar when the scheduled one doesn't fit. It skips │
 │  a post when the visual is off. It varies engagement tone. Same Haiku 4.5 │
 │  model; very different output quality.                                     │
 │                                                                            │
 │  ─────────────────────────────────────────────────────────────────         │
 │                                                                            │
 │  OTHER CHOICES (briefly)                                                   │
 │  ───────────────────────                                                   │
 │  • Haiku 4.5 (not Opus): caption + image-prompt + tool routing don't       │
 │    need Opus-level reasoning. Haiku is ~5× cheaper for identical quality   │
 │    on this task.                                                           │
 │  • Plain-text Telegram (no parse_mode): Markdown escaping is easy to get   │
 │    wrong in an automated run. Plain text always renders cleanly.           │
 │                                                                            │
 └────────────────────────────────────────────────────────────────────────────┘
```

## Gotchas To Watch For

```
 ┌────────────────────────────────────────────────────────────────────────────┐
 │                                                                            │
 │  ✗  Instagram personal accounts can't be auto-posted to                    │
 │     → Convert to Business or Creator BEFORE connecting Composio.           │
 │       Free, 30 seconds, reversible.                                        │
 │                                                                            │
 │  ✗  Composio MCP traffic doesn't go through the sandbox                    │
 │     → If Network Access = Custom, you do NOT need to whitelist              │
 │       composio.dev. Composio runs through Anthropic's connector            │
 │       infrastructure. Only whitelist Telegram, Gemini, and catbox.         │
 │                                                                            │
 │  ✗  Nano Banana can only handle short text overlays                        │
 │     → 3-6 word hooks work reliably. 10+ word phrases get mangled          │
 │       (typos, weird kerning, missing letters). Keep image hooks bumper-   │
 │       sticker length. Let the caption carry the story.                     │
 │                                                                            │
 │  ✗  Telegram getUpdates is a long-poll endpoint by default                 │
 │     → Without params it can hang for 90 seconds. The prompt uses           │
 │       ?limit=20 which returns immediately. (Memory lives in the            │
 │       pinned ledger via getChat, not getUpdates — see Part 5.)             │
 │                                                                            │
 │  ✗  Schedule trigger has a 1-hour minimum interval                         │
 │     → Can't set "every 30 min". For testing, just click Run now.           │
 │       For custom cadences, use /schedule update in Claude Code CLI.       │
 │                                                                            │
 │  ✗  The yellow Connectors warning is LITERAL                               │
 │     → Routines run without permission prompts. Every connector you        │
 │       keep can be used silently. Remove everything you don't need.        │
 │                                                                            │
 │  ✗  Cloud env vars are plaintext, visible to editors                       │
 │     → Anthropic has no secrets store yet. Treat the env as one-purpose.   │
 │       Don't add prod keys for unrelated services here.                     │
 │                                                                            │
 │  ✗  Routine runs count against your daily Anthropic usage cap              │
 │     → If you spam Run now 50 times in an hour you can hit your plan's    │
 │       limit. Iterate slowly during the first day.                          │
 │                                                                            │
 │  ✗  Image-to-image occasionally redraws the reference                      │
 │     → Even with "preserve the subject" in the prompt, Nano Banana          │
 │       sometimes recolors or restyles the reference. Fallback: detect bad   │
 │       output, skip the reference, post a text card.                         │
 │                                                                            │
 │  ✗  IG Graph API rate limits: ~25 posts/day, ~200 comment replies/day     │
 │     → You won't hit these at 1 post/day. If you ever scale up, mind the  │
 │       caps.                                                                │
 │                                                                            │
 └────────────────────────────────────────────────────────────────────────────┘
```

---

# Part 9: Optional Extensions

These are things to add once v1 is running stably. Each is roughly its own build session, not a v1 concern.

```
 ┌────────────────────────────────────────────────────────────────────────────┐
 │                                                                            │
 │  MULTI-PLATFORM (Composio supports way more than IG)                       │
 │  ─────────────────────────────────────────────────                         │
 │  Composio's MCP exposes connectors for Slack, Discord, X, LinkedIn,       │
 │  Reddit, Notion, Linear, Gmail, and many others. Same prompt pattern;     │
 │  swap "Instagram" for "Slack" in the visual + post-to sections. Useful   │
 │  for: announcing to a Discord community, syncing to a LinkedIn page,     │
 │  posting daily updates to a Slack channel.                                │
 │                                                                            │
 │  ─────────────────────────────────────────────────────────────────         │
 │                                                                            │
 │  REACTIVE TRIGGERS (agent fires when you message it)                      │
 │  ──────────────────────────────────────────────────                        │
 │  Add an API trigger to the same Routine. Gives you a per-routine HTTP    │
 │  endpoint. Use a free Zapier/Pipedream zap: Telegram message received →  │
 │  POST to the routine's URL. Now sending "post about X right now"         │
 │  triggers an immediate run instead of waiting for tomorrow.              │
 │                                                                            │
 │  ─────────────────────────────────────────────────────────────────         │
 │                                                                            │
 │  TREND INTEL                                                               │
 │  ───────────                                                               │
 │  Add a section to the prompt: every Tuesday, pull an industry RSS feed   │
 │  (EdSurge, TechCrunch, etc.) and flag relevant headlines as "TrendNote:" │
 │  lines in the report. Future runs see the trend and can swap in a        │
 │  reactive pillar. For social-trend intel, Apify has a hashtag analytics  │
 │  scraper actor — $5/mo credit covers ample usage.                         │
 │                                                                            │
 │  ─────────────────────────────────────────────────────────────────         │
 │                                                                            │
 │  ANALYTICS LOOP (which posts performed?)                                  │
 │  ────────────────────────────────────                                     │
 │  Composio's IG tools also expose reach + engagement metrics. Add a       │
 │  weekly Sunday job that reads metrics for the week's posts, finds the   │
 │  top performer, and adds a "Performance:" section to the report. The    │
 │  agent biases future content toward what worked.                         │
 │                                                                            │
 │  ─────────────────────────────────────────────────────────────────         │
 │                                                                            │
 │  HUMAN APPROVAL GATE (before posting goes live)                           │
 │  ──────────────────────────────────────────────                           │
 │  Have the agent generate the image + caption, send it to Telegram        │
 │  saying "Approve? Reply YES to post, NO to skip", then end the session.  │
 │  A second routine fires hourly: reads Telegram for YES replies, posts   │
 │  to IG. Adds latency but gives you final control before publish.        │
 │                                                                            │
 └────────────────────────────────────────────────────────────────────────────┘
```

---

# Appendix: The Full Daily Flow

```
                    ROUTINE FIRES (e.g., 7:00 AM)
                                │
                                ▼
                   ┌─────────────────────────┐
                   │  Read pinned ledger     │
                   │  via getChat            │
                   │  (= the agent's memory) │
                   └────────────┬────────────┘
                                │
                                ▼
                   ┌─────────────────────────┐
                   │  Read fresh inbox       │
                   │  via getUpdates         │
                   │  (user messages since   │
                   │   last run)             │
                   └────────────┬────────────┘
                                │
                                ▼
                ┌──────────────────────────────┐
                │  User said skip / pause /    │
                │  stop in latest message?     │
                └────────────┬─────────────────┘
                             │
                  YES        │        NO
            ┌────────────────┼─────────────────┐
            ▼                                   ▼
    ┌──────────────┐               ┌─────────────────────────┐
    │ Send confirm │               │  Parse history:         │
    │ Telegram msg │               │  - pillars used         │
    │ End session  │               │  - assets used in       │
    └──────────────┘               │    last 14 days         │
                                   │  - active overrides     │
                                   │  - reference assets     │
                                   │    uploaded by user     │
                                   └────────────┬────────────┘
                                                │
                                                ▼
                                   ┌─────────────────────────┐
                                   │  Pick today's pillar    │
                                   │  (rotation + judgment)  │
                                   └────────────┬────────────┘
                                                │
                              ┌─────────────────┼─────────────────┐
                              ▼                 ▼                 ▼
                       Pillar uses         Pillar is         Pillar is
                       a reference         text-card         BTS / skip-IG
                              │                 │                 │
                              ▼                 ▼                 ▼
                  ┌────────────────────┐  ┌──────────┐   ┌─────────────┐
                  │ Fetch ref URL from │  │ Generate │   │ Send script │
                  │ prompt's REFERENCE │  │ text card│   │ to user via │
                  │ LIBRARY + base64   │  │ via Nano │   │ Telegram    │
                  └─────────┬──────────┘  │ Banana   │   │ Skip IG     │
                            │             │ (text-only)  └──────┬──────┘
                            ▼             └─────┬────┘          │
                  ┌────────────────────┐        │                │
                  │ Image-to-image via │        │                │
                  │ Nano Banana with   │        │                │
                  │ restyle prompt +   │        │                │
                  │ hook overlay       │        │                │
                  └─────────┬──────────┘        │                │
                            │                   │                │
                            └─────────┬─────────┘                │
                                      │                          │
                                      ▼                          │
                          ┌─────────────────────┐                │
                          │ Upload to litterbox │                │
                          │ Get public URL      │                │
                          └──────────┬──────────┘                │
                                     │                            │
                                     ▼                            │
                          ┌─────────────────────┐                │
                          │ Write caption       │                │
                          │ (voice rules)       │                │
                          └──────────┬──────────┘                │
                                     │                            │
                                     ▼                            │
                          ┌─────────────────────┐                │
                          │ Composio: post to   │                │
                          │ Instagram           │                │
                          └──────────┬──────────┘                │
                                     │                            │
                                     └────────────┬───────────────┘
                                                  │
                                                  ▼
                                  ┌─────────────────────────────┐
                                  │  Engagement round           │
                                  │  - List comments on last 3  │
                                  │    posts                    │
                                  │  - Reply to up to 5 with    │
                                  │    context-specific text    │
                                  │  - List unread DMs (no auto │
                                  │    reply, just triage)      │
                                  └────────────┬────────────────┘
                                               │
                                               ▼
                                  ┌─────────────────────────────┐
                                  │  Update pinned ledger:      │
                                  │  - merge any new directives │
                                  │  - prepend today's run entry│
                                  │  - smart-trim if > 3800 chr │
                                  │  - editMessageText (or      │
                                  │    fallback: send + repin   │
                                  │    + delete old)            │
                                  └────────────┬────────────────┘
                                               │
                                               ▼
                                  ┌─────────────────────────────┐
                                  │  Send daily Telegram report │
                                  │  (separate from ledger;     │
                                  │  human-facing dashboard)    │
                                  └────────────┬────────────────┘
                                               │
                                               ▼
                                       Session ends.
                                       Sleeps until next scheduled fire.
```

---

# Appendix: Adding / Updating Reference Images

To add a new reference image to the agent's library:

**1. Drop the new image into your local refs folder** (e.g., `~/Desktop/marketing-agent-refs/`). Name it with the tag you want (lowercase, no spaces).

**2. Rerun the bulk-upload script** from Part 5. You can run it on the whole folder (re-uploads everything and prints the full list) or just on the new file:

```bash
# macOS / Linux — single file (with the unique-hash suffix)
cp new-ref.jpg /tmp/_up_new-ref.jpg && head -c 8 /dev/urandom >> /tmp/_up_new-ref.jpg
url=$(curl -s -F "reqtype=fileupload" -F "fileToUpload=@/tmp/_up_new-ref.jpg" https://catbox.moe/user/api.php)
rm -f /tmp/_up_new-ref.jpg
echo "new-ref | $url"
```

```powershell
# Windows PowerShell — single file (with the unique-hash suffix)
$bytes = [System.IO.File]::ReadAllBytes("new-ref.jpg")
$suffix = New-Object byte[] 8; (New-Object Random).NextBytes($suffix)
$combined = New-Object byte[] ($bytes.Length + 8)
[Array]::Copy($bytes, 0, $combined, 0, $bytes.Length); [Array]::Copy($suffix, 0, $combined, $bytes.Length, 8)
$tmp = "$env:TEMP\_up_new-ref.jpg"
[System.IO.File]::WriteAllBytes($tmp, $combined)
$url = & curl.exe -s -F "reqtype=fileupload" -F "fileToUpload=@$tmp" https://catbox.moe/user/api.php
Remove-Item $tmp -Force
"new-ref | $url"
```

**3. Copy the new line** (e.g., `new-ref | https://files.catbox.moe/abc123.jpg`).

**4. Open your Routine in Claude Desktop** → edit → find the **REFERENCE LIBRARY** section in the Description → paste the new line in → save.

---

**To remove a reference:** delete the line from the REFERENCE LIBRARY section in your Routine's Description. Save. The image stays hosted on catbox (harmless orphan), but the agent no longer sees it.

**To replace a reference:** upload the new version (use the same filename if you want the tag to stay consistent). Replace the corresponding URL line in the prompt.

**No backups to manage.** catbox keeps URLs permanent. Your single source of truth for "what refs exist" is the REFERENCE LIBRARY block inside the Routine Description.
