Build an AI Instagram Content Engine

Generate a week of Instagram content using Gemini API and Python.

Introduction

30 Second Summary

Creating a week of Instagram content from scratch is exhausting. You need scroll-stopping hooks, strategic hashtags, varied formats, and calls to action that actually drive engagement. Now imagine doing that every single week while keeping your brand voice consistent.

In this project, you will build an AI-powered content engine that generates a full week of Instagram posts for a premium Indian mango brand called Aam India. You will define the brand's DNA as structured configuration, then use Google's Gemini API to produce ready-to-review content calendars in minutes.

What You'll Build

A Python-based AI content engine that lives in a public GitHub repository and calls the Gemini API to generate strategically-aligned Instagram content from structured brand configuration.

By the end of this project, you'll have:

  • A public GitHub repository with clean open-source structure that anyone on your team can fork and run.
  • A brand DNA system that encodes Aam India's positioning, voice, and content pillars into reusable AI prompts.
  • A content generation pipeline that produces a complete weekly Instagram calendar with DM-triggering CTAs, hook variations for A/B testing, and keyword-rich captions.
  • Secret Mission: Add a seasonal content intelligence module that generates timely content based on the US mango season calendar and trending food topics.

Are there any prerequisites?

You need a Google account and a ChatGPT Pro subscription with Codex access. You do not need to know how to write Python. Codex will write the code for you based on your instructions.

Set Up Accounts and Repository

Before you can build the content engine, you need three things in place: a GitHub account to host your code, a Gemini API key to power the AI generation, and a repository where the project lives.

In this step, you will create all three. Codex (the AI coding agent built into your ChatGPT Pro subscription) will handle the repository creation for you.

In this step, get ready to:
  • Create a GitHub account using your Google login.
  • Get a free Gemini API key from Google AI Studio.
  • Use Codex to create your project repository on GitHub.
Create a GitHub account

GitHub is where your content engine code will live. Creating an account takes about a minute when you use your existing Google account.

  • Open your browser and navigate to https://github.com.
  • Click Sign up in the top right corner.
  • Click Continue with Google to use your existing Google account for fast signup.
  • Select your Google account and authorize the connection.
  • Complete any remaining prompts (username selection, email preferences).
  • Check your email inbox and verify your email address if prompted.

What is GitHub?

GitHub is a platform for hosting and sharing code. Your project will live in a GitHub repository (a folder for your code that tracks every change).

Making it public means anyone on your team can find and run it.

Already have a GitHub account?

If you already have a GitHub account, skip this substep and move straight to getting your Gemini API key. Help me decide if my existing GitHub account is ready for this project.

Get your Gemini API key

The content engine uses Google's Gemini AI to generate Instagram posts. You need an API key to authenticate your requests.

  • Open a new browser tab and go to https://aistudio.google.com.
  • Sign in with your Google account.
  • Accept the Terms of Service when prompted. Google AI Studio automatically creates a project and API key for new users.
  • Click Get API key in the left sidebar to view your keys.
  • Copy your API key and save it somewhere safe on your computer (a password manager or a local text file that you will not commit to GitHub).

Why Gemini instead of OpenAI's API?

Your ChatGPT Pro subscription gives you access to Codex for writing code, but it does not include OpenAI API credits. The OpenAI Platform requires separate billing.

Google's Gemini API free tier is genuinely free for this project's workload. No credit card required.

Keep your API key private

Never paste your API key directly into code that gets pushed to GitHub. In the next step, you will store it as an environment variable instead. For now, just save it somewhere secure on your machine.

Not seeing an API key?

If Google AI Studio does not show a key automatically, click Create API key and select a Google Cloud project (AI Studio will create one for you if needed). Help me get my Gemini API key set up.

Create the repository with Codex

Now you will use Codex to create your project repository on GitHub. Codex runs in the cloud, clones your repos, writes code, and shows you the changes as a diff to approve.

  • Open Codex in your browser by navigating to chatgpt.com/codex.
  • If this is your first time using Codex, it will prompt you to connect your GitHub account. Follow the authorization flow to grant Codex access.
  • Once connected, type the following prompt into Codex and click Code to start the task:
Create a new public GitHub repository called "aam-india-content-engine" with this Python project structure:
- brand_brief.py (empty file with a comment: # Aam India Brand DNA Configuration)
- content_engine.py (empty file with a comment: # Aam India AI Content Engine)
- requirements.txt (empty file)
- README.md (empty file with a heading: # Aam India Content Engine)
- .env.example (containing exactly: GEMINI_API_KEY=your-gemini-api-key-here)

Make sure the repository is public and has a brief description: "AI-powered Instagram content engine for Aam India."

What happens when you click Code?

Codex works in its own cloud environment. It will create the repository, add the files, and show you a diff of everything it changed. This typically takes 1-5 minutes.

Review the changes Codex shows you, then approve the pull request or merge to push the files to your new repo.

  • Wait for Codex to complete the task. Watch the progress logs in real time.
  • Review the proposed changes in the diff view. Confirm that all five files are present.
  • Approve the changes to push them to your GitHub repository.
  • Open your new repository in a browser tab by navigating to:
github.com/[[GITHUB_USERNAME="your GitHub username"]]/aam-india-content-engine
  • Verify that you see all five files listed: brand_brief.py, content_engine.py, requirements.txt, README.md, and .env.example.

Codex not creating the repo?

Make sure your GitHub account is connected. Go to your ChatGPT settings and check that GitHub appears under connected apps.

If Codex asks you to select a repository but you want to create a new one, rephrase your prompt to explicitly say "create a new repository" rather than "set up a project." Help me troubleshoot my Codex and GitHub connection.

✔️ I see all five files in my repo

Your repository is ready. Click on the .env.example file in your repo to confirm it contains:

GEMINI_API_KEY=your-gemini-api-key-here

If it matches, you are all set for the next step.

ⓧ Some files are missing or named differently

Codex sometimes interprets prompts slightly differently. Send a follow-up prompt to fix the issue:

In the aam-india-content-engine repo, please make sure these exact files exist at the root:
- brand_brief.py
- content_engine.py
- requirements.txt
- README.md
- .env.example (containing exactly: GEMINI_API_KEY=your-gemini-api-key-here)

Remove any extra files that don't belong.

Approve the changes and refresh your GitHub repo page to confirm all five files appear.

Your accounts are set up and your repository is live on GitHub. Next up, you will define Aam India's brand DNA as structured Python configuration that the AI engine can use to generate on-brand content.

Define Aam India's Brand DNA

For an AI to generate on-brand content, it needs a structured understanding of the brand. Without clear rules about audience, voice, and content strategy, the AI produces generic output that sounds like every other food brand on Instagram.

In this step, you will encode Aam India's entire brand strategy into a Python configuration file called brand_brief.py. This file becomes the single source of truth that your AI content engine will reference every time it generates a post.

In this step, get ready to:
  • Build the core brand configuration (audience, voice, content pillars).
  • Define the content format strategy for Reels, carousels, and single posts.
  • Define engagement triggers with DM-triggering CTAs and Instagram rules.
Build the brand configuration

A brand brief traditionally lives in a PDF or slide deck. For an AI pipeline, you need it in structured data that code can parse and inject into prompts.

  • Switch to your Codex thread that is connected to the aam-india-content-engine repository.
  • Type the following prompt and click Code to start the task:
Write the brand_brief.py file with the following structured brand DNA configuration:

1. A BRAND dictionary with: name "Aam India", tagline "Premium Indian Mangoes for the American Table", mission about building awareness before Summer 2027 launch, launch_date "Summer 2027", stage "Pre-launch community building (0 to 10K engaged followers)"

2. A TARGET_AUDIENCE dictionary with: demographics (US food enthusiasts ages 25-45), psychographics list (premium ingredients, food Instagram, pay for provenance, origin stories, early adopters), and pain_points list (only know grocery mangoes, never tasted Alphonso/Kesar, unaware of Indian varieties, think all mangoes are the same)

3. A BRAND_VOICE dictionary with: tone "Authoritative yet approachable", language_style "Sensory-rich, evocative descriptions", personality list (knowledgeable insider, generous educator, exclusive without pretentious, passionate about cultivation), avoid list (generic marketing, pushy CTAs, agricultural jargon, negative comparisons)

4. A CONTENT_PILLARS dictionary with 5 pillars: origin_stories, us_food_integration, scarcity_exclusivity, community_building, mango_education. Each pillar has: description, goal, frequency, and example_topics list with 4 topic ideas.

Use Python dictionaries. Add a module docstring explaining this encodes brand strategy for AI content generation.

What does this prompt do?

You are giving Codex a detailed specification of the data structures you want. Codex will generate the complete brand_brief.py file with all four dictionaries populated with the brand strategy data.

By structuring the brand as Python dictionaries, your content engine can programmatically inject any piece of the brand DNA into AI prompts later.

  • Wait for Codex to finish processing. This typically takes 1-3 minutes.
  • Review the diff that Codex shows you. You should see brand_brief.py populated with four dictionaries: BRAND, TARGET_AUDIENCE, BRAND_VOICE, and CONTENT_PILLARS.

Here is what the core brand dictionaries should look like in the diff:

"""
Aam India Brand DNA Configuration
Encodes brand strategy into structured data for AI content generation.
"""

BRAND = {
    "name": "Aam India",
    "tagline": "Premium Indian Mangoes for the American Table",
    "mission": "Build awareness and demand for premium Indian mangoes in the United States before Summer 2027 launch.",
    "launch_date": "Summer 2027",
    "stage": "Pre-launch community building (0 to 10K engaged followers)"
}

TARGET_AUDIENCE = {
    "demographics": "US-based food enthusiasts, ages 25-45",
    "psychographics": [
        "Interested in premium, exotic, and artisanal ingredients",
        "Active on food Instagram (follow chefs, food brands, recipe creators)",
        "Willing to pay premium for quality and provenance",
        "Value origin stories and cultural authenticity",
        "Early adopters of new food trends"
    ],
    "pain_points": [
        "Only know grocery store mangoes (Tommy Atkins variety)",
        "Have never tasted a real Alphonso or Kesar mango",
        "Unaware that Indian mango varieties exist or are available in the US",
        "Think all mangoes taste the same"
    ]
}

BRAND_VOICE = {
    "tone": "Authoritative yet approachable",
    "language_style": "Sensory-rich, evocative descriptions",
    "personality": [
        "Knowledgeable insider who has tasted the best",
        "Generous educator, not gatekeeping",
        "Exclusive without being pretentious",
        "Passionate about the craft of mango cultivation"
    ],
    "avoid": [
        "Generic food marketing language",
        "Overly salesy or pushy CTAs",
        "Technical agricultural jargon",
        "Comparing negatively to other fruits or brands"
    ]
}

Why separate dictionaries instead of one big config?

Each dictionary serves a different role in prompt engineering. BRAND provides context about the business stage. TARGET_AUDIENCE tells the AI who to write for. BRAND_VOICE controls how the AI sounds.

Separating them lets you swap out one section without touching the others. A different brand could replace TARGET_AUDIENCE while keeping the same content pillar structure.

  • Verify that CONTENT_PILLARS in the diff includes all five pillars: origin_stories, us_food_integration, scarcity_exclusivity, community_building, and mango_education.
  • Each pillar should have a description, goal, frequency, and example_topics list with 4 items.

Codex produced different dictionary names or structure?

Codex sometimes interprets prompts slightly differently. If the variable names or nesting do not match what is shown above, send a follow-up prompt asking Codex to rename the dictionaries to match: BRAND, TARGET_AUDIENCE, BRAND_VOICE, CONTENT_PILLARS.

If Codex is taking longer than 5 minutes, check that your GitHub connection is still active in the Codex settings.

help me fix my Codex prompt for brand_brief.py

Define the content format strategy

Instagram rewards different content formats with different algorithmic signals. Reels drive discovery among non-followers. Carousels drive saves. Single posts drive DMs and comments.

  • In the same Codex thread, send this follow-up prompt and click Code:
Add a CONTENT_FORMATS dictionary to brand_brief.py with three format types:

1. "reels" - purpose: discovery and reach to non-followers, specs: 7-30 seconds with hook in first 3 seconds, frequency: 4-5 per week, best_for: ["origin_stories", "us_food_integration", "scarcity_exclusivity"]

2. "carousels" - purpose: education and depth that drives saves, specs: 5-8 slides with strong cover slide hook, frequency: 2 per week, best_for: ["mango_education", "us_food_integration"]

3. "single_posts" - purpose: community engagement that drives DMs and comments, specs: strong visual with conversation-starting caption, frequency: 1-2 per week, best_for: ["community_building", "scarcity_exclusivity"]

Place this after CONTENT_PILLARS in the file.
  • Wait for Codex to finish, then review the diff.
  • You should see a new CONTENT_FORMATS dictionary added below CONTENT_PILLARS with all three format entries.

Here is what CONTENT_FORMATS should look like:

CONTENT_FORMATS = {
    "reels": {
        "purpose": "Discovery and reach to non-followers",
        "specs": "7-30 seconds, hook in first 3 seconds",
        "frequency": "4-5 per week",
        "best_for": ["origin_stories", "us_food_integration", "scarcity_exclusivity"]
    },
    "carousels": {
        "purpose": "Education and depth, drives saves",
        "specs": "5-8 slides, strong cover slide hook",
        "frequency": "2 per week",
        "best_for": ["mango_education", "us_food_integration"]
    },
    "single_posts": {
        "purpose": "Community engagement, drives DMs and comments",
        "specs": "Strong visual with conversation-starting caption",
        "frequency": "1-2 per week",
        "best_for": ["community_building", "scarcity_exclusivity"]
    }
}

Why map formats to content pillars?

The best_for field links each format to the pillars it serves best. When the AI generates a week of content, it uses this mapping to pick the right format for each pillar. Origin stories work as short Reels. Mango education works as swipeable carousels.

Codex placed the dictionary in the wrong location?

If CONTENT_FORMATS appears before CONTENT_PILLARS or in a separate file, send a follow-up prompt: "Move CONTENT_FORMATS to appear after CONTENT_PILLARS in brand_brief.py."

help me fix the order of dictionaries in brand_brief.py

Define engagement triggers

Instagram's algorithm in 2026 prioritizes DMs and shares as top engagement signals. Every post needs a CTA that triggers one of these actions. You also need guardrails to keep content within Instagram's best practices.

  • Send this final prompt in the same Codex thread and click Code:
Add two more dictionaries to brand_brief.py after CONTENT_FORMATS:

1. CTA_TEMPLATES dictionary with:
   - "dm_triggers": list of 3 template strings like "DM us '{keyword}' and we will send you {reward}"
   - "share_prompts": list of 3 templates for tagging/sharing
   - "save_hooks": list of 3 templates for save-worthy content
   - "keywords": ["ALPHONSO", "MANGO", "WAITLIST", "TASTE", "SEASON"]

2. INSTAGRAM_RULES dictionary with:
   - max_hashtags: 5
   - caption_style: "Keyword-rich for SEO discoverability"
   - hook_position: "First line of caption must stop the scroll"
   - posting_cadence: "5-7 posts per week across all formats"
   - avoid: list of 4 things to avoid (more than 5 hashtags, reposting more than 10 times in 30 days, generic hashtags like #food #yummy, captions without a clear CTA)
  • Wait for Codex to finish, then review the diff.
  • You should see CTA_TEMPLATES and INSTAGRAM_RULES added at the bottom of brand_brief.py.

Here is what the engagement section should look like:

CTA_TEMPLATES = {
    "dm_triggers": [
        "DM us '{keyword}' and we will send you {reward}",
        "Reply '{keyword}' in the comments for early access",
        "Send this to someone who needs to taste a real mango"
    ],
    "share_prompts": [
        "Tag someone who thinks they know mangoes",
        "Share this with your foodie friend who has never tried Alphonso",
        "Send this to someone who deserves better mangoes"
    ],
    "save_hooks": [
        "Save this for mango season",
        "Bookmark this variety guide for when we launch",
        "Save this to remember what real mango tastes like"
    ],
    "keywords": ["ALPHONSO", "MANGO", "WAITLIST", "TASTE", "SEASON"]
}

INSTAGRAM_RULES = {
    "max_hashtags": 5,
    "caption_style": "Keyword-rich for SEO discoverability",
    "hook_position": "First line of caption must stop the scroll",
    "posting_cadence": "5-7 posts per week across all formats",
    "avoid": [
        "More than 5 hashtags per post",
        "Reposting content more than 10 times in 30 days",
        "Generic hashtags like #food #yummy #delicious",
        "Captions without a clear CTA"
    ]
}

Why limit hashtags to 5?

Instagram's updated recommendation caps effective hashtags at 5 per post. More than that dilutes discoverability and can trigger spam filters. The INSTAGRAM_RULES dictionary enforces this constraint so the AI never overloads a post with tags.

The keywords list gives the AI a set of DM trigger words to rotate through. Each keyword maps to a specific reward or action that builds the community.

  • Once you are satisfied with the diff, approve the changes so Codex commits brand_brief.py to your GitHub repository.
  • Open your repository on GitHub. You should see brand_brief.py with a recent commit showing all the brand configuration data.

Don't see the commit on GitHub?

Make sure you approved (not just reviewed) the Codex diff. Codex commits changes to a branch and may need you to merge the pull request if it created one.

Check the repository's Pull requests tab. If Codex opened a PR instead of committing directly to main, click Merge pull request to get the code onto your main branch.

help me get my Codex changes onto the main branch

✔️ Awesome, I've got everything!

Great. Double check that brand_brief.py is visible on your repository's main branch on GitHub before moving on.

ⓧ I'd like to double check the full code

Here is the complete brand_brief.py file. Compare it against what Codex generated in your repository:

"""
Aam India Brand DNA Configuration
Encodes brand strategy into structured data for AI content generation.
"""

BRAND = {
    "name": "Aam India",
    "tagline": "Premium Indian Mangoes for the American Table",
    "mission": "Build awareness and demand for premium Indian mangoes in the United States before Summer 2027 launch.",
    "launch_date": "Summer 2027",
    "stage": "Pre-launch community building (0 to 10K engaged followers)"
}

TARGET_AUDIENCE = {
    "demographics": "US-based food enthusiasts, ages 25-45",
    "psychographics": [
        "Interested in premium, exotic, and artisanal ingredients",
        "Active on food Instagram (follow chefs, food brands, recipe creators)",
        "Willing to pay premium for quality and provenance",
        "Value origin stories and cultural authenticity",
        "Early adopters of new food trends"
    ],
    "pain_points": [
        "Only know grocery store mangoes (Tommy Atkins variety)",
        "Have never tasted a real Alphonso or Kesar mango",
        "Unaware that Indian mango varieties exist or are available in the US",
        "Think all mangoes taste the same"
    ]
}

BRAND_VOICE = {
    "tone": "Authoritative yet approachable",
    "language_style": "Sensory-rich, evocative descriptions",
    "personality": [
        "Knowledgeable insider who has tasted the best",
        "Generous educator, not gatekeeping",
        "Exclusive without being pretentious",
        "Passionate about the craft of mango cultivation"
    ],
    "avoid": [
        "Generic food marketing language",
        "Overly salesy or pushy CTAs",
        "Technical agricultural jargon",
        "Comparing negatively to other fruits or brands"
    ]
}

CONTENT_PILLARS = {
    "origin_stories": {
        "description": "Farm-to-US journey, specific varieties, grower profiles",
        "goal": "Build authenticity and emotional connection",
        "frequency": "2x per week",
        "example_topics": [
            "How Alphonso mangoes are hand-picked at dawn",
            "The 48-hour journey from Ratnagiri to your doorstep",
            "Why mango trees take 5 years before first harvest",
            "Meet the family that has grown Kesar for 4 generations"
        ]
    },
    "us_food_integration": {
        "description": "Mangoes in cocktails, charcuterie, fine dining, recipes",
        "goal": "Make Indian mangoes feel relevant to American food culture",
        "frequency": "1-2x per week",
        "example_topics": [
            "Alphonso mango on a summer charcuterie board",
            "3 cocktails that need real mango puree",
            "Why top NYC chefs are obsessed with Hapus",
            "Mango lassi but make it a craft cocktail"
        ]
    },
    "scarcity_exclusivity": {
        "description": "Seasonal windows, limited availability, pre-launch anticipation",
        "goal": "Create urgency and FOMO",
        "frequency": "1x per week",
        "example_topics": [
            "Alphonso season is only 8 weeks. Here is why.",
            "We can only ship to 500 families in year one",
            "The waitlist is open. Summer 2027.",
            "Why Indian mangoes disappear by July"
        ]
    },
    "community_building": {
        "description": "Polls, questions, taste test invites, DM conversations",
        "goal": "Drive engagement signals (DMs, shares, saves)",
        "frequency": "1-2x per week",
        "example_topics": [
            "Have you ever tasted a mango that made you cry?",
            "DM us MANGO and we will send you our variety guide",
            "Poll: Which US city should we ship to first?",
            "Tag someone who thinks they know mangoes"
        ]
    },
    "mango_education": {
        "description": "Why Indian mangoes are different, variety guides, ripeness tips",
        "goal": "Position as the authority, create save-worthy content",
        "frequency": "1x per week",
        "example_topics": [
            "Alphonso vs Kesar vs Dasheri: A taste guide",
            "Why grocery store mangoes taste like nothing",
            "The GI tag: What makes Ratnagiri Alphonso special",
            "How to tell when an Alphonso is perfectly ripe"
        ]
    }
}

CONTENT_FORMATS = {
    "reels": {
        "purpose": "Discovery and reach to non-followers",
        "specs": "7-30 seconds, hook in first 3 seconds",
        "frequency": "4-5 per week",
        "best_for": ["origin_stories", "us_food_integration", "scarcity_exclusivity"]
    },
    "carousels": {
        "purpose": "Education and depth, drives saves",
        "specs": "5-8 slides, strong cover slide hook",
        "frequency": "2 per week",
        "best_for": ["mango_education", "us_food_integration"]
    },
    "single_posts": {
        "purpose": "Community engagement, drives DMs and comments",
        "specs": "Strong visual with conversation-starting caption",
        "frequency": "1-2 per week",
        "best_for": ["community_building", "scarcity_exclusivity"]
    }
}

CTA_TEMPLATES = {
    "dm_triggers": [
        "DM us '{keyword}' and we will send you {reward}",
        "Reply '{keyword}' in the comments for early access",
        "Send this to someone who needs to taste a real mango"
    ],
    "share_prompts": [
        "Tag someone who thinks they know mangoes",
        "Share this with your foodie friend who has never tried Alphonso",
        "Send this to someone who deserves better mangoes"
    ],
    "save_hooks": [
        "Save this for mango season",
        "Bookmark this variety guide for when we launch",
        "Save this to remember what real mango tastes like"
    ],
    "keywords": ["ALPHONSO", "MANGO", "WAITLIST", "TASTE", "SEASON"]
}

INSTAGRAM_RULES = {
    "max_hashtags": 5,
    "caption_style": "Keyword-rich for SEO discoverability",
    "hook_position": "First line of caption must stop the scroll",
    "posting_cadence": "5-7 posts per week across all formats",
    "avoid": [
        "More than 5 hashtags per post",
        "Reposting content more than 10 times in 30 days",
        "Generic hashtags like #food #yummy #delicious",
        "Captions without a clear CTA"
    ]
}

Your brand DNA is now locked into structured data that any AI model can read. Next up, you will build the content generation pipeline that feeds this brand brief into the Gemini API and produces a full week of Instagram content.

Build the Content Generation Pipeline

Your brand DNA is locked in. Now it is time to build the engine that actually uses it.

In this step, you will direct Codex to build a Python pipeline that reads from brand_brief.py, constructs a detailed prompt, and calls the Gemini API to generate structured Instagram content. The pipeline returns clean JSON that is ready for human review.

In this step, get ready to:
  • Set up the Gemini API client with the google-genai SDK.
  • Engineer a system prompt that encodes your full brand strategy for the AI.
  • Configure structured JSON output so Gemini returns a weekly content calendar.
Set up the Gemini API client

Before the engine can generate content, it needs a connection to Google's Gemini model. The google-genai SDK provides a simple client that authenticates with your API key and sends requests.

  • Switch back to your Codex thread in ChatGPT.
  • Give Codex the following task:

In the task input, describe what you want Codex to build:

  • Write the core of content_engine.py. Import os, json, the google-genai SDK (from google import genai and from google.genai import types), and all the brand configuration dicts from brand_brief.py (BRAND, TARGET_AUDIENCE, BRAND_VOICE, CONTENT_PILLARS, CONTENT_FORMATS, CTA_TEMPLATES, INSTAGRAM_RULES). Create a generate_weekly_content() function that reads the API key from the GEMINI_API_KEY environment variable, initializes a genai.Client, and calls client.models.generate_content with the gemini-2.5-flash model. Also add google-genai==2.10.0 to requirements.txt.

Codex will process your task and show a diff. In the diff, you should see a new requirements.txt with the SDK pinned:

google-genai==2.10.0

And the beginning of content_engine.py should include these imports:

"""
Aam India AI Content Engine
Generates a weekly Instagram content calendar using Google Gemini API.
"""

import os
import json
from google import genai
from google.genai import types
from brand_brief import (
    BRAND, TARGET_AUDIENCE, BRAND_VOICE,
    CONTENT_PILLARS, CONTENT_FORMATS, CTA_TEMPLATES, INSTAGRAM_RULES
)

What do these imports do?

  • os reads the GEMINI_API_KEY environment variable so the key never lives in source code.
  • json parses the structured JSON response from Gemini into a Python dictionary.
  • genai and types are the official Google AI SDK. The types module provides configuration objects like GenerateContentConfig.
  • The brand_brief imports pull in every config dictionary you built in Step 2. This is what makes the engine reusable. Swap the brand config, and the entire pipeline adapts.
  • Review the diff in Codex. Confirm you see the imports and a generate_weekly_content() function skeleton that initializes the client.
  • Do not approve the diff yet. You will add more to this file in the next substeps.

Codex may generate more than you asked for

Codex sometimes writes the entire file in one go. If it does, review all the code carefully against the references in the next two substeps before approving.

Engineer the content generation prompt

A generic prompt produces generic content. The power of this engine is that it feeds Gemini your entire brand strategy as system context, constraining every output to match Aam India's voice, audience, and Instagram rules.

  • If Codex did not already generate a create_system_prompt() function, give it this follow-up task:
  • Add a create_system_prompt() function to content_engine.py that builds a detailed system prompt using the brand config. The prompt should tell Gemini it is an expert Instagram content strategist for Aam India. Include: brand context (mission, stage, launch date, tagline), target audience demographics and psychographics, brand voice rules, all 5 content pillars, Instagram rules (max 5 hashtags, keyword-rich captions, hook in first line, format mix), CTA strategy with DM trigger keywords, and a critical instruction to generate 3 hook variations per post for A/B testing.

The resulting create_system_prompt() function should look like this:

def create_system_prompt():
    """Build the system prompt from brand DNA configuration."""
    return f"""You are an expert Instagram content strategist for {BRAND['name']}.

BRAND CONTEXT:
- Mission: {BRAND['mission']}
- Stage: {BRAND['stage']}
- Launch: {BRAND['launch_date']}
- Tagline: {BRAND['tagline']}

TARGET AUDIENCE:
- {TARGET_AUDIENCE['demographics']}
- Psychographics: {', '.join(TARGET_AUDIENCE['psychographics'][:3])}
- Key pain point: They only know grocery store mangoes and have never tasted premium Indian varieties.

BRAND VOICE:
- Tone: {BRAND_VOICE['tone']}
- Style: {BRAND_VOICE['language_style']}
- Personality: {', '.join(BRAND_VOICE['personality'][:2])}
- Avoid: {', '.join(BRAND_VOICE['avoid'][:2])}

CONTENT PILLARS (rotate through these across the week):
1. Origin Stories - Farm-to-US journey, specific varieties
2. US Food Culture Integration - Mangoes in cocktails, charcuterie, fine dining
3. Scarcity/Exclusivity - Seasonal windows, limited availability
4. Community Building - Polls, DM conversations, engagement
5. Mango Education - Variety guides, why Indian mangoes are different

INSTAGRAM RULES:
- Maximum {INSTAGRAM_RULES['max_hashtags']} hashtags per post
- Captions must be keyword-rich for SEO discoverability
- First line must be a scroll-stopping hook
- Every post must include a clear CTA
- Format mix: Reels (4-5/week for discovery), Carousels (2/week for education), Single Posts (1-2/week for engagement)

CTA STRATEGY (designed to trigger DMs and shares):
- DM trigger keywords: {', '.join(CTA_TEMPLATES['keywords'])}
- Template: "DM us [KEYWORD] and we will send you [reward]"
- Every CTA should drive either a DM, share, save, or comment

CRITICAL: Generate 3 hook variations per post for A/B testing. Each hook must be different in approach (curiosity, controversy, sensory, social proof)."""

Why build the prompt from config instead of hardcoding it?

The f-string pulls live values from brand_brief.py at runtime. If you update your brand voice or add a content pillar next month, the prompt automatically reflects those changes without touching content_engine.py.

This separation is what makes the repo forkable. Someone else can replace brand_brief.py with their own brand config, and the engine immediately generates content in their voice.

What are the Instagram growth signals encoded here?

The prompt enforces max 5 hashtags per post, keyword-rich captions for discoverability, hook-first formatting, and DM-triggering CTAs. These align with Instagram's 2026 ranking signals: watch time, sends per reach (DMs), and likes per reach.

  • Check that the function in Codex's diff uses f-string interpolation to inject values from the brand config dictionaries.
  • Confirm the prompt mentions all 5 content pillars by name and includes the CTA keywords list.
Add structured JSON output

Without structure, Gemini returns free-form text that you would have to manually parse. By defining a JSON schema and setting response_mime_type to application/json, you guarantee the response matches your exact data structure every time.

  • If Codex has not already added a schema function, give it this task:
  • Add a get_content_calendar_schema() function that returns a JSON schema for a weekly content calendar. The schema should define an object with week_theme (string) and a posts array. Each post has: day, content_pillar, format, hooks (array of strings), caption, hashtags (array of strings), cta, engagement_rationale, and format_rationale. All fields required. Then update generate_weekly_content() to use response_mime_type="application/json" and response_json_schema=get_content_calendar_schema() in the GenerateContentConfig.

The generate_weekly_content() function should now include the full API call with structured output configuration:

def generate_weekly_content():
    """Generate a full week of Instagram content using Gemini API."""
    api_key = os.environ.get("GEMINI_API_KEY")
    if not api_key:
        raise ValueError(
            "GEMINI_API_KEY environment variable is not set. "
            "Get your free key at https://aistudio.google.com"
        )

    client = genai.Client(api_key=api_key)

    system_prompt = create_system_prompt()

    user_prompt = """Generate a complete 7-day Instagram content calendar for this week.

Requirements:
- Include exactly 7 posts (one per day, Monday through Sunday)
- Rotate through all 5 content pillars across the week
- Mix formats: at least 3 Reels, 2 Carousels, and 2 Single Posts
- Each post must have exactly 3 hook variations for A/B testing
- Each post must have 3-5 hashtags (never more than 5)
- Each CTA must use one of the DM trigger keywords or share prompts
- Captions should be 50-150 words, keyword-rich, with the hook as the first line
- Include engagement_rationale explaining which Instagram signal this post targets (DMs, shares, saves, watch time)
- Include format_rationale explaining why this format was chosen for this content"""

    response = client.models.generate_content(
        model="gemini-2.5-flash",
        contents=user_prompt,
        config=types.GenerateContentConfig(
            system_instruction=system_prompt,
            response_mime_type="application/json",
            response_json_schema=get_content_calendar_schema(),
            temperature=0.8,
            max_output_tokens=4000,
        ),
    )

    return json.loads(response.text)

What does this configuration do?

  • system_instruction feeds the entire brand strategy as persistent context. Gemini treats this as its "role" for the conversation.
  • response_mime_type="application/json" forces Gemini to return valid JSON instead of free-form text.
  • response_json_schema constrains the JSON structure to your exact schema. No missing fields, no unexpected shapes.
  • temperature=0.8 adds creative variety. Lower values produce repetitive content. Higher values risk incoherence.
  • json.loads(response.text) parses the response string into a Python dictionary you can save or process further.
  • Review the full diff in Codex. Confirm that content_engine.py has all three functions: create_system_prompt(), get_content_calendar_schema(), and generate_weekly_content().
  • Confirm that requirements.txt contains google-genai==2.10.0.
  • Click Approve in Codex to commit the changes to your GitHub repo.

Codex generated different code?

Codex may name functions differently or use a slightly different SDK pattern. The key things to verify are:

  • The import is from google import genai (not import google.generativeai, which is the old v1 SDK).
  • The client uses genai.Client(api_key=api_key) and calls client.models.generate_content.
  • The config includes response_mime_type and response_json_schema for structured output.

If Codex used the old SDK pattern, ask it to refactor using the google-genai v2 SDK with genai.Client. Help me fix my Codex-generated code to use the google-genai v2 SDK pattern

  • Open your repository on GitHub and navigate to content_engine.py.
  • Verify the file shows all three functions committed.

✔️ Awesome, I've got everything!

Great. Double check you see both content_engine.py and requirements.txt committed on GitHub with the pipeline code.

ⓧ I'd like to double check the full code

Here is the complete content_engine.py as it should look at this point:

"""
Aam India AI Content Engine
Generates a weekly Instagram content calendar using Google Gemini API.
"""

import os
import json
from google import genai
from google.genai import types
from brand_brief import (
    BRAND, TARGET_AUDIENCE, BRAND_VOICE,
    CONTENT_PILLARS, CONTENT_FORMATS, CTA_TEMPLATES, INSTAGRAM_RULES
)


def create_system_prompt():
    """Build the system prompt from brand DNA configuration."""
    return f"""You are an expert Instagram content strategist for {BRAND['name']}.

BRAND CONTEXT:
- Mission: {BRAND['mission']}
- Stage: {BRAND['stage']}
- Launch: {BRAND['launch_date']}
- Tagline: {BRAND['tagline']}

TARGET AUDIENCE:
- {TARGET_AUDIENCE['demographics']}
- Psychographics: {', '.join(TARGET_AUDIENCE['psychographics'][:3])}
- Key pain point: They only know grocery store mangoes and have never tasted premium Indian varieties.

BRAND VOICE:
- Tone: {BRAND_VOICE['tone']}
- Style: {BRAND_VOICE['language_style']}
- Personality: {', '.join(BRAND_VOICE['personality'][:2])}
- Avoid: {', '.join(BRAND_VOICE['avoid'][:2])}

CONTENT PILLARS (rotate through these across the week):
1. Origin Stories - Farm-to-US journey, specific varieties
2. US Food Culture Integration - Mangoes in cocktails, charcuterie, fine dining
3. Scarcity/Exclusivity - Seasonal windows, limited availability
4. Community Building - Polls, DM conversations, engagement
5. Mango Education - Variety guides, why Indian mangoes are different

INSTAGRAM RULES:
- Maximum {INSTAGRAM_RULES['max_hashtags']} hashtags per post
- Captions must be keyword-rich for SEO discoverability
- First line must be a scroll-stopping hook
- Every post must include a clear CTA
- Format mix: Reels (4-5/week for discovery), Carousels (2/week for education), Single Posts (1-2/week for engagement)

CTA STRATEGY (designed to trigger DMs and shares):
- DM trigger keywords: {', '.join(CTA_TEMPLATES['keywords'])}
- Template: "DM us [KEYWORD] and we will send you [reward]"
- Every CTA should drive either a DM, share, save, or comment

CRITICAL: Generate 3 hook variations per post for A/B testing. Each hook must be different in approach (curiosity, controversy, sensory, social proof)."""


def get_content_calendar_schema():
    """Define the JSON schema for structured content calendar output."""
    return {
        "type": "object",
        "properties": {
            "week_theme": {"type": "string"},
            "posts": {
                "type": "array",
                "items": {
                    "type": "object",
                    "properties": {
                        "day": {"type": "string"},
                        "content_pillar": {"type": "string"},
                        "format": {"type": "string"},
                        "hooks": {
                            "type": "array",
                            "items": {"type": "string"}
                        },
                        "caption": {"type": "string"},
                        "hashtags": {
                            "type": "array",
                            "items": {"type": "string"}
                        },
                        "cta": {"type": "string"},
                        "engagement_rationale": {"type": "string"},
                        "format_rationale": {"type": "string"}
                    },
                    "required": [
                        "day", "content_pillar", "format", "hooks",
                        "caption", "hashtags", "cta",
                        "engagement_rationale", "format_rationale"
                    ]
                }
            }
        },
        "required": ["week_theme", "posts"]
    }


def generate_weekly_content():
    """Generate a full week of Instagram content using Gemini API."""
    api_key = os.environ.get("GEMINI_API_KEY")
    if not api_key:
        raise ValueError(
            "GEMINI_API_KEY environment variable is not set. "
            "Get your free key at https://aistudio.google.com"
        )

    client = genai.Client(api_key=api_key)

    system_prompt = create_system_prompt()

    user_prompt = """Generate a complete 7-day Instagram content calendar for this week.

Requirements:
- Include exactly 7 posts (one per day, Monday through Sunday)
- Rotate through all 5 content pillars across the week
- Mix formats: at least 3 Reels, 2 Carousels, and 2 Single Posts
- Each post must have exactly 3 hook variations for A/B testing
- Each post must have 3-5 hashtags (never more than 5)
- Each CTA must use one of the DM trigger keywords or share prompts
- Captions should be 50-150 words, keyword-rich, with the hook as the first line
- Include engagement_rationale explaining which Instagram signal this post targets (DMs, shares, saves, watch time)
- Include format_rationale explaining why this format was chosen for this content"""

    response = client.models.generate_content(
        model="gemini-2.5-flash",
        contents=user_prompt,
        config=types.GenerateContentConfig(
            system_instruction=system_prompt,
            response_mime_type="application/json",
            response_json_schema=get_content_calendar_schema(),
            temperature=0.8,
            max_output_tokens=4000,
        ),
    )

    return json.loads(response.text)

And requirements.txt:

google-genai==2.10.0

Your pipeline is wired up and committed. Next, you will run it for the first time and generate a full 7-day content calendar.

Generate and Validate Your Content Calendar

Your content engine has all the intelligence it needs. The brand DNA is encoded, the system prompt is structured, and the Gemini API call is configured with structured JSON output.

Now it is time to see if this pipeline actually produces content worth posting. In this step, you will add the final execution code, run the pipeline against the live Gemini API, and refine the output until it meets Aam India's quality bar.

In this step, get ready to:
  • Run the content generation pipeline in Codex's cloud environment.
  • Review the generated content calendar for quality and brand alignment.
  • Iterate on the system prompt to improve output quality.
Add the main execution block and run the pipeline

The pipeline functions exist, but there is no code that actually calls them. You need a main execution block that generates the calendar, saves it to a JSON file, and prints a summary to the terminal.

  • Switch back to Codex in ChatGPT.
  • Ask Codex to add a main execution block to content_engine.py that: adds a save_content_calendar() function to save the output as generated_content.json, adds a display_calendar_summary() function to print a readable summary, and includes an if __name__ == "__main__" block that calls all three functions in sequence.

What does the main block do?

The if __name__ == "__main__" pattern is Python's way of saying "run this code only when the script is executed directly." It calls generate_weekly_content() to hit the Gemini API, saves the structured JSON response to a file, and prints a day-by-day summary to the terminal.

When Codex shows you the diff, verify it includes code similar to this:

def save_content_calendar(calendar, filename="generated_content.json"):
    """Save the generated content calendar to a JSON file."""
    with open(filename, "w") as f:
        json.dump(calendar, f, indent=2)
    print(f"Content calendar saved to {filename}")
    return filename


def display_calendar_summary(calendar):
    """Print a readable summary of the generated content."""
    print(f"\nWeek Theme: {calendar['week_theme']}")
    print("=" * 50)
    for post in calendar["posts"]:
        print(f"\n{post['day']} | {post['format'].upper()} | {post['content_pillar']}")
        print(f"  Hook 1: {post['hooks'][0]}")
        print(f"  CTA: {post['cta']}")
        print(f"  Hashtags: {' '.join(post['hashtags'])}")
        print(f"  Why: {post['engagement_rationale']}")
    print("\n" + "=" * 50)


if __name__ == "__main__":
    print("Generating Aam India weekly content calendar...")
    print("Using Gemini API (free tier)\n")

    calendar = generate_weekly_content()
    save_content_calendar(calendar)
    display_calendar_summary(calendar)

    print(f"\nGenerated {len(calendar['posts'])} posts for the week.")
    print("Review generated_content.json for full details including all hook variations.")

What does this code do?

  • save_content_calendar() writes the JSON response to generated_content.json with readable indentation.
  • display_calendar_summary() prints a compact overview of each day's post including format, pillar, first hook, CTA, and hashtags.
  • The __main__ block ties the full workflow together: generate, save, display, and confirm the post count.

Before Codex can run the script, it needs access to your Gemini API key. Codex environment variables persist for the full duration of a task, so your key stays available whenever the script runs.

  • In the Codex interface, open your environment settings for the aam-india-content-engine repository.
  • Add an environment variable with the name GEMINI_API_KEY and paste your Gemini API key as the value.
  • Save the environment settings.

Why an environment variable instead of a secret?

In Codex, secrets are only available during the setup script phase and are removed before the agent runs. Environment variables persist for the full task duration, including when the agent executes your Python script. Since content_engine.py reads the key at runtime, it needs to be an environment variable.

  • Ask Codex to run content_engine.py and generate the weekly content calendar.
  • Wait for the task to complete. Codex will install google-genai from requirements.txt automatically, then execute the script.

You should see terminal output showing the week theme, a summary of all 7 posts with their formats, hooks, CTAs, and hashtags, and a confirmation that generated_content.json was saved.

Seeing a GEMINI_API_KEY error?

  • Confirm the environment variable name is exactly GEMINI_API_KEY (all caps, underscores).
  • Check that you added it as an environment variable in Codex settings, not as a secret. Secrets are wiped before the agent phase runs your code.
  • If you just saved the environment settings, try running the task again so Codex picks up the new configuration.

Still stuck? Help me troubleshoot my GEMINI_API_KEY not being found in Codex

Review output quality

A content calendar is only useful if it meets Aam India's brand standards. You need to evaluate the generated output against five quality signals before calling it done.

  • Open the generated_content.json file in the Codex diff view or ask Codex to show you the contents.
  • Check hook quality: each post should have 3 distinct hook variations using different approaches (curiosity, controversy, sensory, social proof).
  • Check CTA relevance: CTAs should drive DMs, shares, or saves using the keywords from your brand brief (ALPHONSO, MANGO, WAITLIST, TASTE, SEASON).
  • Check hashtag specificity: each post should have 3-5 niche hashtags relevant to premium mangoes, not generic food hashtags.
  • Check format mix: verify you have at least 3 Reels, 2 Carousels, and 2 Single Posts across the week.
  • Check brand voice: captions should use sensory-rich language and feel authoritative yet approachable, not salesy or generic.

What does good output look like?

A strong post has a hook that stops the scroll ("The mango most Indians keep secret from the world"), a CTA that drives a specific action ("DM us ALPHONSO for our ripeness guide"), and niche hashtags like #AlphonsoMango or #IndianMangoSeason instead of generic ones like #food or #yummy.

The engagement rationale should name a specific Instagram signal (DMs, saves, watch time, shares) and explain why this format and CTA target it.

Iterate on prompt quality

The first run rarely produces perfect content. Based on what you noticed during your review, you can tighten the system prompt to get better results on the next generation.

  • Identify 1-2 weaknesses from your review. Common issues include: hooks that all sound the same, CTAs that repeat the same keyword, generic captions that lack sensory language, or content pillars that are not evenly distributed across the week.
  • Ask Codex to refine the system prompt in content_engine.py based on your feedback. For example: "Tighten the brand voice constraints to require at least one sensory word per caption" or "Add more specific hook examples for the mango niche" or "Ensure CTAs vary across all 5 DM keywords across the week."
  • Ask Codex to run the pipeline again with the updated prompt.
  • Compare the new output against your first run. The hooks should be more varied, the CTAs should use different keywords, and the voice should be more consistent.

Why iterate instead of getting it perfect the first time?

Prompt engineering is iterative by nature. You cannot predict how a model will interpret every instruction until you see the output. Reviewing real output and tightening constraints is how professional AI engineers build reliable pipelines.

Each iteration narrows the gap between what you want and what the model produces. Two to three rounds is typical for production-quality prompts.

  • Once you are satisfied with the output quality, ask Codex to commit the updated content_engine.py and the final generated_content.json to the repository.
  • Verify on GitHub that both files appear in your aam-india-content-engine repository.

Output still not on-brand?

  • Try raising the temperature value for more creative hooks, or lowering it for more consistent brand voice adherence.
  • Add explicit negative constraints ("Never use generic food marketing phrases like delicious or amazing") to the system prompt.
  • Ask Codex to add 2-3 example hooks directly in the system prompt as few-shot examples for the model to follow.

Need more help? Help me improve my content engine output quality

✔️ Awesome, I've got everything!

Great. Double check that both content_engine.py (with the main block) and generated_content.json are committed to your GitHub repository.

ⓧ I'd like to double check the full code

Here is the complete content_engine.py file with the main execution block:

"""
Aam India AI Content Engine
Generates a weekly Instagram content calendar using Google Gemini API.
"""

import os
import json
from google import genai
from google.genai import types
from brand_brief import (
    BRAND, TARGET_AUDIENCE, BRAND_VOICE,
    CONTENT_PILLARS, CONTENT_FORMATS, CTA_TEMPLATES, INSTAGRAM_RULES
)


def create_system_prompt():
    """Build the system prompt from brand DNA configuration."""
    return f"""You are an expert Instagram content strategist for {BRAND['name']}.

BRAND CONTEXT:
- Mission: {BRAND['mission']}
- Stage: {BRAND['stage']}
- Launch: {BRAND['launch_date']}
- Tagline: {BRAND['tagline']}

TARGET AUDIENCE:
- {TARGET_AUDIENCE['demographics']}
- Psychographics: {', '.join(TARGET_AUDIENCE['psychographics'][:3])}
- Key pain point: They only know grocery store mangoes and have never tasted premium Indian varieties.

BRAND VOICE:
- Tone: {BRAND_VOICE['tone']}
- Style: {BRAND_VOICE['language_style']}
- Personality: {', '.join(BRAND_VOICE['personality'][:2])}
- Avoid: {', '.join(BRAND_VOICE['avoid'][:2])}

CONTENT PILLARS (rotate through these across the week):
1. Origin Stories - Farm-to-US journey, specific varieties
2. US Food Culture Integration - Mangoes in cocktails, charcuterie, fine dining
3. Scarcity/Exclusivity - Seasonal windows, limited availability
4. Community Building - Polls, DM conversations, engagement
5. Mango Education - Variety guides, why Indian mangoes are different

INSTAGRAM RULES:
- Maximum {INSTAGRAM_RULES['max_hashtags']} hashtags per post
- Captions must be keyword-rich for SEO discoverability
- First line must be a scroll-stopping hook
- Every post must include a clear CTA
- Format mix: Reels (4-5/week for discovery), Carousels (2/week for education), Single Posts (1-2/week for engagement)

CTA STRATEGY (designed to trigger DMs and shares):
- DM trigger keywords: {', '.join(CTA_TEMPLATES['keywords'])}
- Template: "DM us [KEYWORD] and we will send you [reward]"
- Every CTA should drive either a DM, share, save, or comment

CRITICAL: Generate 3 hook variations per post for A/B testing. Each hook must be different in approach (curiosity, controversy, sensory, social proof)."""


def get_content_calendar_schema():
    """Define the JSON schema for structured content calendar output."""
    return {
        "type": "object",
        "properties": {
            "week_theme": {"type": "string"},
            "posts": {
                "type": "array",
                "items": {
                    "type": "object",
                    "properties": {
                        "day": {"type": "string"},
                        "content_pillar": {"type": "string"},
                        "format": {"type": "string"},
                        "hooks": {
                            "type": "array",
                            "items": {"type": "string"}
                        },
                        "caption": {"type": "string"},
                        "hashtags": {
                            "type": "array",
                            "items": {"type": "string"}
                        },
                        "cta": {"type": "string"},
                        "engagement_rationale": {"type": "string"},
                        "format_rationale": {"type": "string"}
                    },
                    "required": [
                        "day", "content_pillar", "format", "hooks",
                        "caption", "hashtags", "cta",
                        "engagement_rationale", "format_rationale"
                    ]
                }
            }
        },
        "required": ["week_theme", "posts"]
    }


def generate_weekly_content():
    """Generate a full week of Instagram content using Gemini API."""
    api_key = os.environ.get("GEMINI_API_KEY")
    if not api_key:
        raise ValueError(
            "GEMINI_API_KEY environment variable is not set. "
            "Get your free key at https://aistudio.google.com"
        )

    client = genai.Client(api_key=api_key)

    system_prompt = create_system_prompt()

    user_prompt = """Generate a complete 7-day Instagram content calendar for this week.

Requirements:
- Include exactly 7 posts (one per day, Monday through Sunday)
- Rotate through all 5 content pillars across the week
- Mix formats: at least 3 Reels, 2 Carousels, and 2 Single Posts
- Each post must have exactly 3 hook variations for A/B testing
- Each post must have 3-5 hashtags (never more than 5)
- Each CTA must use one of the DM trigger keywords or share prompts
- Captions should be 50-150 words, keyword-rich, with the hook as the first line
- Include engagement_rationale explaining which Instagram signal this post targets (DMs, shares, saves, watch time)
- Include format_rationale explaining why this format was chosen for this content"""

    response = client.models.generate_content(
        model="gemini-2.5-flash",
        contents=user_prompt,
        config=types.GenerateContentConfig(
            system_instruction=system_prompt,
            response_mime_type="application/json",
            response_json_schema=get_content_calendar_schema(),
            temperature=0.8,
            max_output_tokens=4000,
        ),
    )

    return json.loads(response.text)


def save_content_calendar(calendar, filename="generated_content.json"):
    """Save the generated content calendar to a JSON file."""
    with open(filename, "w") as f:
        json.dump(calendar, f, indent=2)
    print(f"Content calendar saved to {filename}")
    return filename


def display_calendar_summary(calendar):
    """Print a readable summary of the generated content."""
    print(f"\nWeek Theme: {calendar['week_theme']}")
    print("=" * 50)
    for post in calendar["posts"]:
        print(f"\n{post['day']} | {post['format'].upper()} | {post['content_pillar']}")
        print(f"  Hook 1: {post['hooks'][0]}")
        print(f"  CTA: {post['cta']}")
        print(f"  Hashtags: {' '.join(post['hashtags'])}")
        print(f"  Why: {post['engagement_rationale']}")
    print("\n" + "=" * 50)


if __name__ == "__main__":
    print("Generating Aam India weekly content calendar...")
    print("Using Gemini API (free tier)\n")

    calendar = generate_weekly_content()
    save_content_calendar(calendar)
    display_calendar_summary(calendar)

    print(f"\nGenerated {len(calendar['posts'])} posts for the week.")
    print("Review generated_content.json for full details including all hook variations.")

Your content engine now produces a full week of strategically-aligned Instagram content in minutes. Next up, you will make this project open source so anyone on your team can fork and run it.

Make It Open Source Ready

Your content engine works. It generates a full week of on-brand Instagram content in minutes. But right now, only you know how to use it.

In this step, you will turn the repo into something anyone on your team (or any startup founder who finds it) can pick up, fork, and run within five minutes. That means documentation, a reusable template, and a one-command run script.

In this step, get ready to:
  • Write a comprehensive README explaining the project and how to use it.
  • Create a blank brand brief template that anyone can fill in for their own brand.
  • Add a run script so the engine launches with a single command.
Write the README

A good README is the front door to any open-source project. Someone landing on your GitHub repo needs to understand what the project does, why it exists, and how to run it in under a minute of reading.

  • Switch back to your Codex thread in ChatGPT.
  • Ask Codex to generate a comprehensive README.md that includes: a project description, an explanation of the content strategy behind the engine, instructions for how to fork and customize it for any brand, a setup guide showing how to get a free Gemini API key, and instructions for modifying brand_brief.py for different niches.
  • Review the diff that Codex shows you and approve the commit.

What should a good README include?

Your README should cover five key sections:

  • Project title and one-line description of what it does.
  • The content strategy logic (why max 5 hashtags, why DM-trigger CTAs, why hook variations).
  • Setup instructions pointing to the Gemini API free tier at aistudio.google.com.
  • Fork-and-customize instructions explaining how to replace the brand config.
  • Usage examples for generating content for different time periods or cadences.
  • Open your repo on GitHub and confirm the README renders on the main page.
  • You should see formatted headings, code blocks, and a clear explanation of how to use the engine.

README not showing on GitHub?

Make sure the file is named exactly README.md (uppercase). GitHub only auto-renders the file if the name matches this convention.

If the commit is not showing, wait 30 seconds and refresh the page. Codex commits sometimes take a moment to propagate.

Why isn't my README showing on my GitHub repo page?

Create a brand brief template

The power of this engine is that the AI logic is decoupled from the brand-specific data. Anyone can swap in their own brand by filling in a fresh brand_brief.py. A blank template makes that obvious and easy.

  • Ask Codex to create a new file called brand_brief_template.py. Tell it to make a blank version of the brand configuration with the same dictionary structure (BRAND, TARGET_AUDIENCE, BRAND_VOICE, CONTENT_PILLARS, CONTENT_FORMATS, CTA_TEMPLATES, INSTAGRAM_RULES) but with clear placeholder values and instructional comments that any startup can fill in.
  • Tell Codex to include a docstring at the top explaining that users should copy this file as brand_brief.py and customize all values.
  • Review the diff and approve the commit.

Why separate the template from the real config?

Keeping a blank template means someone who forks your repo can immediately see what fields to fill in without accidentally overwriting your real Aam India data. It also shows the structure at a glance, which is faster than reading through a fully populated config.

  • Confirm brand_brief_template.py appears in your repo on GitHub.
  • Click the file and verify it contains all seven dictionaries with placeholder values and comments explaining each field.

✔️ Awesome, I've got everything!

Great. Make sure the template has placeholder strings (not your real Aam India data) so anyone forking can fill in their own brand.

ⓧ I'd like to double check the full code

Here is what brand_brief_template.py should look like. Your Codex output may differ slightly in wording, but it should have the same structure and placeholder approach.

"""
Brand Brief Template
Fork this file and fill in your brand's details to use the content engine.
Copy this file as brand_brief.py and customize all values.
"""

BRAND = {
    "name": "Your Brand Name",
    "tagline": "Your one-line brand positioning",
    "mission": "What you are trying to achieve with Instagram",
    "launch_date": "When your product/service launches (or 'Already live')",
    "stage": "Current growth stage (e.g., 'Pre-launch', '0-1K followers', '1K-10K')"
}

TARGET_AUDIENCE = {
    "demographics": "Who they are (age, location, interests)",
    "psychographics": [
        "What they care about",
        "What communities they belong to",
        "What they spend money on"
    ],
    "pain_points": [
        "Problem 1 your brand solves",
        "Problem 2 your brand solves"
    ]
}

BRAND_VOICE = {
    "tone": "How you sound (e.g., 'Professional but warm')",
    "language_style": "How you write (e.g., 'Short punchy sentences')",
    "personality": [
        "Trait 1 (e.g., 'Expert insider')",
        "Trait 2 (e.g., 'Generous teacher')"
    ],
    "avoid": [
        "What you never say or do",
        "Language or topics that are off-brand"
    ]
}

CONTENT_PILLARS = {
    "pillar_1": {
        "description": "What this pillar covers",
        "goal": "What it achieves for your brand",
        "frequency": "How often per week",
        "example_topics": ["Topic A", "Topic B", "Topic C"]
    },
    "pillar_2": {
        "description": "What this pillar covers",
        "goal": "What it achieves for your brand",
        "frequency": "How often per week",
        "example_topics": ["Topic A", "Topic B", "Topic C"]
    }
}

CONTENT_FORMATS = {
    "reels": {
        "purpose": "Discovery and reach",
        "specs": "7-30 seconds, hook in first 3 seconds",
        "frequency": "4-5 per week",
        "best_for": ["pillar_1", "pillar_2"]
    },
    "carousels": {
        "purpose": "Education and saves",
        "specs": "5-8 slides",
        "frequency": "2 per week",
        "best_for": ["pillar_2"]
    },
    "single_posts": {
        "purpose": "Community engagement",
        "specs": "Strong visual with conversation caption",
        "frequency": "1-2 per week",
        "best_for": ["pillar_1"]
    }
}

CTA_TEMPLATES = {
    "dm_triggers": [
        "DM us '{keyword}' for {reward}"
    ],
    "share_prompts": [
        "Tag someone who needs to see this"
    ],
    "save_hooks": [
        "Save this for later"
    ],
    "keywords": ["KEYWORD1", "KEYWORD2"]
}

INSTAGRAM_RULES = {
    "max_hashtags": 5,
    "caption_style": "Keyword-rich for discoverability",
    "hook_position": "First line stops the scroll",
    "posting_cadence": "5-7 posts per week",
    "avoid": [
        "More than 5 hashtags",
        "Generic hashtags",
        "No CTA in caption"
    ]
}
Add a run script and documentation

A one-command run script removes friction. Someone cloning the repo should be able to set their API key and run a single command to generate content.

  • Ask Codex to create a run.sh bash script that checks if the GEMINI_API_KEY environment variable is set (exits with a helpful error if not), installs dependencies from requirements.txt if the google-genai package is not already available, then runs python3 content_engine.py.
  • Also ask Codex to add usage examples to the README showing how to generate content for different time periods, customize content pillars, and adjust the posting cadence.
  • Review both diffs and approve the commits.

What does run.sh do?

The script handles three things in order: it checks that the user has set their API key (so they get a clear error message instead of a Python traceback), it auto-installs dependencies if missing, and it runs the engine. One command, no guesswork.

  • Confirm run.sh appears in your repo on GitHub.
  • Click the file and check that it starts with #!/bin/bash and includes a check for GEMINI_API_KEY.

✔️ Awesome, I've got everything!

Great. Double check that your repo now has all the files: brand_brief.py, content_engine.py, requirements.txt, .env.example, generated_content.json, README.md, brand_brief_template.py, and run.sh.

ⓧ I'd like to double check the full code

Here is what run.sh should look like. Your Codex output may differ slightly, but it should follow the same check-install-run pattern.

#!/bin/bash
# Aam India Content Engine - Quick Run Script
# Usage: ./run.sh

echo "Aam India AI Content Engine"
echo "=========================="
echo ""

# Check for API key
if [ -z "$GEMINI_API_KEY" ]; then
    echo "Error: GEMINI_API_KEY is not set."
    echo ""
    echo "Set it with:"
    echo "  export GEMINI_API_KEY='your-key-here'"
    echo ""
    echo "Get a free key at: https://aistudio.google.com"
    exit 1
fi

# Install dependencies if needed
if ! python3 -c "from google import genai" 2>/dev/null; then
    echo "Installing dependencies..."
    pip install -r requirements.txt
fi

# Run the content engine
python3 content_engine.py

Codex not committing the files?

Make sure you approve each diff Codex shows you. If you dismiss or skip the diff, the file does not get committed. You can scroll up in the Codex thread and look for any unapproved changes.

If Codex created the files in a branch instead of main, ask it to merge the branch or create the files directly on main.

Why are my Codex commits not showing on my GitHub repo?

Secret mission

Add Seasonal Content Intelligence

Your content engine generates great posts, but it has no sense of timing. What if it could automatically adjust its hooks and urgency based on where you are in the mango season calendar? In this mission, you'll add a seasonal intelligence module that makes every piece of content feel timely and relevant to the moment.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely in the cloud using Codex and the Gemini API free tier, so there are no ongoing costs. Decide whether to keep your resources available, pause them, or delete them entirely.

Resources you used:

  • GitHub repository (aam-india-content-engine).
  • Gemini API key (stored in Google AI Studio / Google Cloud).

Keep everything running

No action needed. Choose this if you plan to keep generating content with the engine or want to showcase the repo on your GitHub profile.

  • Your GitHub repo stays public and accessible at any time.
  • Your Gemini API key remains active on the free tier with no charges.

Pause - I'll come back to this later

Free up access without permanently deleting anything. You can come back and regenerate content whenever you like.

  • No action is strictly required since both the repo and the API key are free. However, if you want to reduce your exposure, you can restrict your Gemini API key in Google AI Studio by navigating to the API Keys page and adding restrictions.

Delete - I don't want to use this again

Remove all project resources completely. This cannot be undone.

Delete the GitHub repository:

  • Open your aam-india-content-engine repository on GitHub.
  • Click Settings in the repository navigation bar.
  • Scroll down to the Danger Zone section and click Delete this repository.
  • Click I want to delete this repository, then click I have read and understand these effects.
  • Type the repository name to confirm, then click Delete this repository.

Delete your Gemini API key:

  • Open the Google Cloud API Credentials page.
  • Find the API key you created for this project and click the Actions icon next to it.
  • Select Delete API key.
  • In the confirmation modal, click Delete. Deletion takes a few minutes to propagate.

Nice Work!

Nice Work!

You just built a complete AI-powered content engine that generates a full week of strategically-aligned Instagram content for Aam India in minutes. From brand DNA configuration to structured JSON output, you now have a repeatable system that any team member can run.

You learned how to:

  • Build a structured prompt engineering pipeline that encodes brand voice, content pillars, and engagement rules into reusable AI instructions using the Gemini API free tier.
  • Generate structured JSON output from an LLM using response_mime_type and response_json_schema to produce a 7-day content calendar with hook variations for A/B testing.
  • Create a public, open-source GitHub repository with clean architecture that anyone can fork and customize for their own brand by replacing brand_brief.py.
  • Secret Mission: Added a seasonal content intelligence module that automatically adjusts content angles based on the US mango season calendar and Aam India's Summer 2027 launch timeline.

Ready to quiz yourself?