<!-- section: rules -->
# INI Scout agent guide

You are setting up SEO on {{site_url}} with INI Scout {{version}}. Door: {{door}}. Site language: {{locale}}.
Public post types: {{post_types}}. Public taxonomies: {{taxonomies}}. Other active SEO plugins: {{other_seo_plugins}}. Open session: {{session}}.

## 0. Ground rules (re-read them any time: `wp iniseo guide --section=rules`)

1. Open a session before any write: `wp iniseo session start "<label>"`. Every write joins it; the user can undo it in one step.
2. Every write is a dry run unless you add `--apply` (REST: `"apply": true`). Always preview first, show the user the diff, then apply exactly that preview with `--if-plan=<plan>` (REST: `"if_plan"`). If you get `iniseo_plan_changed`, preview again and show the new diff.
3. A non-empty field or a customised setting is kept (`skip`, `overwrite_needed`) unless you add `--overwrite`. Only overwrite what the user agreed to replace.
4. Lines marked RISKY can hide the site from search or break URLs. Quote each one to the user and get an explicit yes before adding `--risky`. Never add it on your own.
5. Never write INI Scout data with `wp option`, `wp post meta` or SQL. Use only `wp iniseo …` or `iniseo/v1/…`.
6. Never change anything outside INI Scout: themes, other plugins, users, WordPress settings (including "Discourage search engines"). If one needs changing, tell the user.
7. Never invent facts: business name, address, phone, opening hours, prices, verification codes come from the user. Descriptions come from the actual content.
8. Write descriptions and titles in the site's language ({{locale}}).
9. End the session when done and give the user the undo command.

<!-- section: connect -->
## 1. Connect

With a shell on the server: `wp iniseo status --path={{path}}` (add `--format=json` to any command for JSON output).

Without a shell: use REST with an administrator's Application Password (HTTP Basic auth: `curl -u "<user>:<application password>" "{{rest_status}}"`). A route's full URL is {{rest_url}} followed by the part after `iniseo/v1/`, e.g. `{{rest_example}}` for `iniseo/v1/session`; add query parameters with `{{qs}}`, e.g. `{{rest_example_query}}`. Send JSON bodies with `Content-Type: application/json`. REST access enabled: {{rest_enabled}}. If you get `iniseo_agent_rest_off`, ask the user to turn on agent access in INI Scout → Dashboard. Ask the user to keep the password in your tool's settings or an environment variable, not in the chat, where possible.

Both doors take the same operations and return the same JSON. Dry-run results contain `plan`, `counts` and `changes` (one entry per field: `before`, `after`, `status` change|same|skip, `reason`, `risky`, `side_effect`).

| WP-CLI | REST ({{rest_url}}) |
| --- | --- |
| `wp iniseo guide [--section=<name>]` | `GET iniseo/v1/guide?section=` |
| `wp iniseo status` | `GET iniseo/v1/status` |
| `wp iniseo schema [settings\|post\|term\|redirect]` | `GET iniseo/v1/schema?type=` |
| `wp iniseo audit` | `GET iniseo/v1/audit` |
| `wp iniseo settings get [<path>]` / `wp iniseo settings set <path> <value>` | `GET iniseo/v1/settings?path=` / `POST iniseo/v1/settings {"path","value"}` or `{"values":{path:value}}` |
| `wp iniseo meta get <post\|term> <ids>` / `wp iniseo meta set <post\|term> <id> <field> <value>` | `GET iniseo/v1/meta?type=&ids=` / `POST iniseo/v1/meta {"type","items":[{"id",field:value}]}` |
| `wp iniseo meta export <post\|term> [--missing=desc]` / `wp iniseo meta import <file> --type=post` | `GET iniseo/v1/meta/export?type=&missing=` / `POST iniseo/v1/meta` (at most 500 fields per request) |
| `wp iniseo redirects list` / `wp iniseo redirects add <from> <to>` / `wp iniseo redirects delete <ids>` | `GET iniseo/v1/redirects` / `POST iniseo/v1/redirects {"ops":[{"op":"add","rule":{…}}]}` |
| `wp iniseo changes <file>` (any targets, `{"changes":[…]}`) | `POST iniseo/v1/changes` |
| `wp iniseo session start\|status\|end\|undo\|list` | `POST iniseo/v1/session`, `GET iniseo/v1/session`, `POST iniseo/v1/session/end`, `POST iniseo/v1/session/undo`, `GET iniseo/v1/sessions` |
| `wp iniseo import list\|run\|undo` | `POST iniseo/v1/import`, `POST iniseo/v1/import/step`, `POST iniseo/v1/import/undo` |

Flags on writes: `--apply`, `--overwrite`, `--risky`, `--if-plan=<plan>` (REST: `apply`, `overwrite`, `risky`, `if_plan`). Field names and allowed values: `wp iniseo schema` — do not guess them.

<!-- section: interview -->
## 2. Interview the user

Ask, in one short message, and wait for the answers:
- What the site is: business or organization name, and whether it is a local business (shop, office, venue) or not.
- For a local business: street, city, postal code, country, phone, opening hours. Only use what they give; never look it up or guess.
- Who the audience is and which pages or content types matter most.
- Official social profile URLs; the X/Twitter handle.
- Verification codes they have (Google Search Console, Bing).
- Old URLs that moved (for redirects), if any.
- Whether existing titles and descriptions may be replaced, or only empty ones filled.

<!-- section: audit -->
## 3. Audit

Run `wp iniseo audit` (REST `GET iniseo/v1/audit`). Each finding has `severity`, `message`, and either `fix` (a ready change set) or `needs` (fields to fill from the user's answers or the content). Summarise the findings for the user in plain language, critical first. Findings about things outside INI Scout (`blog_not_public`) are for the user to fix.

<!-- section: setup -->
## 4. Set up, in this order

Preview → show → apply with `--if-plan` for each step. Keep each change set focused on one step.

1. **Another SEO plugin.** If `status` lists one, offer the importer first: `wp iniseo import run <source> --steps=settings,posts,terms,redirects --dry-run --format=json` shows every setting it would change (current and new value), sample titles and descriptions, and notes. Show the user what matters, then run it without `--dry-run` (add `--yes`) once they agree, inside your session; the session's undo also undoes the import. Imported values count as existing values: if the old plugin's text is poor, agree with the user whether to replace it (`--overwrite`) or skip importing posts. Afterwards the user should deactivate the old plugin.
2. **Site basics.** `separator`; if the home page shows latest posts: `titles.home`, `titles.home_desc`; otherwise the front page's own `title`/`desc` (post fields). `schema.home_type`, `schema.org_name`, `schema.logo`, and for a local business the address fields from the interview, plus `schema.opens`, `schema.closes` and `schema.days` (the opening days, e.g. `["Tuesday",…,"Sunday"]`; an empty list means every day).
3. **Templates.** For each important post type and taxonomy: `titles.post_types.<type>.title|desc`, `titles.taxonomies.<tax>.title|desc`. Empty means the sensible default; only change what the user wants different.
4. **Indexing.** Usually: `cleanup.noindex_search=1`, `cleanup.noindex_date=1`, `cleanup.noindex_author=1` on single-author sites, `sitemap.enabled=1`, `sitemap.exclude_noindex=1`. Never noindex `post` or `page` types without the user's explicit yes.
5. **Social.** `social.default_og_image` (1200×630), `social.twitter_site`, `social.profiles` (one URL per line).
6. **Descriptions in bulk.** `wp iniseo meta export post --missing=desc` gives each item's `name`, `url` and `about` (the start of its content). Write one description per item: unique, 120–160 characters, from that item's content, in {{locale}}, plain sentences, no keyword stuffing, no quotes around it. Send them with `wp iniseo meta import <file> --type=post` (a JSON list of `{"id":…, "desc":"…"}`): preview, show a sample of 5–10 to the user, then apply with `--apply --if-plan=<plan>` from that preview (one plan covers the whole file). Do the same for terms if their archives matter.
7. **Redirects.** Only for URLs the user gave you or that appear in the 404 log they ask you to fix. Prefer `exact` rules; regex, starts/ends/contains and a source of `/` are RISKY.
8. **Optional, if the user wants them:** `llms.enabled` + `llms.summary`, `indexnow.enabled`, `robots.enabled` (any `robots.content` change is RISKY).

<!-- section: verify -->
## 5. Verify

1. Run the audit again; only findings the user chose to leave should remain.
2. Fetch the home page and two or three important pages and check `<title>`, `<meta name="description">`, `<link rel="canonical">`, `og:*` tags and the JSON-LD block. Add `?nocache=1` if a page cache may serve an old copy.
3. Fetch INI Scout's sitemap at {{sitemap_url}} and confirm it lists the important content (while another SEO plugin is still active, its own sitemap may answer at other addresses; that one is not INI Scout's).

<!-- section: wrap -->
## 6. Wrap up

1. `wp iniseo session end` (REST `POST iniseo/v1/session/end`).
2. Tell the user, briefly: what you changed (counts per area), anything left for them (e.g. Search Console, deactivating the old SEO plugin), and how to undo everything: `wp iniseo session undo <id>` (REST `POST iniseo/v1/session/undo {"id":<id>}`) or INI Scout → Agent sessions → Undo.
3. Suggest they revoke the Application Password if they created one for this.

<!-- section: troubleshooting -->
## Troubleshooting

- **401 with a correct password.** Call `GET {{rest_probe}}` with credentials. If `authorization_received` is false, the server strips the Authorization header. Fixes for the user (or their host): Apache — add `CGIPassAuth On` or `SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1` to `.htaccess`; nginx with PHP-FPM — `fastcgi_param HTTP_AUTHORIZATION $http_authorization;`.
- **No "Application Passwords" section in the profile.** WordPress offers them only over HTTPS (or locally), and some security plugins turn them off. The user must enable HTTPS or allow them in that plugin.
- **`iniseo_agent_rest_off`.** INI Scout → Dashboard → "Set up with an AI agent" → turn on REST access.
- **`rest_forbidden`.** The account is not an administrator.
- **`iniseo_busy`.** A session is already open (`wp iniseo session status`) or an import is running; finish or end it.
- **`iniseo_plan_changed`.** Someone changed something since your preview; preview again.
- **`iniseo_too_many_ops`.** Send at most 500 fields per request.
- **Claude.ai in the browser.** Its code sandbox can reach the site only if the user's network settings allow the site's domain.
