Skip to content

Instantly share code, notes, and snippets.

@quietcactus
Created June 10, 2026 15:54
Show Gist options
  • Select an option

  • Save quietcactus/445839b309a2e1c8d62e7d7f506ccb24 to your computer and use it in GitHub Desktop.

Select an option

Save quietcactus/445839b309a2e1c8d62e7d7f506ccb24 to your computer and use it in GitHub Desktop.
theme-init
name theme-init
description Use when the user wants to set up or clean up a WordPress site over the WP Engine SSH gateway using WP CLI. Trigger on requests like "run theme setup", "clean up the WordPress install", "delete the old pages/menus/themes/categories", "deactivate plugins on the site", "update the site title", or any WP Engine SSH + WP CLI cleanup task. Gathers all selections, shows one plan, confirms once, then executes.
disable-model-invocation true
argument-hint [environment]
allowed-tools Bash, AskUserQuestion

Theme Init

Guided WordPress site cleanup over the WP Engine SSH gateway using WP CLI. Gathers every selection up front, shows ONE consolidated plan of all destructive commands, confirms ONCE, then runs the approved operations.

Connection

WP Engine exposes WP CLI through an SSH gateway. Use plain one-shot SSH per call:

SSH="ssh <env>@<env>.ssh.wpengine.net"

<env> is the WP Engine install name (same in username and hostname). SSH key auth must already be configured. If a command returns Permission denied (publickey), stop and tell the user their SSH key is not registered with WP Engine. Run each WP CLI command as a single quoted argument (one-shot, non-interactive). Do NOT open an interactive session.

Do NOT use SSH multiplexing (ControlMaster/ControlPath). It is unreliable on Windows OpenSSH (fails with getsockname failed: Not a socket) and the speed gain is negligible once the read pass is collapsed. The performance comes from Step 2's single read pass, not from connection reuse.

Step 1: Get the environment

  1. If $1 is provided, use it as the environment name; otherwise ask "Which WP Engine environment? (the install name)".

Step 2: Single read pass (verifies connection + populates the whole menu)

Run ONE command that fetches everything the menu needs. This also verifies the connection — if it fails, report the exact error and stop.

$SSH "
  echo '===PREFIX==='; wp config get table_prefix;
  echo '===BLOGNAME==='; wp option get blogname;
  echo '===POSTTYPES==='; wp post-type list --_builtin=0 --field=name | grep -v '^acf-';
  echo '===CATEGORIES==='; wp term list category --format=count;
  echo '===MENUS==='; wp menu list --fields=term_id,name;
  echo '===THEMES==='; wp theme list --fields=name,status;
  echo '===PLUGINS==='; wp plugin list --fields=name,status --status=active,inactive;
"

Split the output on the ===MARKER=== lines. Use <PREFIX> (the real table prefix) everywhere a table name appears — never hardcode wp_.

Step 3: Gather ALL selections (no execution yet)

Use AskUserQuestion to collect every choice before running anything. Max 4 options per question; split across questions if needed.

  1. Operations (multiSelect): Delete pages, Delete categories, Delete menus, Delete themes, Manage plugins, Update site title. Only ask the follow-ups for operations the user picks.
  2. Delete pages → always offer builtin post and page, plus each custom post type from ===POSTTYPES===, each as its own individual option (multiSelect, 4 per question, split as needed). ===POSTTYPES=== lists custom types only — it may be empty, in which case offer just post and page. Build a single-quoted comma list <TYPES> from the picks. Never group into "All".
  3. Delete menus → ask all menus or specific IDs (from ===MENUS===).
  4. Delete themes → present theme slugs (multiSelect) for which to KEEP; everything else is deleted. Active theme can't be deleted.
  5. Manage plugins → ask action (Delete / Deactivate / Both), then present plugin slugs (multiSelect) for each chosen action.
  6. Update site title → ask for the new title string.

Step 4: Show ONE consolidated plan + confirm once

Print the full set of exact commands that will run, with affected counts, then a single AskUserQuestion (yes/no). Example:

## Plan — <env>

1. Delete pages: types ('post','page') — 14 posts
   wp post delete $(wp db query "SELECT ID FROM <PREFIX>posts WHERE post_type IN ('post','page') AND post_status != 'auto-draft'" --skip-column-names) --force
2. Delete categories: 6 terms
   wp term delete category $(wp term list category --format=ids)
3. Delete menus: all (3)
   wp menu delete $(wp menu list --format=ids)
4. Delete themes: keep twentytwentyfour — deletes 2
   wp theme delete $(wp theme list --field=name | grep -v "twentytwentyfour")
5. Plugins: delete akismet hello; deactivate jetpack
6. Site title -> "New Title"

Run all of the above?

Any operation whose preview matched nothing is reported and dropped from the plan. If the user declines, stop — run nothing.

Step 5: Execute (discrete, multiplexed)

On approval, run each operation as its own command over the shared connection, in this order: pages, categories, menus, themes, plugins (delete then deactivate), title. Capture each result so success/failure is attributable per operation.

$SSH "wp post delete \$(wp db query \"SELECT ID FROM <PREFIX>posts WHERE post_type IN (<TYPES>) AND post_status != 'auto-draft'\" --skip-column-names) --force"
$SSH "wp term delete category \$(wp term list category --format=ids)"
$SSH "wp menu delete \$(wp menu list --format=ids)"     # or: wp menu delete 1 2 3
$SSH "wp theme delete \$(wp theme list --field=name | grep -v \"<keep-slug>\")"   # multiple: grep -v -e a -e b
$SSH "wp plugin delete slug-a slug-b"
$SSH "wp plugin deactivate slug-c slug-d"
$SSH "wp option update blogname \"<new title>\""

WP CLI errors (Error:) — quote them exactly and stop that operation; do not auto-retry.

Step 6: Summary

## Theme Init — <env>
- Pages: <N / skipped>      - Categories: <N / skipped>   - Menus: <N / skipped>
- Themes: <deleted N, kept X / skipped>
- Plugins: <deactivated / deleted / skipped>   - Site title: <set / skipped>

Notes

  • Destructive and irreversible. Recommend a WP Engine restore point before running.
  • \$(...) must be escaped so it runs remotely, not locally.
  • Active theme cannot be deleted; tell the user to activate a keeper first if needed.
  • Use <PREFIX> from the read pass, never a hardcoded table prefix.
  • Plugin list is filtered to --status=active,inactive; must-use plugins and dropins are intentionally excluded from the menu.
@quietcactus

Copy link
Copy Markdown
Author

What this is

theme-init is a Claude Code skill that automates fresh-WordPress-install cleanup over the WP Engine SSH gateway using WP CLI. It's the boilerplate teardown you do at the start of a new build — deleting default pages/posts (and any custom post types), categories, menus, and unused themes; deactivating or deleting plugins; and setting the site title.

Its design is "gather first, confirm once": it does a single read pass to verify the connection and populate every menu, collects all your selections up front, prints one consolidated plan of the exact destructive commands (with affected counts), and only runs anything after a single yes/no confirmation. Operations whose preview matches nothing are dropped automatically.

Prerequisites

  • WP Engine SSH gateway access. Connection is one-shot SSH per call: ssh <env>@<env>.ssh.wpengine.net, where <env> is your install name (it's the same in both the username and hostname).
  • SSH key auth already configured with WP Engine. If you get Permission denied (publickey), your key isn't registered on the install — fix that before running.
  • WP CLI, which is available by default through the WP Engine gateway, so there's nothing to install. Each command runs as a single quoted, non-interactive argument — no interactive shell.
  • The skill has disable-model-invocation: true, so it only runs when you call it explicitly (e.g. /theme-init <environment>), not on its own. It takes an optional environment argument; if omitted, it'll ask.

A few things to know before running

  • It's destructive and irreversible — take a WP Engine restore point first.
  • The active theme can't be deleted; activate a keeper theme first if you want the current one gone.
  • It uses the install's real table prefix from the read pass rather than assuming wp_.
  • Plugin selection is limited to active/inactive plugins; must-use plugins and dropins are intentionally excluded.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment