Skip to main content

Command Palette

Search for a command to run...

I Built a Creator Command Center with Coral β€” Here's the Exact Route πŸ΄β€β˜ οΈ

Updated
β€’9 min read

I Built a Creator Command Center with Coral β€” Here's the Exact Route πŸ΄β€β˜ οΈ

Query your YouTube, Twitter & Discord as SQL. No ETL. No warehouse. Just Coral.


I'm a developer and content creator, and I was tired of the same daily ritual β€” open YouTube, check views, switch to Twitter, check engagement, hop to Discord, see what the community is asking. Three tabs. Three APIs. Zero unified picture.

So for the Pirates of the Coral-bean Hackathon by WeMakeDevs, I built something I actually needed: a Creator Command Center β€” a personal agent that queries all three platforms as SQL using Coral, and spits out a live dashboard.

This is the exact route I took. Follow it and you'll have your own running in under a day.


πŸ—ΊοΈ What We're Building

A Python agent that:

  • Connects YouTube and Twitter as SQL sources via Coral

  • Runs cross-platform queries like SELECT ... FROM youtube.videos JOIN twitter.tweets

  • Generates a live HTML dashboard from the results

  • Runs inside Docker β€” works on Windows, Mac, Linux

Live demo: creator-command-center-seven.vercel.app
Source code: github.com/Soumya9107/creator-command-center


πŸ› οΈ Prerequisites

  • Docker Desktop installed

  • A YouTube channel (even with 1 video)

  • A Twitter/X account

  • A Discord account

  • About 2–3 hours of focused time


Step 1 β€” Project Setup

Create your project structure:

mkdir creator-command-center
cd creator-command-center
mkdir sources, queries, agent

Create a Dockerfile:

FROM ubuntu:24.04

RUN apt-get update && apt-get install -y \
    curl python3 python3-pip \
    && rm -rf /var/lib/apt/lists/*

RUN curl -fsSL https://withcoral.com/install.sh | sh

RUN pip3 install groq --break-system-packages

WORKDIR /app
COPY . .

ENV PATH="/root/.local/bin:$PATH"
CMD ["bash"]

Create docker-compose.yml:

services:
  coral-agent:
    build: .
    env_file:
      - .env
    volumes:
      - .:/app
      - coral-config:/root/.config/coral
    stdin_open: true
    tty: true

volumes:
  coral-config:

Build and enter the container:

docker-compose build
docker-compose run coral-agent bash

Verify Coral is working:

coral --version
# coral 0.4.1+43d8309

Step 2 β€” Get Your API Keys

You need four things:

Key Where to get it
YOUTUBE_API_KEY console.cloud.google.com β†’ Enable YouTube Data API v3 β†’ Credentials β†’ API Key
YOUTUBE_CHANNEL_ID Your YouTube channel URL β†’ copy the UCxxxxxxx part
TWITTER_BEARER_TOKEN developer.twitter.com β†’ App β†’ Keys & Tokens β†’ Bearer Token
TWITTER_USER_ID Go to tweeterid.com β†’ enter your username β†’ get numeric ID
DISCORD_BOT_TOKEN discord.com/developers/applications β†’ New App β†’ Bot β†’ Reset Token
GROQ_API_KEY console.groq.com β†’ API Keys β†’ Create (free!)

Create .env in your project root:

YOUTUBE_API_KEY=your_key_here
YOUTUBE_CHANNEL_ID=UCxxxxxxxxxxxxxxx
TWITTER_BEARER_TOKEN=your_bearer_token_here
TWITTER_USER_ID=your_numeric_id_here
DISCORD_BOT_TOKEN=your_token_here
DISCORD_GUILD_ID=your_server_id
DISCORD_CHANNEL_ID=your_channel_id
GROQ_API_KEY=your_key_here

⚠️ Important: Add .env to .gitignore right away β€” never commit API keys!


Step 3 β€” Write Custom Source Specs

This is where Coral shines. You define any API as a YAML file and Coral exposes it as SQL.

Create sources/youtube.yaml:

name: youtube
version: 0.1.0
dsl_version: 3
backend: http
base_url: https://www.googleapis.com/youtube/v3

inputs:
  YOUTUBE_API_KEY:
    kind: secret
    hint: YouTube Data API v3 key
  YOUTUBE_CHANNEL_ID:
    kind: variable
    hint: Your YouTube Channel ID

tables:
  - name: videos
    description: Videos from your YouTube channel
    request:
      method: GET
      path: /search
      query:
        - name: part
          from: literal
          value: snippet
        - name: type
          from: literal
          value: video
        - name: channelId
          from: template
          template: "{{input.YOUTUBE_CHANNEL_ID}}"
        - name: maxResults
          from: literal
          value: "50"
        - name: key
          from: template
          template: "{{input.YOUTUBE_API_KEY}}"
    response:
      rows_path:
        - items
    columns:
      - name: id__videoId
        type: Utf8
      - name: snippet__title
        type: Utf8
      - name: snippet__publishedAt
        type: Utf8
      - name: snippet__channelTitle
        type: Utf8

Create sources/twitter.yaml:

name: twitter
version: 0.1.0
dsl_version: 3
backend: http
base_url: https://api.twitter.com/2

inputs:
  TWITTER_BEARER_TOKEN:
    kind: secret
    hint: Twitter API v2 Bearer Token from developer.twitter.com
  TWITTER_USER_ID:
    kind: variable
    hint: Your numeric Twitter User ID

auth:
  type: HeaderAuth
  headers:
    - name: Authorization
      from: template
      template: "Bearer {{input.TWITTER_BEARER_TOKEN}}"

tables:
  - name: tweets
    description: Your recent tweets with engagement metrics
    request:
      method: GET
      path: "/users/{{input.TWITTER_USER_ID}}/tweets"
      query:
        - name: max_results
          from: literal
          value: "100"
        - name: tweet.fields
          from: literal
          value: "public_metrics,created_at,text"
    response:
      rows_path:
        - data
    columns:
      - name: id
        type: Utf8
      - name: text
        type: Utf8
      - name: created_at
        type: Utf8

  - name: mentions
    description: Recent mentions of your account
    request:
      method: GET
      path: "/users/{{input.TWITTER_USER_ID}}/mentions"
      query:
        - name: max_results
          from: literal
          value: "100"
        - name: tweet.fields
          from: literal
          value: "public_metrics,created_at,text"
    response:
      rows_path:
        - data
    columns:
      - name: id
        type: Utf8
      - name: text
        type: Utf8
      - name: created_at
        type: Utf8

⚠️ Twitter API Note: Twitter's free tier has very limited read credits. If you hit a CreditsDepleted error, use the mock data fallback below β€” it still demonstrates the cross-source SQL perfectly.

Twitter mock data fallback β€” create sources/twitter_mock.json:

[
  {"id": "1", "text": "Just posted a new video! Check it out", "created_at": "2026-05-28T10:00:00Z", "likes": 42, "retweets": 12},
  {"id": "2", "text": "Working on something exciting for my audience", "created_at": "2026-05-27T10:00:00Z", "likes": 38, "retweets": 8},
  {"id": "3", "text": "What content do you want to see next?", "created_at": "2026-05-26T10:00:00Z", "likes": 55, "retweets": 15},
  {"id": "4", "text": "Behind the scenes of my latest project", "created_at": "2026-05-25T10:00:00Z", "likes": 61, "retweets": 20},
  {"id": "5", "text": "Thanks for 1000 followers! More content coming soon", "created_at": "2026-05-24T10:00:00Z", "likes": 89, "retweets": 30}
]

And sources/twitter_local.yaml:

name: twitter_local
version: 0.1.0
dsl_version: 3
backend: file

tables:
  - name: tweets
    description: Twitter tweets (local mock)
    format: json
    source:
      location: file:///app/sources/
      glob: "twitter_mock.json"
    columns:
      - name: id
        type: Utf8
      - name: text
        type: Utf8
      - name: created_at
        type: Utf8
      - name: likes
        type: Int64
      - name: retweets
        type: Int64

Create sources/discord.yaml:

name: discord
version: 0.1.0
dsl_version: 3
backend: http
base_url: https://discord.com/api/v10

inputs:
  DISCORD_BOT_TOKEN:
    kind: secret
    hint: Discord Bot Token
  DISCORD_GUILD_ID:
    kind: variable
    hint: Your Discord Server ID
  DISCORD_CHANNEL_ID:
    kind: variable
    hint: Your Discord Channel ID

auth:
  type: HeaderAuth
  headers:
    - name: Authorization
      from: template
      template: "Bot {{input.DISCORD_BOT_TOKEN}}"

tables:
  - name: messages
    description: Messages from your Discord channel
    request:
      method: GET
      path: "/channels/{{input.DISCORD_CHANNEL_ID}}/messages"
      query:
        - name: limit
          from: literal
          value: "100"
    response:
      rows_path:
        - $
    columns:
      - name: id
        type: Utf8
      - name: content
        type: Utf8
      - name: timestamp
        type: Utf8

Lint to validate:

coral source lint /app/sources/youtube.yaml
# Manifest is valid βœ…

Step 4 β€” Connect the Sources

Load your environment and add sources:

# Fix Windows line endings first
sed -i 's/\r//' /app/.env

# Load env vars
set -a && source /app/.env && set +a

# Add sources
coral source add --file /app/sources/youtube.yaml
coral source add --file /app/sources/twitter_local.yaml   # use twitter.yaml if you have API credits
coral source add --file /app/sources/discord.yaml

# Fix variable storage (important!)
printf 'YOUTUBE_CHANNEL_ID=%s\n' "$YOUTUBE_CHANNEL_ID" \
  > /root/.config/coral/workspaces/default/sources/youtube/variables.env

printf 'DISCORD_GUILD_ID=%s\nDISCORD_CHANNEL_ID=%s\n' \
  "\(DISCORD_GUILD_ID" "\)DISCORD_CHANNEL_ID" \
  > /root/.config/coral/workspaces/default/sources/discord/variables.env

Verify it works:

coral source list
# discord        0.1.0   imported   file (plaintext)
# twitter_local  0.1.0   imported   file (plaintext)
# youtube        0.1.0   imported   file (plaintext)

Step 5 β€” Write the SQL Queries

Create queries/weekly_summary.sql:

SELECT
  'YouTube' AS platform,
  title AS content,
  likes AS engagement
FROM youtube_local.videos
UNION ALL
SELECT
  'Twitter' AS platform,
  text AS content,
  likes AS engagement
FROM twitter_local.tweets
ORDER BY engagement DESC
LIMIT 10;

Test it:

coral sql < /app/queries/weekly_summary.sql

You'll see results from both platforms ranked by engagement β€” that cross-source JOIN is the whole magic of Coral.


Step 6 β€” Build the Agent & Dashboard

Create agent/main.py:

import subprocess

def run_coral_query(sql):
    result = subprocess.run(
        ["coral", "sql", sql],
        capture_output=True, text=True
    )
    return result.stdout if result.returncode == 0 else ""

def parse_table(output):
    rows = []
    lines = output.strip().split('\n')
    data_lines = [l for l in lines if l.startswith('|') and '---' not in l]
    if len(data_lines) < 2:
        return [], []
    headers = [h.strip() for h in data_lines[0].split('|')[1:-1]]
    for line in data_lines[1:]:
        values = [v.strip() for v in line.split('|')[1:-1]]
        if values:
            rows.append(values)
    return headers, rows

queries = [
    {
        "question": "Most viewed videos",
        "sql": "SELECT title, views, likes FROM youtube_local.videos ORDER BY views DESC LIMIT 5"
    },
    {
        "question": "Top tweets by likes",
        "sql": "SELECT text, likes, retweets FROM twitter_local.tweets ORDER BY likes DESC LIMIT 5"
    },
    {
        "question": "Cross-platform engagement",
        "sql": """
            SELECT 'YouTube' AS platform, title AS content, likes AS engagement
            FROM youtube_local.videos
            UNION ALL
            SELECT 'Twitter' AS platform, text AS content, likes AS engagement
            FROM twitter_local.tweets
            ORDER BY engagement DESC LIMIT 10
        """
    }
]

if __name__ == "__main__":
    print("\nπŸ΄β€β˜ οΈ Creator Command Center\n" + "="*40)
    for q in queries:
        print(f"\nπŸ€” {q['question']}")
        print(run_coral_query(q['sql']))

Run it:

python3 /app/agent/main.py

Step 7 β€” Deploy

Push to GitHub:

# Add a .gitignore first!
echo ".env" > .gitignore
git init
git add .
git commit -m "Initial commit - Creator Command Center"
git remote add origin https://github.com/YOUR_USERNAME/creator-command-center.git
git push -u origin main

Deploy to Vercel:

  1. Go to vercel.com β†’ Sign in with GitHub

  2. Click Add New Project β†’ Import your repo

  3. Framework: Other β†’ Click Deploy

Your live URL:

https://creator-command-center.vercel.app

πŸ’‘ What I Learned

1. Coral's DSL is opinionated β€” in a good way. Every field has a reason. The from: literal vs from: template distinction forces you to be explicit about what's static and what's dynamic. It caught bugs I didn't know I had.

2. Docker + Windows = line ending hell. The \r carriage return issue cost me an hour. Always run sed -i 's/\r//' .env before loading env vars in a Linux container on Windows.

3. API free tiers are fragile. Twitter's free tier ran out of credits mid-build. Lesson: always have a mock data fallback. Real engineering means handling real constraints.

4. Cross-source SQL is genuinely magical. The moment UNION ALL across YouTube and Twitter returned results in one table β€” that's when it clicked. This is what Coral is actually for.


πŸ† Bounty Tip

Submit your source specs as PRs to the Coral GitHub repo. Each accepted custom source spec earns \(100 cash. YouTube, Twitter, and Discord are three separate PRs β€” potentially \)300 in bounties just from the YAML files you already wrote!


πŸ”— Resources


Built by Soumyajit Maity for the PiratesπŸ΄β€β˜ οΈ of the Coral-bean Hackathon Β· WeMakeDevs 2026