Skip to content

SEO Content Agent

SEO Content Agent #66

Workflow file for this run

name: SEO Content Agent
on:
schedule:
- cron: '0 14 * * 1,3,5'
workflow_dispatch:
inputs:
topic:
description: Optional topic override for a manual run
required: false
type: string
keyword:
description: Optional target keyword override for a manual run
required: false
type: string
dry_run:
description: Generate artifacts without creating a Ghost draft
required: true
default: false
type: boolean
max_drafts:
description: Maximum Ghost drafts to create
required: true
default: '1'
type: choice
options:
- '1'
content_focus:
description: Optional content focus override
required: true
default: auto
type: choice
options:
- auto
- logistics-software
- supply-chain-software
- fleetbase-api-tutorial
generate_feature_image:
description: Generate and attach an OpenAI feature image when creating the Ghost draft
required: true
default: true
type: boolean
generate_feature_image_in_dry_run:
description: Also call OpenAI Images during dry runs
required: true
default: false
type: boolean
allow_seed_fallback:
description: Allow continuing without Ahrefs results
required: true
default: false
type: boolean
use_ahrefs:
description: Use Ahrefs keyword research for this run
required: true
default: false
type: boolean
topic_mode:
description: Topic generation mode
required: true
default: auto
type: choice
options:
- auto
- integration
- api-tutorial
- software-seo
- comparison
integration_target:
description: Optional integration target for integration topics
required: false
type: string
permissions:
contents: read
id-token: write
jobs:
generate:
name: Generate SEO content draft
runs-on: ubuntu-latest
env:
AHREFS_API_TOKEN: ${{ secrets.AHREFS_API_TOKEN }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_CONTENT_MODEL: ${{ vars.OPENAI_CONTENT_MODEL || 'gpt-4.1' }}
OPENAI_IMAGE_MODEL: ${{ vars.OPENAI_IMAGE_MODEL || 'gpt-image-1.5' }}
GHOST_ADMIN_API_URL: ${{ vars.GHOST_ADMIN_API_URL || 'https://blog.fleetbase.io' }}
GHOST_ADMIN_API_KEY: ${{ secrets.GHOST_ADMIN_API_KEY }}
GHOST_API_URL: ${{ vars.GHOST_API_URL || 'https://blog.fleetbase.io' }}
GHOST_CONTENT_API_KEY: ${{ secrets.GHOST_CONTENT_API_KEY }}
GHOST_API_VERSION: ${{ vars.GHOST_API_VERSION || 'v6.0' }}
CONTENT_AGENT_NOTIFY_WEBHOOK_URL: ${{ secrets.CONTENT_AGENT_NOTIFY_WEBHOOK_URL }}
CONTENT_AGENT_TOPIC: ${{ inputs.topic || '' }}
CONTENT_AGENT_KEYWORD: ${{ inputs.keyword || '' }}
CONTENT_AGENT_FOCUS: ${{ inputs.content_focus || 'auto' }}
CONTENT_AGENT_TOPIC_MODE: ${{ inputs.topic_mode || 'auto' }}
CONTENT_AGENT_INTEGRATION_TARGET: ${{ inputs.integration_target || '' }}
CONTENT_AGENT_MAX_DRAFTS: ${{ inputs.max_drafts || '1' }}
CONTENT_AGENT_OUTPUT_DIR: content-agent-output
CONTENT_AGENT_ALLOW_SEED_FALLBACK: ${{ inputs.allow_seed_fallback != false }}
CONTENT_AGENT_USE_AHREFS: ${{ inputs.use_ahrefs == true }}
CONTENT_AGENT_MAX_AHREFS_CLUSTERS: 3
CONTENT_AGENT_MAX_AHREFS_ROWS: 10
CREATE_GHOST_DRAFT: ${{ github.event_name == 'schedule' || inputs.dry_run == false }}
GENERATE_FEATURE_IMAGE: ${{ inputs.generate_feature_image }}
GENERATE_FEATURE_IMAGE_IN_DRY_RUN: ${{ inputs.generate_feature_image_in_dry_run }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
- name: Checkout core-api source truth
uses: actions/checkout@v4
with:
repository: fleetbase/core-api
path: source-truth/core-api
token: ${{ secrets.FLEETBASE_SOURCE_READ_TOKEN || github.token }}
- name: Checkout fleetops source truth
uses: actions/checkout@v4
with:
repository: fleetbase/fleetops
path: source-truth/fleetops
token: ${{ secrets.FLEETBASE_SOURCE_READ_TOKEN || github.token }}
- name: Checkout ledger source truth
uses: actions/checkout@v4
with:
repository: fleetbase/ledger
path: source-truth/ledger
token: ${{ secrets.FLEETBASE_SOURCE_READ_TOKEN || github.token }}
- name: Checkout storefront source truth
uses: actions/checkout@v4
with:
repository: fleetbase/storefront
path: source-truth/storefront
token: ${{ secrets.FLEETBASE_SOURCE_READ_TOKEN || github.token }}
- name: Checkout dev-engine source truth
uses: actions/checkout@v4
with:
repository: fleetbase/dev-engine
path: source-truth/dev-engine
token: ${{ secrets.FLEETBASE_SOURCE_READ_TOKEN || github.token }}
- name: Checkout iam-engine source truth
uses: actions/checkout@v4
with:
repository: fleetbase/iam-engine
path: source-truth/iam-engine
token: ${{ secrets.FLEETBASE_SOURCE_READ_TOKEN || github.token }}
- name: Checkout ember-core source truth
uses: actions/checkout@v4
with:
repository: fleetbase/ember-core
path: source-truth/ember-core
token: ${{ secrets.FLEETBASE_SOURCE_READ_TOKEN || github.token }}
- name: Checkout pallet source truth
uses: actions/checkout@v4
with:
repository: fleetbase/pallet
path: source-truth/pallet
token: ${{ secrets.FLEETBASE_SOURCE_READ_TOKEN || github.token }}
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Verify Ghost target
run: |
set -euo pipefail
expected_url="https://blog.fleetbase.io"
if [ "${GHOST_ADMIN_API_URL}" != "${expected_url}" ]; then
echo "::error::GHOST_ADMIN_API_URL must target ${expected_url}."
exit 1
fi
if [ "${GHOST_API_URL}" != "${expected_url}" ]; then
echo "::error::GHOST_API_URL must target ${expected_url}."
exit 1
fi
echo "Ghost target verified: ${expected_url}"
- name: Run content agent self-tests
run: pnpm content-agent:test
- name: Prepare content research
run: pnpm content-agent:prepare
- name: Generate structured content artifacts
run: pnpm content-agent:generate-artifacts
- name: Create Ghost draft from structured artifacts
run: pnpm content-agent:generate
- name: Upload content agent artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: content-agent-output
path: content-agent-output
if-no-files-found: ignore