# GEO Agent Article Optimizer Local MVP for optimizing pasted GEO-related articles while preserving confirmed brand facts. The app extracts a fact card, requires user confirmation, rewrites under those constraints, runs QA gates, and exports Markdown, Word, and JSON. ## Setup ```bash npm install cp .env.example .env.local npm run dev ``` Open `http://localhost:3000`. ## Environment Required for live model calls: ```text LLM_PROVIDER=deepseek DEEPSEEK_API_KEY= DEEPSEEK_BASE_URL=https://api.deepseek.com DEEPSEEK_MODEL=deepseek-v4-pro DEEPSEEK_THINKING=disabled APP_DATA_DIR=./data API_ACCESS_KEY=local-dev-key API_AUTH_DISABLED=false ``` OpenAI-compatible fallback keys are also accepted: ```text OPENAI_API_KEY= OPENAI_MODEL=gpt-4.1-mini ``` When a DeepSeek or OpenAI-compatible key is configured, the workflow uses the provider for fact extraction, article optimization, QA enrichment, and targeted rewrite. Every LLM response is validated with Zod before use. When no API key is configured, or when the provider response is invalid, deterministic local fallbacks keep the workflow usable for tests and local review. All API requests require the configured access key: ```text x-api-key: ``` ## LLM Runtime Logs LLM calls emit regular server-side logs from `src/lib/llm/client.ts`. These logs are intended for local development, customer demos, and production troubleshooting. Example: ```text [llm:start] provider=deepseek model=deepseek-v4-pro task=article_optimizer [llm:response] task=article_optimizer duration_ms=18342 raw={"title":"..."} [llm:validated] task=article_optimizer ok=true ``` The `raw=` field is truncated to 4000 characters to avoid log explosions. Set `LLM_LOG_RAW_LIMIT=0` to suppress raw response snippets, or set a larger number when diagnosing model output. API keys, prompts, system messages, and environment secrets are not logged. Workflow task names are: - `fact_extractor` - `article_optimizer` - `quality_inspector` - `targeted_rewriter` ## Commands ```bash npm test npm run build npx playwright test ``` ## Cloudflare Workers Deployment Cloudflare deployment is manual. Pushing to Git does not deploy or hot-update the production Worker. Local development: ```bash npm run dev ``` Cloudflare preview: ```bash cp .dev.vars.example .dev.vars npm run d1:migrate:local npm run preview:worker ``` Create private staging resources: ```bash npx wrangler d1 create geo-agent-article-optimizer-staging npx wrangler r2 bucket create geo-agent-article-optimizer-staging ``` Create private production resources: ```bash npx wrangler d1 create geo-agent-article-optimizer-production npx wrangler r2 bucket create geo-agent-article-optimizer-production ``` After D1 creation, copy the returned database IDs into the matching `wrangler.jsonc` environment entries. Keep R2 buckets private; do not add public bucket domains. Set secrets: ```bash npx wrangler secret put API_ACCESS_KEY --env staging npx wrangler secret put DEEPSEEK_API_KEY --env staging npx wrangler secret put API_ACCESS_KEY --env production npx wrangler secret put DEEPSEEK_API_KEY --env production ``` D1 schema changes only through migrations. Runtime code must not rebuild or clear production tables. Apply migrations in this order: ```bash npm run d1:migrate:local npm run d1:migrate:staging npm run deploy:worker:staging npm run d1:migrate:production npm run deploy:worker:production ``` ## Exports Local generated files are written under: ```text data/exports// ``` Cloudflare exports are written to the private R2 bucket bound as `EXPORT_BUCKET` and served only through authenticated API routes. Each passing or warning-only QA run can produce: - `optimized.md` - `optimized.docx` - `qa_report.json` Hard QA failures block export. ## MVP Limits - Pasted text only; no direct `.docx` parsing. - Local SQLite by default; Cloudflare deployments use D1. - No account permissions or collaboration. - No publishing platform APIs. - No batch queue. - Basic Word export layout only. - Unconfirmed facts are never used as truth.