| name | geoflow-content-automation |
| description | GEOFlow open-source GEO/SEO content production system with AI generation, review workflow, and publishing pipeline built on PHP and PostgreSQL. |
| triggers | ["set up GEOFlow content automation","configure AI content generation with GEOFlow","create content tasks in GEOFlow","manage articles with GEOFlow review workflow","deploy GEOFlow with Docker","integrate GEOFlow API for content publishing","troubleshoot GEOFlow worker or scheduler","add AI model to GEOFlow"] |
GEOFlow Content Automation
Skill by ara.so — Daily 2026 Skills collection.
GEOFlow is an open-source PHP/PostgreSQL system for automated GEO/SEO content production. It chains model configuration, material management, task scheduling, draft review, and front-end publishing into a single pipeline. It supports any OpenAI-compatible API, runs on Docker Compose, and exposes both a REST API and a CLI.
Installation
Docker (Recommended)
git clone https://github.com/yaojingang/GEOFlow.git
cd GEOFlow
cp .env.example .env
vi .env
docker compose --profile scheduler up -d --build
open http://localhost:18080
open http://localhost:18080/geo_admin/
Local PHP Server
git clone https://github.com/yaojingang/GEOFlow.git
cd GEOFlow
export DB_DRIVER=pgsql
export DB_HOST=127.0.0.1
export DB_PORT=5432
export DB_NAME=geo_system
export DB_USER=geo_user
export DB_PASSWORD=geo_password
export APP_SECRET_KEY=$(openssl rand -hex 32)
export SITE_URL=http://localhost:8080
php -S localhost:8080 router.php
open http://localhost:8080/geo_admin/
Requirements: PHP 7.4+, extensions pdo_pgsql and curl, PostgreSQL instance.
Environment Variables
# .env (copy from .env.example)
HOST_PORT=18080
SITE_URL=http://localhost:18080
APP_SECRET_KEY=$APP_SECRET_KEY # 32+ char random string — never hardcode
CRON_INTERVAL=60 # Scheduler poll interval in seconds
TZ=Asia/Shanghai
# Database
DB_DRIVER=pgsql
DB_HOST=postgres
DB_PORT=5432
DB_NAME=geo_system
DB_USER=geo_user
DB_PASSWORD=$DB_PASSWORD # Set via environment, not hardcoded
Generate a secure key:
openssl rand -hex 32
php -r "echo bin2hex(random_bytes(32));"
Default Credentials
After first boot, log in at /geo_admin/ with:
- Username:
admin
- Password:
admin888
Change both immediately and rotate APP_SECRET_KEY before any public deployment.
Docker Services
| Service | Purpose | Profile |
|---|
web | Front-end + admin HTTP | always |
postgres | PostgreSQL database | always |
scheduler | Scans tasks, writes job queue | scheduler |
worker | Calls AI API, generates content | scheduler |
docker compose up -d
docker compose --profile scheduler up -d
docker compose logs -f worker
docker compose logs -f scheduler
docker compose restart worker
docker compose --profile scheduler down
Content Generation Pipeline
Admin: configure model + prompts + materials
↓
Create Task (title library, model, prompt, image library, publish rules)
↓
Scheduler (bin/cron.php) → writes to job_queue table
↓
Worker (bin/worker.php) → calls AI API → generates article body
↓
Optional: insert images, build SEO meta
↓
Article enters draft → review → publish states
↓
Front-end renders article + SEO/OG tags
Admin Workflow
1. Add an AI Model
Navigate to AI配置中心 → AI模型管理 and fill in:
- API Base URL (e.g.
https://api.openai.com/v1)
- Model ID (e.g.
gpt-4o)
- API Key (stored encrypted; reference
$AI_API_KEY in your env)
2. Create Materials
- 标题库 — Title pool the task draws from
- 图片库 — Image pool for article illustrations
- 知识库 — Reference knowledge injected into prompts
- 提示词模板 — Prompt templates with placeholders
3. Create a Task
Navigate to 任务管理 → 新建任务:
Title Library → selects article titles
Model → which AI model to call
Prompt → which prompt template
Image Library → optional illustration set
Publish Mode → draft | auto-publish
Schedule → immediate | cron-based
4. Monitor Execution
- 任务管理 — Task status, retry controls
- 文章管理 — Draft/review/published states
- 审核中心 — Review queue for human approval
CLI (bin/geoflow)
The CLI is the programmatic interface used by automation scripts and the companion skill yaojingang/yao-geo-skills.
php bin/geoflow help
php bin/geoflow task:status --task-id=<id>
php bin/geoflow task:create \
--title-library=<id> \
--model=<id> \
--prompt=<id> \
--publish-mode=draft
php bin/geoflow article:create \
--title="Article Title" \
--body="<markdown content>" \
--status=draft
php bin/geoflow article:review --article-id=<id> --action=approve
php bin/geoflow article:publish --article-id=<id>
php bin/cron.php
php bin/worker.php --once
REST API (/api/v1)
All endpoints require Bearer token authentication.
Generate an API Token
php bin/api/create_token.php --name="my-integration"
Common Endpoints
BASE=http://localhost:18080/api/v1
TOKEN=$GEOFLOW_API_TOKEN
curl -H "Authorization: Bearer $TOKEN" "$BASE/tasks"
curl -H "Authorization: Bearer $TOKEN" "$BASE/tasks/<id>"
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title_library_id": 1,
"model_id": 2,
"prompt_id": 3,
"publish_mode": "draft"
}' \
"$BASE/tasks"
curl -H "Authorization: Bearer $TOKEN" "$BASE/articles"
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
"$BASE/articles/<id>/publish"
curl -H "Authorization: Bearer $TOKEN" "$BASE/jobs?status=pending"
PHP Code Examples
Connect to the Database (using GEOFlow's own layer)
<?php
require_once __DIR__ . '/includes/db_support.php';
$pdo = get_db_connection();
$stmt = $pdo->prepare("SELECT * FROM articles WHERE status = :status ORDER BY created_at DESC LIMIT 10");
$stmt->execute([':status' => 'published']);
$articles = $stmt->fetchAll(PDO::FETCH_ASSOC);
Call the Article Service
<?php
require_once __DIR__ . '/includes/article_service.php';
$articleId = create_article([
'title' => 'My Article Title',
'body' => '## Introduction\n\nContent here...',
'status' => 'draft',
'task_id' => 42,
'seo_title' => 'My Article Title | Site Name',
'seo_desc' => 'Short description for search engines.',
]);
approve_article($articleId);
publish_article($articleId);
Trigger the AI Engine Directly
<?php
require_once __DIR__ . '/includes/ai_engine.php';
$result = run_task_job(taskId: 5, jobId: 101);
if ($result['success']) {
echo "Article created: " . $result['article_id'];
} else {
echo "Error: " . $result['error'];
}
Queue a Job Manually
<?php
require_once __DIR__ . '/includes/job_queue_service.php';
$job = claim_next_job();
if ($job) {
try {
complete_job($job['id']);
} catch (Exception $e) {
fail_job($job['id'], $e->getMessage());
}
}
enqueue_task_jobs(taskId: 5);
Custom Prompt Template in PHP
<?php
$template = "You are an SEO writer. Write a 600-word article about: {{title}}\n\nBackground knowledge:\n{{knowledge}}";
$prompt = str_replace(
['{{title}}', '{{knowledge}}'],
[$articleTitle, $knowledgeBaseContent],
$template
);
require_once __DIR__ . '/includes/ai_service.php';
$response = call_ai_model(modelId: 2, prompt: $prompt);
echo $response['content'];
Directory Reference
GEOFlow/
├── index.php Front-end article list
├── article.php Article detail page (SEO + OG)
├── router.php Local dev router for php -S
├── admin/ All admin UI pages
├── api/v1/index.php REST API single entry point
├── bin/
│ ├── geoflow CLI entrypoint
│ ├── cron.php Scheduler (run every CRON_INTERVAL seconds)
│ └── worker.php AI generation worker (long-running)
├── includes/
│ ├── config.php Global config + constants
│ ├── database.php Front-end data access
│ ├── ai_engine.php Core generation orchestration
│ ├── ai_service.php OpenAI-compatible HTTP client
│ ├── job_queue_service.php Claim / complete / fail / retry
│ ├── task_service.php Task CRUD
│ ├── article_service.php Article lifecycle
│ └── api_auth.php Bearer token auth
└── docs/ Deployment + API + FAQ docs
Common Patterns
Auto-publish without review
When creating a task, set publish_mode = auto. The worker will call publish_article() immediately after generation, skipping the review queue.
Batch retry failed jobs
php bin/geoflow job:retry-failed --task-id=<id>
psql $DATABASE_URL -c "UPDATE job_queue SET status='pending', attempts=0 WHERE status='failed' AND task_id=5;"
Run scheduler outside Docker
* * * * * cd /path/to/GEOFlow && php bin/cron.php >> /var/log/geoflow-cron.log 2>&1
while true; do php bin/cron.php; sleep 60; done
Database maintenance
php bin/db_maintenance.php --vacuum
php bin/db_maintenance.php --clean-old-jobs --days=30
Troubleshooting
| Symptom | Likely Cause | Fix |
|---|
| Worker generates nothing | Scheduler not running | Start with --profile scheduler or run cron.php |
pdo_pgsql not found | Extension missing | Add extension=pdo_pgsql to php.ini or install php-pgsql |
| 500 on admin pages | DB not initialized | Let the web container complete its entrypoint migration on first boot |
| AI calls return 401 | Wrong API key | Re-enter key in AI模型管理; check key has no leading/trailing spaces |
| Articles stuck in draft | Auto-publish off | Set publish_mode=auto on the task, or manually approve in 审核中心 |
| Jobs pile up in queue | Worker crashed | docker compose restart worker; check docker compose logs worker |
| CSRF token mismatch | Session expired | Re-login to admin; ensure APP_SECRET_KEY is stable across restarts |
Useful Log Commands
docker compose logs -f
docker compose logs worker 2>&1 | grep -i error
docker compose logs scheduler | tail -50
docker compose exec postgres psql -U geo_user -d geo_system \
-c "SELECT status, COUNT(*) FROM job_queue GROUP BY status;"
Security Checklist