wordpress-agent-kit 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/.agents/skills/wp-bootstrap/SKILL.md +314 -0
  2. package/.agents/skills/wp-bootstrap/references/composer-setup.md +275 -0
  3. package/.agents/skills/wp-bootstrap/references/monorepo-patterns.md +184 -0
  4. package/.agents/skills/wp-bootstrap/scripts/bootstrap.sh +151 -0
  5. package/.agents/skills/wp-bootstrap/scripts/detect-structure.mjs +466 -0
  6. package/.agents/skills/wp-bootstrap/scripts/package-wp.sh +173 -0
  7. package/.agents/skills/wp-bootstrap/scripts/playground-start.sh +148 -0
  8. package/.agents/skills/wp-bootstrap/scripts/playground-verify.sh +165 -0
  9. package/.agents/skills/wp-bootstrap/scripts/setup-github.sh +417 -0
  10. package/.agents/skills/wp-wpengine/SKILL.md +76 -12
  11. package/.agents/skills/wp-wpengine/references/github-actions-deploy.md +16 -9
  12. package/README.md +1 -1
  13. package/dist/cli.js +2 -0
  14. package/dist/commands/bootstrap.js +105 -0
  15. package/dist/lib/api.js +1 -0
  16. package/dist/lib/bootstrap.js +352 -0
  17. package/extensions/wp-agent-kit/index.ts +143 -3
  18. package/package.json +1 -1
  19. package/skills-custom/wp-bootstrap/SKILL.md +314 -0
  20. package/skills-custom/wp-bootstrap/references/composer-setup.md +275 -0
  21. package/skills-custom/wp-bootstrap/references/monorepo-patterns.md +184 -0
  22. package/skills-custom/wp-bootstrap/scripts/bootstrap.sh +151 -0
  23. package/skills-custom/wp-bootstrap/scripts/detect-structure.mjs +466 -0
  24. package/skills-custom/wp-bootstrap/scripts/package-wp.sh +173 -0
  25. package/skills-custom/wp-bootstrap/scripts/playground-start.sh +148 -0
  26. package/skills-custom/wp-bootstrap/scripts/playground-verify.sh +165 -0
  27. package/skills-custom/wp-bootstrap/scripts/setup-github.sh +417 -0
  28. package/skills-custom/wp-wpengine/SKILL.md +76 -12
  29. package/skills-custom/wp-wpengine/references/github-actions-deploy.md +16 -9
  30. package/.github/skills/blueprint/SKILL.md +0 -418
  31. package/.github/skills/wordpress-router/SKILL.md +0 -52
  32. package/.github/skills/wordpress-router/references/decision-tree.md +0 -55
  33. package/.github/skills/wp-abilities-api/SKILL.md +0 -108
  34. package/.github/skills/wp-abilities-api/references/delegate-helper-pattern.md +0 -241
  35. package/.github/skills/wp-abilities-api/references/domain-vs-projection.md +0 -113
  36. package/.github/skills/wp-abilities-api/references/error-code-vocabulary.md +0 -123
  37. package/.github/skills/wp-abilities-api/references/grouping-heuristic.md +0 -89
  38. package/.github/skills/wp-abilities-api/references/input-schema-gotchas.md +0 -265
  39. package/.github/skills/wp-abilities-api/references/php-registration.md +0 -94
  40. package/.github/skills/wp-abilities-api/references/plugin-family-patterns.md +0 -233
  41. package/.github/skills/wp-abilities-api/references/rest-api.md +0 -13
  42. package/.github/skills/wp-abilities-api/references/shared-core-service.md +0 -184
  43. package/.github/skills/wp-abilities-audit/SKILL.md +0 -199
  44. package/.github/skills/wp-abilities-audit/references/audit-schema.md +0 -300
  45. package/.github/skills/wp-abilities-audit/references/capability-gate-tracing.md +0 -197
  46. package/.github/skills/wp-abilities-audit/references/controller-enumeration.md +0 -116
  47. package/.github/skills/wp-abilities-verify/SKILL.md +0 -215
  48. package/.github/skills/wp-abilities-verify/references/annotation-correctness.md +0 -154
  49. package/.github/skills/wp-abilities-verify/references/audit-schema-validation.md +0 -131
  50. package/.github/skills/wp-abilities-verify/references/permission-roundtrip.md +0 -190
  51. package/.github/skills/wp-abilities-verify/references/runtime-harness.md +0 -462
  52. package/.github/skills/wp-abilities-verify/references/schema-lints.md +0 -118
  53. package/.github/skills/wp-abilities-verify/references/static-enumeration.md +0 -126
  54. package/.github/skills/wp-block-development/SKILL.md +0 -175
  55. package/.github/skills/wp-block-development/references/attributes-and-serialization.md +0 -22
  56. package/.github/skills/wp-block-development/references/block-json.md +0 -49
  57. package/.github/skills/wp-block-development/references/creating-new-blocks.md +0 -46
  58. package/.github/skills/wp-block-development/references/debugging.md +0 -36
  59. package/.github/skills/wp-block-development/references/deprecations.md +0 -24
  60. package/.github/skills/wp-block-development/references/dynamic-rendering.md +0 -23
  61. package/.github/skills/wp-block-development/references/inner-blocks.md +0 -25
  62. package/.github/skills/wp-block-development/references/registration.md +0 -30
  63. package/.github/skills/wp-block-development/references/supports-and-wrappers.md +0 -18
  64. package/.github/skills/wp-block-development/references/tooling-and-testing.md +0 -21
  65. package/.github/skills/wp-block-development/scripts/list_blocks.mjs +0 -121
  66. package/.github/skills/wp-block-themes/SKILL.md +0 -117
  67. package/.github/skills/wp-block-themes/references/creating-new-block-theme.md +0 -37
  68. package/.github/skills/wp-block-themes/references/debugging.md +0 -24
  69. package/.github/skills/wp-block-themes/references/patterns.md +0 -18
  70. package/.github/skills/wp-block-themes/references/style-variations.md +0 -14
  71. package/.github/skills/wp-block-themes/references/templates-and-parts.md +0 -16
  72. package/.github/skills/wp-block-themes/references/theme-json.md +0 -59
  73. package/.github/skills/wp-block-themes/scripts/detect_block_themes.mjs +0 -117
  74. package/.github/skills/wp-interactivity-api/SKILL.md +0 -180
  75. package/.github/skills/wp-interactivity-api/references/debugging.md +0 -29
  76. package/.github/skills/wp-interactivity-api/references/directives-quickref.md +0 -30
  77. package/.github/skills/wp-interactivity-api/references/server-side-rendering.md +0 -310
  78. package/.github/skills/wp-performance/SKILL.md +0 -147
  79. package/.github/skills/wp-performance/references/autoload-options.md +0 -24
  80. package/.github/skills/wp-performance/references/cron.md +0 -20
  81. package/.github/skills/wp-performance/references/database.md +0 -20
  82. package/.github/skills/wp-performance/references/http-api.md +0 -15
  83. package/.github/skills/wp-performance/references/measurement.md +0 -21
  84. package/.github/skills/wp-performance/references/object-cache.md +0 -24
  85. package/.github/skills/wp-performance/references/query-monitor-headless.md +0 -38
  86. package/.github/skills/wp-performance/references/server-timing.md +0 -22
  87. package/.github/skills/wp-performance/references/wp-cli-doctor.md +0 -24
  88. package/.github/skills/wp-performance/references/wp-cli-profile.md +0 -32
  89. package/.github/skills/wp-performance/scripts/perf_inspect.mjs +0 -128
  90. package/.github/skills/wp-phpstan/SKILL.md +0 -98
  91. package/.github/skills/wp-phpstan/references/configuration.md +0 -52
  92. package/.github/skills/wp-phpstan/references/third-party-classes.md +0 -76
  93. package/.github/skills/wp-phpstan/references/wordpress-annotations.md +0 -124
  94. package/.github/skills/wp-phpstan/scripts/phpstan_inspect.mjs +0 -263
  95. package/.github/skills/wp-playground/SKILL.md +0 -233
  96. package/.github/skills/wp-playground/references/blueprints.md +0 -36
  97. package/.github/skills/wp-playground/references/cli-commands.md +0 -39
  98. package/.github/skills/wp-playground/references/debugging.md +0 -16
  99. package/.github/skills/wp-playground/references/e2e-playwright.md +0 -115
  100. package/.github/skills/wp-plugin-development/SKILL.md +0 -113
  101. package/.github/skills/wp-plugin-development/references/data-and-cron.md +0 -19
  102. package/.github/skills/wp-plugin-development/references/debugging.md +0 -19
  103. package/.github/skills/wp-plugin-development/references/lifecycle.md +0 -33
  104. package/.github/skills/wp-plugin-development/references/security.md +0 -29
  105. package/.github/skills/wp-plugin-development/references/settings-api.md +0 -22
  106. package/.github/skills/wp-plugin-development/references/structure.md +0 -16
  107. package/.github/skills/wp-plugin-development/scripts/detect_plugins.mjs +0 -122
  108. package/.github/skills/wp-plugin-directory-guidelines/SKILL.md +0 -133
  109. package/.github/skills/wp-plugin-directory-guidelines/references/gpl-compliance.md +0 -217
  110. package/.github/skills/wp-plugin-directory-guidelines/references/guideline-review-checklist.md +0 -592
  111. package/.github/skills/wp-plugin-directory-guidelines/references/naming-rules.md +0 -121
  112. package/.github/skills/wp-project-triage/SKILL.md +0 -39
  113. package/.github/skills/wp-project-triage/references/triage.schema.json +0 -143
  114. package/.github/skills/wp-project-triage/scripts/detect_wp_project.mjs +0 -610
  115. package/.github/skills/wp-rest-api/SKILL.md +0 -115
  116. package/.github/skills/wp-rest-api/references/authentication.md +0 -18
  117. package/.github/skills/wp-rest-api/references/custom-content-types.md +0 -20
  118. package/.github/skills/wp-rest-api/references/discovery-and-params.md +0 -20
  119. package/.github/skills/wp-rest-api/references/responses-and-fields.md +0 -30
  120. package/.github/skills/wp-rest-api/references/routes-and-endpoints.md +0 -36
  121. package/.github/skills/wp-rest-api/references/schema.md +0 -22
  122. package/.github/skills/wp-wpcli-and-ops/SKILL.md +0 -124
  123. package/.github/skills/wp-wpcli-and-ops/references/automation.md +0 -30
  124. package/.github/skills/wp-wpcli-and-ops/references/cron-and-cache.md +0 -23
  125. package/.github/skills/wp-wpcli-and-ops/references/debugging.md +0 -17
  126. package/.github/skills/wp-wpcli-and-ops/references/multisite.md +0 -22
  127. package/.github/skills/wp-wpcli-and-ops/references/packages-and-updates.md +0 -22
  128. package/.github/skills/wp-wpcli-and-ops/references/safety.md +0 -30
  129. package/.github/skills/wp-wpcli-and-ops/references/search-replace.md +0 -40
  130. package/.github/skills/wp-wpcli-and-ops/scripts/wpcli_inspect.mjs +0 -90
  131. package/.github/skills/wp-wpengine/SKILL.md +0 -127
  132. package/.github/skills/wpds/SKILL.md +0 -59
@@ -0,0 +1,417 @@
1
+ #!/usr/bin/env bash
2
+ # setup-github.sh — configure GitHub repo for WP Engine CI/CD using the gh CLI.
3
+ #
4
+ # Checks authentication, reports missing secrets, sets them interactively or
5
+ # from provided values, and configures branch protection for deploy branches.
6
+ #
7
+ # Usage:
8
+ # bash .agents/skills/wp-bootstrap/scripts/setup-github.sh [OPTIONS]
9
+ #
10
+ # Options:
11
+ # --check-only Report status without making any changes (default if no --set-*)
12
+ # --set-secrets Interactively set missing WP Engine secrets
13
+ # --set-protection Configure branch protection for main/staging/develop
14
+ # --set-all Set secrets + branch protection
15
+ # --wpe-key=<file> Path to WP Engine SSH private key (default: ~/.ssh/wpengine_ed25519)
16
+ # --wpe-prod=<slug> WP Engine production install slug
17
+ # --wpe-staging=<slug> WP Engine staging install slug
18
+ # --wpe-dev=<slug> WP Engine dev install slug
19
+ # --no-confirm Skip confirmation prompts (use with care)
20
+ #
21
+ # Requires: gh CLI installed and authenticated (gh auth login)
22
+ #
23
+ # Exit codes:
24
+ # 0 All checks passed / actions completed
25
+ # 1 Error or user cancelled
26
+ # 2 gh CLI not installed or not authenticated
27
+
28
+ set -uo pipefail
29
+
30
+ # ── Parse args ────────────────────────────────────────────────────────────────
31
+ CHECK_ONLY=true
32
+ SET_SECRETS=false
33
+ SET_PROTECTION=false
34
+ WPE_KEY_FILE="${HOME}/.ssh/wpengine_ed25519"
35
+ WPE_PROD=""
36
+ WPE_STAGING=""
37
+ WPE_DEV=""
38
+ NO_CONFIRM=false
39
+
40
+ for arg in "$@"; do
41
+ case "$arg" in
42
+ --check-only) CHECK_ONLY=true ;;
43
+ --set-secrets) SET_SECRETS=true; CHECK_ONLY=false ;;
44
+ --set-protection) SET_PROTECTION=true; CHECK_ONLY=false ;;
45
+ --set-all) SET_SECRETS=true; SET_PROTECTION=true; CHECK_ONLY=false ;;
46
+ --wpe-key=*) WPE_KEY_FILE="${arg#--wpe-key=}" ;;
47
+ --wpe-prod=*) WPE_PROD="${arg#--wpe-prod=}" ;;
48
+ --wpe-staging=*) WPE_STAGING="${arg#--wpe-staging=}" ;;
49
+ --wpe-dev=*) WPE_DEV="${arg#--wpe-dev=}" ;;
50
+ --no-confirm) NO_CONFIRM=true ;;
51
+ esac
52
+ done
53
+
54
+ # ── Helpers ───────────────────────────────────────────────────────────────────
55
+ ok() { printf ' \033[32m✓\033[0m %s\n' "$*"; }
56
+ fail() { printf ' \033[31m✗\033[0m %s\n' "$*" >&2; }
57
+ warn() { printf ' \033[33m⚠\033[0m %s\n' "$*"; }
58
+ info() { printf ' \033[34m→\033[0m %s\n' "$*"; }
59
+ sep() { printf '\n\033[1m── %s\033[0m\n' "$*"; }
60
+
61
+ confirm() {
62
+ local prompt="${1} [y/N] "
63
+ $NO_CONFIRM && { echo "(auto-confirmed)"; return 0; }
64
+ read -r -p "$prompt" ans
65
+ [[ "${ans,,}" == "y" || "${ans,,}" == "yes" ]]
66
+ }
67
+
68
+ # ── 1. Check gh CLI ───────────────────────────────────────────────────────────
69
+ sep "GitHub CLI check"
70
+
71
+ if ! command -v gh >/dev/null 2>&1; then
72
+ fail "gh CLI not installed."
73
+ echo " Install: https://cli.github.com"
74
+ echo " macOS: brew install gh"
75
+ echo " Linux: sudo apt install gh (or see above URL)"
76
+ exit 2
77
+ fi
78
+ ok "gh $(gh --version | head -1 | awk '{print $3}')"
79
+
80
+ # ── 2. Check authentication ────────────────────────────────────────────────────
81
+ sep "Authentication"
82
+
83
+ AUTH_STATUS=$(gh auth status 2>&1)
84
+ if ! echo "$AUTH_STATUS" | grep -q "Logged in to github.com"; then
85
+ fail "Not authenticated. Run: gh auth login"
86
+ exit 2
87
+ fi
88
+
89
+ ACCOUNT=$(echo "$AUTH_STATUS" | grep -o 'account [^ ]*' | awk '{print $2}' | head -1)
90
+ ok "Authenticated as @${ACCOUNT}"
91
+
92
+ # ── 3. Detect repo ────────────────────────────────────────────────────────────
93
+ sep "Repository"
94
+
95
+ ORIGIN=$(git remote get-url origin 2>/dev/null || echo "")
96
+ if [ -z "$ORIGIN" ]; then
97
+ fail "No 'origin' remote found. Is this a git repo with a GitHub remote?"
98
+ exit 1
99
+ fi
100
+
101
+ # Parse owner/repo from SSH or HTTPS URL
102
+ if [[ "$ORIGIN" =~ git@github\.com[:/]([^/]+)/([^.]+)(\.git)?$ ]]; then
103
+ OWNER="${BASH_REMATCH[1]}"
104
+ REPO="${BASH_REMATCH[2]}"
105
+ elif [[ "$ORIGIN" =~ https?://github\.com/([^/]+)/([^/.]+) ]]; then
106
+ OWNER="${BASH_REMATCH[1]}"
107
+ REPO="${BASH_REMATCH[2]}"
108
+ else
109
+ fail "Cannot parse GitHub owner/repo from remote: $ORIGIN"
110
+ exit 1
111
+ fi
112
+
113
+ # Verify repo is accessible
114
+ REPO_INFO=$(gh repo view "${OWNER}/${REPO}" --json name,owner,url,defaultBranchRef,visibility 2>&1)
115
+ if ! echo "$REPO_INFO" | grep -q '"name"'; then
116
+ fail "Cannot access repo ${OWNER}/${REPO}. Check permissions."
117
+ exit 1
118
+ fi
119
+
120
+ VISIBILITY=$(echo "$REPO_INFO" | python3 -c "import sys,json; print(json.load(sys.stdin)['visibility'])" 2>/dev/null || echo "unknown")
121
+ DEFAULT_BRANCH=$(echo "$REPO_INFO" | python3 -c "import sys,json; print(json.load(sys.stdin)['defaultBranchRef']['name'])" 2>/dev/null || echo "main")
122
+ REPO_URL=$(echo "$REPO_INFO" | python3 -c "import sys,json; print(json.load(sys.stdin)['url'])" 2>/dev/null || echo "")
123
+
124
+ ok "${OWNER}/${REPO} (${VISIBILITY,,}) — ${REPO_URL}"
125
+ info "Default branch: ${DEFAULT_BRANCH}"
126
+
127
+ # ── 4. Check secrets ──────────────────────────────────────────────────────────
128
+ sep "GitHub Secrets"
129
+
130
+ EXISTING_JSON=$(gh secret list --repo "${OWNER}/${REPO}" --json name 2>/dev/null || echo "[]")
131
+ EXISTING_SECRETS=$(echo "$EXISTING_JSON" | python3 -c "
132
+ import sys,json
133
+ secrets = json.load(sys.stdin)
134
+ for s in secrets: print(s['name'])
135
+ " 2>/dev/null || echo "")
136
+
137
+ # Define required secrets and their descriptions
138
+ declare -A SECRET_DESC
139
+ SECRET_DESC[WPE_SSH_KEY]="WP Engine SSH private key (contents of wpengine_ed25519)"
140
+ SECRET_DESC[WPE_SSH_KNOWN_HOSTS]="Known hosts for git.wpengine.com + ssh.wpengine.net"
141
+ SECRET_DESC[WPE_PROD_INSTALL]="Production install slug (e.g. mysite)"
142
+ SECRET_DESC[WPE_PROD_GIT_URL]="Production git push URL from WPE portal"
143
+ SECRET_DESC[WPE_STAGING_INSTALL]="Staging install slug (e.g. mysitestg)"
144
+ SECRET_DESC[WPE_STAGING_GIT_URL]="Staging git push URL from WPE portal"
145
+ SECRET_DESC[WPE_DEV_INSTALL]="Development install slug (e.g. mysitedev)"
146
+ SECRET_DESC[WPE_DEV_GIT_URL]="Development git push URL from WPE portal"
147
+ SECRET_DESC[WPE_API_USER]="WP Engine API username (from my.wpengine.com/api_access)"
148
+ SECRET_DESC[WPE_API_PASSWORD]="WP Engine API password"
149
+ SECRET_DESC[SLACK_WEBHOOK_URL]="Slack incoming webhook URL (optional)"
150
+
151
+ REQUIRED_SECRETS=(
152
+ WPE_SSH_KEY WPE_SSH_KNOWN_HOSTS
153
+ WPE_PROD_INSTALL WPE_PROD_GIT_URL
154
+ WPE_STAGING_INSTALL WPE_STAGING_GIT_URL
155
+ WPE_DEV_INSTALL WPE_DEV_GIT_URL
156
+ WPE_API_USER WPE_API_PASSWORD
157
+ )
158
+
159
+ MISSING_SECRETS=()
160
+ for secret in "${REQUIRED_SECRETS[@]}"; do
161
+ if echo "$EXISTING_SECRETS" | grep -qx "$secret"; then
162
+ ok "$secret"
163
+ else
164
+ warn "$secret — MISSING"
165
+ MISSING_SECRETS+=("$secret")
166
+ fi
167
+ done
168
+
169
+ # Optional
170
+ if echo "$EXISTING_SECRETS" | grep -qx "SLACK_WEBHOOK_URL"; then
171
+ ok "SLACK_WEBHOOK_URL (optional)"
172
+ else
173
+ info "SLACK_WEBHOOK_URL — not set (optional)"
174
+ fi
175
+
176
+ echo ""
177
+ if [ "${#MISSING_SECRETS[@]}" -eq 0 ]; then
178
+ ok "All required secrets are set ✓"
179
+ else
180
+ warn "${#MISSING_SECRETS[@]} secret(s) missing — run with --set-secrets to configure"
181
+ fi
182
+
183
+ # ── 5. Set missing secrets ────────────────────────────────────────────────────
184
+ if $SET_SECRETS && [ "${#MISSING_SECRETS[@]}" -gt 0 ]; then
185
+ sep "Setting missing secrets"
186
+
187
+ for secret in "${MISSING_SECRETS[@]}"; do
188
+ echo ""
189
+ info "Setting: ${secret}"
190
+ info "Purpose: ${SECRET_DESC[$secret]}"
191
+
192
+ VALUE=""
193
+
194
+ case "$secret" in
195
+ WPE_SSH_KEY)
196
+ if [ -f "$WPE_KEY_FILE" ]; then
197
+ info "Reading from $WPE_KEY_FILE"
198
+ if confirm " Set WPE_SSH_KEY from $WPE_KEY_FILE?"; then
199
+ VALUE=$(cat "$WPE_KEY_FILE")
200
+ fi
201
+ else
202
+ warn "Key file not found: $WPE_KEY_FILE"
203
+ info "Alternatives:"
204
+ info " 1. op read 'op://Employee/wpengine_ed25519/private key' > ~/.ssh/wpengine_ed25519"
205
+ info " 2. Paste the key manually below (enter blank line when done)"
206
+ if confirm " Enter key manually?"; then
207
+ LINES=()
208
+ echo " Paste private key (empty line to finish):"
209
+ while IFS= read -r line; do
210
+ [ -z "$line" ] && break
211
+ LINES+=("$line")
212
+ done
213
+ VALUE=$(printf '%s\n' "${LINES[@]}")
214
+ fi
215
+ fi
216
+ ;;
217
+
218
+ WPE_SSH_KNOWN_HOSTS)
219
+ info "Generating from ssh-keyscan..."
220
+ VALUE=$(
221
+ {
222
+ # git push host — RSA key
223
+ ssh-keyscan -t rsa git.wpengine.com 2>/dev/null
224
+ # Include hashed entry for git.wpengine.com too
225
+ ssh-keyscan -H git.wpengine.com 2>/dev/null
226
+ # Note: SSH gateway subdomains ({install}.ssh.wpengine.net) use
227
+ # StrictHostKeyChecking=accept-new in the workflow SSH config,
228
+ # so they don't need pre-scanned entries here.
229
+ }
230
+ )
231
+ if [ -n "$VALUE" ]; then
232
+ info "Generated $(echo "$VALUE" | wc -l) known_hosts entries"
233
+ info "Gateway subdomains auto-accepted via StrictHostKeyChecking=accept-new"
234
+ confirm " Set WPE_SSH_KNOWN_HOSTS?" && true || VALUE=""
235
+ else
236
+ warn "ssh-keyscan failed — check network connectivity"
237
+ fi
238
+ ;;
239
+
240
+ WPE_PROD_INSTALL)
241
+ [ -n "$WPE_PROD" ] && VALUE="$WPE_PROD" || {
242
+ read -r -p " Production install slug (e.g. mysite): " VALUE
243
+ }
244
+ ;;
245
+
246
+ WPE_PROD_GIT_URL)
247
+ SLUG="${WPE_PROD:-}"
248
+ if [ -z "$SLUG" ]; then
249
+ # Try to get from wp-cli.yml
250
+ SLUG=$(grep -A2 '@production' wp-cli.yml 2>/dev/null | grep 'ssh:' | awk '{print $2}' | cut -d@ -f1 || echo "")
251
+ fi
252
+ info "Get exact URL from: https://my.wpengine.com/installs/${SLUG:-<install>}/git_push"
253
+ read -r -p " Production git URL (e.g. git@git.wpengine.com:mysite.git): " VALUE
254
+ ;;
255
+
256
+ WPE_STAGING_INSTALL)
257
+ [ -n "$WPE_STAGING" ] && VALUE="$WPE_STAGING" || {
258
+ read -r -p " Staging install slug (e.g. mysitestg): " VALUE
259
+ }
260
+ ;;
261
+
262
+ WPE_STAGING_GIT_URL)
263
+ SLUG="${WPE_STAGING:-}"
264
+ info "Get exact URL from: https://my.wpengine.com/installs/${SLUG:-<install>}/git_push"
265
+ read -r -p " Staging git URL (e.g. git@git.wpengine.com:mysitestg.git): " VALUE
266
+ ;;
267
+
268
+ WPE_DEV_INSTALL)
269
+ [ -n "$WPE_DEV" ] && VALUE="$WPE_DEV" || {
270
+ read -r -p " Dev install slug (e.g. mysitedev): " VALUE
271
+ }
272
+ ;;
273
+
274
+ WPE_DEV_GIT_URL)
275
+ SLUG="${WPE_DEV:-}"
276
+ info "Get exact URL from: https://my.wpengine.com/installs/${SLUG:-<install>}/git_push"
277
+ read -r -p " Dev git URL (e.g. git@git.wpengine.com:mysitedev.git): " VALUE
278
+ ;;
279
+
280
+ WPE_API_USER)
281
+ info "Find at: https://my.wpengine.com/api_access"
282
+ read -r -p " WP Engine API username: " VALUE
283
+ ;;
284
+
285
+ WPE_API_PASSWORD)
286
+ info "Find at: https://my.wpengine.com/api_access"
287
+ read -r -s -p " WP Engine API password (hidden): " VALUE
288
+ echo ""
289
+ ;;
290
+ esac
291
+
292
+ if [ -n "$VALUE" ]; then
293
+ echo "$VALUE" | gh secret set "$secret" --repo "${OWNER}/${REPO}"
294
+ ok "$secret set"
295
+ else
296
+ warn "$secret skipped"
297
+ fi
298
+ done
299
+ fi
300
+
301
+ # ── 6. Branch protection ──────────────────────────────────────────────────────
302
+ sep "Branch Protection"
303
+
304
+ check_branch_protection() {
305
+ local branch="$1"
306
+ local prot
307
+ prot=$(gh api "repos/${OWNER}/${REPO}/branches/${branch}/protection" 2>/dev/null || echo "")
308
+ if [ -z "$prot" ] || echo "$prot" | grep -q '"message"'; then
309
+ warn "${branch}: not protected"
310
+ return 1
311
+ fi
312
+ local checks
313
+ checks=$(echo "$prot" | python3 -c "
314
+ import sys,json
315
+ p = json.load(sys.stdin)
316
+ checks = p.get('required_status_checks',{}).get('contexts',[])
317
+ reviews = p.get('required_pull_request_reviews',{}).get('required_approving_review_count',0)
318
+ print(f'status checks={checks}, reviewers={reviews}')
319
+ " 2>/dev/null || echo "configured")
320
+ ok "${branch}: protected (${checks})"
321
+ return 0
322
+ }
323
+
324
+ BRANCHES_NEEDING_PROTECTION=()
325
+
326
+ for branch in "$DEFAULT_BRANCH" "staging" "develop"; do
327
+ if ! check_branch_protection "$branch" 2>/dev/null; then
328
+ BRANCHES_NEEDING_PROTECTION+=("$branch")
329
+ fi
330
+ done
331
+
332
+ if [ "${#BRANCHES_NEEDING_PROTECTION[@]}" -eq 0 ]; then
333
+ ok "Branch protection looks good"
334
+ elif ! $SET_PROTECTION; then
335
+ warn "${#BRANCHES_NEEDING_PROTECTION[@]} branch(es) not protected — run with --set-protection to configure"
336
+ fi
337
+
338
+ # ── 7. Apply branch protection ────────────────────────────────────────────────
339
+ if $SET_PROTECTION && [ "${#BRANCHES_NEEDING_PROTECTION[@]}" -gt 0 ]; then
340
+ sep "Configuring Branch Protection"
341
+
342
+ for branch in "${BRANCHES_NEEDING_PROTECTION[@]}"; do
343
+ echo ""
344
+ info "Configuring: ${branch}"
345
+
346
+ # Determine required reviewer count
347
+ if [ "$branch" = "$DEFAULT_BRANCH" ]; then
348
+ REVIEWERS=2 # Production needs 2
349
+ else
350
+ REVIEWERS=1 # Staging/develop needs 1
351
+ fi
352
+
353
+ # Required status checks — gate-passed is the canonical check from ci-gate.yml
354
+ CONTEXTS='["gate-passed"]'
355
+
356
+ # For production, also require staging-source-check
357
+ if [ "$branch" = "$DEFAULT_BRANCH" ]; then
358
+ CONTEXTS='["gate-passed","staging-source-check"]'
359
+ fi
360
+
361
+ if ! confirm " Set ${branch} protection (${REVIEWERS} reviewers, checks: ${CONTEXTS})?"; then
362
+ warn "${branch}: skipped"
363
+ continue
364
+ fi
365
+
366
+ PAYLOAD=$(python3 -c "
367
+ import json
368
+ contexts = ${CONTEXTS}
369
+ payload = {
370
+ 'required_status_checks': {
371
+ 'strict': True,
372
+ 'contexts': contexts
373
+ },
374
+ 'enforce_admins': True,
375
+ 'required_pull_request_reviews': {
376
+ 'dismiss_stale_reviews': True,
377
+ 'require_code_owner_reviews': False,
378
+ 'required_approving_review_count': ${REVIEWERS}
379
+ },
380
+ 'restrictions': None,
381
+ 'allow_force_pushes': False,
382
+ 'allow_deletions': False
383
+ }
384
+ print(json.dumps(payload))
385
+ ")
386
+
387
+ if echo "$PAYLOAD" | gh api "repos/${OWNER}/${REPO}/branches/${branch}/protection" \
388
+ --method PUT --input - >/dev/null 2>&1; then
389
+ ok "${branch}: protection configured (${REVIEWERS} reviewer(s), checks: ${CONTEXTS})"
390
+ else
391
+ fail "${branch}: failed to configure protection"
392
+ info "You may need admin access or a GitHub token with 'repo' scope"
393
+ fi
394
+ done
395
+ fi
396
+
397
+ # ── 8. Summary ────────────────────────────────────────────────────────────────
398
+ sep "Summary"
399
+
400
+ TOTAL_MISSING="${#MISSING_SECRETS[@]}"
401
+ TOTAL_UNPROTECTED="${#BRANCHES_NEEDING_PROTECTION[@]}"
402
+
403
+ if [ "$TOTAL_MISSING" -eq 0 ] && [ "$TOTAL_UNPROTECTED" -eq 0 ]; then
404
+ ok "GitHub repo is fully configured for WP Engine CI/CD"
405
+ ok "Secrets: all set | Branch protection: all configured"
406
+ else
407
+ [ "$TOTAL_MISSING" -gt 0 ] && warn "Secrets missing: $TOTAL_MISSING (run --set-secrets)"
408
+ [ "$TOTAL_UNPROTECTED" -gt 0 ] && warn "Branches unprotected: ${BRANCHES_NEEDING_PROTECTION[*]} (run --set-protection)"
409
+ echo ""
410
+ info "Re-run: bash ${0} --set-all"
411
+ fi
412
+
413
+ echo ""
414
+ info "GitHub Actions: https://github.com/${OWNER}/${REPO}/actions"
415
+ info "Secrets: https://github.com/${OWNER}/${REPO}/settings/secrets/actions"
416
+ info "Branch rules: https://github.com/${OWNER}/${REPO}/settings/branches"
417
+ echo ""
@@ -42,8 +42,10 @@ chmod 600 ~/.ssh/wpengine_ed25519
42
42
 
43
43
  # Trust WP Engine git push host (RSA — what WP Engine's git.wpengine.com serves)
44
44
  ssh-keyscan -t rsa git.wpengine.com >> ~/.ssh/known_hosts
45
- # Trust WP Engine SSH gateway
46
- ssh-keyscan -H ssh.wpengine.net >> ~/.ssh/known_hosts
45
+ # Gateway: scan the specific install hostname (each install has its own subdomain)
46
+ # Do this once per environment you connect to:
47
+ ssh-keyscan -H <install>.ssh.wpengine.net >> ~/.ssh/known_hosts
48
+ # e.g.: ssh-keyscan -H mysite.ssh.wpengine.net >> ~/.ssh/known_hosts
47
49
  ```
48
50
 
49
51
  Add to `~/.ssh/config` (before any `Host *` block):
@@ -55,15 +57,18 @@ Host git.wpengine.com
55
57
  IdentityFile ~/.ssh/wpengine_ed25519
56
58
  IdentitiesOnly yes
57
59
 
58
- # WP Engine SSH gateway (WP-CLI + direct access)
60
+ # WP Engine SSH gateway (WP-CLI + file transfer)
59
61
  Host *.ssh.wpengine.net
60
62
  IdentityFile ~/.ssh/wpengine_ed25519
61
63
  IdentitiesOnly yes
62
64
  ControlMaster auto
63
65
  ControlPath ~/.ssh/wpe-%r@%h:%p
64
66
  ControlPersist 10m
67
+ StrictHostKeyChecking accept-new
65
68
  ```
66
69
 
70
+ > **`StrictHostKeyChecking accept-new`**: automatically accepts and stores the host key on first connection, then rejects any change to that key (MITM protection). Safer than `no`; avoids having to manually `ssh-keyscan` each install hostname.
71
+ >
67
72
  > **ControlMaster / ControlPersist**: multiplexes SSH connections so subsequent commands over the same gateway reuse the existing connection. Cuts per-command latency from ~2 s to ~100 ms for repeated WP-CLI invocations.
68
73
 
69
74
  Verify git push access:
@@ -92,13 +97,19 @@ where `<environment>` is `production`, `staging`, or `development`.
92
97
 
93
98
  ```bash
94
99
  # Production (copy exact URL from portal)
95
- git remote add wpengine-prod git@git.wpengine.com:production/<install-name>.git
96
-
97
- # Staging
98
- git remote add wpengine-staging git@git.wpengine.com:staging/<install-name>stg.git
99
-
100
- # Development
101
- git remote add wpengine-dev git@git.wpengine.com:development/<install-name>dev.git
100
+ # ⚠️ Always copy the exact URL from the WP Engine portal — formats vary by account:
101
+ # https://my.wpengine.com/installs/<ENV>/git_push
102
+ #
103
+ # Modern accounts (most common):
104
+ git remote add wpengine-prod git@git.wpengine.com:<install-name>.git
105
+ # Legacy accounts (some plans add an environment prefix):
106
+ # git remote add wpengine-prod git@git.wpengine.com:production/<install-name>.git
107
+
108
+ # Staging (check portal for exact URL)
109
+ git remote add wpengine-staging git@git.wpengine.com:<install-name>stg.git
110
+
111
+ # Development (check portal for exact URL)
112
+ git remote add wpengine-dev git@git.wpengine.com:<install-name>dev.git
102
113
  ```
103
114
 
104
115
  Deploy:
@@ -179,6 +190,59 @@ wp @production search-replace 'old-domain.com' 'new-domain.com' --dry-run
179
190
 
180
191
  > Commit `wp-cli.yml` to the repo so all team members and CI pipelines share the same remote aliases.
181
192
 
193
+ #### Method D — SCP / rsync for file transfer
194
+
195
+ The SSH gateway also accepts SCP and rsync (port 22). Use this to pull/push files without a full git push:
196
+
197
+ ```bash
198
+ # SCP: download a file from the server
199
+ scp -P 22 <install>@<install>.ssh.wpengine.net:sites/<install>/wp-content/uploads/large-file.zip ./
200
+
201
+ # SCP: upload a file to the server
202
+ scp -P 22 ./my-patch.php <install>@<install>.ssh.wpengine.net:sites/<install>/wp-content/plugins/my-plugin/
203
+
204
+ # rsync: sync wp-content/uploads from production to local (read-only pull)
205
+ rsync -avz --progress \
206
+ -e "ssh -p 22" \
207
+ <install>@<install>.ssh.wpengine.net:sites/<install>/wp-content/uploads/ \
208
+ ./local-uploads/
209
+
210
+ # rsync: push a theme to staging (careful with --delete)
211
+ rsync -avz --dry-run \
212
+ -e "ssh -p 22" \
213
+ ./my-theme/ \
214
+ <install>stg@<install>stg.ssh.wpengine.net:sites/<install>stg/wp-content/themes/my-theme/
215
+ ```
216
+
217
+ > **WP Engine server path**: WordPress root is `sites/<install>/` relative to the SSH home, or `/home/wpe-user/sites/<install>` as an absolute path. `wp-content/` lives inside that root.
218
+
219
+ #### Method E — Multiple commands via heredoc
220
+
221
+ Run several commands in one SSH session without reconnecting:
222
+
223
+ ```bash
224
+ # Heredoc over SSH (most efficient — one connection for all commands)
225
+ ssh <install>@<install>.ssh.wpengine.net bash -s << 'EOF'
226
+ set -e
227
+ wp cache flush --skip-plugins --skip-themes
228
+ wp rewrite flush --skip-plugins --skip-themes
229
+ wp cron event run --due-now --skip-plugins --skip-themes
230
+ wp core version --skip-plugins --skip-themes
231
+ EOF
232
+
233
+ # Interactive WP-CLI commands need -t (pseudo-TTY allocation)
234
+ # e.g. wp shell for a REPL session
235
+ ssh -t <install>@<install>.ssh.wpengine.net wp shell
236
+ ```
237
+
238
+ #### SSH gateway environment notes
239
+
240
+ - **Restricted shell**: The gateway provides a limited shell environment. WP-CLI, PHP, basic POSIX utilities (echo, cat, stat, du, find, grep) and rsync/SCP are available. Package installation (`apt`, `yum`), sudo, and arbitrary service management are **not** available.
241
+ - **PHP version**: Matches the PHP version configured for that WP Engine install. `php --version` to confirm.
242
+ - **WordPress path**: `~/sites/<install>/` (relative to SSH home) or `/home/wpe-user/sites/<install>` (absolute).
243
+ - **`--path` flag**: If WP-CLI returns "not a WordPress installation", add `--path=/home/wpe-user/sites/<install>` explicitly.
244
+ - **Legacy gateway**: `ssh.wpengine.net` (no subdomain) is the old generic gateway address. Current convention always uses `<install>.ssh.wpengine.net`.
245
+
182
246
  ---
183
247
 
184
248
  ### 4) Common remote WP-CLI operations
@@ -376,9 +440,9 @@ curl -fsSL https://raw.githubusercontent.com/wpengine/wpe-labs-platform-skills/m
376
440
  | Symptom | Fix |
377
441
  |---|---|
378
442
  | `Host key verification failed` (git) | `ssh-keyscan git.wpengine.com >> ~/.ssh/known_hosts` |
379
- | `Host key verification failed` (gateway) | `ssh-keyscan -H ssh.wpengine.net >> ~/.ssh/known_hosts` |
443
+ | `Host key verification failed` (gateway) | Run `ssh-keyscan -H <install>.ssh.wpengine.net >> ~/.ssh/known_hosts` for that specific install hostname. Or add `StrictHostKeyChecking accept-new` to the `*.ssh.wpengine.net` SSH config block — it will auto-accept on first connect. |
380
444
  | `Permission denied` | Confirm key at `~/.ssh/wpengine_ed25519`, `chmod 600`. Check the key is registered under **SSH Keys** in the WP Engine portal (separate from git push keys). |
381
- | `git push rejected` | Verify remote URL includes environment prefix (`production/<install>.git`). Get the exact URL from the portal: `https://my.wpengine.com/installs/<ENV>/git_push` |
445
+ | `git push rejected` | Get the exact URL from the portal (`https://my.wpengine.com/installs/<ENV>/git_push`). URL format varies by account — copy it verbatim. |
382
446
  | SSH gateway hangs | Kill stale ControlMaster socket: `ssh -O stop <install>@<install>.ssh.wpengine.net` |
383
447
  | `wp: command not found` on gateway | WP Engine's WP-CLI path: try `php /usr/local/bin/wp` or contact WP Engine support |
384
448
  | WP-CLI returns wrong site | Add `--path=/home/wpe-user/sites/<install>` explicitly |
@@ -83,17 +83,21 @@ Add under **Settings → Secrets and variables → Actions**:
83
83
  | Secret | Value |
84
84
  |--------|-------|
85
85
  | `WPE_SSH_KEY` | Private key (contents of `wpengine_ed25519`) |
86
- | `WPE_SSH_KNOWN_HOSTS` | Output of `ssh-keyscan -t rsa git.wpengine.com && ssh-keyscan -H ssh.wpengine.net` |
86
+ | `WPE_SSH_KNOWN_HOSTS` | Output of `ssh-keyscan -t rsa git.wpengine.com && ssh-keyscan -H git.wpengine.com` (git push host only; gateway subdomains use `StrictHostKeyChecking accept-new`) |
87
87
  | `WPE_PROD_INSTALL` | Production install slug (e.g., `mysite`) |
88
+ | `WPE_PROD_GIT_URL` | Production git remote URL from portal (`git_push` page) |
88
89
  | `WPE_STAGING_INSTALL` | Staging install slug (e.g., `mysitestg`) |
90
+ | `WPE_STAGING_GIT_URL` | Staging git remote URL from portal |
89
91
  | `WPE_DEV_INSTALL` | Development install slug (e.g., `mysitedev`) |
92
+ | `WPE_DEV_GIT_URL` | Development git remote URL from portal |
90
93
  | `WPE_API_USER` | WP Engine API username (for backup snapshots) |
91
94
  | `WPE_API_PASSWORD` | WP Engine API password |
92
95
  | `SLACK_WEBHOOK_URL` | Slack incoming webhook (optional, for notifications) |
93
96
 
94
97
  Generate the known hosts value once and save:
95
98
  ```bash
96
- { ssh-keyscan -t rsa git.wpengine.com; ssh-keyscan -H ssh.wpengine.net; } 2>/dev/null
99
+ { ssh-keyscan -t rsa git.wpengine.com; ssh-keyscan -H git.wpengine.com; } 2>/dev/null
100
+ # Note: SSH gateway subdomains use StrictHostKeyChecking=accept-new in workflows
97
101
  ```
98
102
 
99
103
  ---
@@ -254,7 +258,8 @@ jobs:
254
258
  env:
255
259
  INSTALL: ${{ secrets.WPE_DEV_INSTALL }}
256
260
  run: |
257
- git remote add wpe-dev git@git.wpengine.com:development/${INSTALL}.git
261
+ # URL from portal: https://my.wpengine.com/installs/<ENV>/git_push (set WPE_DEV_GIT_URL secret)
262
+ git remote add wpe-dev "${WPE_DEV_GIT_URL:-git@git.wpengine.com:${INSTALL}.git}"
258
263
  # Force-add built assets (normally gitignored)
259
264
  git add -f dist/ build/ 2>/dev/null || true
260
265
  git diff --cached --quiet || git commit -m "ci: add built assets [skip ci]"
@@ -264,7 +269,7 @@ jobs:
264
269
  env:
265
270
  INSTALL: ${{ secrets.WPE_DEV_INSTALL }}
266
271
  run: |
267
- ssh -o StrictHostKeyChecking=no ${INSTALL}@${INSTALL}.ssh.wpengine.net \
272
+ ssh -o StrictHostKeyChecking=accept-new ${INSTALL}@${INSTALL}.ssh.wpengine.net \
268
273
  wp cache flush --skip-plugins --skip-themes
269
274
  ```
270
275
 
@@ -355,7 +360,8 @@ jobs:
355
360
  env:
356
361
  INSTALL: ${{ secrets.WPE_STAGING_INSTALL }}
357
362
  run: |
358
- git remote add wpe-staging git@git.wpengine.com:staging/${INSTALL}.git
363
+ # URL from portal: set WPE_STAGING_GIT_URL secret
364
+ git remote add wpe-staging "${WPE_STAGING_GIT_URL:-git@git.wpengine.com:${INSTALL}.git}"
359
365
  git add -f dist/ build/ 2>/dev/null || true
360
366
  git diff --cached --quiet || git commit -m "ci: add built assets [skip ci]"
361
367
  git push wpe-staging HEAD:main --force
@@ -364,7 +370,7 @@ jobs:
364
370
  env:
365
371
  INSTALL: ${{ secrets.WPE_STAGING_INSTALL }}
366
372
  run: |
367
- ssh -o StrictHostKeyChecking=no ${INSTALL}@${INSTALL}.ssh.wpengine.net bash -s <<'EOF'
373
+ ssh -o StrictHostKeyChecking=accept-new ${INSTALL}@${INSTALL}.ssh.wpengine.net bash -s <<'EOF'
368
374
  set -e
369
375
  wp cache flush --skip-plugins --skip-themes
370
376
  wp rewrite flush --skip-plugins --skip-themes
@@ -534,7 +540,8 @@ jobs:
534
540
  env:
535
541
  INSTALL: ${{ secrets.WPE_PROD_INSTALL }}
536
542
  run: |
537
- git remote add wpe-prod git@git.wpengine.com:production/${INSTALL}.git
543
+ # URL from portal: set WPE_PROD_GIT_URL secret
544
+ git remote add wpe-prod "${WPE_PROD_GIT_URL:-git@git.wpengine.com:${INSTALL}.git}"
538
545
  git add -f dist/ build/ 2>/dev/null || true
539
546
  git diff --cached --quiet || git commit -m "ci: add built assets [skip ci]"
540
547
  git push wpe-prod HEAD:main --force
@@ -543,7 +550,7 @@ jobs:
543
550
  env:
544
551
  INSTALL: ${{ secrets.WPE_PROD_INSTALL }}
545
552
  run: |
546
- ssh -o StrictHostKeyChecking=no ${INSTALL}@${INSTALL}.ssh.wpengine.net bash -s <<'EOF'
553
+ ssh -o StrictHostKeyChecking=accept-new ${INSTALL}@${INSTALL}.ssh.wpengine.net bash -s <<'EOF'
547
554
  set -e
548
555
  wp cache flush --skip-plugins --skip-themes
549
556
  wp rewrite flush --skip-plugins --skip-themes
@@ -603,7 +610,7 @@ jobs:
603
610
  "https://${INSTALL}.wpenginepowered.com/" --max-time 30)
604
611
  echo "Post-rollback status: HTTP $STATUS"
605
612
  # Flush cache after rollback
606
- ssh -o StrictHostKeyChecking=no ${INSTALL}@${INSTALL}.ssh.wpengine.net \
613
+ ssh -o StrictHostKeyChecking=accept-new ${INSTALL}@${INSTALL}.ssh.wpengine.net \
607
614
  wp cache flush --skip-plugins --skip-themes
608
615
  echo "❌ Deployment was ROLLED BACK to $PREV_SHA"
609
616
  exit 1 # Mark the job as failed so Slack notifies