Skill: ugenesys/genviral-skill (version 1.0.0)

--- name: genviral description: Complete genviral Partner API automation. Create and schedule posts (video + slideshow) across TikTok, Instagram, and any supported platform. Includes slideshow generation, file uploads, template/pack management, analytics, and full content pipeline automation. homepage: https://github.com/fdarkaou/genviral-skill metadata: openclaw: emoji: "🎬" requires: bins: ["curl", "jq", "bash"] --- # genviral Partner API Skill > **TL;DR:** Wraps genviral's Partner API into 50+ bash commands. Core flow: `get-pack` → analyze images (metadata + vision) → `generate` with `pinned_images` → `render` → visual review (hard gate) → `create-post` → log to `workspace/performance/log.json`. Studio AI: `studio-models` → `studio-generate-image` (sync) or `studio-generate-video` → `studio-video-status --poll` (async). Auth via `GENVIRAL_API_KEY`. Config in `defaults.yaml`. Instance data in `workspace/`. ## What This Skill Does - **Multi-Platform Posting:** Video or slideshow posts across TikTok, Instagram, YouTube, Pinterest, LinkedIn, Facebook - **Studio AI Generation:** Generate images (sync) and videos (async) via AI models through the API - **File Management:** Upload videos/images to genviral's CDN - **AI Slideshow Generation:** Photo carousels from prompts, rendered to images - **Template System:** Reusable slideshow structures, convert winners to templates - **Pack Management:** Image packs as slideshow backgrounds - **Analytics:** KPIs, post-level metrics, tracked accounts, refresh triggers - **Niche Intelligence:** One-call trend research (`trend-brief`) for hashtags, sounds, creators, posting windows, and hook angles - **Content Pipeline:** Full automation from prompt to posted content - **Performance Tracking:** Post log, hook tracking, weekly review - **Hook Library:** Maintain and evolve a library of proven content hooks ## How It Works 1. Generate or upload media 2. Create a post targeting one or more accounts 3. Schedule or publish (for TikTok slideshows, optionally save as drafts so you can add trending audio before publishing — music selection requires human judgment for best results) 4. Track performance via analytics 5. Learn and optimize All configuration in `defaults.yaml`. Secrets via environment variables. Everything posted shows up in the Genviral dashboard. ## First-Time Setup If fresh install, read `docs/setup.md` and walk the user through onboarding conversationally: 1. Set API key and verify it works 2. List accounts and pick which to post to 3. Discuss image strategy (existing packs, create new, generate per post, or mix) 4. Optionally set up product context and brand voice together No hardcoded defaults. Ask the user what they prefer and adapt. Everything done through this skill shows up in the Genviral dashboard, so the user always has full visibility and control. ## File Structure ``` genviral/ SKILL.md # This file (kernel + routing) README.md # Human-facing overview defaults.yaml # API config and defaults docs/ setup.md # Onboarding guide (conversational, 5 phases) api/ accounts-files.md # accounts, upload, list-files posts.md # create-post, update-post, retry, list, get, delete slideshows.md # generate, render, review, update, regenerate, duplicate, list + text styles packs.md # pack CRUD + smart image selection (MANDATORY reading for any pack workflow) templates.md # template CRUD + create-from-slideshow analytics.md # all analytics commands studio.md # Studio AI: generate images/videos, list models, poll status subscription.md # subscription status, credits, tier pipeline.md # content pipeline, performance loop, CTA testing, platform tips errors.md # error codes and troubleshooting references/ analytics-loop.md # Full analytics feedback loop and weekly review process competitor-research.md # How to research competitors prompts/ slideshow.md # Prompt templates for slideshow generation hooks.md # Prompt templates for hook brainstorming workspace/ # All instance/customer data (override with GENVIRAL_WORKSPACE_DIR) content/ scratchpad.md # Working content plan and drafts calendar.json # Upcoming planned posts context/ product.md # Product description, value props, target audience brand-voice.md # Tone, style, do's and don'ts niche-research.md # Platform research for the niche hooks/ library.json # Hook instances (grows over time, tracks performance) formulas.md # Hook formula patterns and psychology performance/ log.json # CANONICAL post record (single source of truth) hook-tracker.json # Hook and CTA tracking with metrics (the feedback loop) insights.md # Agent learnings from performance data weekly-review.md # Weekly review notes competitor-insights.md # Competitor research findings scripts/ genviral.sh # Main API wrapper (all commands) update-skill.sh # Self-updater ``` ## Command Routing Load only what you need for the current task: | Task | Read | |------|------| | Account discovery, file upload | `docs/api/accounts-files.md` | | Create, update, list, delete posts | `docs/api/posts.md` | | Slideshow generation, rendering, editing, text styles | `docs/api/slideshows.md` | | Pack management, image selection (ANY pack workflow) | `docs/api/packs.md` | | Template creation and management | `docs/api/templates.md` | | Analytics queries and target management | `docs/api/analytics.md` | | Studio AI: generate images, videos, list models | `docs/api/studio.md` | | Subscription: check credits, tier, renewal dates | `docs/api/subscription.md` | | Research a specific niche quickly (trend + competitors + hooks) | `docs/api/analytics.md`, `docs/references/competitor-research.md`, `docs/prompts/hooks.md` | | Full content pipeline, performance loop, CTA testing | `docs/api/pipeline.md` | | Error codes, troubleshooting | `docs/api/errors.md` | ## Niche Research Mode (When user asks to research a niche) When asked things like "research this niche", "find what works in this niche", or "give me niche intelligence": 1. Run `trend-brief` first for the niche keyword (`7d` baseline, then `24h` for freshness if needed). 2. Extract and report: top hashtags, top sounds, top creators, posting windows (UTC), recommended hook angles. 3. Run competitor deep-dive using `docs/references/competitor-research.md` (3-5 accounts minimum). 4. Produce a short actionable output with: - 3 hook angles to test - 2 CTA suggestions - 2 best posting windows - 5 hashtags to start with - 1 "gap to exploit" insight 5. Save findings to `workspace/performance/competitor-insights.md` and use them in subsequent content prompts. ## Non-Negotiable Rules These apply regardless of what docs you've loaded: 1. **ALWAYS use `pinned_images`** when generating a slideshow with a pack. Never call `generate` with just `--pack-id` — the server will pick random images. Read `docs/api/packs.md` before any pack workflow. 2. **ALWAYS visually review every rendered slide** before posting. If any slide fails readability, fix it. This is a hard gate — not a suggestion. 3. **ALWAYS log to `workspace/performance/log.json`** immediately after posting. This is the canonical record. 4. **ALWAYS add a hook-tracker entry** after posting. No tracking = no learning. 5. **Never use em-dashes** in any generated content. 6. **Respect `workspace/`** — all instance data lives here. Do not write state files to the skill root. ## Script Usage ```bash /path/to/genviral/scripts/genviral.sh <command> [options] ``` Requires `GENVIRAL_API_KEY` as an environment variable (format: `public_id.secret`). Loads defaults from `defaults.yaml`. Set `GENVIRAL_WORKSPACE_DIR` to override the workspace path (defaults to `workspace/` relative to the skill dir). ## Auto-Updates This skill includes a self-updater that keeps skill-owned files in sync with the latest version from `fdarkaou/genviral-skill`. ```bash bash scripts/update-skill.sh # check + apply if updates available bash scripts/update-skill.sh --dry-run # preview only, no changes bash scripts/update-skill.sh --force # force re-apply even if already current ``` **What gets updated (skill-owned):** `SKILL.md`, `scripts/`, `docs/` (all subdirs) **What never gets touched (user-owned):** `workspace/` — your data, context, hooks, and performance logs are always preserved. --- ## Notes - Works with any platform genviral supports (TikTok, Instagram, etc.) - Supports both video and slideshow posts - Works with hosted and BYO accounts - Posts can be scheduled or queued for immediate publishing - TikTok slideshow drafts: use `post_mode: MEDIA_UPLOAD` to save to drafts inbox for audio addition

path: README.md
content: # Genviral Skill

An [OpenClaw](https://openclaw.ai) skill for the [Genviral](https://genviral.io) Partner API.

Generates slideshows, posts them across platforms, tracks what performs, and adjusts strategy based on real data. Every cycle makes the next one better.

## Supported Platforms

| Platform | Posting | Analytics | Notes |
|----------|---------|-----------|-------|
| **TikTok** | Yes | Yes | Slideshow carousels, videos. BYO or hosted accounts. |
| **Instagram** | Yes | Yes | Reels, stories, carousels, square/portrait/landscape posts. |
| **YouTube** | Yes | Yes | Shorts and standard video uploads. |
| **Facebook** | Yes | - | Posts, square posts, event covers. |
| **Pinterest** | Yes | - | Standard pins, square pins. |
| **LinkedIn** | Yes | - | Posts, square posts, company pages. |

Analytics (performance tracking, post metrics, account insights) is available for TikTok, Instagram, and YouTube.

## How It Works

The skill runs a closed loop:

1. **Generate** slideshows from a prompt + image pack (with deliberate image selection — no random picks)
2. **Review** every slide visually before anything gets posted (hard gate)
3. **Post** to connected accounts across any supported platform
4. **Track** performance through analytics endpoints
5. **Learn** what hooks, formats, and timing actually work
6. **Adapt** strategy weights, retire underperformers, double down on winners

## Quick Start

```bash
# Set your API key
export GENVIRAL_API_KEY="your_public_id.your_secret"

# See what accounts you have
./scripts/genviral.sh accounts

# Pull niche intelligence in one call
./scripts/genviral.sh trend-brief --keyword "indie hacker" --range 7d --limit 10

# List image packs
./scripts/genviral.sh list-packs

# Generate a slideshow (always pin images — see docs/api/packs.md)
./scripts/genviral.sh generate \
  --prompt "5 morning habits that changed my life" \
  --pack-id PACK_ID \
  --slides 5 \
  --slide-config-json '{"total_slides":5,"slide_types":["image_pack","image_pack","image_pack","image_pack","image_pack"],"pinned_images":{"0":"URL_0","1":"URL_1","2":"URL_2","3":"URL_3","4":"URL_4"}}'

# Render and post
./scripts/genviral.sh render --id SLIDESHOW_ID
./scripts/genviral.sh create-post \
  --caption "Caption with #hashtags" \
  --media-type slideshow \
  --media-urls "url1,url2,url3,url4,url5" \
  --accounts ACCOUNT_ID

# Check performance
./scripts/genviral.sh analytics-summary --range 30d
```

## Installation

Clone into your OpenClaw skills directory:

```bash
cd /path/to/your/workspace/agent/skills
git clone https://github.com/fdarkaou/genviral-skill.git genviral
```

Requires: `bash` 4+, `curl`, `jq`, and a [Genviral](https://genviral.io) account with Partner API access.

## What's Inside

```
genviral/
  SKILL.md                  # Kernel: agent entry point, routing table, non-negotiable rules
  README.md                 # This file
  setup.md                  # Conversational onboarding guide
  defaults.yaml             # API config and defaults (no secrets here)

  docs/
    api/
      accounts-files.md     # accounts, upload, list-files
      posts.md              # create-post, update-post, retry, list, get, delete
      slideshows.md         # generate, render, review, update, text styles, fonts
      packs.md              # pack CRUD + mandatory smart image selection workflow
      templates.md          # template CRUD, create-from-slideshow
      analytics.md          # analytics summary, posts, targets, refresh
      pipeline.md           # full content pipeline, performance loop, CTA testing
      errors.md             # error codes and troubleshooting

  workspace/                # Your instance data — never overwritten by updates
    content/
      scratchpad.md         # Working content plan and drafts
      calendar.json         # Upcoming planned posts
    context/
      product.md            # Product description, value props, target audience
      brand-voice.md        # Tone, style, do's and don'ts
      niche-research.md     # Platform research for the niche
    hooks/
      library.json          # Hook instances with performance data
      formulas.md           # Hook formula patterns and psychology
    performance/
      log.json              # Canonical post record (single source of truth)
      hook-tracker.json     # Hook and CTA tracking with metrics
      insights.md           # Agent learnings from performance data
      weekly-review.md      # Weekly review notes
      competitor-insights.md

  references/
    analytics-loop.md       # Full analytics feedback loop and weekly review process
    competitor-research.md  # How to research competitors

  scripts/
    genviral.sh             # 42+ commands wrapping every Partner API endpoint
    update-skill.sh         # Self-updater (keeps skill files current, never touches workspace/)

  prompts/
    slideshow.md            # Prompt templates for slideshow generation
    hooks.md                # Prompt templates for hook brainstorming
```

## Commands

| Category | Commands |
|----------|----------|
| **Accounts & Files** | `accounts`, `upload`, `list-files` |
| **Posts** | `create-post`, `update-post`, `retry-posts`, `list-posts`, `get-post`, `delete-posts` |
| **Slideshows** | `generate`, `render`, `review`, `update`, `regenerate-slide`, `duplicate`, `delete`, `list-slideshows` |
| **Packs** | `list-packs`, `get-pack`, `create-pack`, `update-pack`, `delete-pack`, `add-pack-image`, `delete-pack-image` |
| **Templates** | `list-templates`, `get-template`, `create-template`, `update-template`, `delete-template`, `create-template-from-slideshow` |
| **Analytics & Trends** | `analytics-summary`, `analytics-posts`, `analytics-targets`, `analytics-target-create`, `analytics-target-refresh`, `analytics-refresh`, `analytics-workspace-suggestions`, `trend-brief` |

```bash
./scripts/genviral.sh help  # full command list
```

## Auto-Updates

The skill can update itself without touching your data:

```bash
bash scripts/update-skill.sh           # check + apply if updates available
bash scripts/update-skill.sh --dry-run # preview only, no changes
bash scripts/update-skill.sh --force   # force re-apply even if already current
```

**Skill-owned (updated automatically):** `SKILL.md`, `scripts/`, `docs/` (all subdirs)

**User-owned (never touched):** `workspace/` — your product context, hooks, performance logs, and content data are always preserved.

## The Self-Improving Loop

The skill builds a feedback loop over time:

- **After every post:** log to `workspace/performance/log.json` + tag the hook in `hook-tracker.json`
- **At 48h and 7d:** pull analytics, fill in real metrics per post
- **Every Monday:** review last 7 days, categorize hooks (double down / keep rotating / testing / dropped), update strategy
- **Ongoing:** high performers get used more, dead angles get retired

After 4+ weeks the patterns become clear enough to make data-driven decisions about hooks, CTA types, and post timing.

## Links

- [Genviral](https://genviral.io)
- [Partner API docs](https://docs.genviral.io)
- [OpenClaw](https://openclaw.ai)

## License

MIT


path: defaults.yaml
content: # genviral Partner API Configuration
# ==================================================
# API key set via: GENVIRAL_API_KEY environment variable
# Run `genviral.sh list-accounts` to find your account IDs
# Run `genviral.sh list-packs` to find your pack IDs
# ==================================================

genviral:
  base_url: "https://www.genviral.io/api/partner/v1"

# Instance data directory (relative to skill root, or set GENVIRAL_WORKSPACE_DIR env var to override)
workspace_dir: "workspace"

content:
  # Your default image pack ID (run list-packs to find it)
  default_pack_id: ""
  default_slide_count: 5
  default_aspect_ratio: "9:16"
  default_type: "educational"
  default_style_preset: "tiktok"
  language: "en"
  advanced:
    font_size: null
    text_preset: null
    text_width: null

posting:
  # Your default account ID (run list-accounts to find it)
  default_account_ids: ""
  privacy_level: "PUBLIC_TO_EVERYONE"
  post_mode: "DIRECT_POST"

schedule:
  posts_per_day: 1
  target_timezone: "America/New_York"
  posting_times:
    - "14:00"
  active_days: [0, 1, 2, 3, 4, 5, 6]

hooks:
  formula_weights:
    pain_point: 35
    educational: 30
    person_conflict: 15
    before_after: 15
    pov_lifestyle: 5
  max_uses_per_hook: 3
  min_active_hooks: 20

hashtags:
  tiktok:
    always: []
    pool: []

performance:
  check_intervals_hours: [24, 48, 168]
  thresholds:
    winner: 5000
    solid: 500
    underperformer: 200

http:
  connect_timeout: 10
  max_time: 120
  retries: 2
  retry_delay: 2


path: docs/api/accounts-files.md
content: # Account & File Commands

## accounts
List connected BYO and hosted accounts in your scope. Use this to discover account IDs for posting.

```bash
genviral.sh accounts
genviral.sh accounts --json
```

Returns:
- Account ID (use in --accounts for create-post)
- Platform (tiktok, instagram, etc.)
- Type (byo or hosted)
- Username, display name, status

## upload
Upload a file to genviral's CDN using the presigned URL flow. Returns a CDN URL you can use in posts.

```bash
genviral.sh upload --file video.mp4 --content-type video/mp4
genviral.sh upload --file slide1.jpg --content-type image/jpeg --filename "slide1.jpg"
```

Supported content types:
- Videos: `video/mp4`, `video/quicktime`, `video/x-msvideo`, `video/webm`, `video/x-m4v`
- Images: `image/jpeg`, `image/png`, `image/gif`, `image/webp`, `image/heic`, `image/heif`

Returns the CDN URL (use in create-post).

## list-files
List files uploaded via the Partner API.

```bash
genviral.sh list-files
genviral.sh list-files --type video --limit 20 --offset 0
genviral.sh list-files --type image --context ai-studio,media-upload
genviral.sh list-files --context all  # include all contexts
genviral.sh list-files --json
```

`--type` accepts: `image` or `video`.


path: docs/api/analytics.md
content: # Analytics Commands

## analytics-summary (alias: `get-analytics-summary`)
Get analytics summary with KPIs, trends, and content mix.

```bash
genviral.sh analytics-summary
genviral.sh analytics-summary --range 30d
genviral.sh analytics-summary --start 2026-01-01 --end 2026-01-31
genviral.sh analytics-summary --platforms tiktok,instagram
genviral.sh analytics-summary --accounts TARGET_ID_1,TARGET_ID_2
genviral.sh analytics-summary --json
```

Options:
- `--range` - Date preset: `14d`, `30d`, `90d`, `1y`, `all`
- `--start` / `--end` - Custom date range (YYYY-MM-DD), must use both together
- `--platforms` - Comma-separated platform filter
- `--accounts` - Comma-separated analytics target IDs

Returns:
- `kpis` - publishedVideos, activeAccounts, views, likes, comments, shares, saves, engagementRate (with deltas)
- `interactionSeries` - Daily interactions
- `engagementSeries` - Daily engagement rate
- `postingHeatmap` - Daily post counts
- `postingStreak` - Consecutive posting days
- `contentMix` - Posts by platform

---

## analytics-posts (alias: `list-analytics-posts`)
List post-level analytics.

```bash
genviral.sh analytics-posts
genviral.sh analytics-posts --range 90d --sort-by views --sort-order desc --limit 25
genviral.sh analytics-posts --start 2026-01-01 --end 2026-01-31 --platforms tiktok
genviral.sh analytics-posts --json
```

Options:
- `--range` - Date preset: `14d`, `30d`, `90d`, `1y`, `all`
- `--start` / `--end` - Custom date range
- `--platforms`, `--accounts` - Filters
- `--sort-by` - `published_at`, `views`, `likes`, `comments`, `shares`
- `--sort-order` - `asc` or `desc`
- `--limit` (max 100), `--offset`

---

## analytics-targets
List tracked analytics accounts.

```bash
genviral.sh analytics-targets
genviral.sh analytics-targets --json
```

---

## analytics-target-create
Add a new tracked account.

```bash
genviral.sh analytics-target-create --platform tiktok --identifier @brand
genviral.sh analytics-target-create --platform instagram --identifier @brand --alias "Brand HQ"
```

Options: `--platform` (tiktok/instagram/youtube, required), `--identifier` (required), `--alias`

**Propagation note:** New targets are not always queryable instantly. Wait ~30-90 seconds after `analytics-target-create` before expecting full data population in summary/posts endpoints.

---

## analytics-target
Get details for a single analytics target.

```bash
genviral.sh analytics-target --id TARGET_ID
```

---

## analytics-target-update
```bash
genviral.sh analytics-target-update --id TARGET_ID --display-name "New Name"
genviral.sh analytics-target-update --id TARGET_ID --favorite true
genviral.sh analytics-target-update --id TARGET_ID --clear-display-name
genviral.sh analytics-target-update --id TARGET_ID --refresh-policy-json '{"freeDailyRefresh":true}'
genviral.sh analytics-target-update --id TARGET_ID --clear-refresh-policy
```

---

## analytics-target-delete
```bash
genviral.sh analytics-target-delete --id TARGET_ID
```

---

## analytics-target-refresh
Trigger a refresh for an analytics target.

```bash
genviral.sh analytics-target-refresh --id TARGET_ID
```

Returns: refresh ID, status, `wasFree`.

**Freshness note:** A refresh is asynchronous. After triggering `analytics-target-refresh`, wait ~30-90 seconds, then poll `analytics-refresh --id REFRESH_ID` until `status=completed` before trusting the updated numbers.

---

## analytics-refresh | get-analytics-refresh
Check refresh status.

```bash
genviral.sh analytics-refresh --id REFRESH_ID
```

Returns: `status` (pending/processing/completed/failed), `credits_used`, `free_refresh_used`, `started_at`, `completed_at`, `error`.

---

## analytics-workspace-suggestions (alias: `get-analytics-workspace-suggestions`)
List other workspace/personal scopes with tracked accounts.

```bash
genviral.sh analytics-workspace-suggestions
genviral.sh get-analytics-workspace-suggestions
genviral.sh analytics-workspace-suggestions --json
```

---

## trend-brief (alias: `get-trend-brief`)
Generate a one-call TikTok trend brief for a keyword (hashtags, sounds, creators, posting windows, hook angles, and sample videos).

```bash
genviral.sh trend-brief --keyword "morning routine"
genviral.sh trend-brief --keyword "fitness" --range 24h --limit 15
genviral.sh get-trend-brief --keyword "grwm" --range 30d --json
```

Options:
- `--platform` - currently only `tiktok` (default)
- `--keyword` - required trend query seed
- `--limit` - number of returned sample videos (`1..30`, default `10`)
- `--range` - `24h`, `7d`, or `30d` (default `7d`)
- `--json` - print only the `.data` payload

Returns:
- `summary.top_hashtags`, `summary.top_sounds`, `summary.top_creators`, `summary.posting_windows_utc`
- `recommendations.hook_angles`, `recommendations.hashtags`, `recommendations.sounds`
- `evidence.sample_videos`

Caching:
- Cached for ~3 hours by `platform + keyword + range`
- `limit` changes only `evidence.sample_videos` length in the final response

### Niche Research Playbook (fast)

Use this when asked to "research a niche":

```bash
# 1) Baseline trend picture
genviral.sh trend-brief --keyword "YOUR NICHE" --range 7d --limit 10

# 2) Fresh momentum check
genviral.sh trend-brief --keyword "YOUR NICHE" --range 24h --limit 10
```

Then combine with competitor deep-dive (`docs/references/competitor-research.md`) and output:
- top 5 hashtags
- top 3 sounds
- 2 best posting windows (UTC)
- 3 hook angles to test
- 1 gap competitors are missing


path: docs/api/errors.md
content: # API Error Codes & Troubleshooting

## Common Error Codes

- `401 unauthorized` - API key missing, malformed, or invalid
- `402 subscription_required` - workspace/account needs an active subscription
- `403 tier_not_allowed` - current plan tier does not include the attempted capability
- `422 invalid_payload` - request shape or enum values are invalid
- `429 rate_limited` - too many requests in a short window

---

## Troubleshooting

**"GENVIRAL_API_KEY is required"**
Export the environment variable: `export GENVIRAL_API_KEY="your_public_id.your_secret"`

**"No rendered image URLs found"**
The slideshow has not been rendered yet. Run `render` first.

**API returns 401, 402, or 403**
- `401`: verify API key format (`public_id.secret`) and token validity
- `402 subscription_required`: activate or upgrade subscription
- `403 tier_not_allowed`: your tier does not permit that feature

**Render takes too long**
Each slide takes 2-5 seconds. For 5 slides, expect up to 25 seconds.

**`update-slideshow` returns 422**
- Check for `null` values (omit fields instead of setting `null`)
- Check that `background_filters` has all 10 sub-fields
- Check that no slide has an `index` field
- Check that every slide has at minimum `image_url` and `text_elements`


path: docs/api/packs.md
content: # Pack Commands

Packs are collections of background images used in slideshows.

## list-packs
List available image packs.

```bash
genviral.sh list-packs
genviral.sh list-packs --search motivation --include-public false
genviral.sh list-packs --limit 20 --offset 0 --json
```

**`--search` is metadata-aware:** Matches across pack names AND AI image metadata (descriptions + keywords).

Returns pack summaries: `id`, `name`, `image_count`, `preview_image_url`, `is_public`, `created_at`.

---

## get-pack
Get a single pack with the full ordered image list.

```bash
genviral.sh get-pack --id PACK_ID
```

Returns top-level fields: `id`, `name`, `image_count`, `is_public`, `created_at`.

Returns `images[]` ordered by creation time, each with:
- `id`, `url`
- `metadata` — AI-generated enrichment:
  - `status`: `pending` | `processing` | `completed` | `failed`
  - `description`: One-sentence description of the image content
  - `keywords`: Array of lowercase search-friendly keywords
  - `model`, `generated_at`, `error`

**Example image with metadata:**
```json
{
  "id": "22222222-2222-2222-2222-222222222222",
  "url": "https://cdn.example.com/packs/motivation/01.jpg",
  "metadata": {
    "status": "completed",
    "description": "Woman lifting dumbbells in a bright, minimal gym environment.",
    "keywords": ["fitness", "workout", "strength", "gym", "motivation"],
    "model": "gpt-4.1-nano",
    "generated_at": "2026-02-17T11:02:00.000Z",
    "error": null
  }
}
```

---

## Smart Image Selection (MANDATORY — DO NOT SKIP)

**If you pass `--pack-id` to `generate` without `pinned_images`, the server picks random images. That produces incoherent slideshows. You MUST select images deliberately and pin them.**

### Step-by-step (required every time a pack is used)

**1. Fetch pack images:**
```bash
genviral.sh get-pack --id PACK_ID
```

**2. Use metadata to shortlist images:**
- Read each image's `metadata.description` and `metadata.keywords`
- Match images to slide topics
- If metadata `status` is `pending` or `failed`, use vision tool for those images

**When to use the vision/image tool additionally:**
- When metadata is unavailable
- When assessing **readability** (clean space for text, contrast, visual complexity)
- For rendered slide review (always — see pipeline)

Example vision call:
```
image(image="https://...", prompt="Assess for slideshow text overlay: Where is clean space? How busy is the background? What text style would be most readable?")
```

**3. Plan your slides, then match images:**
- Slide 0: Hook text
- Slide 1: Problem/setup
- Slide 2: Discovery/shift
- Slide 3: Feature/proof
- Slide 4: CTA

For each slide, pick the best-matching image. Consider topic match, text readability, visual variety.

**4. Build `pinned_images` and pass to generate:**
```bash
genviral.sh generate \
  --prompt "Your prompt" \
  --pack-id PACK_ID \
  --slides 5 \
  --slide-config-json '{
    "total_slides": 5,
    "slide_types": ["image_pack","image_pack","image_pack","image_pack","image_pack"],
    "pinned_images": {
      "0": "https://...hook-bg...",
      "1": "https://...problem-bg...",
      "2": "https://...discovery-bg...",
      "3": "https://...feature-bg...",
      "4": "https://...cta-bg..."
    }
  }'
```

**Without `pinned_images`, your visual inspection is wasted.**

#### Quick reference: what NOT to do

```bash
# BAD: server picks random images, your image analysis was pointless
genviral.sh generate --prompt "..." --pack-id PACK_ID --slides 5

# GOOD: you control which image goes on which slide
genviral.sh generate --prompt "..." --pack-id PACK_ID --slides 5 \
  --slide-config-json '{"total_slides":5,"slide_types":["image_pack","image_pack","image_pack","image_pack","image_pack"],"pinned_images":{"0":"URL_0","1":"URL_1","2":"URL_2","3":"URL_3","4":"URL_4"}}'
```

#### Alternative: `custom_images` approach
Use `custom_image` type with `custom_images` for full manual control (images AND text):
```json
{
  "total_slides": 5,
  "slide_types": ["custom_image","custom_image","custom_image","custom_image","custom_image"],
  "custom_images": {
    "0": {"image_url": "https://...", "image_name": "hook-bg"},
    "1": {"image_url": "https://...", "image_name": "problem-bg"}
  },
  "slide_texts": {"0": "your hook text", "1": "your problem text"}
}
```

Use `custom_images + --skip-ai` for full manual control. Use `pinned_images` + AI for AI-written text with your image choices.

### Choosing text styles (THINK — don't follow a rigid table)

Different backgrounds call for different text styles. Do NOT use the same style on every slide, and do NOT follow rigid rules. Use your judgment based on what you see.

**Available styles and what they do:**
- `tiktok` — White text with strong black outline/stroke. The "default" TikTok look.
- `inverted` — Black text on a white box. High contrast, cuts through anything.
- `shadow` — White text with heavy drop shadow. Subtle separation from background.
- `white` — Plain white text, minimal styling.
- `black` — Plain black text, minimal styling.
- `snapchat` — White text on a translucent dark bar.

**What to consider when choosing:**
- How busy/detailed is the background? (metadata keywords hint at this; vision tool confirms)
- What's the dominant color/brightness of the area where text lands?
- Does the slide have a clear zone for text, or is the whole image complex?
- What's the overall aesthetic of the slideshow? Consistency matters, but readability matters more.

**You have full control per slide.** Set `style_preset` per text element using `slides[].text_elements[].style_preset` in the `update-slideshow` command. Mix styles across slides when it makes sense.

**You can also adjust the background itself** with `background_filters` via `update-slideshow`. Must include all 10 sub-fields:
- Darken a busy image: `{"brightness": 0.5, "contrast": 1, "saturation": 1, "hue": 0, "blur": 0, "grayscale": 0, "sepia": 0, "invert": 0, "drop_shadow": 0, "opacity": 1}`
- Blur: same object with `"blur": 2`

**The goal is readability.** If text is hard to read at a glance, fix it — change the style, adjust the background, or pick a different image. There's no single "correct" style; the correct one is whatever makes the slide look good and the text instantly readable.

---

## create-pack
Create a new pack.

```bash
genviral.sh create-pack --name "My Pack"
genviral.sh create-pack --name "Public Pack" --is-public
genviral.sh create-pack --name "Private Pack" --is-public false
```

---

## update-pack
Update pack name or visibility.

```bash
genviral.sh update-pack --id PACK_ID --name "New Name"
genviral.sh update-pack --id PACK_ID --is-public true
```

---

## delete-pack
```bash
genviral.sh delete-pack --id PACK_ID
```

---

## add-pack-image
Add an image to a pack. AI metadata generates asynchronously — re-fetch later to get completed metadata.

```bash
genviral.sh add-pack-image --pack-id PACK_ID --image-url "https://cdn.example.com/image.jpg"
genviral.sh add-pack-image --pack-id PACK_ID --image-url "https://..." --file-name "hero-1.jpg"
```

---

## delete-pack-image
Remove an image from a pack.

```bash
genviral.sh delete-pack-image --pack-id PACK_ID --image-id IMAGE_ID
```


path: docs/api/pipeline.md
content: # Content Creation Pipeline

All post tracking goes to `workspace/performance/log.json` — this is the single source of truth.

---

## Slideshow Workflow

### 1. Hook Selection
Read `workspace/hooks/library.json` and pick a hook. Rotate through categories.

### 2. Pack Discovery
```bash
genviral.sh list-packs
genviral.sh get-pack --id PACK_ID  # get full image list with metadata
```

### 3. Analyze Pack Images (MANDATORY — DO NOT SKIP)
Read each image's AI metadata (`description`, `keywords`) from the `get-pack` response. Use the vision/image tool for readability assessment (clean space for text, contrast, complexity) or when metadata is unavailable. See `docs/api/packs.md` for full details.

### 4. Map Images to Slides
- Slide 0: Hook text
- Slide 1: Problem/setup
- Slide 2: Discovery/shift
- Slide 3: Feature/proof
- Slide 4: CTA

Assign best-matching image to each slide. Build a `pinned_images` map.

### 5. Prompt Assembly
Use the selected hook and visual direction to build a slideshow prompt. Reference `workspace/content/scratchpad.md` and `docs/prompts/slideshow.md`.

### 6. Generate WITH Pinned Images
```bash
genviral.sh generate \
  --prompt "Your prompt" \
  --pack-id PACK_ID \
  --slides 5 \
  --slide-config-json '{"total_slides":5,"slide_types":["image_pack","image_pack","image_pack","image_pack","image_pack"],"pinned_images":{"0":"URL_0","1":"URL_1","2":"URL_2","3":"URL_3","4":"URL_4"}}'
```
**Never call generate with just `--pack-id` and no `pinned_images`.** See `docs/api/packs.md`.

### 7. Review Slide Text
Check each slide for clarity, readability, and flow. Update or regenerate weak slides.

### 8. Render
```bash
genviral.sh render --id SLIDESHOW_ID
```

### 9. Visual Review + Fix Loop (MANDATORY — HARD GATE)
Visually inspect EVERY rendered slide. For each slide check:
- (a) Is text readable at a glance? Can you read it in under 2 seconds?
- (b) Does the background match your intent?
- (c) Any text overflow or clipping?

**If ANY slide fails readability: fix it before moving on.** Do not just report "needs fixing."
```bash
genviral.sh update --id SLIDESHOW_ID --slides '[...slides with adjusted style_preset, filters, etc...]'
genviral.sh render --id SLIDESHOW_ID
```
Fix options: change `style_preset`, add `background_filters` (darken/blur), swap image, adjust text position.
Re-review after each fix. **You cannot proceed to step 10 until all slides pass.**

### 10. Post
```bash
genviral.sh create-post \
  --caption "Caption with #hashtags" \
  --media-type slideshow \
  --media-urls "url1,url2,url3,url4,url5" \
  --accounts "account_id"
```

Or for TikTok drafts (add music before publishing):
```bash
genviral.sh post-draft --id SLIDESHOW_ID --caption "Caption" --account-ids "account_id"
```

### 11. Log the Post (MANDATORY)
Immediately after posting, append to `workspace/performance/log.json`:
```json
{
  "id": "post-id",
  "date": "2026-01-01",
  "time_utc": "14:32:00",
  "type": "slideshow",
  "hook": "hook text snippet",
  "caption_snippet": "first 80 chars of caption",
  "status": "posted",
  "pack_id": "...",
  "slideshow_id": "...",
  "account_id": "...",
  "platform": "tiktok",
  "metrics": {"views": null, "likes": null, "comments": null, "shares": null, "saves": null}
}
```

### 12. Tag in Hook Tracker (MANDATORY)
Add an entry to `workspace/performance/hook-tracker.json`:
```json
{
  "post_id": "...",
  "slideshow_id": "...",
  "hook_text": "...",
  "hook_category": "person-conflict|relatable-pain|educational|pov|before-after|feature-spotlight",
  "cta_text": "...",
  "cta_type": "link-in-bio|search-app-store|app-name-only|soft-cta|no-cta",
  "pack_id": "...",
  "template_id": null,
  "posted_at": "2026-02-17T10:00:00Z",
  "platform": "tiktok|instagram",
  "account_id": "...",
  "metrics": {
    "views": null, "likes": null, "comments": null,
    "shares": null, "saves": null, "last_checked": null
  },
  "status": "posted"
}
```

### 13. Performance Check (48h and 7d after posting)
```bash
genviral.sh analytics-posts --range 7d --sort-by views --sort-order desc --json
```
Match posts to hook-tracker entries by `post_id`. Update metrics, set `last_checked` to now, set `status` to `tracking`.

### 14. Weekly Review (every Monday)
Pull analytics, apply diagnostic framework, categorize hooks, write summary in `workspace/performance/weekly-review.md`. See `docs/references/analytics-loop.md`.

---

## Video Post Workflow

1. Upload via `upload` or reference existing CDN URL
2. Write caption (brand voice, hashtags)
3. `create-post --media-type video`
4. Log to `workspace/performance/log.json`

---

## Performance Feedback Loop

### Core Files
- `workspace/performance/log.json` - canonical post record
- `workspace/performance/hook-tracker.json` - hook/CTA tracking with metrics
- `workspace/performance/insights.md` - agent learnings
- `workspace/performance/weekly-review.md` - weekly review notes
- `docs/references/analytics-loop.md` - full analytics reference

### The Diagnostic Framework

Once you have views and engagement rate (likes + comments + shares + saves / views):

**High views + High engagement:** Hook and content both work. SCALE — make 3 variations now. → `double_down`

**High views + Low engagement:** Hook stops scroll, content/CTA fails. Keep hook, fix content arc or swap CTA type. → investigate

**Low views + High engagement:** Content resonates but hook doesn't stop scroll. Rework hook — more direct, more charged, more pattern-interrupting. Keep content structure.

**Low views + Low engagement:** Nothing works. Drop this angle, try something radically different.

### Decision Rules

| Views | Action |
|-------|--------|
| 50K+ | Double down. 3 variations now. → `rules.double_down` |
| 10K–50K | Keep in rotation. → `rules.keep_rotating` |
| 1K–10K | Test one more variation. → `rules.testing` |
| Under 1K, twice | Drop it. → `rules.dropped` |

"Twice" = two posts with same hook category both failed. One bad post can be distribution. Two is a pattern.

### Weekly Review (every Monday)

1. `analytics-summary --range 7d`
2. `analytics-posts --range 7d --sort-by views --sort-order desc`
3. Update hook-tracker with fresh metrics
4. Categorize each recent post
5. Check `hook_categories` aggregates: which has highest `avg_views`?
6. Check `cta_performance`: which CTA type has best `avg_engagement_rate`?
7. Decide next week's focus from data, not intuition
8. Write brief summary in `workspace/performance/weekly-review.md`

After 4+ weeks of data, patterns become clear. Before that, keep posting a variety of hook categories to build the sample size.

See `docs/references/analytics-loop.md` for the full weekly review process and template.

---

## CTA Testing Framework

### CTA Types

| Type | Example | When to Use |
|------|---------|-------------|
| `link-in-bio` | "Link in bio to try for free" | Traffic to URL |
| `search-app-store` | "Search [App Name] on the App Store" | App is the product |
| `app-name-only` | Just say the app name | Soft brand awareness |
| `soft-cta` | "Worth checking out" | When hard CTAs feel off |
| `no-cta` | Nothing | Brand-building or when CTA kills the vibe |

### How to Rotate Systematically

1. Try each CTA type 2-3 times across different posts
2. Track in hook-tracker under `cta_type`
3. After 10+ posts, check which type has highest `avg_engagement_rate`
4. Shift weight toward the winner, keep testing others occasionally

### Pairing CTAs with Hook Categories

Not all CTAs work equally well with all hook types. Track these combinations in hook-tracker.

As you accumulate data, look for patterns like:
- "Relatable-pain hooks with soft CTAs get higher engagement than relatable-pain hooks with link-in-bio"
- "Feature-spotlight hooks with search-app-store CTAs convert better than with no-cta"

After 20+ posts across categories, you will have enough data to make these calls with confidence. Until then, vary and track.

### The Goal

Identify the CTA type that converts best for each hook category. This maximizes the value of every piece of content you produce.

---

## Platform Best Practices

### TikTok
- **Slide Count:** 5-6 slides is the sweet spot
- **Aspect Ratio:** `9:16` for fullscreen, `4:5` for feed
- **Text Readability:** One idea per slide. 8-16 words max. Avoid text in bottom 20% (UI overlap)
- **Narrative Structure (5-slide arc):**
  1. Hook (stop the scroll)
  2. Problem detail
  3. Shift (introduce the solution)
  4. Feature/proof
  5. CTA

### Instagram
- **Slide Count:** 5-10 slides for carousel
- **Aspect Ratio:** Reels: `9:16` / Feed: `4:5` or `1:1`

---

## Legacy Pipeline Commands

### post-draft
Post a rendered slideshow as a TikTok draft (forces `privacy_level=SELF_ONLY`, `post_mode=MEDIA_UPLOAD`).

```bash
genviral.sh post-draft \
  --id SLIDESHOW_ID \
  --caption "Caption with #hashtags" \
  --account-ids "account_id"
```

Guardrail: the CLI warns near TikTok's pending-share cap and blocks at 5+ MEDIA_UPLOAD drafts in a rolling 24h window. Use `--force-media-upload-cap` only for intentional testing.

### full-pipeline
End-to-end: generate -> render -> review -> post draft.

```bash
genviral.sh full-pipeline \
  --prompt "Your hook and content prompt" \
  --caption "Caption with #hashtags" \
  --pack-id PACK_ID \
  --slides 5 \
  --type educational \
  --style tiktok \
  --account-ids ACC_ID
```

Use `--skip-post` to stop after rendering.


path: docs/api/posts.md
content: # Post Commands

## create-post
Create a post (video OR slideshow) targeting one or more accounts. This is the core posting command.

**Video post:**

```bash
genviral.sh create-post \
  --caption "Your caption with #hashtags" \
  --media-type video \
  --media-url "https://cdn.genviral.com/your-video.mp4" \
  --accounts "account_id_1,account_id_2" \
  --scheduled-at "2025-03-01T15:00:00Z"
```

**Slideshow post:**

```bash
genviral.sh create-post \
  --caption "Your caption" \
  --media-type slideshow \
  --media-urls "url1,url2,url3,url4,url5,url6" \
  --accounts "account_id_1" \
  --music-url "https://www.tiktok.com/@user/video/1234567890"
```

**TikTok-specific settings** (only when ALL accounts are TikTok BYO):

```bash
genviral.sh create-post \
  --caption "Caption" \
  --media-type slideshow \
  --media-urls "url1,url2,url3,url4,url5,url6" \
  --accounts "tiktok_account_id" \
  --tiktok-title "Optional title" \
  --tiktok-description "Optional description" \
  --tiktok-post-mode "MEDIA_UPLOAD" \
  --tiktok-privacy "SELF_ONLY" \
  --tiktok-disable-comment \
  --tiktok-disable-duet \
  --tiktok-disable-stitch \
  --auto-add-music true \
  --is-commercial false \
  --is-branded-content false \
  --user-consent true \
  --is-your-brand false
```

Boolean TikTok toggles support both forms:
- `--tiktok-disable-comment` (sets `true`)
- `--tiktok-disable-comment false` (explicit false)

Same behavior applies to: `--tiktok-disable-duet`, `--tiktok-disable-stitch`, `--auto-add-music`, `--is-commercial`, `--is-branded-content`, `--user-consent`, `--is-your-brand`.

TikTok `post_mode` options:
- `DIRECT_POST` - publish immediately (default)
- `MEDIA_UPLOAD` - save to TikTok drafts inbox (only supported for slideshow media)

TikTok `privacy_level` options:
- `PUBLIC_TO_EVERYONE` (default)
- `MUTUAL_FOLLOW_FRIENDS`
- `FOLLOWER_OF_CREATOR`
- `SELF_ONLY` (draft mode)

**Pinterest-specific settings** (when at least one account is Pinterest):

```bash
genviral.sh create-post \
  --caption "10 minimalist desk setups" \
  --media-type slideshow \
  --media-urls "url1,url2,url3" \
  --accounts "pinterest_account_id" \
  --pinterest-board-id "BOARD_ID" \
  --pinterest-title "Pin title (max 100 chars)" \
  --pinterest-link "https://yourblog.com/article" \
  --pinterest-tags "desk setup,minimalism,home office"
```

Pinterest options:
- `--pinterest-board-id` - Target board ID (max 128 chars). Omit to use account defaults.
- `--pinterest-title` - Pin title override (max 100 chars)
- `--pinterest-link` - Destination URL on the pin
- `--pinterest-tags` - Comma-separated topic tags (up to 30, multi-word supported)

**Important:** TikTok and Pinterest settings are mutually exclusive in one request. Use only one platform-specific block per post.

**Scheduling:**

- Omit `--scheduled-at` or set it within 30 seconds of now: post is queued for immediate publish (status: `pending`)
- Provide future ISO timestamp: post is scheduled (status: `scheduled`)
- `--scheduled-at` must be ISO 8601 with timezone offset (example: `2026-02-14T19:47:00Z`)

**Draft timing note:** For TikTok draft uploads (`post_mode=MEDIA_UPLOAD`), `pending` is normal. It means the post is queued and waiting for the posting worker (typically every ~5 minutes). `pending` can persist for a few minutes before the account state flips to `posted`. Check `accounts.states[].last_attempted_at` and `published_at` before concluding it failed.

**Draft cap guardrail:** TikTok may reject draft uploads with [REDACTED_SECRET] when you exceed ~5 pending-share uploads in a rolling 24h window. The CLI now enforces a guardrail:
- Warn at 4 uploads in last 24h per account
- Block at 5+ by default
- Override only if you really need to test: `--force-media-upload-cap` (supported in `create-post` and `post-draft`)

`--music-url` must point to a TikTok URL.

**Multi-account posting:**

You can target up to 10 accounts per post. Mix TikTok, Instagram, etc. Music is only supported when ALL accounts support it (currently TikTok only). TikTok-specific settings only work when ALL accounts are TikTok BYO.

---

## update-post
Update an existing post (only editable if status is draft, pending, scheduled, retry, or failed).

```bash
genviral.sh update-post \
  --id POST_ID \
  --caption "Updated caption" \
  --media-type video \
  --media-url "https://new-video.mp4" \
  --accounts "new_account_id_1,new_account_id_2" \
  --scheduled-at "2025-03-15T18:00:00Z"
```

Clear operations:
- Remove music: `--music-url null`
- Clear scheduled time: `--clear-scheduled-at`
- Clear all TikTok settings: `--clear-tiktok`
- Clear all Pinterest settings: `--clear-pinterest`

Pinterest update example:
```bash
genviral.sh update-post \
  --id POST_ID \
  --pinterest-board-id "NEW_BOARD_ID" \
  --pinterest-title "Updated pin title" \
  --pinterest-link "https://yourblog.com/new-article" \
  --pinterest-tags "new tag 1,new tag 2"
```

Validation notes:
- `--scheduled-at` must be ISO 8601 with timezone offset
- `--music-url` must be a TikTok URL (unless using `null` to clear)
- TikTok/Pinterest boolean toggles support both flag form and explicit values
- TikTok and Pinterest settings are mutually exclusive

---

## retry-posts
Retry failed or partial posts.

```bash
genviral.sh retry-posts --post-ids "post_id_1,post_id_2"
genviral.sh retry-posts --post-ids "post_id_1" --account-ids "account_id_1"
```

Limits:
- `post_ids`: 1-20 IDs
- `account_ids`: 1-10 IDs

---

## list-posts
List posts with optional filters.

```bash
genviral.sh list-posts
genviral.sh list-posts --status scheduled --limit 20
genviral.sh list-posts --since "2025-02-01T00:00:00Z" --until "2025-02-28T23:59:59Z"
genviral.sh list-posts --json
```

`--since` and `--until` must be ISO 8601 datetimes with timezone offset.

Status filters: `draft`, `pending`, `scheduled`, `posted`, `failed`, `partial`, `retry`

---

## get-post
Get details for a specific post.

```bash
genviral.sh get-post --id POST_ID
```

---

## delete-posts (alias: `delete-post`)
Bulk delete posts by IDs.

```bash
genviral.sh delete-posts --ids "post_id_1,post_id_2,post_id_3"
genviral.sh delete-posts --post-ids "post_id_1,post_id_2,post_id_3"
genviral.sh delete-post --ids "post_id_1,post_id_2"
```

Limit: up to 50 IDs per request.

Returns structured delete results including:
- `deletedIds`
- `blockedStatuses` (posts that can't be deleted due to status)
- `skipped`
- `errors`


path: docs/api/slideshows.md
content: # Slideshow Commands

## generate | generate-slideshow
Generate a slideshow from a prompt (AI mode), or build it manually with explicit slide config (`--skip-ai`).

```bash
# AI mode (default)
genviral.sh generate \
  --prompt "Your hook and content prompt" \
  --pack-id PACK_ID \
  --slides 5 \
  --type educational \
  --aspect-ratio 4:5 \
  --style tiktok \
  --language en \
  --font-size small \
  --text-width narrow \
  --product-id PRODUCT_ID

# Manual/mixed mode with slide_config
genviral.sh generate \
  --skip-ai \
  --slide-config-file slide-config.json

# Pass slide_config inline
genviral.sh generate \
  --skip-ai \
  --slide-config-json '{"total_slides":2,"slide_types":["image_pack","custom_image"],...}'
```

Options:
- `--prompt` -> `prompt` (required unless `--skip-ai true` or `--product-id` is provided)
- `--product-id` -> `product_id` (UUID, optional)
- `--pack-id` -> `pack_id` (UUID, optional global image pack)
- `--slides` -> `slide_count` (`1-10`, default `5`)
- `--type` -> `slideshow_type` (`educational` or `personal`)
- `--aspect-ratio` -> `aspect_ratio` (`9:16`, `4:5`, `1:1`)
- `--language` -> `language` (2-32 chars, e.g. `en`, `es`, `fr`)
- `--style` / `--text-preset` -> `advanced_settings.text_preset`
- `--font-size` -> `advanced_settings.font_size` (`default`, `small`, or numeric `8-200`)
- `--text-width` -> `advanced_settings.text_width` (`default` or `narrow`)
- `--skip-ai` -> `skip_ai` (bool)
- `--slide-config-json` / `--slide-config` -> `slide_config` (inline JSON)
- `--slide-config-file` -> `slide_config` (JSON file)

`slide_config` supports:
- `total_slides` (1-10)
- `slide_types` (exact length = `total_slides`, each `image_pack` or `custom_image`)
- `custom_images` map: `{"index": {"image_url", "image_id", "image_name?"}}` (required for each `custom_image` slide)
- `pinned_images` map: `{"index": "https://..."}`
- `slide_texts` map: `{"index": "text"}`
- `slide_text_elements` map: `{"index": [{"content", "x", "y", "id?", "font_size?", "width?"}]}`
- `pack_assignments` map: `{"index": "pack_uuid"}` (only for `image_pack` slides)

Validation rules:
- All slide-config map keys must be numeric 0-based indices in range.
- `slide_types.length` must equal `total_slides`.
- Every `image_pack` slide must resolve a pack via global `pack_id` or per-slide `pack_assignments[index]`.
- Every `custom_image` slide must have `custom_images[index]`.

---

## render | render-slideshow
Render a slideshow to images via Remotion.

```bash
genviral.sh render --id SLIDESHOW_ID
```

Returns:
- Updated slideshow with rendered image URLs
- Status: `rendered`

---

## review | get-slideshow
Get full slideshow details for review. Shows slide text, status, rendered URLs.

```bash
genviral.sh review --id SLIDESHOW_ID
genviral.sh review --id SLIDESHOW_ID --json
genviral.sh get-slideshow --id SLIDESHOW_ID  # alias
```

---

## update | update-slideshow
Update slideshow fields, settings, or slides. Re-render after updating slides.

```bash
# Update title
genviral.sh update --id SLIDESHOW_ID --title "New Title"

# Update status
genviral.sh update --id SLIDESHOW_ID --status draft

# Update settings
genviral.sh update --id SLIDESHOW_ID --settings-json '{"aspect_ratio":"9:16","advanced_settings":{"text_width":"narrow"}}'

# Update slides (full replacement)
genviral.sh update --id SLIDESHOW_ID --slides '[{"image_url":"...","text_elements":[{"content":"..."}]}]'

# Load slides from file
genviral.sh update --id SLIDESHOW_ID --slides-file slides.json

# Update product_id or clear it
genviral.sh update --id SLIDESHOW_ID --product-id NEW_PRODUCT_ID
genviral.sh update --id SLIDESHOW_ID --clear-product-id
```

Options:
- `--title` - Update title
- `--status` - `draft` or `rendered`
- `--slideshow-type` - `educational` or `personal`
- `--product-id` / `--clear-product-id` - Link or detach product
- `--settings-json` / `--settings-file` - Partial settings patch
- `--slides` / `--slides-file` - Full slides array replacement

**Critical: `--slides` payload rules (the API is strict about these):**

1. **No `null` values.** Omit any field you don't want to set.
2. **No `index` field.** Slide order is determined by array position (0-based).
3. **`background_filters` requires ALL 10 sub-fields** when present:
   ```json
   "background_filters": {
     "brightness": 0.5, "contrast": 1, "saturation": 1, "hue": 0,
     "blur": 0, "grayscale": 0, "sepia": 0, "invert": 0,
     "drop_shadow": 0, "opacity": 1
   }
   ```
   Defaults: brightness=1, contrast=1, saturation=1, hue=0, blur=0, grayscale=0, sepia=0, invert=0, drop_shadow=0, opacity=1.
4. **Each slide needs at minimum:** `image_url` and `text_elements` array (can be empty `[]`).
5. **Preserve existing fields** — `--slides` fully replaces all slides.

**Example: darken slide 0's background:**
```json
[
  {
    "image_url": "https://cdn.example.com/slide-0-bg.jpg",
    "text_elements": [{"content": "Your hook text", "x": 50, "y": 50, "font_size": 48, "width": 80, "style_preset": "tiktok"}],
    "background_filters": {"brightness": 0.5, "contrast": 1, "saturation": 1, "hue": 0, "blur": 0, "grayscale": 0, "sepia": 0, "invert": 0, "drop_shadow": 0, "opacity": 1}
  }
]
```

---

## Text Styles, Fonts, and Formatting

### Global generation controls (`advanced_settings`)
- `font_size`: `default`, `small`, or numeric `8-200`
- `text_width`: `default` (wide) or `narrow`
- `text_preset`: style preset string

`font_size` preset mapping:
- `default`: title/single `48px`, body `40px`
- `small`: title/single `40px`, body `33px`
- If user says "bigger than small" without a number, use a midpoint like `44`.

### Text presets
- `tiktok` - White text with strong black outline/stroke
- `inverted` - Black text on a white text box
- `shadow` - White text with heavy shadow
- `white` - Plain white text
- `black` - Plain black text
- `snapchat` - White text on translucent black bar

**Partner API note:** `PATCH /slideshows/{id}` `slides[].text_elements[].style_preset` validates: `tiktok`, `inverted`, `shadow`, `white`, `black`.

### Available fonts
Anton, Arial, Bebas Neue, Bitcount, Cinzel, Della, Eagle Lake, Georgia, Helvetica, Inter, Open Sans, Oswald, Playwrite, Poppins, Roboto, Russo One, TikTok Display (default), TikTok Sans, Times New Roman

Apply font per text element using `slides[].text_elements[].font_family`.

### Per-text-element formatting fields
- `content`, `x`, `y`, `font_size`, `width`, `height`
- `style_preset`, `font_family`
- `background_color`, `text_color`, `border_radius`
- `editable`

### Other slide-level visual controls
- `grid_images` + `grid_type` (`2x2`, `1+2`, `vertical`, `horizontal`)
- `background_filters` (brightness, contrast, saturation, hue, blur, grayscale, sepia, invert, drop_shadow, opacity)
- `image_overlays` (id, image_url, x, y, width, height, rotation, opacity)

---

## regenerate-slide
Regenerate AI text for a single slide (0-indexed).

```bash
genviral.sh regenerate-slide --id SLIDESHOW_ID --index 2
genviral.sh regenerate-slide --id SLIDESHOW_ID --index 2 --instruction "Make this shorter and more punchy"
```

Constraints:
- `--index` must be a non-negative integer
- `--instruction` max length: 500 characters

---

## duplicate | duplicate-slideshow
Clone an existing slideshow as a new draft.

```bash
genviral.sh duplicate --id SLIDESHOW_ID
```

---

## delete | delete-slideshow
Delete a slideshow.

```bash
genviral.sh delete --id SLIDESHOW_ID
```

---

## list-slideshows
List slideshows with filtering and pagination.

```bash
genviral.sh list-slideshows
genviral.sh list-slideshows --status rendered --search "hook" --limit 20 --offset 0
genviral.sh list-slideshows --json
```


path: docs/api/studio.md
content: # Studio AI

Generate AI images and videos through the Partner API. Same model catalog and credit rules as the Genviral web app.

All commands are key-scoped and use the standard Partner API response envelope.

## Commands

| Command | API | Description |
|---------|-----|-------------|
| `studio-models` | `GET /studio/models` | List available image/video models |
| `studio-generate-image` | `POST /studio/images/generate` | Generate an image synchronously |
| `studio-generate-video` | `POST /studio/videos/generate` | Start async video generation |
| `studio-video-status` | `GET /studio/videos/{videoId}` | Poll video generation status |

---

## studio-models

List available Studio models with capabilities, credit costs, and supported params.

```bash
genviral.sh studio-models                  # all models
genviral.sh studio-models --mode image     # image models only
genviral.sh studio-models --mode video     # video models only
genviral.sh studio-models --json           # raw JSON output
```

### Options

| Flag | Type | Description |
|------|------|-------------|
| `--mode` | `image` or `video` | Filter by model type. Omit for both |
| `--json` | flag | Output raw JSON data |

### Response Data

Each model includes:
- `id` - Model ID to use in generation requests (e.g., `google/nano-banana`)
- `mode` - `image` or `video`
- `name` - Display name
- `provider` - Provider label
- `credits.default_cost` - Credit estimate per generation
- `inputs` - Required/optional input fields
- `params` - Supported normalized params

**Always call this first** to discover model IDs and what params they support before generating.

---

## studio-generate-image

Generate one AI image synchronously. Returns hosted output URL immediately.

```bash
# Basic image generation
genviral.sh studio-generate-image \
  --model-id "google/nano-banana" \
  --prompt "A cinematic beach sunset with palm trees" \
  --aspect-ratio "16:9"

# With output format
genviral.sh studio-generate-image \
  --model-id "google/nano-banana" \
  --prompt "Minimalist product photo, white background" \
  --output-format jpeg \
  --aspect-ratio "1:1"

# With reference images
genviral.sh studio-generate-image \
  --model-id "some/model" \
  --prompt "Transform this photo into anime style" \
  --image-urls "https://example.com/photo.jpg"

# With model-specific raw params
genviral.sh studio-generate-image \
  --model-id "google/nano-banana" \
  --prompt "Abstract art" \
  --raw-params '{"seed": 42}'
```

### Options

| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--model-id` | string | Yes | Model ID from `studio-models` |
| `--prompt` | string | Yes | Generation prompt |
| `--image-urls` | string | No | Comma-separated input/reference image URLs |
| `--aspect-ratio` | string | No | e.g., `16:9`, `9:16`, `1:1` |
| `--resolution` | string | No | Target resolution |
| `--output-format` | string | No | e.g., `jpeg`, `png` |
| `--size` | string | No | Size hint |
| `--max-images` | number | No | Max images to return |
| `--scale-factor` | number | No | Upscale factor |
| `--raw-params` | JSON | No | Model-specific passthrough params |
| `--json` | flag | No | Output raw JSON data |

### Response

Returns `file_id`, `output_url`, `preview_url`, `credits_used`, `model_id`, and `provider`.

The `output_url` is a hosted CDN URL you can use directly in slideshows, posts, or download.

---

## studio-generate-video

Start async video generation. Returns a `video_id` for polling.

```bash
# Text-to-video
genviral.sh studio-generate-video \
  --model-id "openai/sora-2" \
  --prompt "A drone shot over neon city streets at night" \
  --duration-seconds 8 \
  --aspect-ratio "16:9"

# Image-to-video
genviral.sh studio-generate-video \
  --model-id "some/img2vid-model" \
  --prompt "Slowly zoom in with cinematic motion" \
  --image-url "https://cdn.example.com/photo.jpg" \
  --duration-seconds 5

# With speech/lipsync
genviral.sh studio-generate-video \
  --model-id "some/talking-model" \
  --speech-text "Welcome to our channel" \
  --voice-id "voice_abc" \
  --image-url "https://cdn.example.com/avatar.jpg"

# Video-to-video
genviral.sh studio-generate-video \
  --model-id "some/vid2vid-model" \
  --prompt "Apply cinematic color grading" \
  --video-url "https://cdn.example.com/input.mp4"
```

### Options

| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--model-id` | string | Yes | Model ID from `studio-models` |
| `--prompt` | string | No | Main generation prompt |
| `--speech-text` | string | No | Speech script for talking/lipsync models |
| `--voice-id` | string | No | Voice identifier (used with `--speech-text`) |
| `--image-url` | string | No | Input image URL for image-to-video |
| `--video-url` | string | No | Input video URL for video-to-video |
| `--audio-url` | string | No | External audio URL |
| `--negative-prompt` | string | No | Negative prompt |
| `--duration-seconds` | number | No | Video duration in seconds |
| `--aspect-ratio` | string | No | e.g., `16:9`, `9:16` |
| `--resolution` | string | No | Target resolution |
| `--fps` | number | No | Frames per second |
| `--generate-audio` | bool | No | Auto-generate audio |
| `--raw-params` | JSON | No | Model-specific passthrough params |
| `--json` | flag | No | Output raw JSON data |

### Response

Returns `video_id`, `status` (`processing`), `credits_used`, `model_id`, and `provider`.

**Important:** Video generation is async. Use `studio-video-status` to poll for completion.

---

## studio-video-status

Poll video generation status. Supports both one-shot checks and auto-polling.

```bash
# One-shot status check
genviral.sh studio-video-status --video-id "22222222-2222-2222-2222-222222222222"

# Auto-poll until done (default: every 5s, max 120s)
genviral.sh studio-video-status --video-id VIDEO_UUID --poll

# Custom poll intervals
genviral.sh studio-video-status --video-id VIDEO_UUID --poll --poll-interval 10 --poll-max 300
```

### Options

| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--video-id` | string | Yes | Video UUID from `studio-generate-video` |
| `--poll` | flag | No | Auto-poll until succeeded/failed |
| `--poll-interval` | number | No | Seconds between polls (default: 5) |
| `--poll-max` | number | No | Max seconds to poll (default: 120) |
| `--json` | flag | No | Output raw JSON data |

### Status Values

- `processing` - Still generating
- `succeeded` - Done. `output_url` available
- `failed` - Generation failed. Check `error` field

---

## Typical Workflow

### Image Generation
```bash
# 1. Check available models
genviral.sh studio-models --mode image

# 2. Generate image
genviral.sh studio-generate-image \
  --model-id "google/nano-banana" \
  --prompt "Your prompt" \
  --aspect-ratio "9:16"
# → instant output_url

# 3. Use in a slideshow or post
genviral.sh create-post \
  --caption "Check this out" \
  --media-type slideshow \
  --media-urls "OUTPUT_URL_HERE" \
  --accounts "ACCOUNT_ID"
```

### Video Generation
```bash
# 1. Check available models
genviral.sh studio-models --mode video

# 2. Start generation
genviral.sh studio-generate-video \
  --model-id "openai/sora-2" \
  --prompt "Your prompt" \
  --duration-seconds 8
# → video_id

# 3. Poll until ready
genviral.sh studio-video-status --video-id VIDEO_ID --poll
# → output_url when succeeded

# 4. Post the video
genviral.sh create-post \
  --caption "New video drop" \
  --media-type video \
  --media-url "OUTPUT_URL_HERE" \
  --accounts "ACCOUNT_ID"
```

## Input Contract

Studio uses normalized partner input (`snake_case`) through the `params` object, with optional `raw_params` for model-specific passthrough:

- **params** - stable normalized keys: `aspect_ratio`, `duration_seconds`, `resolution`, `fps`, `output_format`, `size`, `max_images`, `scale_factor`, `generate_audio`
- **raw_params** - optional advanced passthrough for model-specific controls

This keeps payloads consistent across models while allowing fine-grained control when needed.

## Error Codes

| Code | Meaning |
|------|---------|
| `401` | Auth failed (missing/invalid/revoked token) |
| `402 subscription_required` | Active Creator/Professional/Business plan required |
| `403 tier_not_allowed` | Scheduler tier cannot use Partner API |
| `403 insufficient_credits` | Not enough credits |
| `404 video_not_found` | Invalid video ID |
| `422 invalid_payload` | Bad request body or unsupported model_id |
| `422 model_requires_image` | Model needs an input image |
| `422 model_input_not_supported` | Input type not supported by model |
| `500 generation_failed` | Server-side generation error |


path: docs/api/subscription.md
content: # Subscription

Check subscription status, tier, credits usage, and renewal dates.

## Command

```bash
genviral.sh subscription              # human-friendly output
genviral.sh subscription --json       # raw JSON data
genviral.sh get-subscription          # alias
```

### Options

| Flag | Type | Description |
|------|------|-------------|
| `--json` | flag | Output raw JSON data |

## Response Data

| Field | Description |
|-------|-------------|
| `scope` | `workspace` or `personal` |
| `workspace_id` | Workspace ID (null for personal keys) |
| `subscription.status` | e.g., `active`, `canceled` |
| `subscription.tier` | `small`, `medium`, `large`, etc. |
| `subscription.billing_interval` | `month` or `year` |
| `subscription.renews_at` | Next renewal timestamp |
| `subscription.credits.limit` | Monthly credit allowance |
| `subscription.credits.used_this_period` | Credits consumed |
| `subscription.credits.remaining` | Credits available |
| `subscription.credits.reset_at` | When credits reset |

## Use Cases

- **Before generating** - Check if enough credits remain
- **Budget tracking** - Monitor usage across billing periods
- **Tier awareness** - Know what features are available

## Error Codes

| Code | Meaning |
|------|---------|
| `401` | Auth failed |
| `402 subscription_required` | No active plan |
| `403 tier_not_allowed` | Scheduler tier blocked |
| `500 subscription_lookup_failed` | Server error |


path: docs/api/templates.md
content: # Template Commands

Templates are reusable slideshow structures. Convert winning slideshows into templates for faster iteration.

## list-templates
```bash
genviral.sh list-templates
genviral.sh list-templates --search hooks --limit 20 --offset 0
genviral.sh list-templates --json
```

## get-template
```bash
genviral.sh get-template --id TEMPLATE_ID
```

## create-template
Create a template from a validated template config object.

```bash
# File input
genviral.sh create-template \
  --name "My Template" \
  --description "Description" \
  --visibility private \
  --config-file template-config.json

# Inline JSON
genviral.sh create-template \
  --name "My Template" \
  --visibility workspace \
  --config-json '{"version":1,"structure":{"slides":[]},"content":{},"visuals":{}}'
```

Use exactly one of `--config-file` or `--config-json`. Config must match the template config v1 schema.

## update-template
```bash
genviral.sh update-template --id TEMPLATE_ID --name "New Name"
genviral.sh update-template --id TEMPLATE_ID --visibility workspace
genviral.sh update-template --id TEMPLATE_ID --config-file new-config.json
genviral.sh update-template --id TEMPLATE_ID --config-json '{"version":1,"structure":{"slides":[]},"content":{},"visuals":{}}'
genviral.sh update-template --id TEMPLATE_ID --clear-description
```

Config input: use exactly one of `--config-file` or `--config-json` (not both).

## delete-template
```bash
genviral.sh delete-template --id TEMPLATE_ID
```

## create-template-from-slideshow
Convert an existing slideshow into a reusable template.

```bash
genviral.sh create-template-from-slideshow \
  --slideshow-id SLIDESHOW_ID \
  --name "Winning Format" \
  --description "Built from high-performing slideshow" \
  --visibility workspace \
  --preserve-text
```

`--preserve-text` supports both forms: `--preserve-text` (true) or `--preserve-text true|false`.


path: docs/prompts/hooks.md
content: # Hook Brainstorming Prompts

These are prompts the agent uses to generate batches of new hooks. Each prompt is designed to produce 5 to 10 hooks at once for a specific formula category.

Before using any prompt:
1. Read `workspace/context/product.md` for the product, features, and audience.
2. Read `workspace/context/brand-voice.md` for the tone and style.
3. Read `workspace/hooks/formulas.md` for the formula patterns.
4. Read `workspace/performance/insights.md` for what has worked and what has not.

Replace ALL placeholders before using.

---

## Prompt: Generate Person + Conflict Hooks

```
I need 8 TikTok slideshow hooks using the "Person + Conflict" formula for [PRODUCT NAME].

Product: [ONE-LINER DESCRIPTION]
Target audience: [TARGET AUDIENCE]
Key features: [FEATURE 1], [FEATURE 2], [FEATURE 3]
Brand voice: [VOICE DESCRIPTION]

The formula is: [Person] said [doubt or objection about the product's value]. So I showed them [what the product does]. [Reaction].

Rules:
- Each hook should name a specific person (roommate, mom, boyfriend, coworker, trainer, sister, best friend, neighbor)
- Each hook should have a different specific objection
- The objection should feel real and natural, like something that person would actually say
- Keep the slide 1 text under 15 words
- Use conversational language, no marketing speak
- Do not use em-dashes
- Vary the relationship and the objection across all 8 hooks

For each hook, provide:
1. The full hook concept (one sentence summary)
2. The exact slide 1 text (what people see first)

[OPTIONAL: If performance data exists]
These hook styles have worked well before: [TOP PERFORMING HOOKS]
These hook styles have not worked: [UNDERPERFORMING HOOKS]
Lean toward the style of the winners.
```

---

## Prompt: Generate Educational / Tips Hooks

```
I need 6 TikTok slideshow hooks using the "Educational / Tips" formula for [PRODUCT NAME].

Product: [ONE-LINER DESCRIPTION]
Problem space: [THE PROBLEM THE PRODUCT SOLVES]
Target audience: [TARGET AUDIENCE]
Brand voice: [VOICE DESCRIPTION]

The formula is: [Number] [things/mistakes/hacks] about [topic] that [surprising claim]

Rules:
- Each hook should teach something genuinely useful about [PROBLEM SPACE]
- Include a number in each hook (3, 5, or 7 work best)
- The claim should be surprising, counterintuitive, or provoke curiosity
- Keep the slide 1 text under 15 words
- The product is introduced naturally on slide 5, not in the hook itself
- Conversational tone, no jargon
- Do not use em-dashes
- Vary the topics across all hooks. Cover different angles of the problem space.

For each hook, provide:
1. The full hook concept
2. The exact slide 1 text
3. Brief notes on what the 3 tips would be
```

---

## Prompt: Generate Before / After Hooks

```
I need 5 TikTok slideshow hooks using the "Before / After" formula for [PRODUCT NAME].

Product: [ONE-LINER DESCRIPTION]
Key benefits: [BENEFIT 1], [BENEFIT 2], [BENEFIT 3]
Target audience: [TARGET AUDIENCE]
Brand voice: [VOICE DESCRIPTION]

The formula is: [Metric or situation] before [product] vs after. [Specific improvement].

Rules:
- Each hook should focus on a different benefit or metric
- Include specific, believable numbers or timelines where possible
- The "before" state should be universally relatable for the target audience
- The "after" state should be aspirational but realistic
- Keep the slide 1 text under 15 words
- Conversational tone
- Do not use em-dashes
- Avoid exaggeration. Understated honesty beats hype.

For each hook, provide:
1. The full hook concept
2. The exact slide 1 text
3. The specific before/after contrast
```

---

## Prompt: Generate Relatable Pain Point Hooks

```
I need 6 TikTok slideshow hooks using the "Relatable Pain Point" formula for [PRODUCT NAME].

Product: [ONE-LINER DESCRIPTION]
Problems it solves: [PROBLEM 1], [PROBLEM 2], [PROBLEM 3]
Target audience: [TARGET AUDIENCE]
Brand voice: [VOICE DESCRIPTION]

The formula is: That moment when [universal frustration]... [expansion] [plot twist: there is a fix]

Rules:
- Each hook should describe a frustration that the target audience experiences regularly
- The frustration should be specific and vivid, not vague
- Use "that moment when", "POV:", "when you realize", or similar framing
- Humor is encouraged but not required. Empathy works too.
- The product appears on slide 4 or 5, not in the hook itself
- Keep the slide 1 text under 15 words
- Conversational, authentic tone
- Do not use em-dashes
- These hooks should make people want to tag a friend or comment "this is me"

For each hook, provide:
1. The full hook concept
2. The exact slide 1 text
3. The specific pain point being addressed
```

---

## Prompt: Generate POV / Lifestyle Hooks

```
I need 4 TikTok slideshow hooks using the "POV / Lifestyle" formula for [PRODUCT NAME].

Product: [ONE-LINER DESCRIPTION]
Lifestyle benefits: [HOW THE PRODUCT IMPROVES DAILY LIFE]
Target audience: [TARGET AUDIENCE]
Brand voice: [VOICE DESCRIPTION]

The formula is: POV: You [aspirational or relatable scenario that the product enables]

Rules:
- Each hook should paint a vivid, desirable scenario
- The product should be a natural part of the lifestyle, not the main focus
- The viewer should be able to imagine themselves in the scene
- Mix aspirational scenarios (things people want) with relatable ones (things people do)
- Keep the slide 1 text under 15 words
- The tone should feel like a daydream or a calm, satisfying moment
- Do not use em-dashes

For each hook, provide:
1. The full hook concept
2. The exact slide 1 text
3. The lifestyle scenario in 1 to 2 sentences
```

---

## Prompt: Refresh Hooks Based on Performance

Use this when updating the hook library after a weekly review.

```
Based on performance data, I need to refresh the hook library for [PRODUCT NAME].

Product: [ONE-LINER DESCRIPTION]
Target audience: [TARGET AUDIENCE]

Top performing hooks (model these):
[LIST TOP 3-5 HOOKS WITH THEIR VIEW COUNTS]

Underperforming hooks (avoid these patterns):
[LIST BOTTOM 3-5 HOOKS WITH THEIR VIEW COUNTS]

Analysis of what works:
[INSIGHTS FROM workspace/performance/insights.md]

Generate 10 new hooks:
- 4 using the Person + Conflict formula (varied from winners)
- 2 using Educational / Tips
- 2 using Relatable Pain Point
- 1 using Before / After
- 1 using POV / Lifestyle

Rules:
- Each hook should feel fresh, not a copy of an existing winner
- Lean into the patterns that work, avoid the patterns that fail
- Vary the angle, person, and situation across all hooks
- Keep slide 1 text under 15 words
- Conversational, authentic, human-sounding
- Do not use em-dashes

For each hook, provide:
1. Formula category
2. The full hook concept
3. The exact slide 1 text
4. Why this hook should work (based on the performance patterns)
```

---

## Usage Notes

- Generate hooks in batches of 5 to 10 at a time for efficiency.
- Always review generated hooks against the quality criteria in `workspace/hooks/formulas.md` before adding to the library.
- Present the top candidates to the user for feedback before finalizing.
- Track which prompts produce the best hooks over time and note it in `workspace/performance/insights.md`.
- Refresh the library weekly. Target 20 to 40 active hooks at all times.


path: docs/prompts/slideshow.md
content: # Slideshow Prompt Templates

Use these templates when generating slideshows via the genviral API. Before using any template:

1. Read `workspace/context/product.md` to know the product, features, and audience.
2. Read `workspace/context/brand-voice.md` to match the correct tone.
3. Replace ALL placeholders with product-specific content. Never send placeholders to the API.

## Mandatory Pack Image Selection Workflow

If you are using a pack for slideshow backgrounds, this is required. **Do not skip any step.**

1. Run `genviral.sh get-pack --id PACK_ID` and collect all `images[].url` and `images[].metadata` values.
2. **Use AI metadata first:** Read each image's `metadata.description` and `metadata.keywords` to understand what it shows. This lets you shortlist candidates without fetching every image visually.
3. **Use vision tools when needed:** For images with `pending`/`failed` metadata, or when you need to assess readability (clean space, background complexity, text overlay suitability), use a vision/image tool.
4. Plan your slides first (hook text, body text per slide), then map the best-matching image to each slide based on metadata + visual analysis.
5. Consider for each match:
   - Does the image's description/keywords relate to the slide's message?
   - Is there space for readable text? (vision tool helps here)
   - Is the composition visually appealing?
6. Do not pick randomly and do not reuse near-identical images unless deliberate.
7. **Use `pinned_images` in `slide_config`** when calling `generate` to force the server to use your chosen images. Without `pinned_images`, the server picks randomly from the pack and your analysis is wasted. See SKILL.md "Smart Image Selection From Packs" for the exact command format.

## Template 1: Person + Conflict (6 slides)

Best for: Person + conflict hooks where someone doubts the product and gets proven wrong.

```
Create a 6-slide TikTok photo carousel about [PRODUCT NAME], [ONE SENTENCE PRODUCT DESCRIPTION].

Story arc:
Slide 1 (Hook): "[PERSON] said [DOUBT/OBJECTION]..."
Slide 2 (Setup): Show why they thought that. What was the problem or misconception?
Slide 3 (Discovery): "So I showed them [PRODUCT]" or "Then I opened the app"
Slide 4 (Feature): Show the specific feature that solves the problem. [FEATURE DESCRIPTION]
Slide 5 (Result): The transformation. "[PERSON]'s reaction" or "Now they [POSITIVE OUTCOME]"
Slide 6 (CTA): "[CTA TEXT - e.g., Link in bio, Try it free]"

Rules:
- Keep text short. Max 15 words per slide.
- Use [BRAND VOICE DESCRIPTION - e.g., casual, conversational] language. Talk like a friend, not a brand.
- Never use corporate jargon or marketing speak.
- Slide 1 must create curiosity. Make them want to swipe.
- Each slide should be readable in under 3 seconds.
- Do not use em-dashes.
```

### How to fill this template:

Replace:
- `[PRODUCT NAME]` with the product name from product.md
- `[ONE SENTENCE PRODUCT DESCRIPTION]` with the one-liner from product.md
- `[PERSON]` with a relatable character (roommate, mom, friend, coworker, etc.)
- `[DOUBT/OBJECTION]` with a specific objection related to the product's problem space
- `[FEATURE DESCRIPTION]` with a key feature from product.md
- `[CTA TEXT]` with the standard CTA
- `[BRAND VOICE DESCRIPTION]` with the voice from brand-voice.md

---

## Template 2: Educational / Tips (6 slides)

Best for: Listicle-style content teaching useful tips related to the product's problem space.

```
Create a 6-slide TikTok photo carousel with tips about [TOPIC RELATED TO PRODUCT].

Topic: [SPECIFIC TOPIC]

Slide 1 (Hook): "[HOOK - attention-grabbing claim, number, or question]"
Slide 2 (Tip 1): First tip with a brief, specific explanation
Slide 3 (Tip 2): Second tip with a brief, specific explanation
Slide 4 (Tip 3): Third tip with a brief, specific explanation
Slide 5 (Solution): "Or just use [PRODUCT] to [BENEFIT SUMMARY]" - introduce the product naturally
Slide 6 (CTA): "[CTA TEXT]"

Rules:
- Each tip should be specific and actionable, not obvious
- Use surprising facts or counterintuitive advice when possible
- The product mention on slide 5 should feel like a natural conclusion, not a hard sell
- Keep text short. Max 15 words per slide.
- [BRAND VOICE DESCRIPTION] tone throughout
- Do not use em-dashes.
```

---

## Template 3: Before / After (6 slides)

Best for: Transformation content showing the impact of using the product.

```
Create a 6-slide TikTok photo carousel showing a before/after transformation with [PRODUCT NAME].

Topic: [TRANSFORMATION TOPIC - e.g., time saved, money saved, stress reduced, results improved]

Slide 1 (Hook): "[BEFORE vs AFTER claim with specific metric or situation]"
Slide 2 (Before - Problem): What it looked like before. Be specific with numbers or situations.
Slide 3 (Before - Pain): The frustration, waste, or cost of the old way
Slide 4 (The Change): "Then I started using [PRODUCT]" - simple, no hype
Slide 5 (After - Result): The measurable improvement. Use specific numbers or clear outcomes.
Slide 6 (CTA): "Try it yourself. [CTA TEXT]"

Rules:
- Be specific. Numbers and timelines beat vague claims.
- The "before" should be universally relatable in the target audience.
- The "after" should be believable, not exaggerated.
- Keep text short. Max 15 words per slide.
- [BRAND VOICE DESCRIPTION] tone.
- Do not use em-dashes.
```

---

## Template 4: Relatable Pain Point (6 slides)

Best for: Content about universal frustrations that drives comments and shares.

```
Create a 6-slide TikTok photo carousel about a relatable frustration that [PRODUCT NAME] solves.

Pain point: [SPECIFIC FRUSTRATION that the target audience experiences]

Slide 1 (Hook): "[POV or 'That moment when' statement that everyone in the audience relates to]"
Slide 2 (The Frustration): Expand on the feeling. Make it vivid and specific.
Slide 3 (It Gets Worse): The consequences, the spiral, or the embarrassing moment
Slide 4 (Plot Twist): "But then I found [PRODUCT]" or "There is actually a fix for this"
Slide 5 (The Fix): How [PRODUCT] specifically solves this exact pain point. [FEATURE]
Slide 6 (CTA): Soft CTA. "Changed my life honestly. [CTA TEXT]"

Rules:
- The first 3 slides should feel like a meme or relatable rant
- The product introduction should feel like sharing a genuine discovery with a friend
- Use humor where it fits naturally for the brand voice
- Keep text short. Max 15 words per slide.
- [BRAND VOICE DESCRIPTION] tone.
- Do not use em-dashes.
```

---

## Template 5: POV / Lifestyle (5 to 6 slides)

Best for: Aspirational or relatable scenario content that builds the brand.

```
Create a [5-6]-slide TikTok photo carousel with a POV scenario related to [PRODUCT NAME].

Scenario: [ASPIRATIONAL or RELATABLE SCENARIO that the product enables]

Slide 1 (Hook): "POV: You [scenario that hooks the viewer]"
Slide 2 (Scene): Set the scene with a specific, vivid detail
Slide 3 (Experience): Show the experience unfolding naturally
Slide 4 (Payoff): The satisfying moment or positive outcome
Slide 5 (Product): [PRODUCT] shown naturally as part of the lifestyle (not the focus)
Slide 6 (Optional CTA): Soft CTA or just let it end naturally

Rules:
- Focus on the experience, not the product
- The product should feel woven into the lifestyle, not selling
- Keep it aspirational but believable
- Keep text short. Max 15 words per slide.
- [BRAND VOICE DESCRIPTION] tone.
- Do not use em-dashes.
```

---

## Template 6: Quick Feature Spotlight (4 to 5 slides)

Best for: Showcasing one specific product feature quickly.

```
Create a [4-5]-slide TikTok photo carousel spotlighting a feature of [PRODUCT NAME].

Feature: [FEATURE NAME and what it does]

Slide 1 (Hook): "[Compelling question or claim about the feature's benefit]"
Slide 2 (Problem): The old way of doing this (manual, tedious, forgettable, expensive)
Slide 3 (Demo): How the feature works in one simple sentence
Slide 4 (Result): What changes when you use it. Specific outcome.
Slide 5 (CTA): "[CTA TEXT]" (optional, can end at 4)

Rules:
- Focus on ONE feature only
- Show the benefit, not the feature list
- Keep it snappy. This format works best at 4 to 5 slides max.
- Conversational, zero jargon.
- Do not use em-dashes.
```

---

## Prompt Writing Rules

These rules apply to every prompt sent to the genviral API:

1. **Include the exact hook text in slide 1.** Do not let the AI rephrase your hook. The hook is deliberate and tested.

2. **Set the tone explicitly.** Include a phrase like "casual, conversational, like talking to a friend" to prevent corporate-sounding output. Match `workspace/context/brand-voice.md`.

3. **Cap the word count.** Always include "Max 15 words per slide" or similar constraint.

4. **Be specific in the story arc.** The more detail you provide about each slide's purpose, the better the output.

5. **Match the prompt to the pack, and inspect pack images first.** Do not rely only on pack name. Fetch the pack, read image metadata (descriptions + keywords), and use vision tools where needed for readability assessment. Align slide scenarios with the strongest visuals.

6. **Assign visuals slide-by-slide using `pinned_images`.** Explicitly decide which image best supports each slide's hook/body text, then pass `pinned_images` in `slide_config` so the server uses your choices. Without `pinned_images`, the server picks randomly and your visual analysis is wasted.

7. **Always review before rendering.** Check each slide's text before calling render. Fix weak slides with update or regenerate-slide.

8. **Never send placeholders to the API.** Every bracket must be replaced with real content before generating.

9. **No em-dashes.** Instruct the AI explicitly: "Do not use em-dashes."

10. **Track the hook you use.** After posting, log the hook text, hook category, and CTA to `workspace/performance/hook-tracker.json`. The tracker is how you learn what actually works. If you never log hooks, you are flying blind -- you will repeat failed hooks and underuse winners. See `docs/references/analytics-loop.md` for the full process.


path: docs/references/analytics-loop.md
content: # Analytics Feedback Loop

The genviral skill has full analytics built in. Use it. This document explains how to pull data, cross-reference it with `workspace/performance/hook-tracker.json`, and make decisions that improve content over time.

---

## The Core Idea

Every post is a data point. After enough posts, patterns emerge: certain hook categories consistently outperform others, certain CTAs drive more engagement, certain formats hit better in certain niches.

The job is to close the loop. Post content, track what happens, adjust based on results. This is not complicated -- it just requires discipline to actually do it.---

## Available Analytics Commands

### KPI Summary
```bash
genviral.sh analytics-summary --range 30d
genviral.sh analytics-summary --range 7d --platforms tiktok
genviral.sh analytics-summary --start 2026-01-01 --end 2026-01-31 --json
```

Use this for high-level trends:
- Are views trending up or down week over week?
- Is engagement rate improving?
- How many posts published in the period?
- Which platform is performing better?

### Per-Post Metrics
```bash
genviral.sh analytics-posts --range 30d --sort-by views --sort-order desc --limit 50
genviral.sh analytics-posts --range 7d --platforms tiktok --json
```

Use this to see individual post performance. Sort by views descending to immediately surface winners.

The output gives you per-post: views, likes, comments, shares, saves, and published timestamp.

Cross-reference the post IDs here with entries in `workspace/performance/hook-tracker.json` to connect metrics back to the specific hook, hook category, and CTA used.

---

## Pulling and Cross-Referencing Data

**Step 1: Pull post-level analytics**
```bash
genviral.sh analytics-posts --range 30d --sort-by views --sort-order desc --json
```

Save or parse the output. For each post, note:
- `post_id`
- `views`
- `likes`
- `comments`
- `shares`
- `saves`

**Step 2: Match to hook-tracker**

Open `workspace/performance/hook-tracker.json`. Find each post by `post_id`. Update the `metrics` block:
```json
{
  "views": 47200,
  "likes": 1340,
  "comments": 89,
  "shares": 230,
  "saves": 410,
  "last_checked": "2026-02-17T10:00:00Z"
}
```

Set `status` to `tracking` (data collected, not yet reviewed) or `reviewed` (reviewed in a weekly session).

**Step 3: Calculate engagement rate**

Engagement rate = (likes + comments + shares + saves) / views

This is the key signal for whether content resonates beyond just getting served by the algorithm.

---

## The Diagnostic Framework

Once you have views and engagement rate for a post, apply this framework:

**High views + High engagement rate**
The hook stopped the scroll AND the content delivered. This is the combination to scale.
Action: Make 3 variations of this hook. Keep the same hook category, same CTA type, try different angles within it.

**High views + Low engagement rate**
The hook is working (algorithm is serving it, people are clicking in). But the content itself or the CTA is not landing.
Action: Keep the hook. Fix the content arc or swap the CTA. Try a different CTA type and see if engagement improves.

**Low views + High engagement rate**
The people who see it love it. But the hook is not stopping the scroll, so the algorithm is not amplifying it.
Action: The content is good. The hook needs reworking. Try a more pattern-interrupting or emotionally direct hook for the same content angle.

**Low views + Low engagement rate**
Neither the hook nor the content is working. This angle is not resonating with this audience on this platform.
Action: Drop this angle. Try something radically different. Do not keep iterating on something that is failing at both signals.

---

## Decision Rules

Apply these rules when deciding what to do with a post after checking metrics:

| Views | Action |
|-------|--------|
| 50K+ | Double down. Make 3 variations of this hook immediately. Move to `rules.double_down` in hook-tracker. |
| 10K - 50K | Keep in rotation. This is working. Keep posting similar content while improving it gradually. Move to `rules.keep_rotating`. |
| 1K - 10K | Test one more variation. Give it another shot with a tweak. Move to `rules.testing`. |
| Under 1K (twice) | Drop it. If a hook fails twice with low views, it is not an algorithm problem, it is a content problem. Move to `rules.dropped`. |

"Views" here means TikTok/Instagram native views -- what the analytics-posts command returns.

Note: "twice" means you tested this hook category or specific hook type in two separate posts and both failed. A single post can have a bad day. Two posts with the same pattern failing is a signal.

---

## Updating hook-tracker.json

After reviewing a post's metrics, update its entry in `workspace/performance/hook-tracker.json`:

1. Fill in the `metrics` block with current data
2. Set `status` to `reviewed`
3. Based on the decision rules above, add the `hook_text` to the appropriate `rules` array:
   - `rules.double_down` - hooks over 50K views
   - `rules.keep_rotating` - hooks in 10K-50K range
   - `rules.testing` - hooks in 1K-10K, being tested again
   - `rules.dropped` - hooks that failed twice

4. Update the aggregate data in `hook_categories`:
   - Increment `total_posts` for the relevant category
   - Recalculate `avg_views` (rolling average is fine)
   - Update `best_post_id` if this post beats the current best

5. Update `cta_performance` for the CTA used:
   - Increment `times_used`
   - Add to `total_views`
   - Recalculate `avg_engagement_rate`

---

## Weekly Review Process

Run this every Monday (or the first day of your content week).

**1. Pull the last 7 days of analytics**
```bash
genviral.sh analytics-summary --range 7d
genviral.sh analytics-posts --range 7d --sort-by views --sort-order desc --json
```

**2. Update hook-tracker with fresh metrics**
For every post from the past week, fill in current views, likes, comments, shares, saves in `hook-tracker.json`.

**3. Categorize each post**
Apply the decision rules. Move hooks to `double_down`, `keep_rotating`, `testing`, or `dropped` based on actual numbers.

**4. Identify the week's winners and losers**
- Top 3 posts by views: what hook categories? What CTAs?
- Bottom 3 posts by views: what patterns do they share?

**5. Look for patterns across categories**
Check `hook_categories` aggregate data in `hook-tracker.json`. After 10+ posts, patterns become clear:
- Which category has the highest `avg_views`?
- Which CTA type in `cta_performance` has the highest `avg_engagement_rate`?

**6. Decide next week's content strategy**
Based on the data:
- Lead with the top-performing hook category
- Make 3 variations of any hook that crossed 50K
- Rotate CTAs for hooks in the testing range to find what converts
- If a category has 3+ posts in `dropped`, stop testing it for now
- Reference `workspace/performance/competitor-insights.md` for niche context

**7. Write a brief review**
Append to `workspace/performance/weekly-review.md`:
```markdown
## Week of [DATE]

**Posts published:** [N]
**Total views this week:** [N]
**Avg views per post:** [N]
**Top performer:** [POST ID] - [VIEWS] views - [HOOK CATEGORY] hook

**What worked:**
- [Hook category / CTA / angle that performed]

**What didn't:**
- [Hook category / format that underperformed]

**Next week's focus:**
- [2-3 specific decisions based on data]
```

---

## Common Mistakes to Avoid

**Checking analytics too early.** TikTok posts can take 24-72 hours to fully distribute. Check at 48 hours minimum, ideally 7 days for a full picture.

**Optimizing on too little data.** One viral post does not make a hook a winner. One failed post does not make it a loser. You need 3+ posts in a category before drawing conclusions.

**Ignoring engagement rate in favor of views.** A post with 100K views and 0.5% engagement is worse long-term than 20K views and 5% engagement. The algorithm learns from engagement signals, not just reach.

**Never updating hook-tracker.** The tracker is only useful if it actually has data. Update it after every performance check.

**Posting without checking what is already in `dropped`.** Do not repeat hooks that already failed twice. Check `rules.dropped` before finalizing a content plan.


path: docs/references/competitor-research.md
content: # Competitor Research Guide

Do this BEFORE creating content for a new product. Understanding the competitive landscape shapes every creative decision: hooks, formats, view baselines, gaps to exploit.

This is not optional busywork. If you skip it, you are guessing. Niche context turns generic content into targeted content that actually performs.

---

## When to Run Competitor Research

- First time setting up the skill for a new product
- When content has been underperforming for 2+ weeks
- When entering a new content angle or format
- When the niche or audience shifts

---

## Step 0: Pull a Trend Brief First (Fast Signal)

Before manual competitor browsing, run trend brief for the niche keyword:

```bash
genviral.sh trend-brief --keyword "YOUR NICHE" --range 7d --limit 10
genviral.sh trend-brief --keyword "YOUR NICHE" --range 24h --limit 10
```

Capture these signals as your starting baseline:
- `summary.top_hashtags`
- `summary.top_sounds`
- `summary.top_creators`
- `summary.posting_windows_utc`
- `recommendations.hook_angles`

This gives instant niche intelligence so your manual deep-dive is focused instead of random.

## Step 1: Define the Niche Keywords

Before searching, nail down what niche you are searching in.

Pull from `workspace/context/product.md`:
- What problem does the product solve?
- Who is the target audience?
- What would someone search for if they had this problem?

Write 3-5 search queries. Examples:
- "budget tracking app tiktok"
- "save money tips"
- "personal finance for beginners"
- "how to stop overspending"

---

## Step 2: Search TikTok for the Niche

Use the browser tool to search TikTok. Do not guess what performs well -- look.

```
browser(action="navigate", url="https://www.tiktok.com/search?q=YOUR+KEYWORD")
browser(action="snapshot")
```

For each keyword:
1. Open TikTok search for the query
2. Take a snapshot of results
3. Note the top 5-10 accounts appearing
4. Identify which accounts post regularly (multiple recent videos)

You are looking for accounts that are clearly in this niche and posting content similar to what you will create.

Also check:
```
browser(action="navigate", url="https://www.tiktok.com/@ACCOUNT_USERNAME")
browser(action="snapshot")
```

Look at their top posts (sorted by views if possible). What is getting traction?

---

## Step 3: Analyze 3-5 Competitor Accounts

For each competitor account you find, answer these questions:

**Account overview:**
- Username and platform (TikTok / Instagram)
- Approximate follower count
- Posting frequency (daily / 3x week / sporadic)
- How long have they been active?

**Content format:**
- What format dominates their top posts? (slideshow, video talking head, screen recordings, voiceovers)
- How many slides in their slideshows?
- Aspect ratio used?
- Do they use text overlays? Heavy or minimal?

**Hook analysis:**
- What is the first line of text on their top 5 posts?
- What hook category does each fall into? (person-conflict, relatable-pain, educational, pov, before-after, feature-spotlight)
- Which hook style appears most in their top-performing content?

**View baseline:**
- What is a typical view count for this account?
- What is their best performing post and why does it likely work?
- What is the floor (posts that bombed)? What seems to cause that?

**CTA patterns:**
- What CTA do they use most? (link in bio, search app name, soft mention, no CTA)
- Do they vary CTAs or stick to one?

**Engagement signals:**
- Are comments mostly from discovery (new people) or fans?
- Are saves high relative to views? (indicates utility content)
- Are shares high? (indicates emotionally resonant or shareable content)

---

## Step 4: Gap Analysis

After analyzing 3-5 competitors, identify what nobody is doing.

Ask:
- Which hook categories are overused in this niche? (avoid those, or do them significantly better)
- Which hook categories are barely used? (opportunity)
- What angles is nobody taking? (different audience subsets, different pain points, different tones)
- What format is missing? (if everyone does video, slideshows stand out -- and vice versa)
- Is there an emotional angle nobody is hitting? (humor, frustration, aspiration, nostalgia)
- Are competitors making content errors? (low readability, weak hooks, no CTA) - these are free wins

Write down 2-3 specific gaps you can exploit.

---

## Step 5: Save Findings

Save competitor research to `workspace/performance/competitor-insights.md`.

Use this structure:

```markdown
# Competitor Insights - [PRODUCT NAME]

Last updated: [DATE]

## Niche Keywords Searched
- [keyword 1]
- [keyword 2]

## Trend Brief Snapshot
- Top hashtags: [list]
- Top sounds: [list]
- Top creators: [list]
- Best posting windows (UTC): [list]
- Recommended hook angles: [list]

## Competitors Analyzed

### @[account_name] ([PLATFORM])
- Followers: ~[NUMBER]
- Posting: [FREQUENCY]
- Top format: [FORMAT]
- Top hook categories: [CATEGORIES]
- View baseline: [RANGE]
- Best post: [URL or description]
- Notes: [anything notable]

### @[account_name] ([PLATFORM])
...

## View Baseline for This Niche
- Average performing post: [RANGE]
- Good post: [RANGE]
- Viral for this niche: [RANGE]

## Hook Analysis
- Most common hook category: [CATEGORY]
- Least used hook category: [CATEGORY]
- Hook patterns that consistently work: [EXAMPLES]

## Gaps to Exploit
1. [GAP 1 - specific]
2. [GAP 2 - specific]
3. [GAP 3 - specific]

## Implications for Content Strategy
- Lead with [HOOK CATEGORY] since competitors underuse it
- Avoid [HOOK CATEGORY] unless we do it significantly better
- Target [AUDIENCE SUBSET] that competitors are ignoring
- Use [FORMAT] since competitors mostly use [OTHER FORMAT]
```

---

## Step 6: Use Competitor Data When Creating Content

Every time you write a hook, check `workspace/performance/competitor-insights.md` first:

- Is this hook category already saturated in the niche?
- What view baseline should I expect for this format?
- Am I targeting a gap, or imitating what everyone else does?

When picking a hook from `workspace/hooks/library.json`, prefer hooks that fill a gap over hooks that replicate what competitors already do.

When writing prompts for slideshow generation, mention the competitive context:
> "Competitors in this niche mostly use relatable-pain hooks. This slideshow should use a before-after format to stand out."

---

## Refresh Schedule

Competitor research goes stale. Refresh it:
- Every 4 weeks for active campaigns
- Any time you notice a format or hook suddenly performing differently than expected
- When starting a new content angle

The niche changes. What works today may be saturated in 6 weeks. Stay current.


path: docs/setup.md
content: # Setup Guide

This guide is for agents helping users get started with Genviral. Read this during onboarding and walk your human through it conversationally.

## What to Tell Your Human First

Before anything else, explain:

> Everything you do through this skill (posts, slideshows, image packs, scheduled content) shows up in the Genviral dashboard at genviral.io. You can always see, edit, or manage things in the UI. The agent and the dashboard are two views of the same system.

This is important. People worry about agents doing things they can't see or control. Reassure them.

---

## Onboarding Flow

Follow these phases in order. Each phase is conversational -- ask questions, listen, then move forward. Do not dump a checklist on them.

### Phase 1: Get to Know Their Product

Before touching any config or API, understand what you are working with.

Ask these questions naturally (not all at once -- let it be a conversation):

- "What's the product? Give me the one-sentence pitch."
- "Who is it for? What kind of person downloads or buys it?"
- "What problem does it solve? What is the pain before they find this product?"
- "What are the 2-3 features people actually care about?"
- "Do you have any social media presence already? What's worked before, if anything?"
- "What's the website or app store link?"

As they answer, fill in `workspace/context/product.md` and `workspace/context/brand-voice.md`. You are building the foundation that every piece of content will reference.

Do not rush this phase. A weak product brief produces weak content. A good one makes everything downstream easier.

### Phase 2: Competitor Research

Before creating any content, understand the competitive landscape. This takes 15-20 minutes and saves weeks of guessing.

Read `docs/references/competitor-research.md` for the full process. The short version:

1. Run `genviral.sh trend-brief --keyword "NICHE" --range 7d --limit 10` (and optionally `24h`) for fast niche intelligence.
2. Ask: "Who else is making content for this niche? Any accounts you admire or follow?"
3. Use the browser tool to search TikTok (and Instagram if relevant) for the niche keywords.
4. Find 3-5 accounts posting in this space.
5. Analyze their top content: what hooks work, what formats dominate, what view baseline looks like.
6. Identify gaps: what is nobody doing?

Save findings to `workspace/performance/competitor-insights.md`.

Tell your human what you found: "In this niche, most content uses relatable-pain hooks and talking-head videos. Nobody is doing educational slideshows. That is our opening."

This research shapes every creative decision from here on.

### Phase 3: Image and Content Strategy

Now figure out the visual approach.

**First, set up API access:**

Get the API key from https://www.genviral.io (Settings > API Keys), then:

```bash
export GENVIRAL_API_KEY="your_public_id.your_secret"
```

Test it:
```bash
genviral.sh accounts
```

If this returns connected accounts, you are good.

**Then, discuss image strategy.** Do NOT hardcode a pack without asking. Explain the options:

**Option A: Use an existing image pack**
```bash
genviral.sh list-packs
```
Show them what is available. If they like one, great. Set it in config or pass it per-slideshow.

**Option B: Create a new pack**
Ask what kind of images fit their brand. Then either:
- Help them upload images: `genviral.sh create-pack --name "My Pack"` then `genviral.sh add-pack-image --pack-id X --image-url "https://..."`
- Suggest they create one in the Genviral UI (easier for bulk uploads)

**Option C: Generate images per post**
The agent can generate images (via OpenAI, etc.) for each slideshow instead of pulling from a pack. No pack needed. More variety, less visual consistency.

**Option D: Mix and match**
Use a pack for some posts, generate fresh images for others. The agent decides based on content needs.

Set the account IDs in `defaults.yaml`:
```yaml
posting:
  default_account_ids: "ACCOUNT_ID_1"  # comma-separated for multiple
```

### Phase 4: First Test Slideshow

Make the first post together. This is iterative -- do not aim for perfect on the first try.

1. Pick a hook from `workspace/hooks/library.json` or brainstorm one based on competitor research
2. Pick a template from `docs/prompts/slideshow.md`
3. Generate the slideshow, review the slides together, refine until it looks right
4. Post as a TikTok draft (so they can add trending audio before publishing)

```bash
genviral.sh generate --prompt "Your prompt here" --pack-id PACK_ID --slides 5
genviral.sh render --id SLIDESHOW_ID
genviral.sh create-post --caption "your caption" --media-type slideshow --media-urls "url1,url2,..." --accounts ACCOUNT_ID --tiktok-post-mode MEDIA_UPLOAD --tiktok-privacy SELF_ONLY
```

After posting, log the hook text, category, and CTA to `workspace/performance/hook-tracker.json`. This is the start of the feedback loop.

Remind them: for TikTok, posting as a draft lets them add trending music before publishing. That is usually the best workflow.

### Phase 5: Set Up the Feedback Loop

The skill gets smarter over time, but only if you actually track results.

After the first post goes live (give it 48-72 hours minimum before checking):

1. Pull analytics: `genviral.sh analytics-posts --range 7d --sort-by views --sort-order desc`
2. Update `workspace/performance/hook-tracker.json` with the actual view and engagement numbers
3. Apply the diagnostic framework (see `docs/references/analytics-loop.md`)
4. Set up a weekly review routine -- every Monday, check the previous week's posts and adjust the content plan

Explain this to your human:
> "We will check how each post performs after a few days and use that data to figure out what hooks and formats to double down on. It takes a few weeks to build enough data to see patterns, but once we do, the content gets measurably better."

---

## TikTok Account Warmup (New Accounts Only)

If your human is using a brand-new TikTok account, do NOT post immediately. New accounts need to warm up first or the algorithm will suppress content.

**Warmup period: 7-14 days of normal usage before any posting.**

What to do during warmup:
- Scroll the For You page normally for 10-15 minutes per day
- Like content sparingly (about 1 in every 10 videos, not every single one)
- Follow 5-10 accounts in the niche
- Leave a few genuine, non-spammy comments per day
- Do NOT post, do NOT go to the profile repeatedly, do NOT make the account look like a bot

**Signal that the account is ready:** The For You page starts showing mostly niche-relevant content (the algorithm has figured out the account's interests). When 70%+ of recommended content matches the target niche, the account is warmed up.

Tell your human: "A new account that starts posting immediately gets worse distribution. Give it 1-2 weeks of normal usage first. We can use that time to get content ready."

**Skip warmup entirely for established accounts** (any account with existing followers and posting history). This only applies to brand-new accounts.

---

## What's Next?

Once they are posting, the agent can:
- Set up a daily content cron (see **Cron Setup** section below)
- Track performance via analytics and update `workspace/performance/hook-tracker.json`
- Run weekly strategy reviews (see `docs/references/analytics-loop.md`)
- Build and refine the hook library based on what actually works
- Research competitors periodically to stay ahead of niche trends

The skill self-improves over time. The more it posts and tracks, the better it gets at picking hooks, timing, and format.

---

## Onboarding Tone

Be helpful, not overwhelming. Do not dump all 42 commands on them. The conversation should feel like:

1. "Tell me about your product and who it's for."
2. "Let me look at what's working in your niche right now."
3. "What kind of images do you want to use?"
4. "Let's make your first post together."
5. "Here's how we'll track what works."

That's it. Everything else comes naturally as they use the skill.

---

## Cron Setup

Automate the content pipeline with OpenClaw cron jobs so content gets generated, posted, and reviewed without manual intervention.

### Recommended Schedule

| Job | Frequency | What it does |
|-----|-----------|-------------|
| **Daily Content** | Every day at 9:00 AM | Generate, render, review, and post a slideshow |
| **Performance Check** | Every day at 6:00 PM | Pull analytics, log metrics for recent posts |
| **Weekly Review** | Every Sunday at 10:00 AM | Analyze the week, update hook weights, plan next week |

### 0. Daily Skill Update (run first)

Keep the skill itself current automatically:

```bash
openclaw cron add \
  --name "Genviral: Daily Skill Update" \
  --cron "0 6 * * *" \
  --tz "YOUR_TIMEZONE" \
  --session isolated \
  --message "Run the genviral skill self-updater: bash scripts/update-skill.sh. It will check for updates to SKILL.md, scripts/, and docs/ from the upstream repo and apply them. Never touches workspace/. Report what was updated or confirm already up to date." \
  --announce
```

### 1. Daily Content Generation

Replace `YOUR_TIMEZONE` (e.g. `Europe/Brussels`, `America/New_York`).

```bash
openclaw cron add \
  --name "Genviral: Daily Content" \
  --cron "0 9 * * *" \
  --tz "YOUR_TIMEZONE" \
  --session isolated \
  --message "Run the Genviral daily content pipeline. Read the genviral SKILL.md, then: 1) Pick a topic from workspace/content/scratchpad.md or generate a new one based on workspace/performance/insights.md. 2) Generate a slideshow with a strong hook using pinned_images. 3) Render all slides. 4) Review each slide visually — if any slide is below quality, fix it before continuing. 5) Post to the default account. 6) Log the post in workspace/performance/log.json and tag the hook in workspace/performance/hook-tracker.json." \
  --announce
```

### 2. Daily Performance Check

```bash
openclaw cron add \
  --name "Genviral: Performance Check" \
  --cron "0 18 * * *" \
  --tz "YOUR_TIMEZONE" \
  --session isolated \
  --message "Run the Genviral performance check. Read the genviral SKILL.md, then: 1) Run analytics-summary to get overall stats. 2) Run analytics-posts to get individual post metrics. 3) Update workspace/performance/log.json with latest metrics for posts older than 24h. 4) Update workspace/performance/hook-tracker.json with real view/engagement numbers. 5) If any post significantly outperformed or underperformed, note why in workspace/performance/insights.md. 6) Report a brief summary." \
  --announce
```

### 3. Weekly Strategy Review

```bash
openclaw cron add \
  --name "Genviral: Weekly Review" \
  --cron "0 10 * * 0" \
  --tz "YOUR_TIMEZONE" \
  --session isolated \
  --message "Run the Genviral weekly review. Read the genviral SKILL.md, then: 1) Analyze all posts from the past 7 days in workspace/performance/log.json. 2) Identify top 3 and bottom 3 performers. 3) Update hook weights in workspace/hooks/library.json (increase for high engagement, decrease for underperformers). 4) Update workspace/performance/insights.md with this week's learnings. 5) Generate 5 new content ideas for next week in workspace/content/scratchpad.md. 6) Write the weekly review in workspace/performance/weekly-review.md." \
  --announce
```

### Customization

- **2 posts/day:** Change daily cron to `0 9,15 * * *` (9 AM and 3 PM)
- **Platform targeting:** Add platform-specific instructions to the message (e.g. "Post to TikTok vertical format" or "Instagram 4:5")
- **Content themes:** Add a theme schedule to `workspace/content/calendar.json` and reference it in the daily message

### Verify Setup

```bash
openclaw cron list                    # list all jobs
openclaw cron run <job-id> --force    # test a job manually
openclaw cron runs --id <job-id>      # check run history
```


## Without OpenClaw

If you're using the skill outside OpenClaw, you can still automate with system cron jobs or any scheduler/orchestrator:

- Run `scripts/genviral.sh` commands directly in your own cron jobs
- Or run an agent runner with the same prompts used above
- Keep the same guardrails: pinned_images, visual review gate, and performance logging in `workspace/performance/log.json`



path: scripts/update-skill.sh
content: #!/usr/bin/env bash
# =============================================================================
# update-skill.sh — Auto-updater for the genviral skill
# =============================================================================
#
# Checks fdarkaou/genviral-skill for updates and applies only skill-owned files.
# Safe to run anytime — never touches user-owned files (workspace/).
#
# Usage: ./update-skill.sh [--force] [--dry-run]
#
# Called daily by OpenClaw cron. Exit 0 = success (updated or already current).
# =============================================================================

set -euo pipefail

SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
REMOTE_URL="https://github.com/fdarkaou/genviral-skill.git"
REMOTE_BRANCH="main"
LOCK_FILE="$SKILL_DIR/.update.lock"
VERSION_FILE="$SKILL_DIR/.skill-version"

# Files that are SKILL-OWNED (safe to overwrite on update)
# Everything in workspace/ is USER-OWNED and never touched.
SKILL_OWNED_FILES=(
  "SKILL.md"
  "docs/setup.md"
  "scripts/genviral.sh"
  "docs/prompts/slideshow.md"
  "docs/prompts/hooks.md"
  "scripts/update-skill.sh"
  "docs/references/analytics-loop.md"
  "docs/references/competitor-research.md"
  "docs/api/accounts-files.md"
  "docs/api/posts.md"
  "docs/api/slideshows.md"
  "docs/api/packs.md"
  "docs/api/templates.md"
  "docs/api/analytics.md"
  "docs/api/pipeline.md"
  "docs/api/errors.md"
)

DRY_RUN=false
FORCE=false
while [[ $# -gt 0 ]]; do
  case "$1" in
    --dry-run) DRY_RUN=true; shift ;;
    --force)   FORCE=true; shift ;;
    *) echo "Unknown option: $1"; exit 1 ;;
  esac
done

# Prevent concurrent runs
if [[ -f "$LOCK_FILE" ]]; then
  echo "Update already in progress (lock file exists). Skipping."
  exit 0
fi
touch "$LOCK_FILE"
trap "rm -f '$LOCK_FILE'" EXIT

# Read current tracked commit
CURRENT_SHA=""
if [[ -f "$VERSION_FILE" ]]; then
  CURRENT_SHA=$(cat "$VERSION_FILE" | tr -d '[:space:]')
fi

echo "Checking for genviral skill updates..."
echo "Remote: $REMOTE_URL"
echo "Current SHA: ${CURRENT_SHA:-"(none)"}"

# Fetch latest commit SHA from GitHub API (no git required)
# Resolve GitHub token (explicit env var, or from gh CLI)
GH_TOKEN="${GITHUB_TOKEN:-$(gh auth token 2>/dev/null || echo "")}"

REMOTE_SHA=$(curl -sf \
  -H "Accept: application/vnd.github.v3+json" \
  -H "Authorization: Bearer $GH_TOKEN" \
  "https://api.github.com/repos/fdarkaou/genviral-skill/commits/$REMOTE_BRANCH" \
  | jq -r '.sha' 2>/dev/null || echo "")

if [[ -z "$REMOTE_SHA" ]]; then
  echo "ERROR: Could not fetch remote SHA. Check network or GITHUB_TOKEN."
  exit 1
fi

echo "Remote SHA:  $REMOTE_SHA"

if [[ "$CURRENT_SHA" == "$REMOTE_SHA" && "$FORCE" != "true" ]]; then
  echo "Already up to date. Nothing to do."
  exit 0
fi

echo "Update available! Applying..."

UPDATED_FILES=()
FAILED_FILES=()

for FILE in "${SKILL_OWNED_FILES[@]}"; do
  RAW_URL="https://raw.githubusercontent.com/fdarkaou/genviral-skill/$REMOTE_SHA/$FILE"
  TARGET="$SKILL_DIR/$FILE"
  
  if [[ "$DRY_RUN" == "true" ]]; then
    echo "[DRY RUN] Would update: $FILE"
    continue
  fi

  # Fetch and write atomically
  TMPFILE=$(mktemp)
  if curl -sf "$RAW_URL" -o "$TMPFILE"; then
    mkdir -p "$(dirname "$TARGET")"
    mv "$TMPFILE" "$TARGET"
    # Make scripts executable
    [[ "$FILE" == scripts/* ]] && chmod +x "$TARGET"
    UPDATED_FILES+=("$FILE")
    echo "  ✓ Updated: $FILE"
  else
    rm -f "$TMPFILE"
    # File may not exist in remote (optional file) — not a hard error
    FAILED_FILES+=("$FILE")
    echo "  - Skipped (not found in remote): $FILE"
  fi
done

if [[ "$DRY_RUN" == "true" ]]; then
  echo "Dry run complete. No files changed."
  exit 0
fi

# Save new SHA
echo "$REMOTE_SHA" > "$VERSION_FILE"

echo ""
echo "Update complete."
echo "  Updated: ${#UPDATED_FILES[@]} files"
echo "  Skipped: ${#FAILED_FILES[@]} files"
echo "  New version: $REMOTE_SHA"

# Emit openclaw event if anything changed
if [[ ${#UPDATED_FILES[@]} -gt 0 ]]; then
  UPDATE_LIST=$(IFS=', '; echo "${UPDATED_FILES[*]}")
  openclaw system event \
    --text "genviral skill updated to ${REMOTE_SHA:0:7}: $UPDATE_LIST" \
    --mode now 2>/dev/null || true
fi

exit 0


path: workspace/content/calendar.json
content: {
  "description": "Content calendar populated during setup and updated weekly.",
  "week_of": null,
  "plan": []
}


path: workspace/content/scratchpad.md
content: # Content Scratchpad

Brainstorm content ideas here. Your agent picks from this during daily content runs.

## Active Ideas
| # | Hook | Type | Theme | Status |
|---|------|------|-------|--------|
| 1 | [Your hook idea] | pain_point | [theme] | fresh |

## Theme Rotation
- [Theme 1]: [description]
- [Theme 2]: [description]

## Used Ideas
[Move ideas here after posting, with post ID and date]

## Notes
[Strategy notes, what's working, what to try next]


path: workspace/context/brand-voice.md
content: # Brand Voice Guide

Define how your content should sound. Your agent uses this for captions, hooks, and copy.

## Tone
- [e.g., Casual, professional, witty, educational]

## Language Rules
- [e.g., Use lowercase for captions]
- [e.g., No corporate jargon]
- [e.g., Speak like a friend, not a marketer]

## Do
- [What your content SHOULD do]

## Don't
- [What your content should NEVER do]

## Example Captions
[Paste 2-3 examples of captions that match your voice]


path: workspace/context/niche-research.md
content: # Niche Research

Research your target audience and competitors here. Your agent uses this to create relevant content.

## Target Niche
[e.g., "indie hackers building SaaS products"]

## Audience Pain Points
- [Pain point 1]
- [Pain point 2]

## Competitor Accounts
[List TikTok/Instagram accounts in your niche to study]

## What Works in This Niche
[Content formats, hooks, styles that perform well]

## What Doesn't Work
[Approaches to avoid based on research]


path: workspace/context/product.md
content: # Product Context

Fill this in with your product details. Your agent uses this to write relevant captions and hooks.

## What It Is
[Your product name and one-line description]

## Target Audience
- [Who uses your product?]
- [What problem does it solve for them?]

## Key Features
- [Feature 1]
- [Feature 2]
- [Feature 3]

## Value Proposition
[Why should someone use your product over alternatives?]

## Website
[Your product URL]

## Social Accounts
[List your social media handles]


path: workspace/hooks/formulas.md
content: # Hook Formulas

This file documents the underlying hook patterns. These are templates, not finished hooks. The agent generates specific hooks by combining a formula with the product context from `context/product.md`.

Each formula includes:
- The pattern with placeholders
- Why it works psychologically
- Structural guidelines
- Placeholder examples

Replace `[PRODUCT]` with the product name, `[PERSON]` with a relatable character, `[PROBLEM]` with a specific pain point, and `[FEATURE]` with a product capability.

## Formula 1: Person + Conflict

**Pattern:**
`[PERSON] said [DOUBT or OBJECTION about PROBLEM]. So I showed them [PRODUCT / what the product does]. [REACTION or RESULT].`

**Slide structure (6 slides):**
1. "[PERSON] said [doubt]..."
2. Why they thought that. The context for the objection.
3. "So I showed them [PRODUCT]" or "Then I opened the app"
4. The specific [FEATURE] that solved it, shown simply
5. Their reaction or the result
6. Soft CTA (link in bio, try it free)

**Why it works:**

This formula taps into three psychological drivers at once. First, interpersonal tension creates immediate curiosity. People want to see conflicts resolved. Second, the "doubter gets proven wrong" arc is deeply satisfying. Viewers project themselves into the story. Third, naming a specific person (my roommate, my mom, my trainer) makes the story feel real and lived-in, not scripted.

The key is specificity. "My coworker said [specific objection]" is dramatically stronger than "People think [generic doubt]." Naming the person and the exact objection creates a micro-story that hooks attention in under one second.

**Placeholder examples:**
- "My [PERSON] said [DOUBT about PROBLEM]..."
- "My roommate said [nobody needs PRODUCT]..."
- "My mom said [PROBLEM is not worth solving]..."
- "My trainer said [I would never stick to FEATURE]..."
- "My coworker said [the old way is good enough]..."

**Performance notes:**
This is consistently the highest-performing formula across niches. Prioritize this category at 40% of total content mix. Vary the [PERSON] and [OBJECTION] to avoid repetition.

---

## Formula 2: Educational / Tips

**Pattern:**
`[NUMBER] [THINGS/MISTAKES/HACKS] about [TOPIC related to PROBLEM] that [SURPRISING CLAIM]`

**Slide structure (6 slides):**
1. The hook claim (make it surprising or counterintuitive)
2. Tip 1 with brief explanation
3. Tip 2 with brief explanation
4. Tip 3 with brief explanation
5. "Or just use [PRODUCT] to handle all of this for you" (natural transition)
6. CTA (link in bio)

**Why it works:**

Educational content triggers the "I should save this" response. People bookmark tips they find useful, and saves are one of the strongest signals in TikTok's algorithm. The numbered format sets a clear expectation ("5 things" tells the viewer exactly what they are getting) which reduces the decision cost of engaging.

The product introduction on slide 5 feels earned rather than intrusive. The viewer already received value, so the product mention feels like a helpful addition rather than an ad.

**Placeholder examples:**
- "[NUMBER] [THINGS] about [TOPIC] you are doing wrong"
- "5 mistakes people make with [PROBLEM]"
- "The [AMOUNT] [PROBLEM] hack nobody talks about"
- "Stop [BAD HABIT related to PROBLEM]. Here is what to do instead."
- "[NUMBER] signs you need [PRODUCT] (and don't even know it)"

**Performance notes:**
Strong for building saves and follows. Works best when the tips are genuinely surprising or counterintuitive. Avoid obvious advice. The more specific and actionable each tip is, the better.

---

## Formula 3: Before / After

**Pattern:**
`[METRIC or SITUATION] before [PRODUCT] vs after. [SPECIFIC IMPROVEMENT with numbers or timeline].`

**Slide structure (6 slides):**
1. The transformation claim (before vs. after)
2. The "before" state in vivid, specific detail
3. The pain or cost of the "before" state (make them feel it)
4. "Then I started using [PRODUCT]" (simple, no hype)
5. The "after" state with specific, believable improvements
6. CTA (try it yourself, link in bio)

**Why it works:**

Transformation content is inherently shareable because it promises change. The brain is wired to pay attention to contrast. Before/after framing creates a clear narrative arc in just two states, making it cognitively easy to process.

Specificity is critical. "[AMOUNT] to [SMALLER AMOUNT]" is far more compelling than "I saved money." Real numbers, real timelines, and real situations build credibility. The transformation should feel achievable, not miraculous. Exaggeration triggers skepticism; understated honesty triggers trust.

**Placeholder examples:**
- "My [METRIC] before [PRODUCT] vs after"
- "How I went from [BAD STATE] to [GOOD STATE] with one change"
- "[TIME PERIOD] of using [PRODUCT]. Here are the results."
- "What [PROBLEM AREA] looked like before vs. after [PRODUCT]"
- "I tracked [METRIC] for 30 days. Here is what happened."

**Performance notes:**
Works especially well when you can include real numbers or screenshots. Pair with specific timeframes ("after 2 weeks", "in 30 days") to make the claim tangible.

---

## Formula 4: Relatable Pain Point

**Pattern:**
`That moment when [UNIVERSAL FRUSTRATION related to PROBLEM]... [humorous or empathetic expansion] [Plot twist: there is actually a fix]`

**Slide structure (6 slides):**
1. The pain point hook (POV, "that moment when", or "when you realize")
2. Expand on the frustration. Make it vivid and specific.
3. It gets worse (the consequences or the spiral)
4. "But then I found [PRODUCT]" or "There is actually a fix for this"
5. How [PRODUCT] specifically solves this exact pain point
6. Soft CTA ("Changed my life honestly. Link in bio.")

**Why it works:**

Relatable content drives social sharing. When people see their own frustration articulated perfectly, they tag friends, comment "this is literally me," and share to their stories. This organic engagement signals the algorithm to push the content further.

The humor or empathy in slides 1 through 3 builds rapport. The viewer feels understood. When the product appears in slide 4, it feels like a genuine discovery being shared friend-to-friend, not a sales pitch.

**Placeholder examples:**
- "That moment when [COMMON FRUSTRATION with PROBLEM]..."
- "POV: You [EMBARRASSING or RELATABLE SITUATION related to PROBLEM]"
- "When you realize [SURPRISING NEGATIVE CONSEQUENCE of PROBLEM]"
- "Nobody talks about [HIDDEN COST or ANNOYANCE of PROBLEM]"
- "Why does [COMMON FRUSTRATION] still happen in [CURRENT YEAR]?"

**Performance notes:**
This formula drives the most comments and shares. Best used when the pain point is truly universal in the target audience. Humor helps but is not required. Authentic empathy works just as well.

---

## Formula 5: POV / Lifestyle

**Pattern:**
`POV: You [ASPIRATIONAL or RELATABLE SCENARIO that PRODUCT enables or relates to]`

**Slide structure (5 to 6 slides):**
1. "POV: You [scenario]" (place the viewer in the scene)
2. Set the scene with a specific, vivid detail
3. Show the experience unfolding
4. The payoff or satisfying moment
5. Subtle product presence (PRODUCT shown naturally in the scenario)
6. Optional: soft CTA or just let it end

**Why it works:**

POV content places the viewer inside the experience rather than observing it from outside. This first-person framing increases emotional engagement and makes the viewer mentally simulate the scenario. When the simulation is pleasant or aspirational, it creates desire. When it is relatable, it creates connection.

This formula works best when the product is woven into the lifestyle naturally rather than being the focus. The product should feel like a natural part of a desirable or relatable life, not the point of the post.

**Placeholder examples:**
- "POV: You finally [POSITIVE OUTCOME related to PRODUCT]"
- "POV: You [DAILY ROUTINE that is better because of PRODUCT]"
- "POV: It is Sunday morning and [RELAXED SCENARIO enabled by PRODUCT]"
- "POV: Your friend asks how you [ACHIEVEMENT related to PRODUCT]"
- "POV: You stopped [BAD HABIT] and now [POSITIVE RESULT]"

**Performance notes:**
Lower direct conversion but excellent for brand building and follower growth. Use at 10% of the content mix. Best paired with aesthetic visuals and trending audio.

---

## How to Generate New Hooks

When the hook library needs refreshing:

1. Read `context/product.md` to understand the product, audience, and features.
2. Read `context/brand-voice.md` to match the tone.
3. Pick a formula from this file.
4. Replace placeholders with specific, product-relevant details.
5. Test the hook against these criteria:
   - Does it create curiosity in under 1 second?
   - Would a real person say this?
   - Is the [PERSON] or [SITUATION] specific, not generic?
   - Does it pass the "would I stop scrolling for this?" test?
6. Generate 3 to 5 variants per formula and pick the strongest.
7. Add to `hooks/library.json` with status "new."

Use `prompts/hooks.md` for detailed brainstorming prompts to generate hooks at scale.

## Hook Quality Criteria

Every hook should pass ALL of these checks:

- **Specific.** Names a real type of person or situation, not a vague "people."
- **Curiosity-driven.** The viewer needs to see the next slide to resolve the tension.
- **Human-sounding.** Reads like something someone would actually say out loud.
- **One idea.** The hook is about one thing, not two or three.
- **Under 20 words.** For slide 1, shorter is almost always better.
- **No jargon.** No corporate language, no buzzwords, no marketing speak.
- **No em-dashes.** Use periods, commas, or ellipses instead.


path: workspace/hooks/library.json
content: {
  "hooks": [
    {
      "_comment": "Example hook entry. Add your own hooks here or let the agent generate them.",
      "id": "example-1",
      "text": "your hook text here",
      "type": "pain_point",
      "theme": "your niche",
      "uses": 0,
      "avg_views": 0,
      "status": "fresh"
    }
  ],
  "metadata": {
    "last_updated": "",
    "total_hooks": 1,
    "active": 1,
    "retired": 0
  }
}


path: workspace/performance/competitor-insights.md
content: # Competitor Insights

This file is generated by the agent during Phase 2 of onboarding (competitor research).

See `references/competitor-research.md` for the full research process and the template format to use when filling this in.

---

<!-- Fill this in using the template in references/competitor-research.md -->


path: workspace/performance/hook-tracker.json
content: {
  "hooks": [],
  "cta_performance": [],
  "hook_categories": {
    "person-conflict": {
      "total_posts": 0,
      "avg_views": 0,
      "best_post_id": null
    },
    "relatable-pain": {
      "total_posts": 0,
      "avg_views": 0,
      "best_post_id": null
    },
    "educational": {
      "total_posts": 0,
      "avg_views": 0,
      "best_post_id": null
    },
    "pov": {
      "total_posts": 0,
      "avg_views": 0,
      "best_post_id": null
    },
    "before-after": {
      "total_posts": 0,
      "avg_views": 0,
      "best_post_id": null
    },
    "feature-spotlight": {
      "total_posts": 0,
      "avg_views": 0,
      "best_post_id": null
    }
  },
  "rules": {
    "double_down": [],
    "keep_rotating": [],
    "testing": [],
    "dropped": []
  }
}


path: workspace/performance/insights.md
content: # Performance Insights

_Updated after each weekly review. Distilled learnings, not raw data._

## Hook Patterns
[Which hook types perform best for your audience]

## Timing
[Best posting times based on your data]

## Visual
[What image styles get the most engagement]

## Lessons
[Key learnings from your content experiments]


path: workspace/performance/log.json
content: {
  "posts": [
    {
      "_comment": "Example entry. The agent adds entries here after each post.",
      "id": "example-post-id",
      "date": "2026-01-01",
      "hook": "your hook text here",
      "hook_type": "pain_point",
      "pack_id": "",
      "pack_name": "",
      "account_id": "",
      "platform": "tiktok",
      "metrics": {
        "views": 0,
        "likes": 0,
        "comments": 0,
        "shares": 0
      }
    }
  ]
}


path: workspace/performance/weekly-review.md
content: # Weekly Review

## Review Period
- Week of: [start date] to [end date]
- Total posts: 0

## Top Performers
[Updated by the weekly review cron job]

## Underperformers
[Posts that didn't meet thresholds]

## Strategy Adjustments
[What to change based on this week's data]


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)