| Acf Read (4) |
detect_acfDetect ACF | Determine which custom-fields plugin owns the truth on a WordPress site: Advanced Custom Fields (free), ACF PRO, Secure Custom Fields (SCF), or none — plus the version and which fork it belongs to. CRITICAL: this distinguishes the editions by the active plugin's main FILE/SLUG and its header, NOT by class_exists("ACF") — because SCF (the WordPress.org fork of ACF, created 12 October 2024) defines the SAME "ACF" class, the same acf_* functions, and the same storage, so the class alone cannot tell them apart. (ACF free and ACF Pro also share the acf.php main-file name, so the directory slug — advanced-custom-fields vs advanced-custom-fields-pro — and the header edition disambiguate them.) It reports { present, edition, version, pro, fork, plugin_file, pro_features_available }. This matters operationally because: (1) many sites on .org auto-updates were SILENTLY switched from ACF to SCF without the owner realising, so "I have ACF" may actually be SCF — and Keva reports the fork honestly; (2) SCF bundles the formerly-Pro field types (Repeater, Flexible Content, Gallery, Clone, Options Pages, Blocks), so a "Pro field type" is NOT the same as "needs ACF Pro" — only ACF free lacks them, so the edition gates the "Pro type on a Free install" finding. Every read in this category keys off this detection, and it degrades gracefully to edition "none" (never a fatal) when no custom-fields plugin is active. Read-only. Requires Keva Bridge. | Acf Read | low | Unmodeled |
audit_field_groupsAudit Field Groups | Diagnose WHY custom fields disappeared after a deploy — the flagship ACF read. It reads the field groups stored in the DATABASE (via ACF's own acf_get_field_groups() API) and scans the resolved acf-json load paths (by default a folder named acf-json inside the active theme, plus any registered via ACF's load_json filter) for the {key}.json files the developer commits to version control, then computes each group's SYNC STATE by replicating ACF's exact "available for sync" rule: a group is OUT OF SYNC when either its key is ABSENT from the database (it shipped in JSON but was never imported into the DB on this environment — the #1 "my fields vanished after a deploy" cause, reported as json_only) OR the JSON file's modified timestamp is NEWER than the database group's (json_newer); a group opts out with "private": true. It also flags groups registered in PHP via acf_add_local_field_group() as php_local — those are authoritative at runtime and NOT editable in the database, which is the distinct "my field-group edits won't stick / keep reverting" cause (the fix there is editing the code — developer/advisory, never a Keva write). It reports counts plus the out-of-sync list (the candidates for the safe DB-import fix, which is a later action). IMPORTANT: local JSON is a SYNC OFFER, not a silent override; the safe fix imports it INTO the database (no acf-json file is touched). Note ACF's API caches results mid-request, so any write is verified via a fresh read. Works identically on ACF, ACF Pro, and SCF; degrades gracefully when no custom-fields plugin is active. Read-only. Requires Keva Bridge. | Acf Read | low | Unmodeled |
scan_orphaned_acf_metaScan Orphaned ACF Meta | Find orphaned ACF field VALUES left behind in the database — the rows that bloat wp_postmeta after a field or field group is deleted or its key changes. ACF stores each field value as a PAIR: the value under the field name (e.g. staff_email) plus a reference under the same name prefixed with an underscore (_staff_email) whose value is the field KEY (field_xxxxxxxx). That underscore reference is the orphan signal: a value pair is ORPHANED when its field key is no longer the post_name of any live acf-field post (the field definition is gone, but its values remain). This is a bounded, READ-ONLY $wpdb scan that reports the orphan count plus a bounded sample (post id, the reference meta_key, the dead field key) — the candidates for the safe NAMED cleanup, which is a separate gated action. IMPORTANT honesty: do NOT assume a value is orphaned just because a GROUP was deleted from the admin UI — this scan confirms the field KEY itself is truly dead. The cleanup (clean_orphaned_acf_meta, a later action) deletes only orphaned VALUES (never a field DEFINITION), acts on a NAMED/scoped target only (there is no "delete all ACF-looking meta" sweep), and is snapshot-first. Degrades gracefully when no custom-fields plugin is active. Read-only. Requires Keva Bridge. | Acf Read | low | Unmodeled |
audit_field_group_locationsAudit Field Group Locations | Explain why an ACF field group may not be showing where expected, and report ACF block coverage. For each active group it reads the location configuration (an OR-of-AND array of {param, operator, value} rules) and reports the coverage plus obvious STATIC gaps — e.g. "this group shows on post_type == page, not post" — which answers the common "why isn't my field group on this screen?" question. IMPORTANT honesty: it is explicit that DYNAMIC or filtered location rules (computed at runtime via the acf/location/rule_match filter or runtime arguments) cannot be fully evaluated from a static read — those need runtime, so Keva reports the static config and advises. It also reports the registered ACF block types (a Pro/SCF feature — it degrades to none on ACF free) along with the ACF version, ties known block breakages to versions (the 6.3.1 nested-group + conditional-logic validation bug; the WP 6.8 parent:null / align registration regressions), and optionally makes one bounded, marker-free loopback fetch of the home page to flag a "your site doesn't include support for the acf/{block} block" render error. The fix for a location or block issue (editing the location rule, a version pin, a block.json change) is developer/advisory work — Keva detects and advises, it does not redesign the schema. Degrades gracefully when no custom-fields plugin is active. Read-only. Requires Keva Bridge. | Acf Read | low | Unmodeled |
| Acf Write (2) |
sync_field_groupsSync Field Groups | The flagship safe fix for the #1 "my custom fields disappeared after a deploy" emergency: import the out-of-sync local-JSON field groups INTO the database via ACF's own acf_import_field_group() — the SAME API the admin Sync UI and `wp acf json sync`/`import` use (it updates on field-group key match). This is a PURE DATABASE WRITE — NO acf-json file is ever touched (writing the JSON back out to the theme is advisory-only; Keva imports into the DB and, when the DB is then ahead of the committed JSON, advises you to commit it from your OWN environment/Git so version control stays the source of truth). It works identically on ACF, ACF Pro, and SCF. Pass group_keys (the named field-group key[s] to import) or all_out_of_sync:true (import every group the audit flagged json_only or json_newer); there is no blind "import everything" mode, and a group registered in PHP (php_local) or one that opted out (private) is never imported. The Bridge takes a safety snapshot FIRST (and ABORTS if it fails — the snapshot is the ONLY rollback for an import, which has no field-by-field inverse), imports each target's decoded JSON, then VERIFIES via a FRESH, cache-busted read — it queries wp_posts directly for an acf-field-group post whose post_name equals the group key, because acf_get_field_groups() caches mid-request so a stale cached read could not confirm the import — and on a failed verify it RESTORES the snapshot so the prior field-group state returns. Reports the imported keys with their before/after sync_state and the snapshot id. Reversible (via the snapshot). HIGH risk: requires human approval. Requires Keva Bridge. | Acf Write | high | Unmodeled |
clean_orphaned_acf_metaClean Orphaned ACF Meta | Delete a NAMED/SCOPED set of orphaned ACF field VALUES left behind in the database — the value pairs (the value under the field name plus its underscore-prefixed `_name` reference) whose field key is no longer the post_name of any live acf-field post. This is NAMED/SCOPED-ONLY: there is NO "delete all ACF-looking meta" sweep, and it NEVER deletes a field DEFINITION (an acf-field or acf-field-group post) or a LIVE field value — only orphaned VALUES whose field key is CONFIRMED dead at write time. Pass dead_field_key (a confirmed-dead field key like field_dead123 — every orphaned value pair referencing it is cleaned) or pairs (an explicit list of {post_id, value_key} to clean); an unscoped request is REFUSED. Every target is re-confirmed orphaned at write time (its `_name` reference still points at a dead field key), so a race where the field was re-created since the scan can never delete a live value. The Bridge takes a safety snapshot FIRST (and ABORTS if it fails — the snapshot is the ONLY rollback for a deletion), deletes the named value pair(s) via delete_post_meta (which respects hooks and the cache, not a raw SQL DELETE), then VERIFIES via a re-read confirming the targeted orphans are gone, and on a failed verify RESTORES the snapshot so the deleted rows come back. Reports the deleted pairs and the snapshot id. Reversible (via the snapshot). HIGH risk: requires human approval. Requires Keva Bridge. | Acf Write | high | Unmodeled |
| Backup Read (5) |
detect_backup_solutionDetect Backup Solution | Detect which backup solution(s) are present on the site and how automatable each is. Reports UpdraftPlus presence (its history option / backup directory), current UpdraftPlus host-policy restrictions such as Kinsta one-backup-per-month, a best-effort UpdraftPlus-Premium-CLI hint (cannot be fully verified from PHP), a cheap managed-host fingerprint (Kinsta / WP Engine, or null), and native-fallback signals (whether exec is callable + a best-effort mysqldump probe). Read-only — answers "what can we use to back this site up?" before any backup is taken. Requires Keva Bridge. | Backup Read | low | Unmodeled |
list_backupsList Backups | List the backups the site already holds, read from the UpdraftPlus backup history. Each entry includes the timestamp, the component sets it contains (db / plugins / themes / uploads / others), the remote service(s) it was sent to, and an off-site flag (an on-disk-only backup is NOT off-site). Newest-first; an empty list means the site has never been backed up (a normal state, not an error). Read-only. Requires Keva Bridge. | Backup Read | low | Unmodeled |
get_last_backup_statusGet Last Backup Status | Report the most recent backup and whether the site is actually protected: the latest backup's age, components, and off-site flag, plus an overall summary { has_backup, is_recent (under 48h), is_offsite }. The off-site flag is load-bearing — a recent backup that exists only on the same disk is not a safe state. Use this to answer "is my backup current?" before doing anything risky. Read-only. Requires Keva Bridge. | Backup Read | low | Unmodeled |
get_backup_job_statusGet Backup Job Status | Read the status evidence for an async UpdraftPlus backup job by the exact nonce returned by trigger_backup: Keva wrapper trigger evidence, sanitized Updraft jobdata, whether a matching backup history entry exists, whether the Updraft log exists, a bounded/redacted log tail, and an inferred status such as completed_history, completed_log_only, log_seen, keva_trigger_queued, keva_trigger_error, not_seen, or the Updraft jobstatus. Use this after trigger_backup returns solution=updraftplus and backup_nonce, especially when list_backups has not yet shown a new history entry. Read-only: never starts, resumes, aborts, or deletes a backup. The nonce is required; an unrelated newest log is never accepted as attribution. Requires Keva Bridge. | Backup Read | low | Unmodeled |
list_safety_snapshotsList Safety Snapshots | List the Keva safety snapshots taken on this site (from the capped snapshot index), newest-first. Each entry includes the snapshot id (a Unix timestamp), its directory path, the DB-dump method used (mysqldump or php), the dump size, the number of wp-content files recorded, any note, and the creation time. Read-only — answers "what restore points has Keva created?". Requires Keva Bridge. | Backup Read | low | Unmodeled |
| Backup Write (5) |
create_safety_snapshotCreate Safety Snapshot | Take a safety snapshot of the site's CURRENT state before a risky operation, so a bad change is itself reversible. Synchronously dumps the database (mysqldump when exec is available, otherwise a size-capped pure-PHP dump) and records a wp-content manifest (file list + hashes, not a full copy) into a local snapshot directory. Disk-pre-checked: if the dump would exceed free space it REFUSES with a clear error and writes nothing (never a truncated dump). Non-destructive — it only creates files. This is the keystone every destructive backup/restore operation depends on. Optional: note (a label), force_php (force the pure-PHP dump path). Requires Keva Bridge. | Backup Write | medium | None |
trigger_backupTrigger Backup | Start a real, customer-managed backup of the site via its detected backup solution. Prefers a managed-host backup API when configured (Kinsta full backup, or WP Engine create — both run asynchronously), otherwise uses the Keva Bridge: if UpdraftPlus is active and not blocked by its own host-policy checks, it queues Keva's observable WP-Cron wrapper, which invokes UpdraftPlus's own "backup now" in the background and records trigger evidence by backup_nonce (poll get_backup_job_status plus list_backups to confirm). If UpdraftPlus reports a host limit such as Kinsta one-backup-per-month, the Bridge refuses honestly with fallback_available instead of queueing a doomed job. Use prefer="updraftplus" to lock the request to UpdraftPlus and refuse rather than using a configured host API or creating a native backup; use prefer="native" only when a local Keva-native backup is acceptable. With prefer="auto" and no backup plugin, Keva writes a managed full-native backup (database dump + wp-content manifest by default, optionally code-file archives) on the server. This is distinct from create_safety_snapshot, which is an ephemeral local restore point taken right before a risky change. Non-destructive — it only creates a backup. Optional: prefer ("auto", "updraftplus", or "native"), nocloud (UpdraftPlus: 0 = also off-site [default], 1 = local only), include_files/include_uploads (native path only), note (a label). Requires the Keva Bridge unless a host backup API is configured. | Backup Write | medium | None |
restore_database_onlyRestore Database Only | DESTRUCTIVE: overwrite the live WordPress database with a Keva-owned dump — a safety snapshot (from create_safety_snapshot) or a Keva-native backup (from trigger_backup), chosen by its id (NOT an arbitrary file path). Runs the full restore-safety protocol: it takes a fresh safety snapshot of the CURRENT database FIRST and ABORTS without touching anything if that snapshot fails (so the restore is always reversible), enables maintenance mode, imports the chosen dump, optionally rewrites URLs with a serialized-data-safe wp search-replace (only when both from_url and to_url are given; skipped with a warning if WP-CLI/exec is unavailable — never an unsafe raw SQL replace), disables maintenance mode, verifies the site (home returns 200 + database reachable + a sanity row count), and ROLLS BACK to the pre-restore snapshot if verification fails. Restores the database only — it does not restore files/plugins/uploads. For incident recovery, provide original_execution_id: Keva then refuses the restore unless source_id is that exact execution's recorded safety snapshot and the approved public target matches its captured baseline. This is a high-risk, irreversible-in-place operation that requires human approval and is never run automatically. Required: source_id. Optional: source_type ("snapshot" or "backup"), original_execution_id, from_url + to_url (to rewrite site URLs). Requires Keva Bridge. | Backup Write | high | None |
restore_backupRestore Backup (Full) | DESTRUCTIVE: full restore of BOTH the database AND the code files (plugins, themes, mu-plugins — and optionally uploads) from a Keva-native backup that captured file bytes (created by trigger_backup with include_files), chosen by its id (NOT an arbitrary file path). Runs the full restore-safety protocol: it takes a fresh safety snapshot of the CURRENT database FIRST and ABORTS without touching anything if that snapshot fails, enables maintenance mode, imports the database, restores the code directories by copying the archived files over the live ones in place (a copy-merge — it keeps a copy of the current files aside for rollback and never wipes or renames a live directory; the Keva plugin's own directory is always left untouched), optionally rewrites URLs with a serialized-data-safe wp search-replace (only when both from_url and to_url are given; skipped with a warning if WP-CLI/exec is unavailable — never an unsafe raw SQL replace), disables maintenance mode, verifies the site (home returns 200 + database reachable + a sanity row count), and ROLLS BACK BOTH the database and the files if verification fails. This native path restores the code directories by default (plugins/themes/mu-plugins); uploads are restored only when include_uploads is set AND the backup archived them. Because it is a copy-merge, files that exist on the live site but are absent from the backup are left in place (not deleted) — this is intentional and safer. For a complete restore including all media, or on managed hosts, the customer's own backup plugin or the host's backup tier is preferred when available. Safety snapshots are database-only and are not valid full-restore sources. This is a high-risk, irreversible-in-place operation that requires human approval and is never run automatically. Required: source_id. Optional: source_type (backup), include_uploads, from_url + to_url. Requires Keva Bridge. | Backup Write | high | Failure compensation |
restore_partialRestore One Item (Plugin / Theme / File) | DESTRUCTIVE: restore ONE specific item — a single plugin directory, a single theme directory, or one file (e.g. a single mu-plugin) — from a Keva-native backup that captured file bytes (created by trigger_backup with include_files), chosen by the backup id plus a relative target path under wp-content (e.g. "plugins/akismet", "themes/twentytwentyfive", "mu-plugins/foo.php"). This is the surgical alternative to restore_backup: it touches ONLY the named target and ONLY files — the database is NOT touched, so no database snapshot is taken. It is MOUNT-SAFE: the Bridge copies the archived item over the live one in place (a copy-merge — it keeps a copy of the current item aside for rollback and never wipes or renames a live directory; the Keva plugin's own directory can never be the target), enables maintenance mode briefly, copies the files, disables maintenance mode, verifies the site (home returns 200 + database reachable), and ROLLS the target back to its prior state if verification fails. The target path is strictly validated: no "." or ".." segments, no leading slash, no backslashes; it must resolve inside wp-content; and its first segment must be one of plugins, themes, mu-plugins, or uploads (never wp-config.php or any path outside wp-content). The backup must actually contain the requested target in its files/ tree, otherwise the restore fails without changing anything. Safety snapshots are database-only and are not valid partial file sources. This is a high-risk, irreversible-in-place operation that requires human approval and is never run automatically. Required: source_id and target. Optional: source_type ("backup"). Requires Keva Bridge. | Backup Write | high | Failure compensation |
| Billing Read (7) |
wc_list_ordersList Orders | List WooCommerce orders, optionally filtered by status. | Billing Read | low | Unmodeled |
wc_get_orderGet Order | Get details of a specific WooCommerce order. | Billing Read | low | Unmodeled |
wc_list_couponsList Coupons | List WooCommerce coupons. | Billing Read | low | Unmodeled |
get_refundGet Refund | Get details of a specific refund on a WooCommerce order. | Billing Read | low | Unmodeled |
get_couponGet Coupon | Get details of a specific WooCommerce coupon. | Billing Read | low | Unmodeled |
list_payment_gatewaysList Payment Gateways | List configured WooCommerce payment gateways with their status and settings. | Billing Read | low | Unmodeled |
list_shipping_zonesList Shipping Zones | List configured WooCommerce shipping zones. | Billing Read | low | Unmodeled |
| Billing Write (5) |
wc_update_order_statusUpdate Order Status | Change the status of a WooCommerce order. | Billing Write | high | None |
wc_add_order_noteAdd Order Note | Add a note to a WooCommerce order. | Billing Write | high | Unmodeled |
wc_create_couponCreate Coupon | Create a new WooCommerce discount coupon. | Billing Write | medium | Unmodeled |
create_refundCreate Refund | Create a refund for a WooCommerce order. Can optionally attempt a payment gateway refund. | Billing Write | high | Unmodeled |
update_couponUpdate Coupon | Update an existing WooCommerce coupon (code, amount, type, expiry, usage limit). | Billing Write | medium | Unmodeled |
| Content Create (5) |
create_postCreate Post (Metadata Only) | Create a new blog post as a draft with title and metadata only. Body content is NOT writable through this action because direct post_content writes do not render correctly on page-builder sites (Elementor, Divi, Beaver, etc. store layout in post_meta). To add body content after creation, use wordpress.update_page_text once the post exists, or compose the post in the WP admin. | Content Create | medium | Unmodeled |
create_pageCreate Page (Metadata Only) | Create a new page as a draft with title and metadata only. Body content is NOT writable through this action because direct post_content writes do not render correctly on page-builder sites (Elementor, Divi, Beaver, etc. store layout in post_meta). To add body content after creation, use wordpress.update_page_text once the page exists, or compose the page in the WP admin. | Content Create | medium | Unmodeled |
create_categoryCreate Category | Create a new post category. | Content Create | low | Unmodeled |
create_tagCreate Tag | Create a new post tag. | Content Create | low | Unmodeled |
upload_mediaUpload Media | Upload an image or file to the WordPress media library from a URL or base64 data. | Content Create | medium | Unmodeled |
| Content Delete (7) |
delete_postDelete Post | Permanently delete a post. | Content Delete | high | Unmodeled |
delete_pageDelete Page | Permanently delete a page. | Content Delete | high | Unmodeled |
delete_commentDelete Comment | Permanently delete a comment. | Content Delete | medium | Unmodeled |
remove_pricing_tierRemove Pricing Tier | Remove a single pricing tier from a page by id. Delegates to bridge_delete_block under the hood — included as a named action so the AI surface is symmetric (add / update / remove). Requires Keva Bridge plugin. | Content Delete | medium | Unmodeled |
remove_faq_itemRemove FAQ Item | Remove a single FAQ item. Gutenberg: deletes the core/details block by id. Elementor: faq_id is composite "{widget_id}:{tab_id}" — reads current tabs, filters out the matching one, patches widget settings. To remove the entire accordion widget itself on Elementor, use bridge_delete_block with the widget id directly. Bridge required. | Content Delete | medium | Unmodeled |
remove_testimonialRemove Testimonial | Delete a testimonial by id. Delegates to bridge_delete_block under the hood — included as a named action for AI-surface symmetry (add / update / remove). Bridge required. | Content Delete | medium | Unmodeled |
remove_sectionRemove Section | Delete a top-level section from a page by id. Verifies the id IS a root-level child first (so the AI doesn't accidentally remove a column, widget, or nested block via this action — for non-root removals use bridge_delete_block directly). Snapshot-based rollback: the full pre-delete page tree is captured in previousState. Requires Keva Bridge. Gutenberg + Elementor only; other engines return engine_not_supported. | Content Delete | high | Unmodeled |
| Content Read (26) |
list_postsList Posts | List blog posts, optionally filtered by search term or status. | Content Read | low | Unmodeled |
get_postGet Post | Get the current content and metadata of a specific post. | Content Read | low | Unmodeled |
list_pagesList Pages | List pages, optionally filtered by search term or status. | Content Read | low | Unmodeled |
get_pageGet Page | Get the current content and metadata of a specific page. | Content Read | low | Unmodeled |
list_commentsList Comments | List comments, optionally filtered by post or status. | Content Read | low | Unmodeled |
list_mediaList Media | List media library items. | Content Read | low | Unmodeled |
list_categoriesList Categories | List post categories. | Content Read | low | Unmodeled |
list_tagsList Tags | List post tags. | Content Read | low | Unmodeled |
search_postsSearch Posts | Search blog posts by keyword. | Content Read | low | Unmodeled |
search_pagesSearch Pages | Search pages by keyword. | Content Read | low | Unmodeled |
search_commentsSearch Comments | Search comments by keyword. | Content Read | low | Unmodeled |
list_post_revisionsList Post Revisions | List all revisions of a specific post. | Content Read | low | Unmodeled |
get_post_revisionGet Post Revision | Get a specific revision of a post. | Content Read | low | Unmodeled |
list_taxonomiesList Taxonomies | List all registered taxonomies (categories, tags, custom taxonomies). | Content Read | low | Unmodeled |
list_post_typesList Post Types | List all registered post types (post, page, custom post types). | Content Read | low | Unmodeled |
get_mediaGet Media | Get details of a specific media library item including title, source URL, mime type, and media details. | Content Read | low | Unmodeled |
detect_page_builderDetect Page Builder | Detect which page builder (Gutenberg, Elementor, WPBakery, Divi, Avada, Beaver Builder, Bricks, Breakdance, or Classic) a page or post uses. | Content Read | low | Unmodeled |
find_text_on_pageFind Text on Page | Search for text inside the BODY content of a known WordPress page or post. Detects the page builder for that page/post and returns matching body elements with synthetic text-location ids. This does NOT search site-wide headers, footers, Elementor templates, widgets, menus, options, or blog-loop cards rendered onto the page; use wordpress.locate_text first for those. | Content Read | low | Unmodeled |
locate_textLocate Text Site-Wide | Find where a piece of text lives anywhere on the site — pages, posts, builder data, theme templates, widgets, options. Use this FIRST when asked to change visible text and you do not know where it is stored (especially footer/header/template/widget/loop text). Case-insensitive literal search across post_content of ALL post types (incl. wp_template, wp_template_part, wp_block, elementor_library), page-builder data in post meta (Elementor / Bricks / Beaver Builder), and widget / customizer / site-identity options. When an App Password is available, post_meta matches are best-effort enriched with field_paths / exact_field_paths from REST-registered meta objects, which can feed wordpress.read_registered_post_meta_field or wordpress.update_registered_post_meta_field. Matches on normal pages/posts can be followed with wordpress.find_text_on_page or wordpress.update_page_text; matches in templates, elementor_library, widgets, or options are NOT page-body matches and may need Bridge/template-specific reads or writes. Up to 20 matches per source with a snippet around each. Read-only. Requires Keva Bridge (plugin 1.1.8+). | Content Read | low | Unmodeled |
read_registered_post_meta_fieldRead Registered Post Meta Field | Read one field inside a WordPress REST-registered post meta object on a REST-exposed post type. Use after wordpress.locate_text identifies text in post_meta (for example Elementor kit _elementor_page_settings.hello_footer_copyright_text) to confirm the exact active storage value before proposing a write. This uses the authenticated wp/v2 REST API and only sees meta WordPress exposes in context=edit; it does not read arbitrary hidden database rows. | Content Read | low | Unmodeled |
bridge_diagnosticsBridge Diagnostics | Probe the Keva Bridge plugin on a WordPress site. Returns Bridge version, available engines (Gutenberg / Elementor / Classic), detected page builders, and active cache plugins. Use this to verify Bridge is installed and to decide whether to prefer bridge_* actions over the legacy update_page_text path. | Content Read | low | Unmodeled |
bridge_read_pageBridge: Read Page | Read a page through Keva Bridge. Returns the engine discriminator (gutenberg | elementor | classic), the full content tree (parsed blocks for Gutenberg, decoded _elementor_data for Elementor, raw HTML for Classic), plus a writable flag indicating whether Bridge can edit this page. | Content Read | low | Unmodeled |
bridge_read_structureBridge: Read Structure | Compact view of a page through Keva Bridge: IDs + element types + a short hint of visible text. Cheaper than bridge_read_page when the AI just needs to find an element by hint to target a write op. | Content Read | low | Unmodeled |
list_regionsBridge: List Keva-Managed Regions | List all [keva-region] regions discovered across the site. Each entry includes the region id, the post it lives on, current value (or null if untouched), default content, lock state, type (text/html/image/list), and version. Use this to discover what regions the site owner has marked as AI-editable before calling set_region. Optional status filter: "active" (default) | "orphaned" (shortcode removed but value retained) | "all". | Content Read | low | Unmodeled |
get_regionBridge: Get Keva-Managed Region | Fetch a single [keva-region] by ID. Returns current value, default, lock state, version, full change history, and (if the same region id appears on multiple posts) a duplicates list. Always call this immediately before set_region so you can pass the correct expected_version for optimistic concurrency. | Content Read | low | Unmodeled |
find_images_on_pageFind Images on Page | List all image elements that appear in the content of a page (or post). Returns normalized entries with element_id, element_type ("gutenberg:core/image" | "elementor:image" | etc.), current url, alt text, and media library ID where known. Pass each element_id back to update_page_image to swap that specific image. Featured image is included as element_id="featured" unless include_featured=false. With Keva Bridge installed: works on every supported builder. Without Bridge: returns synthetic "img:N" IDs scraped from the rendered HTML (classic-editor pages only). | Content Read | low | Unmodeled |
| Content Update (10) |
update_postUpdate Post (Metadata Only) | Update the title or status of an existing post. Does NOT update body content — for content edits use wordpress.update_page_text (find/replace) or wordpress.remove_page_element. Those actions are page-builder aware and work on Elementor, Gutenberg, Divi, Beaver Builder, Bricks, Breakdance, WPBakery, Avada, and classic posts. Writing post_content directly bypasses page builders and produces an invisible no-op on builder-rendered pages. | Content Update | medium | Snapshot restore |
update_pageUpdate Page (Metadata Only) | Update the title or status of an existing page. Does NOT update body content — for content edits use wordpress.update_page_text (find/replace) or wordpress.remove_page_element. Those actions are page-builder aware and work on Elementor, Gutenberg, Divi, Beaver Builder, Bricks, Breakdance, WPBakery, Avada, and classic pages. Writing post_content directly bypasses page builders and produces an invisible no-op on builder-rendered pages. | Content Update | medium | Snapshot restore |
update_commentModerate Comment | Update the status or content of a comment (approve, spam, trash). | Content Update | medium | Unmodeled |
restore_post_revisionRestore Post Revision | Restore a post to a previous revision by copying the revision content back to the post. | Content Update | medium | Unmodeled |
update_mediaUpdate Media | Update metadata (alt text, caption, description, title) of an existing media item. | Content Update | medium | Snapshot restore |
set_featured_imageSet Featured Image | Set or remove the featured image on a post or page. | Content Update | medium | Snapshot restore |
update_page_imageUpdate Page Image | Swap one image that appears on a page (hero image, team photo, etc.) by element_id. Call find_images_on_page first to discover the element_id. Pass EITHER media_id (preferred — the connector resolves the canonical URL + alt from the WP media library) OR a raw url (advanced — bypasses the library, use only for CDN-hosted assets). DIFFERENT from update_media (which only changes library metadata) and set_featured_image (which sets the post thumbnail). Bridge required for builder pages (Elementor / WPBakery / Divi / Beaver / Bricks / Oxygen / Brizy / Cwicly); classic-editor pages with synthetic "img:N" IDs use a regex HTML rewrite (best-effort). If you see element_not_found (404) after a successful find_images_on_page, the page was edited concurrently — re-discover and retry. | Content Update | medium | Unmodeled |
update_pricing_tierUpdate Pricing Tier | Update an existing pricing tier on a page. In v0.1 this uses a delete-and-re-add pattern (the old tier is removed and a new one with the updated fields is added in its place) — the returned tier_id will differ from the input. To change only text within an existing tier without changing its id, use bridge_replace_text. Requires Keva Bridge plugin. Gutenberg + Elementor only. Use get_page / bridge_read_structure first to discover the tier_id. | Content Update | medium | Unmodeled |
update_faq_itemUpdate FAQ Item | Update an existing FAQ item. Gutenberg: faq_id is a core/details block id — rewrites the question text IN the <summary> markup (core/details' summary is an html-sourced attribute, so its visible value lives in the markup, not the block-delimiter attrs; patching attrs alone silently no-ops). For answer-text changes use bridge_replace_text (the answer lives in a child core/paragraph block which v0.1 does not patch directly — a future Bridge release will support nested innerBlocks patches). Elementor: faq_id is "{accordion_widget_id}:{tab_id}" — reads current tabs, replaces the matching tab in place, patches the widget settings. Bridge required. | Content Update | medium | Unmodeled |
update_testimonialUpdate Testimonial | Update an existing testimonial. Gutenberg: testimonial_id is the core/quote (or core/pullquote) block id — rewrites the attribution text IN the <cite> markup (the citation is an html-sourced attribute, so its visible value lives in the markup, not the block-delimiter attrs; patching attrs alone silently no-ops). Quote-text changes are not supported in v0.1 because the quote lives in a nested paragraph block; use bridge_replace_text for that. Elementor: patches the testimonial widget settings with whatever subset of fields the caller provides (quote → testimonial_content; author_name → testimonial_name; author_role → testimonial_job; attribution → split into name/role if individual fields not supplied). Bridge shallow-merges the settings so untouched fields stay intact. | Content Update | medium | Unmodeled |
| Content Write (21) |
create_commentCreate Comment | Create a comment on a WordPress post. | Content Write | medium | Unmodeled |
update_registered_post_meta_fieldUpdate Registered Post Meta Field | Snapshot-first update of ONE field inside a WordPress REST-registered post meta object on a REST-exposed post type. This is for visually-rendered plugin/theme settings that are not page body text, such as Hello Elementor kit _elementor_page_settings.hello_footer_copyright_text. It refuses broad raw meta writes: you must provide a meta_key, a field_path, and the exact value. It takes a Keva Bridge safety snapshot FIRST and aborts with no write if the snapshot fails, then writes through wp/v2, re-reads the exact field, and can optionally fetch a same-site public URL to confirm rendered text. Requires BOTH WordPress Application Password and Keva Bridge. | Content Write | medium | Unmodeled |
update_page_textUpdate Page Text (Builder-Aware) | PREFERRED action for editing page/post body content. Performs a find-and-replace on text regardless of which page builder is used — detects Gutenberg blocks, Elementor JSON in post_meta, shortcode-based builders (WPBakery, Divi, Avada), Beaver Builder, Bricks, Breakdance, and classic content automatically, then writes back through the correct storage layer so the change actually renders. Use this for any "change X to Y" or "fix this wording" task. Pair with wordpress.find_text_on_page first if you do not know the exact existing text. | Content Write | medium | None |
remove_page_elementRemove Page Element | Remove page builder elements (sections, widgets, blocks) that contain specific text. Works across all supported builders. | Content Write | high | Unmodeled |
regenerate_builder_assetsRegenerate Builder Assets | Operational break-fix action for "the WordPress page returns HTTP 200 but looks visually broken" when the cause appears to be stale/corrupt generated page-builder assets. Elementor-first: clears the page's generated Elementor CSS/meta cache, Elementor element cache, Elementor sitewide file cache when available, and WordPress post/meta cache so the next public render regenerates clean CSS. Use this for Elementor generated CSS corruption, leaked WordPress placeholder tokens such as "{a89e...}" where percent values should be, missing/broken header or hero styling, or stale builder CSS after an update. It does NOT edit page content. Requires Keva Bridge. Always verify with a public URL check such as public_url_not_contains for the leaked token. | Content Write | medium | Unmodeled |
repair_builder_placeholder_unitsRepair Builder Placeholder Units | Snapshot-first operational repair for a specific Elementor visual-corruption failure: the public page returns HTTP 200 but CSS contains leaked WordPress placeholder tokens like "{a89e...}" where Elementor unit fields should contain "%". This action reads the Elementor source tree, repairs ONLY exact {64-hex} placeholder values found in settings.*.unit fields (default replacement "%"), regenerates builder assets, then verifies by re-reading Elementor source and attempting a cache-busted public permalink read for the same tokens. It refuses to edit embedded/non-unit token hits and reports them as suspicious instead. Use this AFTER regenerate_builder_assets does not remove the leaked tokens, or when bridge_read_page shows placeholders stored in Elementor unit settings. HIGH-risk because it mutates builder source data; propose for human approval. Requires Keva Bridge. | Content Write | high | Snapshot restore |
repair_percent_placeholder_escapesRepair Percent Placeholder Escapes | Snapshot-first operational repair for a WordPress database/content corruption class where the public HTML leaks wpdb percent-placeholder escape tokens like "{a89e...}" in places that should contain literal "%" characters (examples: "100{token}", "{token}20" instead of "%20", Amelia booking placeholders such as "{token}appointment_date_time{token}"). This is NOT a page-builder asset repair. It replaces one exact observed {64-hex} token with "%" across persisted post_content, postmeta, and options using WordPress APIs so serialized values are re-saved safely, then verifies by re-scanning source and optionally cache-busted-reading the affected public URL. HIGH-risk because it can touch many content/settings rows; prefer a diagnostic dry-run and one storage layer at a time using include_options/include_postmeta/include_posts, with independent public read-back after each layer. Always run/propose with an exact token from diagnostics and public_url_not_contains verification. | Content Write | high | Snapshot restore |
apply_frontend_patchApply Frontend Patch | Snapshot-first, typed front-end mitigation for plugin/widget behavior that breaks a public page render but is not safely editable through page-builder content. This is NOT arbitrary custom JavaScript. The first supported patch_type is suppress_autofocus: on one same-site public path, Keva emits a fixed script that removes autofocus from a selector, blurs it if it captured focus during load, and optionally restores scroll to the top before the visitor interacts. Use it for evidence-backed focus/autoscroll incidents such as a booking, checkout, login, or form widget focusing an input and making the header/page top appear missing. It is path-scoped, selector-scoped, stored as a Keva-owned option, takes a safety snapshot first, verifies by fresh option read-back, and is reversible by removing/restoring the named patch. Requires Keva Bridge. Always propose with a visual verification object for the original public symptom, e.g. visual_header_visible or visual_logo_visible on the affected URL; do not claim visual parity without a known-good reference. | Content Write | medium | Unmodeled |
remove_frontend_patchRemove Frontend Patch | Snapshot-first removal of a Keva-owned typed front-end patch by patch_id. Use this to roll back a suppress_autofocus mitigation after proving it is no longer needed or if independent verification fails. It removes only the named Keva patch, verifies by fresh option read-back, and keeps a safety snapshot. Requires Keva Bridge. | Content Write | medium | Unmodeled |
bridge_replace_textBridge: Replace Text | Find/replace text on a page through Keva Bridge. Engine-aware: edits Gutenberg block innerHTML or Elementor widget settings in place, then triggers the right cache invalidation (Elementor CSS, per-post cache, sitewide post cache, keva/after_content_update hook). Same surface area as wordpress.update_page_text but with verified writes and correct Elementor handling. | Content Write | medium | Unmodeled |
bridge_append_blockBridge: Append Block | Append a new block (Gutenberg) or element (Elementor) to a page. Use this to add a paragraph, heading, image, or section without naive post_content concatenation. Without target_id, the block goes to the end of the page; with target_id, it nests inside the matching container. Solves the "append a sentence" gap from PR-1. | Content Write | medium | Unmodeled |
bridge_append_textBridge: Append Text Paragraph | Token-saver wrapper around bridge_append_block for the most common case: appending a paragraph of text to a page. Pass post_id and text; the connector picks the right block shape automatically based on the page engine (core/paragraph for Gutenberg, text-editor widget for Elementor). HTML in the text is escaped — pass HTML through bridge_append_block if raw markup is needed. NOTE: with no `color`, the appended text INHERITS the page section / Elementor-kit text color, which can render it unreadable (e.g. invisible white-on-white when the kit text color is light) — pass an explicit `color` when the line must stand out or be reliably readable. | Content Write | medium | Unmodeled |
bridge_update_blockBridge: Update Block | PATCH-style update of a single block/element by ID. Use bridge_read_structure first to find the target ID. For Gutenberg, replaces attrs + innerHTML. For Elementor, shallow-merges into element.settings. Leaf blocks/widgets only — use bridge_replace_text for find/replace across the page. | Content Write | medium | Unmodeled |
bridge_delete_blockBridge: Delete Block | Remove a single block/element by ID. Use bridge_read_structure to find the target ID first. Returns 404 if the ID is no longer in the tree (e.g. someone else already removed it). | Content Write | high | Unmodeled |
set_regionBridge: Update Keva-Managed Region | Set a [keva-region]'s current value. PREFERRED over update_page_text when the target is a known region id — works on ANY page builder. Pass expected_version (from a recent get_region) for optimistic concurrency — the write fails with version_conflict (409) if the stored version has advanced. Regions declared with lock="true" require human_approved: true after the human-approval flow. When the same region id exists on multiple posts, pass post_id explicitly or the call returns ambiguous_region (400). | Content Write | medium | Unmodeled |
add_pricing_tierAdd Pricing Tier | Append a new pricing tier to a page (e.g. "Add a Pro tier at $29/mo with 5 features and a Sign up button"). On Gutenberg, builds a core/column with heading + price + feature-list + button and appends it into target_id (an existing core/columns block) — when target_id is omitted, creates a fresh core/columns row at the end of the page with the new tier inside. On Elementor (free pattern, works without Pro), builds an Elementor column with heading + heading + icon-list + button widgets — appends into target_id (an existing section) or wraps in a new section. Requires Keva Bridge plugin (BRIDGE_NOT_CONFIGURED returned otherwise). For builders other than Gutenberg/Elementor, returns a graceful error pointing at bridge_append_block. Returns the new tier_id which you can pass to remove_pricing_tier / update_pricing_tier later. | Content Write | medium | Unmodeled |
add_faq_itemAdd FAQ Item | Add a single question/answer to a page. On Gutenberg: appends one core/details block (each FAQ is its own block — uses WP 6.3+ native accordion primitive). On Elementor: when target_id points at an existing accordion widget, appends a new tab into its tabs[] array (read-modify-write the widget settings). When target_id is omitted on Elementor, creates a fresh accordion widget with one starter tab. Requires Keva Bridge. Other 7 builders return WIDGET_PATTERN_UNSUPPORTED. | Content Write | medium | Unmodeled |
add_testimonialAdd Testimonial | Append a testimonial to a page. Gutenberg: builds a core/quote block (quote → child paragraph; attribution → citation attr). Elementor: builds a testimonial widget (testimonial_content / testimonial_name / testimonial_job). The attribution string is preferred — when provided it overrides separate author_name/author_role on Gutenberg; on Elementor the discrete fields drive the widget settings and are populated from attribution (split on the first comma) when not supplied. Requires Keva Bridge. | Content Write | medium | Unmodeled |
add_sectionAdd Section | Append an empty top-level section to the end of a page. The connector picks the engine-correct empty container shape (core/group with constrained layout on Gutenberg; elType:container or section on Elementor). Returns the new section_id which you can pass to subsequent bridge_append_block / bridge_append_text / widget actions (add_pricing_tier, add_faq_item, add_testimonial) to fill the section. Use this when the AI wants a fresh wrapper to drop new content into rather than crafting the engine-specific JSON by hand. Requires Keva Bridge (no new PHP endpoint — uses /append-block). For engines other than Gutenberg/Elementor, returns engine_not_supported. | Content Write | high | Unmodeled |
duplicate_sectionDuplicate Section | Deep-clone a section by id and insert the clone immediately after the source. For Elementor, every nested element id is regenerated server-side to a fresh 8-hex (sharing ids breaks Elementor's editor). For Gutenberg the clone gets a fresh deterministic blk_xxxxxxxx id derived from its post-splice path. Atomic single save_post. Returns the new duplicated_id which you can pass to subsequent update_section calls to vary the cloned content. Requires Keva Bridge ≥0.7.0. Gutenberg + Elementor only; other engines return engine_not_supported. | Content Write | high | Unmodeled |
reorder_sectionsReorder Sections | Atomically reorder top-level sections (or, when parent_id is supplied, the children of a specific container) to match ordered_ids. The ordered_ids array MUST be a permutation of the current root-level ids — if it isn't the call returns invalid_order with the actual current ids embedded in the error so the AI can re-read + retry. Single save_post per call (no partial-state risk). Requires Keva Bridge ≥0.7.0. Gutenberg + Elementor only; other engines return engine_not_supported. | Content Write | high | Unmodeled |
| Coupon Write (1) |
delete_couponDelete Coupon | Permanently delete a WooCommerce coupon. | Coupon Write | medium | Unmodeled |
| Customer Write (1) |
wc_update_customerUpdate Customer | Update a WooCommerce customer (name, email, billing/shipping). | Customer Write | medium | Unmodeled |
| Diagnostic Diagnose (2) |
diagnoseDiagnose Site | High-level diagnostician. Gathers a read-only DiagnosticSnapshot from the site (Site Health, failure state, captured fatals, PHP config, server resources, extension inventory, recent changes, recovery status, autoload size, cron status; debug log optionally) by calling the read endpoints in parallel and tolerating individual failures. Returns the assembled snapshot under data.snapshot for downstream root-cause analysis. Read-only — proposes nothing and writes nothing. Requires Keva Bridge. | Diagnostic Diagnose | low | Unmodeled |
diagnose_page_incidentDiagnose Page Incident | Read-only WordPress incident packet for a specific public URL or page id. Fetches the same-site public URL with cache busting, records the real HTTP status/body signatures/placeholder-token leaks, renders the public page in a browser when available to capture deterministic visual evidence (title, blank-screen signal, visible header/logo/hero landmarks, failed assets, console errors, overflow), resolves the page through Keva Bridge when possible, reads the builder/page source and compact structure, runs the site diagnostic snapshot, and returns evidence-backed findings plus recommended next actions. It never writes, never snapshots, and never claims a fix; use it before proposing page-builder/cache/plugin repair actions when a page is visually broken, down, leaking errors, or behaving differently from source. Requires Keva Bridge; visual rendering degrades honestly if Playwright is unavailable. | Diagnostic Diagnose | low | Unmodeled |
| Diagnostic Read (11) |
get_captured_fatalsGet Captured Fatals | Read PHP fatal errors captured by the Keva Bridge shutdown handler. This is the PRIMARY cold-readable fatal source — it does not depend on WP_DEBUG_LOG or an active recovery session. Each fatal includes message, file, line, and the derived responsible component (plugin / mu-plugin / theme / core). Use this to attribute a white-screen / 500 to a specific extension. Requires Keva Bridge. | Diagnostic Read | low | Unmodeled |
get_failure_stateGet Failure State | Deterministic active-failure detection: stuck .maintenance file (with age), recovery-mode state, and the most recent captured fatal. Returns an active_failure boolean summarizing whether the site is currently in a known-broken state. Use this first to decide if the site is actively down vs. merely misconfigured. Requires Keva Bridge. | Diagnostic Read | low | Unmodeled |
get_site_healthGet Site Health | Run WordPress core Site Health direct tests and return them normalized to good / recommended / critical, with counts. Covers PHP version, SQL server, plugin/theme update status, and more — the highest-value first-party introspection source. Requires Keva Bridge. | Diagnostic Read | low | Unmodeled |
get_php_configGet PHP Config | Read PHP runtime configuration relevant to live-site failures: PHP version, memory_limit, max_execution_time, upload caps, disabled functions, and presence of key extensions (mysqli, curl, gd, imagick, zip, etc.). Degrades gracefully when ini_get is itself disabled. Requires Keva Bridge. | Diagnostic Read | low | Unmodeled |
get_server_resourcesGet Server Resources | Read server-level signals: disk free/total (when not blocked by the host), database version + reachability (a cheap SELECT 1 probe), external object-cache status, and the WP memory-limit constants. Use to detect disk-full or database-connection failures. Requires Keva Bridge. | Diagnostic Read | low | Unmodeled |
get_extension_inventoryGet Extension Inventory | Full plugin/theme picture for conflict isolation: active plugins, all installed plugins + versions, must-use plugins, the active theme (and parent), and per-plugin/theme auto-update flags. The foundation for "which plugin caused this" reasoning. Requires Keva Bridge. | Diagnostic Read | low | Unmodeled |
get_recent_changesGet Recent Changes | Best-effort "what changed recently" signal: the file-modification time of each plugin's main file and the active theme's style.css, sorted newest-first with an age. A recent mtime correlates with a recent update — the key signal for "it broke right after an update". Heuristic (a manual edit or redeploy also bumps mtime), so treat as correlation, not proof. Requires Keva Bridge. | Diagnostic Read | low | Unmodeled |
get_recovery_statusGet Recovery Status | Read WordPress recovery-mode state plus the paused-plugins / paused-themes lists. IMPORTANT: those lists are populated only inside an active recovery session, so they are empty on a normal request even when a plugin is fataling for visitors — for cold fatal attribution use get_captured_fatals instead. Requires Keva Bridge. | Diagnostic Read | low | Unmodeled |
read_debug_logRead Debug Log | OPPORTUNISTIC tail of wp-content/debug.log when WP_DEBUG_LOG is enabled and the file is present + readable. Returns an availability flag with a reason when it is off or absent (common — the log is off by default and managed hosts often redirect PHP errors elsewhere). This is supplementary evidence only, never the primary fatal source. Requires Keva Bridge. | Diagnostic Read | low | Unmodeled |
get_autoload_sizeGet Autoload Size | Total bytes of autoloaded options (loaded on every request) plus the top 10 largest offenders. Flags a warning above the wp-doctor 900KB threshold. Bloated autoloaded options are a classic silent performance killer. Requires Keva Bridge. | Diagnostic Read | low | Unmodeled |
get_cron_statusGet Cron Status | WP-Cron health: total scheduled events, how many are overdue, the DISABLE_WP_CRON constant, and the next event time. A pile of overdue events with DISABLE_WP_CRON on (and no system cron) is the signature of "wp-cron is not running", which silently breaks scheduled posts, updates, and many plugins. Requires Keva Bridge. | Diagnostic Read | low | Unmodeled |
| I18n Read (4) |
detect_multilingualDetect Multilingual | Determine which multilingual plugin owns the truth on a WordPress site: WPML, Polylang (free), Polylang Pro, TranslatePress, Weglot, or none — plus the version, the configured/default languages, and which add-ons are active. CRITICAL: this detects the authoritative plugin by the ACTIVE plugin's main FILE plus an option/table fingerprint, NOT by a shared class — because these plugins share NO common class or API (unlike ACF and its fork SCF), so a class check could not tell them apart, and a deactivated plugin's leftover tables are NOT authoritative (the live site is single-language). It reports the STORAGE MODEL — icl_tables (WPML's dedicated wp_icl_* tables, where rows in icl_translations sharing a trid are translations), wp_taxonomies (Polylang's WP-native language/post_translations taxonomies + a serialized lang-code → post-ID map in wp_term_taxonomy.description), trp_tables (TranslatePress's per-language-pair string dictionaries), external (Weglot stores translations off-site), or none — because every other read and every future safe write MUST branch on it; cross-applying one plugin's model to another corrupts the site. It also reports a `writable` gate that is true ONLY for Polylang (the plugin Keva can safely write via its own taxonomy API); WPML, TranslatePress, Weglot, and none are advisory in this build (the destructive WPML write is real-world-unvalidated and could corrupt icl_translations, so it is deferred to certification on a licensed WPML site). Two active multilingual plugins are flagged as a conflict (a real misconfiguration — duelling language layers). Every read in this category keys off this detection, and it degrades gracefully to plugin "none" (never a fatal) when no multilingual plugin is active. Read-only. Requires Keva Bridge. | I18n Read | low | Unmodeled |
audit_translation_coverageAudit Translation Coverage | Diagnose WHY a multilingual site is broken — the flagship multilingual read. It answers "why did the French version disappear?" and "why does every language show the same content?" by scanning the detected plugin's translation data per its storage model. On Polylang it finds posts with NO language assigned (the #1 Polylang cause: a post with no language term fails the language filter and is INVISIBLE on the front end — the safe fix is to ASSIGN a language, which makes invisible content visible without touching any existing language's content) plus posts that have a language but are missing a translation in a configured language (the "the French version disappeared" symptom — the safe fix is to RE-LINK an existing translation, never to translate). On WPML it scans icl_translations grouped by trid for groups missing a configured language and for rows whose element_id points at a deleted post (read-only; the WPML re-link is advisory in this build). IMPORTANT honesty: the content already exists in these cases — the fix is re-linking or assigning, NEVER creating or translating content (translation is human/MT work Keva never performs). The scan is bounded and sampled (counts plus a bounded sample). For a TranslatePress site (string-pair model, no post relationship to repair) or a Weglot site (translations live off-site) it degrades to advisory. Degrades gracefully when no multilingual plugin is active. Read-only. Requires Keva Bridge. | I18n Read | low | Unmodeled |
scan_orphaned_translationsScan Orphaned Translations | Find orphaned translation rows and links left behind in the database after content was deleted — the "ghost" entries that point at posts that no longer exist. Per the detected plugin's storage model, this is a bounded, READ-ONLY $wpdb scan: on Polylang it decodes each post_translations term's serialized lang-code → post-ID map (stored in wp_term_taxonomy.description) and flags any entry referencing a post ID that no longer exists; on WPML it scans icl_translations for rows whose element_id has no live post. It reports the orphan count plus a bounded sample (the term/row identifiers and the dead post IDs) — the candidates for the safe NAMED cleanup, which is a separate gated action. IMPORTANT honesty: the cleanup (clean_orphaned_translations, a later action) removes only the confirmed-dead entry (never a live translation), acts on a NAMED/scoped target only (there is no unbounded "clean all translations" sweep), is snapshot-first, and drops the dead entry by a SCOPED rewrite of the group's serialized post_translations description (a single wp_update_term — the same write Polylang itself uses in delete_translation), NOT pll_save_post_translations (that API cannot prune a member whose post row is already gone). On Polylang the cleanup is tested and safe; on WPML it is advisory in this build (use WPML's own "Remove ghost entries from the translation tables" tool). Degrades gracefully when no multilingual plugin is active. Read-only. Requires Keva Bridge. | I18n Read | low | Unmodeled |
audit_language_configAudit Language Config | Audit a multilingual site's language configuration and switcher. It reports the default language and the full configured-versus-active language list, flagging any language that is configured but NOT active — a real cause, because an inactive language's content 404s or is not offered (the safe fix is to ACTIVATE it, a later gated action). It also makes one bounded, marker-free loopback fetch of the home page to check whether a language switcher is present in the served markup (it detects WPML's and Polylang's switcher classes), and it references Category 6 (SEO) for hreflang correctness rather than re-parsing hreflang itself — Cat-6 reads hreflang on the front end, while Cat-8 owns the language config behind it, so a "wrong hreflang" finding is explained by this read and fixed by a relationship repair. IMPORTANT honesty: changing the DEFAULT language is HAZARDOUS on a populated site (per WPML's own documentation it breaks menus synced from the default, hides untranslated content from archives, and can break the switcher, needing a permalink flush), so Keva reports a suspected default-language misconfiguration and ADVISES the careful manual procedure — it NEVER auto-changes the default. Switcher correctness and placement need the theme, and a language with no published content is hidden by design. Degrades gracefully when no multilingual plugin is active. Read-only. Requires Keva Bridge. | I18n Read | low | Unmodeled |
| I18n Write (4) |
assign_post_languageAssign Post Language | The safest flagship fix for the #1 Polylang cause of "my content disappeared": assign a language to a NAMED post that has NO language assigned, via Polylang's own pll_set_post_language(). A post with no language term fails the language filter and is INVISIBLE on the front end; assigning it a language makes it visible again WITHOUT touching any existing language's content and WITHOUT translating anything. Pass post_id (the no-language post) and language (a CONFIGURED language code — assigning an unconfigured language is refused). This is Polylang-only: on WPML, TranslatePress, Weglot, or no multilingual plugin it returns an honest ADVISORY result and changes nothing (an untested WPML icl_translations write could corrupt the site, so it is deferred to a licensed WPML site). The Bridge takes a safety snapshot FIRST (and ABORTS if it fails — the snapshot is the ONLY rollback for an assign, which has no field-by-field inverse), assigns the language via the plugin API, then VERIFIES via a FRESH pll_get_post_language() read confirming the post now carries the language, and on a failed verify RESTORES the snapshot so the prior taxonomy state returns. Reports the post id, the assigned language, and the snapshot id. Reversible (via the snapshot). HIGH risk: requires human approval. Requires Keva Bridge. | I18n Write | high | Unmodeled |
relink_translationRe-link Translation | Re-associate NAMED EXISTING posts as translations of each other — the fix for "the French version disappeared / isn't linked to the English original", via Polylang's own pll_save_post_translations(). This NEVER creates or translates content: it re-links existing posts that lost their translation relationship (after a migration, a DB cleanup, or a direct DB edit), so the content that already exists becomes reachable as a translation again. Pass translations, a map of { language-code: post-id } with at least two entries; each post must already EXIST and already carry THAT language (a language mismatch is refused — assign the language first with assign_post_language). This is Polylang-only: on WPML (the trid re-link), TranslatePress, Weglot, or no multilingual plugin it returns an honest ADVISORY result and changes nothing (an untested WPML icl_translations re-link could corrupt the translation graph, so it is deferred to a licensed WPML site — use WPML's own "Set language information" tool). The Bridge takes a safety snapshot FIRST (and ABORTS if it fails), writes the language→id map via the plugin API, then VERIFIES via a FRESH pll_get_post_translations() read confirming every requested member resolves in the group, and on a failed verify RESTORES the snapshot. Reports the linked map, the group read back, and the snapshot id. Reversible (via the snapshot). HIGH risk: requires human approval. Requires Keva Bridge. | I18n Write | high | Unmodeled |
activate_languageActivate Language | Flip a NAMED configured-but-inactive language to active, so its content is offered again — distinct from CHANGING the default language (which is hazardous on a populated site and is advisory-only, never automated). Pass language (the configured language code to activate). An already-active language is a verified no-op; a language that is not configured at all is refused (defining a brand-new language is setup work Keva advises, not an automated write). This is Polylang-only: on WPML (the icl_languages active flag), TranslatePress, Weglot, or no multilingual plugin it returns an honest ADVISORY result and changes nothing (deferred to a licensed WPML site). The Bridge takes a safety snapshot FIRST (and ABORTS if it fails), activates the language via the plugin model, then VERIFIES via a FRESH pll_languages_list() read confirming the language is now active, and on a failed verify RESTORES the snapshot. Reports the activated language, the active set before/after, and the snapshot id. Reversible (via the snapshot). HIGH risk: requires human approval. Requires Keva Bridge. | I18n Write | high | Unmodeled |
clean_orphaned_translationsClean Orphaned Translations | Remove a NAMED/SCOPED dead entry (a language→deleted-post-id reference) from a Polylang post_translations group — the "ghost" left after a post was removed by a migration or a direct DB delete (Polylang self-heals on a normal delete, so a surviving dead ref only arises when its hook did not fire). It works by REWRITING the group's serialized post_translations description directly — a single SCOPED wp_update_term that drops the dead language key and keeps every live member verbatim (and wp_delete_term when the whole group is dead), the same write Polylang itself uses in delete_translation. It deliberately does NOT use pll_save_post_translations(), which cannot prune a member whose post row is already gone (its should_update_translation_group short-circuits on a subset map, and its unlink path needs the dead post's term-relationship). This is NAMED/SCOPED-ONLY: there is NO "clean all translations" sweep, and it NEVER removes a LIVE translation — every dead reference is re-confirmed dead at write time, so a race where the post was re-created since the scan can never drop a live member. Pass term_taxonomy_id (the orphaned post_translations group the scan named) or dead_post_ids (the specific dead post-id references to drop); an unscoped request is refused. This is Polylang-only: on WPML (the "Remove ghost entries from the translation tables" tool), TranslatePress, Weglot, or no multilingual plugin it returns an honest ADVISORY result and changes nothing (deferred to a licensed WPML site). The Bridge takes a safety snapshot FIRST (and ABORTS if it fails), rebuilds each group with the dead entry removed, then VERIFIES via a FRESH re-read confirming the dead reference is gone and any live member still resolves, and on a failed verify RESTORES the snapshot. Reports the dropped/kept members and the snapshot id. Reversible (via the snapshot). HIGH risk: requires human approval. Requires Keva Bridge. | I18n Write | high | Unmodeled |
| Infrastructure Read (10) |
list_pluginsList Plugins | List installed plugins with status, version, and other metadata. | Infrastructure Read | low | Unmodeled |
get_pluginGet Plugin | Get details of a specific installed plugin. | Infrastructure Read | low | Unmodeled |
list_themesList Themes | List installed themes with status and metadata. | Infrastructure Read | low | Unmodeled |
get_themeGet Theme | Get details of a specific installed theme. | Infrastructure Read | low | Unmodeled |
get_system_statusGet System Status | Get WooCommerce system status including WP version, PHP version, DB info, active plugins, and theme info. | Infrastructure Read | low | Unmodeled |
list_application_passwordsList Application Passwords | List application passwords for a user. Returns names and creation dates (not actual passwords). Useful for auditing API access. | Infrastructure Read | low | Unmodeled |
get_site_settingsGet Site Settings | Get WordPress site settings including title, description, URL, timezone, date/time format, and language. | Infrastructure Read | low | Unmodeled |
get_global_stylesGet Site Global Styles | Read the active theme's site-wide design tokens (theme.json). For block themes, returns the merged settings + styles (palette, fontFamilies, default typography, per-element overrides) along with is_block_theme:true and the global_styles_id used by subsequent updates. For classic themes, returns is_block_theme:false plus the small subset of color settings exposed via /wp/v2/settings — most classic-theme customizer mods are not yet AI-editable (planned for PR-10.1 with Bridge). | Infrastructure Read | low | Unmodeled |
get_brand_colorsGet Brand Color Palette | Convenience read: returns just the named color palette declared by the active theme. Each entry has { slug, name, color }. Use the slug as the target for update_brand_color. On classic themes, returns a small derived palette built from the WordPress site settings (background_color, header_textcolor). | Infrastructure Read | low | Unmodeled |
list_font_familiesList Font Families | Convenience read: returns the font families declared by the active block theme. Each entry has { slug, name, fontFamily }. Use the slug as the target for update_font_family. Returns unsupported_classic_theme on classic themes — font-family editing for classic themes requires Bridge (planned for PR-10.1). | Infrastructure Read | low | Unmodeled |
| Infrastructure Write (2) |
activate_pluginActivate Plugin | Activate an installed WordPress plugin. | Infrastructure Write | medium | Provider revert |
deactivate_pluginDeactivate Plugin | Deactivate an active WordPress plugin. | Infrastructure Write | medium | Provider revert |
| Order Read (2) |
wc_list_order_notesList Order Notes | List notes on a WooCommerce order. | Order Read | low | Unmodeled |
wc_list_order_refundsList Order Refunds | List refunds for a WooCommerce order. | Order Read | low | Unmodeled |
| Performance Read (4) |
profile_requestProfile Request | Profile what is slow in ONE representative front-end request and report the total database query count and time, peak memory, the slowest queries (with their caller), and how many outbound HTTP calls the request made and how long they took. It works by making a single marker-gated loopback request to the home page that turns on per-query instrumentation for THAT request only (real visitors are never affected and the heavy instrumentation is never left on), then reads the captured metrics back. IMPORTANT honesty: this is a SINGLE SAMPLE under one cache-warmth state, and profiling observes-by-changing (the instrumentation itself adds overhead) — treat it as directional, run it a few times for a stable picture, and act first on the deterministic reads (analyze_autoload, detect_perf_issues). It degrades gracefully (returns sampled:false with a reason) if the loopback cannot run, rather than hanging or erroring. Read-only. Requires Keva Bridge. | Performance Read | low | Unmodeled |
analyze_autoloadAnalyze Autoload | Find the autoloaded options that are loaded on EVERY request (a classic silent performance killer) and, for each of the largest, report its byte size, its autoload value, and a de-autoload candidacy verdict: "transient" (a transient that should never be autoloaded), "orphaned" (its owning plugin appears to be uninstalled — left behind by a removed plugin), "large" (a big plugin-owned option — check the plugin's own setting first), or "keep" (a core/essential option that must stay autoloaded). This EXTENDS the basic autoload-size diagnostic with per-option candidacy so the brain can reason about what is safe to de-autoload. Note that WordPress 6.6+ already declines to autoload very large NEW options, but legacy rows are not migrated — those are the cleanup opportunity. Read-only — it NEVER changes an option; de-autoloading a specific named option is a separate, approval-gated, snapshot-first action (flipping an essential option off-autoload would break the site). Requires Keva Bridge. | Performance Read | low | Unmodeled |
detect_perf_issuesDetect Performance Issues | Produce a ranked performance posture for the site, folding the diagnostic signals and new reads into a list of issues, each with a status (ok / a real warning / advisory-only / not-applicable), what was observed, and a recommendation. It checks: whether a persistent object cache is present and — if not — whether the site has grown past WordPress's own suggestion thresholds; whether a page cache is serving the home page (a loopback cache-header sniff plus a TTFB check against the 600ms good-response threshold); whether WP-Cron runs on every front-end request; expired-transient bloat in the database (correctly reported as NOT-APPLICABLE when a persistent object cache is in use, because transients then live in the cache, not the database); post-revision and spam/trash-comment bloat; the PHP version; whether OPcache is enabled; and render-blocking/image concerns (advisory only, since front-end Core Web Vitals are not measurable server-side). The honest division is the product stance: Keva tells you what is slow and why, the safe database-level fixes are approval-gated writes, and the infrastructure/code work (object cache backend, page cache, CDN, PHP upgrade, query rewrites) is ADVISED, not executed. Read-only. Requires Keva Bridge. | Performance Read | low | Unmodeled |
measure_plugin_load_impactMeasure Plugin Load Impact | Estimate how much each active plugin contributes to front-end load time by measuring the home page with the full set of active plugins, then again with one plugin filtered out, and reporting the difference per plugin (ranked, most expensive first). It does this safely using the same visitor-safe mechanism as conflict isolation — it toggles which plugins load for its own marked test request only, touching no plugin files and never affecting real visitors — and it NEVER toggles the Keva plugin itself. IMPORTANT honesty: this is a NOISY, DIRECTIONAL estimate, not a benchmark — real timing varies request to request, cache warmth matters, and a plugin whose cost is only in the admin area or in cron is invisible to a front-end measurement; it is an investigate-further signal best run repeated and averaged. The deterministic reads (analyze_autoload, detect_perf_issues) are the ones to act on first. Read-only. Optional: slug (a plugin folder slug) to measure just one plugin. Requires Keva Bridge. | Performance Read | low | Unmodeled |
| Performance Write (4) |
deautoload_optionDe-autoload Option | Flip ONE specific named option OFF autoload so it is no longer loaded on every request (loaded on demand instead). This uses the core option API (wp_set_option_autoload), not a raw database edit. It is NAMED-target-only by design — there is NO "de-autoload everything large" sweep, because flipping an essential option (siteurl, home, active_plugins, template, …) off autoload would break the site; the Bridge refuses any known-essential option and refuses an option that does not exist. Use it only on an option that analyze_autoload identified as a safe candidate (a transient that should never autoload, an orphaned option left by a removed plugin, or a large plugin-owned option whose own setting you have already checked). The Bridge takes a safety snapshot FIRST, flips the option off autoload, verifies the site, and AUTO-REVERSES (flips it back on) if the site fails to verify — so the site is never left broken. Reversible. HIGH risk: requires human approval. Requires Keva Bridge. | Performance Write | high | Snapshot restore |
clear_transient_bloatClear Transient Bloat | Clear transient bloat from the options table using the core delete_expired_transients() (the same cleanup WordPress's own daily cron runs). By DEFAULT it removes only EXPIRED transients, which is low-risk because transients regenerate on demand. It is GATED on there being NO persistent object cache: under a persistent object cache transients live in the cache (not the database), so the action skips and changes nothing rather than churning the database pointlessly. An optional "all" mode also clears LIVE (non-expired) transients — this is opt-in and WARNED, because it drops live caches and forces them to be regenerated (a brief transient stampede). The Bridge takes a safety snapshot FIRST and reports before/after transient counts. Transient regeneration is compensation, not an exact rollback: a snapshot is retained for emergency recovery but cannot prove the original volatile cache state was restored. Medium risk: requires human approval. Requires Keva Bridge. | Performance Write | medium | None |
trim_revisionsTrim Post Revisions | Delete old post revisions beyond a kept-recent count per post using the core wp_delete_post_revision() (which respects hooks and cleans up associated metadata — safer than a raw SQL delete). The post content itself is NEVER touched — only its older revisions. By default it keeps the 5 most recent revisions per post; pass "keep" to change that and "post_id" to trim just one post. Because a deleted revision is gone for good, the Bridge takes a safety snapshot FIRST and that snapshot is the ONLY rollback — if the site fails to verify afterwards, restore the snapshot to bring the revisions back. Optionally pass "set_wp_post_revisions" to request bounding FUTURE revision growth, but note Keva's careful wp-config editor only writes boolean constants, so for an integer bound it returns an advisory to set WP_POST_REVISIONS manually (and that constant only affects future saves — it does not delete existing revisions). Reversible only via the snapshot. HIGH risk (data deletion): requires human approval. Requires Keva Bridge. | Performance Write | high | Snapshot restore |
set_cron_modeSet Cron Mode | Switch how WordPress runs its scheduled tasks (WP-Cron) by setting or removing the DISABLE_WP_CRON constant in wp-config.php. mode "real_cron" defines DISABLE_WP_CRON true so WordPress stops piling scheduled work onto visitor page loads — appropriate for a busy site. mode "default" (or "wp_cron") removes the constant to restore the WordPress default, where cron fires opportunistically on front-end requests. The wp-config.php edit goes ONLY through Keva's careful validated editor, which edits a copy, confirms the file still parses, keeps a copy of the original, and only then writes — never a blind edit. The Bridge takes a safety snapshot FIRST, makes the edit, verifies the site, and AUTO-REVERTS to the prior state if the site fails to verify. IMPORTANT ADVISORY: enabling real cron only flips the WordPress-side switch — a real server crontab / system scheduler must then hit wp-cron.php on a schedule (e.g. `wp cron event run --due-now` every few minutes), or scheduled posts and jobs silently stop. Keva CANNOT write a server crontab, so that half is on the operator/host; the result spells this out. Reversible (set the opposite mode). HIGH risk: requires human approval. Requires Keva Bridge. | Performance Write | high | Snapshot restore |
| Product Read (8) |
wc_list_productsList Products | List WooCommerce products. | Product Read | low | Unmodeled |
wc_get_productGet Product | Get details of a specific WooCommerce product. | Product Read | low | Unmodeled |
list_product_categoriesList Product Categories | List WooCommerce product categories. | Product Read | low | Unmodeled |
list_product_reviewsList Product Reviews | List WooCommerce product reviews, optionally filtered by product. | Product Read | low | Unmodeled |
wc_list_product_variationsList Product Variations | List variations of a variable WooCommerce product. | Product Read | low | Unmodeled |
wc_get_product_variationGet Product Variation | Get a specific variation of a WooCommerce product. | Product Read | low | Unmodeled |
wc_get_product_reviewGet Product Review | Get a specific WooCommerce product review. | Product Read | low | Unmodeled |
wc_list_product_tagsList Product Tags | List WooCommerce product tags. | Product Read | low | Unmodeled |
| Product Update (2) |
wc_update_productUpdate Product | Update a WooCommerce product (name, price, stock, status, etc.). | Product Update | medium | Snapshot restore |
update_product_categoryUpdate Product Category | Update a WooCommerce product category (name, description, display, parent). | Product Update | medium | Unmodeled |
| Product Write (7) |
delete_product_reviewDelete Product Review | Permanently delete a WooCommerce product review. | Product Write | high | Unmodeled |
wc_create_productCreate Product | Create a new WooCommerce product. | Product Write | medium | Unmodeled |
wc_delete_productDelete Product | Permanently delete a WooCommerce product. | Product Write | high | Unmodeled |
wc_update_product_variationUpdate Product Variation | Update a WooCommerce product variation (price, stock, SKU). | Product Write | medium | Unmodeled |
wc_create_product_variationCreate Product Variation | Create a new variation for a variable WooCommerce product. | Product Write | medium | Unmodeled |
wc_update_product_reviewUpdate Product Review | Update a WooCommerce product review (status, content). | Product Write | medium | Unmodeled |
wc_create_product_tagCreate Product Tag | Create a WooCommerce product tag. | Product Write | low | Unmodeled |
| Security Read (6) |
scan_core_integrityScan Core Integrity | Check whether WordPress core files have been tampered with or had malicious files added. Diffs the installed core (wp-admin, wp-includes, and root files) against the official wordpress.org checksums for the live version and locale, reporting both MODIFIED files (their fingerprint differs) and ADDED files (unexpected .php files not in the official set — the strongest "a hack introduced new files" signal, since installers only overwrite existing files while hacks add new ones). Works without shell access: it verifies in-process using WordPress's own checksum data plus local hashing, and only runs the `wp core verify-checksums` cross-check when shell exec is available. wp-content is intentionally excluded (it is customer-owned and covered by plugin integrity). Degrades gracefully when the official checksums can't be fetched. Read-only. Requires Keva Bridge. | Security Read | low | Unmodeled |
scan_plugin_integrityScan Plugin Integrity | Check whether installed plugins from the wordpress.org repository have been tampered with. For each .org plugin it diffs the local files against the official plugin-checksums (downloads.wordpress.org/plugin-checksums/<slug>/<version>.json), reporting modified, added, and missing files (added .php files are the injected-backdoor case). Premium, custom, closed, or nulled plugins — and ALL themes, since wordpress.org publishes no theme checksums — have no official checksum source and are reported honestly as "no_checksums" (NOT as "verified" and NOT as a failure). Verifies in-process (no shell access needed). Read-only. Optional: slug (a plugin folder slug) to check just one plugin. Requires Keva Bridge. | Security Read | low | Unmodeled |
detect_suspicious_filesDetect Suspicious Files | Scan the site for files that LOOK like malware/backdoors and rank them by suspicion for human review. It walks the highest-signal locations — every .php under uploads/ (which should never contain executable PHP — one of the two lowest-false-positive signals), mu-plugins/, and the plugin/theme trees, bounded by a hard file cap — and scores each file by location, known backdoor patterns (eval, base64_decode, gzinflate/gzuncompress, str_rot13, assert, create_function, the deprecated preg_replace /e modifier, system/exec/shell_exec/passthru, request data fed straight into an executor, long base64 blobs), and recency. IMPORTANT: this is RANKED SUSPICION ONLY and NEVER auto-acts — these signatures also appear in legitimate code (plugins that legitimately base64_decode, minified vendor libraries), so every finding is for a human to review, not an automatic delete. The Keva plugin is excluded. Read-only. Requires Keva Bridge. | Security Read | low | Unmodeled |
detect_rogue_adminsDetect Rogue Admins | Enumerate administrator-role users and flag the ones that look like attacker persistence — the textbook "an attacker created a new admin account" signal. Reports each administrator's login, email, registration date, and published-post count, and flags (a) admins created recently and (b) — when an expected/allowlist of logins is supplied — admins not on that list. Advisory: a recently-created admin is a SIGNAL, not proof (a legitimate new admin also trips it), and a silently-promoted existing user has no native audit trail so it is not detectable here. Read-only. Optional: expected (a comma-separated allowlist of admin logins). Requires Keva Bridge. | Security Read | low | Unmodeled |
audit_security_postureAudit Security Posture | Read the state of the standard WordPress hardening checklist so the brain can report "you're exposed on X" and (later, behind approval) propose a safe toggle. Reads the state of: the in-dashboard file editor (DISALLOW_FILE_EDIT), all-file-mods block (DISALLOW_FILE_MODS), whether PHP errors are hidden from visitors (WP_DEBUG_DISPLAY), XML-RPC enabled/filtered, anonymous REST user enumeration, admin-over-HTTPS (FORCE_SSL_ADMIN), wp-config.php permissions, and whether PHP execution is disabled in uploads/. Each item carries a stable id, its current state, whether it is in the hardened state, and a severity. Some states are best-effort to read statically (deeper active probes are deferred); 2FA and login-attempt limiting are not WordPress-core features and are not posture toggles. All reads — applying a hardening toggle is a separate, approval-gated action. Requires Keva Bridge. | Security Read | low | Unmodeled |
detect_vulnerable_extensionsDetect Vulnerable Extensions | Flag installed plugins that are a known security risk by querying the wordpress.org plugin directory DIRECTLY (not the update screen). This is important because WordPress shows a plugin that has been CLOSED/removed from wordpress.org — frequently a security removal — as "up to date" with no warning anywhere in the admin, so the update screen hides it. For each installed plugin it reports a status: "closed" (removed from wordpress.org — the strongest signal, since a removal is often a security action and is invisible to the update screen), "abandoned" (the author appears to have stopped maintaining it — its last update is over ~2 years old, or its "tested up to" version lags the live WordPress by two or more major versions; advisory), "outdated" (a newer version is published — the safest fix is a snapshot-first safe update via update_plugin), "unknown_source" (premium, custom, or nulled — not listed on wordpress.org, which is honest, NOT a failure and NOT a clean bill of health), "unknown" (a transient lookup error, not a verdict), or "ok" (listed, current, not stale). Findings are ranked most-severe first and each carries machine-readable signals plus a plain-English advisory. The rich "which installed version has which CVE" map needs a WPScan/Patchstack/Wordfence API key (a real-world step, reported under cve_enrichment); without a key the closed/abandoned/outdated/unknown_source signals still ship. Read-only — it never updates, deactivates, or deletes anything. Optional: slug (a plugin folder slug) to check just one plugin. Requires Keva Bridge. | Security Read | low | Unmodeled |
| Security Write (5) |
quarantine_fileQuarantine File | DESTRUCTIVE: safely QUARANTINE one suspect file by MOVING it aside into a Keva-owned quarantine folder (wp-content/keva-quarantine/<time>/<path>) — it is NEVER deleted, so the move is reversible and preserves forensic evidence. Choose the file by its path relative to wp-content (e.g. "uploads/keva-shell-test.php" or "plugins/acme/evil.php"); it must name ONE file, not a directory. This is the product's "quarantine over delete" stance for containing a flagged backdoor/webshell — the two strongest, lowest-false-positive signals to act on are a checksum mismatch (see scan_core_integrity / scan_plugin_integrity) and a .php file living in uploads/ (which should never contain executable PHP). The path is HARD-validated (no traversal, must resolve strictly inside wp-content under plugins/themes/mu-plugins/uploads, and NEVER the Keva plugin's own directory) and the operation is MOUNT-SAFE: it is a single-file move (copy then remove the original), never a directory rename or wipe. Keva takes a safety snapshot FIRST (and aborts, untouched, if the snapshot fails), enables maintenance mode, moves the file into the hardened quarantine folder (recording the move so it can be restored), disables maintenance mode, verifies the site (home returns 200 + database reachable), and AUTOMATICALLY REVERSES the move (puts the file back) if verification fails. This is a high-risk operation that requires human approval and is never run automatically. Required: target. Requires Keva Bridge. | Security Write | high | Failure compensation |
repair_core_filesRepair Core Files | DESTRUCTIVE: REPAIR tampered or maliciously-added WordPress core files by reinstalling the CURRENT core version's clean official files — the programmatic form of "drag a clean /wp-admin and /wp-includes over FTP". It overwrites modified core files AND removes core-tree files the official set does not include (the "a hack introduced new files" case). It does NOT change the WordPress version (it reinstalls the SAME version's clean package), and it is the correct recovery for a core checksum mismatch (see scan_core_integrity) — Keva prefers reinstalling clean files over surgically editing injected code. It is MOUNT-SAFE: it uses WordPress's own in-process core upgrader, which only touches wp-admin, wp-includes, and root files and NEVER wp-content — so plugins and the Keva plugin itself are never disturbed. Keva takes a safety snapshot FIRST — the database is the rollback point because the reinstall runs the database upgrade step — (and aborts, untouched, if the snapshot fails), enables maintenance mode, reinstalls the clean core files, runs the database upgrade step, disables maintenance mode, verifies the site (home returns 200 + database reachable), and on verification failure ROLLS BACK the database to the pre-repair snapshot. IMPORTANT: core FILE rollback is not automatic — if a repair fails verification, the database is restored but the core files may need a manual core reinstall. This is a high-risk operation that requires human approval and is never run automatically. Optional: safety_note. Requires Keva Bridge. | Security Write | high | None |
remove_rogue_userRemove Rogue User | DESTRUCTIVE: remove an attacker-created administrator account — the textbook persistence an intruder leaves behind (e.g. a rogue admin like "officialwp"). Choose the user by numeric ID or by login. Their authored content is reassigned to an admin you name (reassign_to, by ID or login) so nothing is orphaned; if you omit reassign_to their content is deleted with them (passing one is recommended). Keva REFUSES to remove a user that does not exist, the ONLY administrator (that would lock the site out), or the requesting/owner account — so a mistaken call cannot brick admin access. It takes a safety snapshot FIRST (the database is the rollback point) and aborts, untouched, if the snapshot fails; then deletes the user (via WordPress's own wp_delete_user with content reassignment), and verifies the site (home returns 200 + database reachable). A user deletion cannot be auto-reversed in place, so on a verification failure Keva surfaces the pre-removal snapshot as the rollback point. Evicting a rogue admin is ONE step — also reset the remaining admins' passwords and (after any backdoor is removed) rotate the salts. This is a high-risk operation that requires human approval and is never run automatically. Required: user. Optional: reassign_to, safety_note. Requires Keva Bridge. | Security Write | high | None |
rotate_saltsRotate Salts (Force-Logout) | DESTRUCTIVE: regenerate the eight WordPress authentication keys and salts (AUTH_KEY … NONCE_SALT) in wp-config.php with fresh values, which invalidates every session and LOGS EVERY USER OUT. IMPORTANT — BE HONEST about what this does: rotating salts is a blunt FORCE-LOGOUT, NOT a malware fix on its own. It does NOT remove a backdoor and does NOT change or secure passwords (salts are not used for password hashing); it only evicts someone relying on a stolen, still-live session cookie — and that is moot the instant they re-authenticate. It is meaningful only as ONE step AFTER the backdoor is removed and admin passwords are reset. Keva edits wp-config.php CAREFULLY: it works on an in-memory copy, regenerates the values (from the official api.wordpress.org secret-key service, falling back to strong local randomness with no network), validates that the rewritten file still parses before writing it, and keeps a copy of the original wp-config.php so the change is recoverable. If wp-config.php is not writable it changes nothing and tells you so. It takes a safety snapshot FIRST (and aborts, untouched, if the snapshot fails), rewrites the salts, and verifies the site (home returns 200 + database reachable), restoring the original wp-config.php if verification fails. This is a high-risk operation that requires human approval and is never run automatically. Optional: safety_note. Requires Keva Bridge. | Security Write | high | None |
apply_hardeningApply Hardening | Apply (or revert) ONE named, reversible WordPress hardening toggle, recording it so the change can be undone. Only [safe] toggles are applied automatically: disallow_file_edit (disable the in-dashboard theme/plugin code editor via DISALLOW_FILE_EDIT — a top post-compromise injection path), debug_display_off (stop leaking PHP errors/paths to visitors via WP_DEBUG_DISPLAY), and uploads_php_execution_blocked (an additive uploads/.htaccess rule that blocks PHP execution under uploads/ — the #1 backdoor execution path). [needs care] toggles (e.g. disable_xmlrpc, disallow_file_mods) are NOT applied automatically — Keva returns advise-only and surfaces the tradeoff for a human, because they can break legitimate integrations or updates. Pick the toggle by its stable id (the same id audit_security_posture reports), and set action to "apply" (default) or "revert". wp-config.php toggles use the same careful editor as salt rotation (work on a copy, validate it still parses, keep a copy of the original, then write); the uploads toggle writes/removes a clearly-fenced Keva block. Keva takes a safety snapshot FIRST (and aborts, untouched, if it fails), makes the change, records the prior state so a revert restores it exactly, verifies the site (home returns 200 + database reachable), and AUTOMATICALLY REVERTS the toggle if verification fails. This is a high-risk operation that requires human approval and is never run automatically. Required: toggle. Optional: action, safety_note. Requires Keva Bridge. | Security Write | high | Provider revert |
| Seo Read (4) |
audit_indexabilityAudit Indexability | Diagnose WHY a site is invisible to search. Reads whether search is discouraged (the "blog_public" option — the Settings → Reading → "Discourage search engines from indexing this site" checkbox; left on after launch it is the #1 reason a site disappears from Google) and, when it is on, explains the THREE things it does: it injects a noindex robots meta tag, it disables the core XML sitemap (so wp-sitemap.xml returns 404), and it flips the robots.txt public flag — so the brain can explain the full effect, not just "robots.txt". It detects which SEO plugin is authoritative (Yoast / Rank Math / All in One SEO / SEOPress, or core-only, flagging a conflict if two are active) and reads that plugin's site-wide / per-post-type noindex configuration. It then makes a single bounded loopback request to the home page to read the ACTUAL served truth — the robots meta tag, the X-Robots-Tag response header (a noindex delivered as a header is invisible in the HTML, easy to miss), and the canonical link (flagging a canonical that points at a different/staging host — the classic "staging canonical leaked to production after a migration" bug that can deindex the real page). It also scans any physical .htaccess for a rule blocking Googlebot (advisory — un-blocking that is host-level, not done here). IMPORTANT honesty: robots.txt blocks CRAWLING, not INDEXING (a different layer from noindex), and whether Google ACTUALLY indexed a URL — plus the index-coverage report, manual actions, and penalties — is only in Google Search Console (there is no bulk-coverage export). Keva tells you the technical WHY; the index truth is a GSC connection away. CRUCIALLY, because that in-WP loopback is blocked on many managed hosts (Kinsta, WP Engine) and times out, Keva ALSO fetches the homepage EXTERNALLY from its own backend (a different network path, exactly what Googlebot sees) and returns it as the authoritative "served" block — the REAL HTTP status + robots meta + X-Robots-Tag even when the loopback fails; prefer "served" over the loopback "front_end", and NEVER infer a 503/maintenance root cause from a plugin merely being active when "served.status" shows the real code. It also returns "noindex_sources" — every independent noindex source enumerated (core blog_public, the SEO-plugin config, an X-Robots-Tag header from the host/CDN/server/plugin layer, a theme/meta noindex) so a partial fix escalates the rest without blaming an SEO plugin unless another read proves the plugin emitted the header. It degrades gracefully if both the loopback and the external fetch cannot run. Read-only. Requires Keva Bridge. | Seo Read | low | Unmodeled |
check_sitemapCheck Sitemap | Check whether the site's XML sitemap is present, authoritative, and resolving. It determines which sitemap is authoritative — the core wp-sitemap.xml (which WordPress has shipped since 5.5, so "you need a plugin for a sitemap" is FALSE) or the one the detected SEO plugin serves (e.g. Yoast's sitemap_index.xml) — reports whether the core sitemap is enabled (it is disabled when search is discouraged via blog_public, or by the wp_sitemaps_enabled filter), and makes a bounded loopback fetch of the authoritative sitemap URL to check it is reachable (not a 404), well-formed XML, and non-empty. A missing sitemap is usually SUPPRESSION (blog_public=0, the filter, or a plugin took over and errors), not a missing feature — and when the core sitemap is authoritative and blog_public is 0, the real fix is re-enabling search visibility (a later action), not regenerating. Submitting the sitemap to Google Search Console is advisory (needs the GSC connection). Read-only. Requires Keva Bridge. | Seo Read | low | Unmodeled |
audit_seo_metaAudit SEO Meta | Audit the on-page SEO meta of the home page via a single bounded loopback fetch, parsed in-process: the page title and meta description (presence and length — a signal, not a content rewrite), Open Graph tags (og:title / og:image / …) and Twitter Card tags (twitter:card) for social and SERP presentation, and any JSON-LD structured-data blocks parsed for WELL-FORMEDNESS and @type presence. IMPORTANT honesty: Keva validates JSON-LD well-formedness in-process ONLY — whether your schema actually earns Google rich results is the EXTERNAL Rich Results Test (advisory); never read "your schema earns rich results" from this. (FAQ rich results were dropped by Google in May 2026, so keep schema advice current; and the meta-keywords tag has been ignored by Google since 2009 — not a positive signal.) It degrades gracefully (returns available:false with a reason) when the loopback cannot fetch the front end, rather than hanging or erroring. Read-only. Requires Keva Bridge. | Seo Read | low | Unmodeled |
check_redirectsCheck Redirects & Broken Links | Read the site's redirect picture and flag chains and loops. It reads the authoritative SEO plugin's redirect table where one exists (Rank Math, All in One SEO) read-only, plus any redirect rules in a physical .htaccess, then follows a small bounded sample of redirect sources via loopback to flag chains (more than 2 hops — Googlebot gives up at 10) and loops (a URL that redirects back to itself — ERR_TOO_MANY_REDIRECTS), which waste crawl budget and can block indexing. IMPORTANT honesty: this is a BOUNDED SAMPLE, not a whole-site crawl — a full broken-link / redirect / canonical crawl at scale needs an external crawler (Screaming Frog-class), which Keva advises rather than runs. Nothing here writes; clearing a bad redirect is the plugin's own UI or a later action. Read-only. Requires Keva Bridge. | Seo Read | low | Unmodeled |
| Seo Write (4) |
set_search_visibilitySet Search Visibility | Re-enable search engine visibility — the flagship fix for the #1 "my site disappeared from Google" emergency. It flips the "blog_public" option to 1 (the inverse of the Settings → Reading → "Discourage search engines from indexing this site" checkbox), which instantly does three things: it drops the noindex robots meta tag, it re-enables the core XML sitemap (so wp-sitemap.xml resolves again), and it flips the robots.txt public flag back. IMPORTANT — this action is GATED on prod-vs-staging reasoning: re-enabling search on a deliberately-private staging or development site would be HARMFUL (it would expose a not-for-public site to Google), so when the site shows staging signals (a local/development/staging WP_ENVIRONMENT_TYPE, or a localhost / .test / .local / staging-subdomain host) the action REFUSES unless you pass confirm_public:true to confirm this is a live site that SHOULD be indexed. The Bridge takes a safety snapshot FIRST, flips the flag, and verifies the site is HEALTHY; it AUTO-REVERSES to the prior value ONLY if the site then fails health verification (so a broken site is never left behind). If the site stays healthy but ANOTHER source is still forcing a noindex (an X-Robots-Tag response header from the host/CDN/server/plugin layer, or the theme), the flip is KEPT — blog_public=1 is the correct state — and the result carries residual_noindex:true plus residual_sources and served_after (an authoritative external re-read of the served page) so you can ESCALATE the remaining source rather than silently rolling back a correct change. Do not claim the SEO plugin caused or cleared a header noindex unless an independent read proves that plugin emitted the header. Note: this fixes the technical visibility flag — whether Google re-indexes the site is Google Search Console territory (submit/re-validate the sitemap there to confirm). Reversible. HIGH risk: requires human approval. Requires Keva Bridge. | Seo Write | high | Unmodeled |
regenerate_sitemapRegenerate Sitemap | Rebuild the authoritative XML sitemap so it resolves. For the core wp-sitemap.xml (shipped since WordPress 5.5) it flushes rewrite rules and clears sitemap caches; for a plugin-owned sitemap (Yoast, etc.) it triggers the plugin's own regeneration where one is cleanly available, otherwise it returns an honest advisory to regenerate from the plugin's UI. When search is discouraged (blog_public = 0), the core sitemap is disabled, so the action returns a no-op pointing to set_search_visibility. The Bridge takes a safety snapshot first, but a rewrite flush has no bounded automatic restore: the snapshot is retained only for supervised recovery assessment. Verification requires a fresh external sitemap read, not the Bridge loopback alone. Not reversible. Medium risk: requires human approval. Requires Keva Bridge. | Seo Write | medium | None |
fix_robots_txtFix robots.txt | Repair a broken PHYSICAL robots.txt that is blocking search crawlers — the classic "Disallow: /" rule that blocks ALL crawling (or a rule that disallows the sitemap). This is PHYSICAL-FILE-ONLY: WordPress serves a VIRTUAL robots.txt only when no physical file exists on disk, and a physical file OVERRIDES it, so this action only touches an actual ABSPATH/robots.txt file. If there is NO physical robots.txt (WordPress is serving its virtual one), there is nothing to fix and the action returns an honest "no physical file" result WITHOUT creating one — a virtual/crawl-blocking problem is then blog_public=0 (use set_search_visibility), an SEO plugin's robots filter, or a server/CDN rule, handled elsewhere. If a physical file exists but is already benign, it changes nothing. The fix goes through a dedicated careful keep-old TEXT editor: it takes a safety snapshot FIRST, keeps a timestamped copy of the original in a Keva-owned (web-unreadable) directory under wp-content, writes the repaired contents (the original minus the global Disallow / sitemap-disallow, preserving everything else and keeping the sitemap reference), re-reads the served /robots.txt via a loopback to confirm the global Disallow is gone, and AUTO-REVERTS to the kept-old original if the site fails to verify. It NEVER does a blind overwrite, NEVER renames or deletes a directory, and NEVER touches the Keva plugin. IMPORTANT: robots.txt controls CRAWLING, not INDEXING — re-allowing crawling lets Google re-fetch the site, but whether it re-indexes is Google Search Console territory. Reversible. HIGH risk: requires human approval. Requires Keva Bridge. | Seo Write | high | Unmodeled |
set_post_noindexClear Bad noindex | Clear (or set) the noindex robots directive on a SINGLE NAMED post via the authoritative SEO plugin's own post-meta API — the fix for "this specific page should be indexable but a stray noindex is hiding it." This is NAMED-TARGET-ONLY: it acts on exactly one post_id and there is NO "remove all noindex" sweep, because some noindex is CORRECT (thank-you pages, thin archives, internal search results). The default intent is to CLEAR a wrongly-noindexed post so it can be indexed (noindex:false); pass noindex:true to set noindex on a named post instead. The plugin's real meta key is resolved from detection (Yoast's _yoast_wpseo_meta-robots-noindex, etc.); when no SEO plugin is active, or the authoritative plugin stores per-post robots in a shape Keva can't write cleanly in-process (Rank Math's serialized array, AIOSEO's database table, version-variant or Pro-gated shapes), the action DEGRADES to an honest advisory (changing nothing) rather than guess a wrong key — set it from the plugin's own editor sidebar in that case. The Bridge takes a safety snapshot FIRST (the rollback), captures the prior meta value, writes the new value, verifies the site AND re-reads the meta to confirm, and restores the snapshot if verification fails so the prior meta returns. Reversible via the snapshot. Whether Google re-crawls and updates its index is Google Search Console territory (URL Inspection is per-URL). HIGH risk: requires human approval. Requires Keva Bridge. | Seo Write | high | Unmodeled |
| Settings Change (2) |
update_brand_colorUpdate Brand Color | Change one named color in the active theme's palette by slug (e.g. "primary", "secondary", "accent"). Affects every block, button, link, and element that references this slug via var(--wp--preset--color--<slug>). Call get_brand_colors first to discover valid slugs. Returns { slug, previous, current } for audit/rollback. Block themes only — classic themes return unsupported_classic_theme (planned for PR-10.1 with Bridge). | Settings Change | medium | Unmodeled |
update_font_familyUpdate Font Family | Change the font family used by a given element slot (body, heading, h1..h6, button, caption, link). The new font_family_slug must already exist in the theme's declared fontFamilies (use list_font_families to discover them). The slot is updated to var(--wp--preset--font-family--<slug>). Block themes only — classic themes return unsupported_classic_theme. | Settings Change | medium | Unmodeled |
| Update Read (3) |
list_available_updatesList Available Updates | List the core, plugin, and theme updates currently pending on the site, read from WordPress's update transients (refreshed first so a stale cache doesn't hide an offer). Each plugin/theme entry includes its type, slug, name, current version, and the available new version; the core entry (if any) reports the current and offered WordPress version. A site with nothing pending returns empty lists (a normal, healthy state — not an error). Read-only — answers "what can be updated?" before any update is applied. Requires Keva Bridge. | Update Read | low | Unmodeled |
check_update_compatibilityCheck Update Compatibility | For each pending update, judge whether it is COMPATIBLE with this site before applying it. Compares the target version's "Requires PHP" and "Requires at least" (WordPress version) against the live PHP and WordPress — Keva checks the WordPress-version requirement itself, because WordPress core does NOT check it for plugin/theme auto-updates. A hard incompatibility (PHP or WP below the requirement, or an unmet plugin dependency) is returned as compatible:false with a structured reason; a stale "Tested up to" is a soft warning (abandoned/stale-plugin risk), never a block. Also reports plugin dependency impact (Requires Plugins, WP 6.5+). Read-only. Optional: slug (a plugin "folder/file.php", a theme stylesheet, or "core") to check just one update. Requires Keva Bridge. | Update Read | low | Unmodeled |
get_update_historyGet Update History | Best-effort "what changed recently" plus the site's auto-update configuration. WordPress keeps no authoritative per-extension update log, so this reports each extension main-file modification time (mtime) as a proxy for "last changed" — a redeploy or manual edit also bumps mtime, so it is a correlation signal, not proof — newest-first. It also reports which plugins/themes have per-item auto-updates enabled (the auto_update_plugins / auto_update_themes options) and the WP_AUTO_UPDATE_CORE / AUTOMATIC_UPDATER_DISABLED constants — the "auto-update surprise" surface. Read-only. Requires Keva Bridge. | Update Read | low | Unmodeled |
| Update Write (6) |
update_pluginUpdate Plugin (Safe) | DESTRUCTIVE: safely update ONE installed plugin to its pending version behind a snapshot → update → verify → automatic-rollback loop. Choose the plugin by its installed plugin FILE ("folder/file.php" — the activation identifier, NOT a wordpress.org slug). First Keva checks the update is compatible (the target's "Requires PHP" / "Requires at least" vs the live PHP+WordPress, and plugin dependencies) and REFUSES a hard-incompatible update before changing anything; then it takes a safety snapshot FIRST (and aborts, untouched, if the snapshot fails), enables maintenance mode, applies the update, disables maintenance mode, verifies the site (home returns 200 + database reachable), and AUTOMATICALLY ROLLS BACK the plugin files to the prior version if verification fails. It is MOUNT-SAFE: the Bridge does NOT use WordPress's default upgrader path (which deletes the live plugin directory before unpacking); instead it downloads and unpacks the new version to a scratch directory, then copy-merges the new files over the live plugin directory in place (keeping a copy aside for rollback, never deleting or renaming the live directory, and never touching the Keva plugin's own directory), pruning files the new version removed. It NEVER triggers the plugin's uninstall routine. This is a high-risk, irreversible-in-place operation that requires human approval and is never run automatically. Required: plugin. Optional: package_url (override the package source), safety_note. Requires Keva Bridge. | Update Write | high | Failure compensation |
update_themeUpdate Theme (Safe) | DESTRUCTIVE: safely update ONE installed theme to its pending version behind a snapshot → update → verify → automatic-rollback loop. Choose the theme by its installed stylesheet (directory slug, e.g. "twentytwentyfour"). First Keva checks the update is compatible (the target's "Requires PHP" / "Requires at least" vs the live PHP+WordPress) and REFUSES a hard-incompatible update before changing anything; then it takes a safety snapshot FIRST (and aborts, untouched, if the snapshot fails), enables maintenance mode, applies the update, disables maintenance mode, verifies the site (home returns 200 + database reachable), and AUTOMATICALLY ROLLS BACK the theme files to the prior version if verification fails. It is MOUNT-SAFE: the Bridge does NOT use WordPress's default upgrader path (which deletes the live theme directory before unpacking); instead it downloads and unpacks the new version to a scratch directory, then copy-merges the new files over the live theme directory in place (keeping a copy aside for rollback, never deleting or renaming the live directory), pruning files the new version removed. This is a high-risk, irreversible-in-place operation that requires human approval and is never run automatically. Required: theme. Optional: package_url (override the package source), safety_note. Requires Keva Bridge. | Update Write | high | Failure compensation |
update_coreUpdate Core (Safe) | DESTRUCTIVE: safely update WordPress CORE to its pending version behind a snapshot → update → database-upgrade → verify → automatic-rollback loop. Keva reads the pending core offer and REFUSES if core is already up to date or if the offered version requires a newer PHP or MySQL than this server has; then it takes a safety snapshot FIRST — the database is the rollback point because core updates run database migrations — (and aborts, untouched, if the snapshot fails), enables maintenance mode, applies the core update IN-PROCESS using WordPress's own core upgrader, runs the database upgrade step (equivalent to "wp core update-db", so the site is not left needing a database update), disables maintenance mode, verifies the site (home returns 200 + database reachable), and on verification failure ROLLS BACK by re-importing the pre-update database snapshot so the schema matches the prior version. It is MOUNT-SAFE: a core update only touches wp-admin, wp-includes, and root files — never wp-content — so plugins and the Keva plugin itself are never disturbed. A MINOR/patch update is preferred; if only a MAJOR version jump is offered it is applied but flagged as higher risk (pass allow_major=false to refuse a major jump). IMPORTANT: core FILE rollback is not automatic — if a core update fails verification, the database is restored but the core files may need a manual core reinstall. This is a high-risk operation that requires human approval and is never run automatically. Optional: allow_major (default true), safety_note. Requires Keva Bridge. | Update Write | high | None |
rollback_plugin_to_versionRoll Back Plugin To Version | DESTRUCTIVE: roll ONE installed plugin BACK to a prior version (a code downgrade) behind a snapshot → apply → verify → automatic-rollback loop. Choose the plugin by its installed plugin FILE ("folder/file.php" — the activation identifier, NOT a wordpress.org slug) and the target older version (to_version). The prior version's files come from one of two sources: "wporg" (default) re-pins from the wordpress.org specific-version archive (downloads.wordpress.org/plugin/<slug>.<to_version>.zip), and "backup" restores the plugin directory from a Keva-native backup's files (for premium/custom plugins with no wordpress.org version — pass source_id). Keva takes a safety snapshot FIRST (and aborts, untouched, if the snapshot fails), enables maintenance mode, deactivates the plugin if it is active (so an autoloader does not fatal mid-swap), applies the prior version, reactivates it, disables maintenance mode, verifies the site (home returns 200 + database reachable), and AUTOMATICALLY ROLLS BACK the plugin files to the pre-rollback version if verification fails. It is MOUNT-SAFE: the Bridge does NOT use WordPress's default upgrader path (which deletes the live plugin directory); instead it copy-merges the prior version's files over the live plugin directory in place (keeping a copy aside for rollback, never deleting or renaming the live directory, and never touching the Keva plugin's own directory), pruning files the prior version did not have. It NEVER triggers the plugin's uninstall routine (so database tables, options, and user data are preserved). IMPORTANT: rolling back code does NOT revert a database migration the newer version already ran — the result includes a db_caveat, and a database restore (restore_database_only) may also be needed. This is a high-risk, irreversible-in-place operation that requires human approval and is never run automatically. Required: plugin, to_version. Optional: source ("wporg" or "backup"), source_id (for "backup"), package_url (override the wporg package source), safety_note. Requires Keva Bridge. | Update Write | high | Failure compensation |
isolate_plugin_conflictIsolate Plugin Conflict | Find WHICH active plugin is causing a conflict or fatal error, WITHOUT disturbing real visitors. Keva installs a temporary visitor-safe helper (a must-use plugin dropin) that disables a subset of plugins ONLY for Keva's own internal test requests — every real visitor, bot, and cron job keeps seeing the site with ALL plugins active and the database is never changed — then bisects (binary search): it loads the site internally with successive halves of the active plugins and narrows down to the plugin whose presence reproduces the fatal. It is dependency-aware (a plugin's required plugins are kept together) and is inherently safe because it only filters an option for marked test requests and never moves, renames, or deletes any plugin files. The temporary helper and its token are ALWAYS removed when isolation finishes (even on error). If a single plugin reproduces the fatal on its own it is reported as the culprit; if two plugins only conflict together, the minimal reproducing set is reported. If the full active set does not reproduce a server-side fatal (for example a front-end/JavaScript-only break that throws no PHP error), the result is inconclusive and should be handed to the investigate flow. This changes nothing permanently and is reversible. Optional: candidates (limit which plugins to test), probe_path (which page to test), max_probes. Requires Keva Bridge. | Update Write | medium | Unmodeled |
set_plugin_auto_updateSet Plugin Auto-Update | Enable or disable automatic updates for ONE installed plugin (its per-item flag in the auto_update_plugins option, WordPress 5.5+) — the lever for the "auto-update surprise" failure mode, where an unattended overnight auto-update breaks a site. This is a pure setting change (no files touched) and is idempotent. Restoring the prior setting requires a separately approved inverse action; it is not automatic rollback. Required: plugin (the installed plugin file "folder/file.php") and enabled (true to allow auto-updates, false to hold the plugin). Requires Keva Bridge. | Update Write | medium | None |
| User Management (4) |
list_usersList Users | List WordPress users. | User Management | low | Unmodeled |
wc_list_customersList Customers | List WooCommerce customers. | User Management | low | Unmodeled |
wc_get_customerGet Customer | Get details of a specific WooCommerce customer. | User Management | low | Unmodeled |
get_userGet User | Get details of a specific WordPress user including name, email, and roles. | User Management | low | Unmodeled |
| User Write (1) |
update_userUpdate User | Update a WordPress user (name, email, role). | User Write | high | Unmodeled |
| Woocommerce Read (7) |
detect_woocommerceDetect WooCommerce | Determine whether a WordPress site runs a WooCommerce store, what version, and — critically — what OPERATIONAL MODE its orders are stored in, plus whether the store machinery is healthy. Unlike the multilingual or ACF detections (which solve a which-of-several-plugins or a shared-class-fork problem), WooCommerce is a SINGLE unambiguous plugin, so this detects presence (by class_exists('WooCommerce') plus the active-plugin main file woocommerce/woocommerce.php, slug-robust to the wp-env ".latest-stable" suffix), the version, and the HIGH-PERFORMANCE ORDER STORAGE (HPOS) mode: hpos (orders live in the dedicated wp_wc_* tables — the default for new installs since WooCommerce 8.2), legacy_posts (orders live in wp_posts/wp_postmeta), or compatibility (Woo keeps both table sets in sync). It separately reports whether compatibility synchronization is enabled: WooCommerce deliberately returns is_custom_order_tables_in_sync=false when synchronization is disabled, which is expected configuration rather than a desync. A real repairable incident requires compatibility mode plus a non-zero pending count. Every order read remains HPOS-aware (a raw post_type=shop_order query misses HPOS orders entirely). It also surfaces an Action-Scheduler health summary (counts by status, the past-due count, the purgeable count, and the table sizes — the flagship store-health signal at a glance) and a `writable` gate that signals a SAFE NON-MONEY operational-hygiene write path exists in this build; the gate NEVER implies a money/order write exists, because WooCommerce is MONEY and Keva NEVER touches an order, a payment, a price, a refund, customer PII, or a stock VALUE. Every WooCommerce read keys off this detection, and it degrades gracefully to present:false (never a fatal) when WooCommerce is not active. Read-only. Requires Keva Bridge. | Woocommerce Read | low | Unmodeled |
audit_action_schedulerAudit Action Scheduler | Diagnose WHY a WooCommerce store's order emails, stock sync, or webhooks silently stopped — the flagship WooCommerce read. WooCommerce runs nearly all its asynchronous work — order and transactional emails, stock synchronization, webhook delivery, subscription renewals, and the HPOS backfill — through Action Scheduler, a job-queue library BUNDLED inside WooCommerce (it is NOT a separate plugin you install). A backlog of pending or past-due actions, a high failed count, or a bloated wp_actionscheduler_actions/_logs table is the classic invisible WooCommerce outage: "order emails aren't sending," "stock didn't update," and "webhooks didn't fire" are frequently ONE root cause — the queue stalled or grew unbounded because WooCommerce's own cleanup batch (about 20 rows per pass) cannot keep up on a busy store. This read reports the queue health: counts by status (pending, in-progress, complete, failed, canceled), the PAST-DUE count (pending actions whose scheduled time has already passed — the "the queue isn't keeping up" signal), the oldest pending action's age, the top failing hooks (so you can see WHICH async task is failing), the actionscheduler table sizes, and the PURGEABLE count (complete/failed/canceled actions older than the retention threshold). The counts come from Action Scheduler's own store API; table sizes and the past-due/purgeable counts come from a bounded database read. The #1 SAFE fix (a later gated action) is to PURGE the old terminal actions — the exact operation WooCommerce's own scheduled cleanup performs, snapshot-first — which touches terminal queue HISTORY only and NEVER a pending action and NEVER an order; re-running a failed action is advisory (a failed payment-webhook or renewal re-run can have money side-effects). Read-only and bounded. Degrades gracefully when WooCommerce is not active. Requires Keva Bridge. | Woocommerce Read | low | Unmodeled |
audit_order_healthAudit Order Health | Read the health of a WooCommerce store's orders to surface stuck or failed orders — without ever touching an order. It counts orders by non-terminal status (pending, on-hold, processing) and flags those not progressing beyond a threshold (by default seven days), and it counts failed orders (a payment decline — its own signal). CRITICAL: it reads orders via WooCommerce's own wc_get_orders() / WC_Order_Query, which is HPOS-aware, and NEVER via a raw post_type=shop_order query — under High-Performance Order Storage the orders are not in wp_posts, so a raw post query misses them entirely (a real bug this read is built to avoid). For a bounded sample of stuck orders it reads the order NOTES, which carry the gateway/IPN/webhook message (for example "IPN not received" or "webhook timeout"), to surface the likely cause. IMPORTANT honesty: this is READ-ONLY and the remediation is ADVISORY — the cause of a stuck order is almost always a payment-gateway, IPN, or webhook communications failure, and the fix is a gateway or webhook fix or a merchant decision. Keva NEVER changes an order's status (an order-status transition moves money through the gateway, triggers transactional emails, decrements stock, and fires webhooks — the store's single most consequential write), and the operational brain NEVER reaches for the separate commerce-CRUD order-status path when diagnosing a store. WooCommerce is MONEY; Keva diagnoses and advises, and never moves money. Counts plus a bounded sample. Degrades gracefully when WooCommerce is not active. Read-only. Requires Keva Bridge. | Woocommerce Read | low | Unmodeled |
audit_stockAudit Stock Display | Scan for stock DISPLAY drift in a WooCommerce store — products whose cached/indexed display state disagrees with their authoritative stock data, the "shows out of stock while quantity is greater than zero" or "wrong stock status" symptom. This is a bounded, READ-ONLY database scan that compares each product's stock_status in WooCommerce's product-lookup index (wp_wc_product_meta_lookup) against the authoritative _stock_status, and reports the drift count plus a bounded sample. IMPORTANT honesty: the safe fix (a later gated action) is to RECOUNT / REGENERATE the display by re-running WooCommerce's own "Regenerate product lookup tables" and "Recount terms" System Tools, snapshot-first — this recomputes the cached/indexed view ONLY so it matches the authoritative product data; it NEVER sets an actual stock VALUE. Setting a stock quantity is a money and fulfillment decision and is ADVISORY — Keva never changes inventory levels. A stock DISPLAY recount is not the same as setting a stock value, and this distinction is the guardrail. Counts plus a bounded sample. Degrades gracefully when WooCommerce is not active (and reports cleanly when the product-lookup index table does not exist). Read-only. Requires Keva Bridge. | Woocommerce Read | low | Unmodeled |
audit_store_configAudit Store Config | Read a WooCommerce store's operational configuration to flag obvious misconfigurations — without ever changing it. It reports the registered payment gateways and which are enabled (via WooCommerce's own payment_gateways() API), the store currency, the base location, and whether tax and shipping are configured, and it flags when NO payment gateway is enabled (checkout definitely cannot complete). Note: no_gateway_enabled=false only means a gateway is toggled ON — it does NOT prove checkout works, because an enabled gateway can still be unavailable at checkout (currency/country/amount); the advisory available_gateway_ids / enabled_but_unavailable fields help spot that (both computed outside a cart context, so treat as advisory). IMPORTANT honesty: EVERYTHING here is ADVISORY and READ-ONLY — Keva NEVER toggles a payment gateway (a money and checkout decision), NEVER changes the currency, base location, tax, or shipping (merchant decisions), and NEVER switches the HPOS authoritative storage mode (a data-migration decision — Keva can trigger the posts⇄HPOS sync, but switching which table is authoritative is advised). WooCommerce is MONEY; Keva reports the config and any suspected misconfiguration and leaves every change to the merchant. Degrades gracefully when WooCommerce is not active. Read-only. Requires Keva Bridge. | Woocommerce Read | low | Unmodeled |
check_store_emailsCheck Store Emails | Read whether a WooCommerce store's order and transactional email notifications are enabled and whether email delivery is likely working — without sending anything or changing any setting. It reports which WooCommerce email notifications (new order, processing, completed, customer invoice, and so on) are enabled, flags any that are disabled (a real "customers don't receive the email" cause), and probes whether wp_mail() is available (a function-level check, not a send). CRITICAL correlation: WooCommerce dispatches its transactional emails through Action Scheduler, so if the queue is stalled the email actions never run — this read surfaces a hint that ties a "no emails" symptom back to the flagship Audit Action Scheduler read, whose safe fix is the purge of old terminal queue actions. IMPORTANT honesty: enabling a disabled notification is advisory (a merchant setting), and host-level deliverability problems (wp_mail blocked by the host, SMTP setup, the From address, SPF/DKIM) are ADVISORY (host and configuration). Keva reads and advises; it sends no email and changes no setting. Degrades gracefully when WooCommerce is not active. Read-only. Requires Keva Bridge. | Woocommerce Read | low | Unmodeled |
check_webhooksCheck Webhooks | Read a WooCommerce store's webhook delivery health to surface integrations that have stopped receiving events — without re-enabling anything. It reports each registered webhook's status, topic, delivery URL, and consecutive failure count, and flags any AUTO-DISABLED webhook: a WooCommerce webhook automatically disables after five consecutive delivery failures (non-2xx responses) and will NOT fire again even after the underlying issue is fixed, so an auto-disabled webhook is a real "my integration stopped receiving events" cause. IMPORTANT honesty: the remediation is ADVISORY and re-enabling a webhook is NOT a Keva action — the failing ENDPOINT must be fixed first (re-enabling a webhook whose endpoint is still broken just re-arms the five-failure auto-disable), and a re-fired delivery may have downstream side-effects on the integration, so Keva reads the statuses and advises the manual re-enable (Settings, Advanced, Webhooks) plus the endpoint fix. Webhook delivery also runs through Action Scheduler, so a stalled queue is a separate cause surfaced by the flagship queue read. Degrades gracefully when WooCommerce is not active. Read-only. Requires Keva Bridge. | Woocommerce Read | low | Unmodeled |
| Woocommerce Write (4) |
clean_action_schedulerClean Action Scheduler | The flagship safe fix for the number-one invisible WooCommerce outage: a stuck or bloated Action Scheduler queue silently killing order emails, stock sync, and webhook delivery. WooCommerce runs nearly all its asynchronous work through Action Scheduler (a job-queue library bundled inside WooCommerce, not a separate plugin), and on a busy store its own cleanup batch (about 20 rows per pass) cannot keep up, so the wp_actionscheduler_actions/_logs tables grow unbounded and the queue stalls. This action PURGES only OLD TERMINAL actions — complete, failed, and canceled actions older than the retention threshold — the exact operation WooCommerce's own scheduled cleanup performs, via WooCommerce's own Action Scheduler API (never a raw SQL DELETE). It NEVER deletes a pending or in-progress action (that is live work), NEVER re-runs an action (a failed payment-webhook or renewal re-run can have money side-effects — that is advisory), and NEVER touches an order. The protocol is snapshot-first: a pre-change safety snapshot is taken FIRST (the action aborts if it fails), the old terminal actions are purged through WooCommerce's tool, the site is verified with a fresh re-read of the queue and table sizes, and on a failed verify the snapshot is restored (the snapshot is the only rollback — a purge has no row-by-row inverse). riskLevel high (data deletion, agent-layer human approval), reversible via the snapshot. Requires Keva Bridge. | Woocommerce Write | high | Snapshot restore |
clear_expired_wc_sessionsClear Expired WC Sessions | Clear expired customer-session/cart rows from the wp_woocommerce_sessions table — WooCommerce's own "Clear customer sessions" hygiene operation. WooCommerce stores each shopper's cart and session in this table with an expiry timestamp, and its own cleanup cron deletes only about a thousand expired rows every forty-eight hours, so on a busy store the table accumulates expired rows faster than WooCommerce can prune them. This action deletes ONLY rows whose session_expiry is in the past (expired sessions), bounded, via a scoped database delete. It NEVER deletes a live (non-expired) session (that would log a shopper out or drop their cart mid-checkout) and NEVER touches an order. The protocol is snapshot-first: a pre-change safety snapshot is taken FIRST (the action aborts if it fails), the expired session rows are deleted, the site is verified with a fresh re-scan, and on a failed verify the snapshot is restored (the snapshot is the rollback). riskLevel high (data deletion, agent-layer human approval), reversible via the snapshot. Degrades gracefully (changes nothing) when the sessions table is absent. Requires Keva Bridge. | Woocommerce Write | high | Snapshot restore |
recount_stockRecount Stock Display | True up a WooCommerce store's stock DISPLAY — the "shows out of stock while quantity is greater than zero" or "wrong stock status / wrong category counts" symptom — by re-running WooCommerce's own "Regenerate product lookup tables" and "Recount terms" System Tools. WooCommerce keeps a cached/indexed view of each product's price, stock status, and rating in wp_wc_product_meta_lookup, plus term (category) counts, and this index can drift out of sync with the authoritative product data after bulk edits, imports, or an interrupted background process. This action RECOMPUTES that cache/index so it matches the authoritative product data; it is an idempotent display operation. CRITICAL: it NEVER sets an actual stock VALUE — setting a stock quantity is a money and fulfillment decision and is ADVISORY; Keva never changes inventory levels, only the cached display of them. A stock DISPLAY recount is not the same as setting a stock value, and that distinction is the guardrail. The protocol is snapshot-first: a pre-change safety snapshot is taken FIRST (the action aborts if it fails), WooCommerce's lookup-regenerate and term-recount tools run, the site is verified with a fresh re-read showing the display matching the authoritative data, and on a failed verify the snapshot is restored. riskLevel high (agent-layer human approval), reversible via the snapshot. Requires Keva Bridge. | Woocommerce Write | high | Snapshot restore |
trigger_hpos_syncTrigger HPOS Sync | Re-run WooCommerce's own posts-to-HPOS order backfill/sync to resolve a real compatibility-mode backlog where orders appear missing or stale. WooCommerce must be actively maintaining both the legacy wp_posts and HPOS tables; its canonical in-sync method deliberately returns false when compatibility synchronization is disabled, which is expected configuration and not an incident. This action uses WooCommerce's OWN bounded batch processor, NEVER hand-writes order rows, and NEVER switches the authoritative storage mode. The protocol is snapshot-first when work is active: take a safety snapshot, run WooCommerce's synchronizer, then independently re-read the sync state and exact pending count. It succeeds only when the fresh read is clean. When compatibility synchronization is disabled, it returns an honest no-op before snapshot or mutation and verification must independently confirm the same mode and disabled sync setting. riskLevel high (agent-layer human approval), reversible via the snapshot when a write runs. Requires Keva Bridge. | Woocommerce Write | high | Snapshot restore |