# 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 ``` ## Sample Article E2E Run the live sample-article browser workflow: ```bash npm run test:e2e:samples ``` The runner reads `.env.local`, starts an isolated local Next.js server, loads `samples/articles/*.json`, opens the web UI with Playwright, runs each sample through the one-click optimization flow, validates authenticated exports, and writes a report under: ```text test-results/geo-sample-flow// ``` Useful options: ```bash npm run test:e2e:samples -- --limit 1 npm run test:e2e:samples -- --sample title-quality npm run test:e2e:samples -- --headed npm run test:e2e:samples -- --reuse-server --base-url http://localhost:3000 ``` This workflow uses the real configured LLM provider by default. It requires `API_ACCESS_KEY` and the provider API key in `.env.local` or the shell environment. Reports and exports are written to `test-results/`, which is ignored by Git. ## 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 ``` ## FRP Mainland Access Tunnel The Cloudflare staging URL can be unreachable from mainland networks. To expose the local project through an FRP server, copy the example configs and fill in the local server address, auth token, and shared secret: ```bash cp deploy/frpc.geo-agent-article-optimizer.example.toml deploy/frpc.geo-agent-article-optimizer.toml cp deploy/frpc.geo-agent-article-optimizer-visitor.example.toml deploy/frpc.geo-agent-article-optimizer-visitor.toml ``` The real `deploy/*.toml` files are ignored by Git so local credentials stay out of the public repository. Run the app locally and start frpc with the project config: ```bash npm run dev frpc -c deploy/frpc.geo-agent-article-optimizer.toml ``` The project-side frpc config publishes local `127.0.0.1:3000` as the stcp service `geo_agent_article_optimizer`. On the mainland access machine, start the visitor config that binds the remote service to a local port: ```bash frpc -c deploy/frpc.geo-agent-article-optimizer-visitor.toml ``` Then open `http://127.0.0.1:6009` on that machine. ## 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.