wp-studio 1.7.10 → 1.7.11-beta1

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 (45) hide show
  1. package/dist/cli/{_events-BcapW3eh.mjs → _events-D_POEBYs.mjs} +4 -5
  2. package/dist/cli/appdata-D-luHxJU.mjs +19 -0
  3. package/dist/cli/{certificate-manager-SVYcCL_i.mjs → certificate-manager-v-yNLDFJ.mjs} +134 -15
  4. package/dist/cli/{delete-D1924O3o.mjs → delete-C4R7DV_E.mjs} +3 -3
  5. package/dist/cli/{helpers-oQuItT8n.mjs → helpers-CBl4GQzX.mjs} +2 -2
  6. package/dist/cli/{index-4lan3TI_.mjs → index-Bej4fL6n.mjs} +31 -4
  7. package/dist/cli/{index-DRQnCQvM.mjs → index-DYlrlauo.mjs} +1308 -1109
  8. package/dist/cli/{index-BjzOJKPi.mjs → index-jpyzVGrl.mjs} +90 -52
  9. package/dist/cli/{list-DOFyyV1f.mjs → list-BRllR-8H.mjs} +3 -3
  10. package/dist/cli/{login-BtPZeZ4G.mjs → login-CDVrFvfk.mjs} +3 -3
  11. package/dist/cli/{logout-Cr631QzG.mjs → logout-DLAXkbrZ.mjs} +2 -3
  12. package/dist/cli/main.mjs +2 -2
  13. package/dist/cli/{paths-CqXGLB7R.mjs → paths-D7DniT1Q.mjs} +7 -6
  14. package/dist/cli/plugin/skills/rank-me-up/SKILL.md +166 -0
  15. package/dist/cli/plugin/skills/site-spec/SKILL.md +4 -0
  16. package/dist/cli/process-manager-daemon.mjs +29 -5
  17. package/dist/cli/{process-manager-ipc-BisO0qtU.mjs → process-manager-ipc-GCdebuBH.mjs} +4 -1
  18. package/dist/cli/proxy-daemon.mjs +1 -1
  19. package/dist/cli/{prune-pm-logs-COryxqeo.mjs → prune-pm-logs-Dm_Bwi7l.mjs} +1 -1
  20. package/dist/cli/{resume-BwDwdJtq.mjs → resume-B8M2e-Ii.mjs} +4 -15
  21. package/dist/cli/{rewrite-wp-cli-post-content-2zlfFnKT.mjs → rewrite-wp-cli-post-content-Beo5_Ojo.mjs} +32 -531
  22. package/dist/cli/{set-D5eeqHbp.mjs → set-CizYT2gE.mjs} +2 -3
  23. package/dist/cli/{set-DYnzUz_G.mjs → set-_5ABvjxt.mjs} +4 -5
  24. package/dist/cli/{status-DNvMZBqD.mjs → status-D6Huwi6x.mjs} +2 -2
  25. package/dist/cli/well-known-paths-QcSJNi_l.mjs +95 -0
  26. package/dist/cli/wordpress-server-child.mjs +5 -3
  27. package/dist/cli/{wp-DD2-QiiP.mjs → wp-BevvZcM1.mjs} +2 -2
  28. package/dist/cli/wp-files/latest/available-site-translations.json +1 -1
  29. package/dist/cli/wp-files/sqlite-database-integration/admin-page.php +1 -2
  30. package/dist/cli/wp-files/sqlite-database-integration/constants.php +0 -5
  31. package/dist/cli/wp-files/sqlite-database-integration/load.php +1 -1
  32. package/dist/cli/wp-files/sqlite-database-integration/readme.txt +6 -3
  33. package/dist/cli/wp-files/sqlite-database-integration/wp-includes/database/sqlite/class-wp-pdo-mysql-on-sqlite.php +22 -3
  34. package/dist/cli/wp-files/sqlite-database-integration/wp-includes/database/version.php +1 -1
  35. package/dist/cli/wp-files/sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-db.php +41 -89
  36. package/dist/cli/wp-files/sqlite-database-integration/wp-includes/sqlite/db.php +2 -24
  37. package/dist/cli/wp-files/sqlite-database-integration/wp-includes/sqlite/install-functions.php +7 -16
  38. package/package.json +2 -1
  39. package/dist/cli/well-known-paths-BYA1Bw5o.mjs +0 -214
  40. package/dist/cli/wp-files/sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-lexer.php +0 -2575
  41. package/dist/cli/wp-files/sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-pdo-user-defined-functions.php +0 -899
  42. package/dist/cli/wp-files/sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-query-rewriter.php +0 -343
  43. package/dist/cli/wp-files/sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-token.php +0 -327
  44. package/dist/cli/wp-files/sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-translator.php +0 -4543
  45. package/dist/cli/wp-files/sqlite-database-integration/wp-includes/sqlite/php-polyfills.php +0 -68
@@ -1,17 +1,16 @@
1
- import { b as getAiSessionsDirectoryForDate, c as buildAiSessionFileName, l as listAiSessions, g as getAiSessionsRootDirectory } from "./paths-CqXGLB7R.mjs";
2
- import { S as STUDIO_SITES_ROOT, c as createRemoteSiteTools, a as createStudioTools, r as readAuthToken, b as getSnapshotsFromConfig, i as isSnapshotExpired, d as captureCommandOutput, e as runCommand$3, f as runCommand$4, h as chalk, j as getSitesRunningStatus, k as getWpComSites, l as isSiteRunning, o as openBrowser, m as getSiteUrl, n as closeSharedBrowser } from "./index-DRQnCQvM.mjs";
1
+ import { b as getAiSessionsDirectoryForDate, c as buildAiSessionFileName, l as listAiSessions, g as getAiSessionsRootDirectory } from "./paths-D7DniT1Q.mjs";
2
+ import { S as STUDIO_SITES_ROOT, c as createRemoteSiteTools, a as createStudioTools, r as readAuthToken, b as getSnapshotsFromConfig, i as isSnapshotExpired, d as captureCommandOutput, e as runCommand$3, f as runCommand$4, h as chalk, j as getSitesRunningStatus, k as getWpComSites, l as isSiteRunning, o as openBrowser, m as getSiteUrl, n as closeSharedBrowser } from "./index-DYlrlauo.mjs";
3
3
  import { __, sprintf, _n } from "@wordpress/i18n";
4
4
  import fs__default from "fs";
5
5
  import path__default from "path";
6
6
  import { query } from "@anthropic-ai/claude-agent-sdk";
7
7
  import os from "os";
8
- import { l as lockCliConfig, r as readCliConfig, s as saveCliConfig, u as unlockCliConfig, g as updateCliConfigWithPartial } from "./certificate-manager-SVYcCL_i.mjs";
8
+ import { l as lockCliConfig, r as readCliConfig, s as saveCliConfig, u as unlockCliConfig, L as LoggerError, m as updateCliConfigWithPartial, c as Logger, q as setProgressCallback } from "./certificate-manager-v-yNLDFJ.mjs";
9
9
  import { password } from "@inquirer/prompts";
10
- import { L as LoggerError, d as Logger, s as setProgressCallback } from "./well-known-paths-BYA1Bw5o.mjs";
11
10
  import crypto from "crypto";
12
11
  import fs from "fs/promises";
13
- import { runCommand as runCommand$1 } from "./login-BtPZeZ4G.mjs";
14
- import { runCommand as runCommand$2 } from "./logout-Cr631QzG.mjs";
12
+ import { runCommand as runCommand$1 } from "./login-CDVrFvfk.mjs";
13
+ import { runCommand as runCommand$2 } from "./logout-DLAXkbrZ.mjs";
15
14
  import { ProcessTerminal, TUI, Container, Loader, CombinedAutocompleteProvider, isKeyRelease, matchesKey, Text, SelectList, Input, Markdown, Editor, visibleWidth, CURSOR_MARKER, truncateToWidth } from "@mariozechner/pi-tui";
16
15
  const AI_MODELS = {
17
16
  "claude-sonnet-4-6": "Sonnet 4.6",
@@ -357,6 +356,17 @@ Then continue with:
357
356
  5. **Check the misuse of HTML blocks**: Verify if HTML blocks were used as sections or not. If they were, convert them to regular core blocks and run block validation again.
358
357
  6. **Check the result**: Use take_screenshot to capture the site's landing page on desktop and mobile and verify the design visually on both viewports, check for wrong spacing, alignment, colors, contrast, borders, hover styles and other visual issues. Fix any issues found. Pay particular attention to the navigation menu and the CTA buttons. The design needs to match your original expectations.
359
358
 
359
+ ## Working cadence
360
+
361
+ One \`Write\` or \`Edit\` per turn (read-only \`site_info\`, \`site_list\`, \`wp_cli\` queries may be combined). Short prose between tools — no long design-plan essays. The CLI only renders complete assistant messages, so a turn that batches files or emits >~200 lines spins silently for minutes and can hit gateway timeouts. Cadence is also a quality lever: the screenshot-fix loop only works after small visible increments.
362
+
363
+ **After \`site_create\`** (or "redesign"/"rebuild"/"start over" triggers), the next turn MUST be small: \`site_info\` or a single ≤50-line \`Write\`. Never scaffold a whole theme in one turn.
364
+
365
+ **Long files (>~200 lines): skeleton first, then fill across Edits.**
366
+
367
+ - \`style.css\`: skeleton = \`:root { ... }\` custom properties + 6–10 anchor comments \`/* === <concern> === */\` (e.g. \`reset\`, \`typography\`, \`hero\`, \`features\`, \`cta\`, \`footer\`, \`responsive\`), <2KB total. Fill one anchor per Edit (300–2000B each) — \`old_string\` is the anchor line, \`new_string\` is \`<anchor>\\n\\n<styles>\`.
368
+ - Page content: create the page empty (\`wp_cli post create --post_content=""\`), write \`<theme>/page-content.html\` with \`<!-- section:<concern> -->\` anchors (<1KB), fill one anchor per Edit using only core blocks (never wrap in \`core/html\`), then apply once with \`wp_cli post update <id> --post_content-file=<absolute path>\`.
369
+
360
370
  ## Available Studio Tools (prefixed with mcp__studio__)
361
371
 
362
372
  - site_create: Create a new WordPress site (name only — handles everything automatically)
@@ -373,6 +383,7 @@ Then continue with:
373
383
  - validate_blocks: Validate block content for correctness on a running site (runs each block through its save() function in a real browser). Requires a site name or path. Call after every file write/edit that contains block content.
374
384
  - take_screenshot: Take a full-page screenshot of a URL (supports desktop and mobile viewports). Use this to visually check the site after building it.
375
385
  - need_for_speed: Measure frontend performance metrics (TTFB, FCP, LCP, CLS, page weight, DOM size, JS/CSS/image/font asset breakdown) for a running site. Use this to identify performance bottlenecks and guide optimization.
386
+ - rank_me_up: Run an on-page SEO audit (title/meta tags, headings, image alt text, OpenGraph/Twitter cards, JSON-LD structured data, robots.txt and sitemap.xml availability) for a running site. Use this to identify on-page SEO issues and guide fixes.
376
387
  - site_push: Push a local site to a WordPress.com site. Requires authentication (studio auth login). Specify the remote site URL or ID and sync options (all, sqls, uploads, plugins, themes, contents).
377
388
  - site_pull: Pull a WordPress.com site to a local site. Requires authentication. Specify the remote site URL or ID and sync options.
378
389
  - site_import: Import a backup file (.zip, .tar.gz, .sql, .wpress) into a local site.
@@ -464,7 +475,7 @@ function startAiAgent(config) {
464
475
  prompt,
465
476
  env,
466
477
  model = DEFAULT_MODEL,
467
- maxTurns = 50,
478
+ maxTurns = 75,
468
479
  resume,
469
480
  autoApprove,
470
481
  activeSite,
@@ -579,6 +590,7 @@ function createBaseEnvironment() {
579
590
  delete env.ANTHROPIC_BASE_URL;
580
591
  delete env.ANTHROPIC_CUSTOM_HEADERS;
581
592
  delete env.CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS;
593
+ delete env.CLAUDE_CODE_MAX_RETRIES;
582
594
  if (!env.CLAUDE_CODE_MAX_RETRIES) {
583
595
  env.CLAUDE_CODE_MAX_RETRIES = "1";
584
596
  }
@@ -596,7 +608,7 @@ const AI_PROVIDER_DEFINITIONS = {
596
608
  }
597
609
  throw new LoggerError(__("WordPress.com login required. Use /login to authenticate."));
598
610
  },
599
- resolveEnv: async () => {
611
+ resolveEnv: async (options) => {
600
612
  const inlineToken = readInlineWpcomToken();
601
613
  const accessToken = inlineToken ?? (await readAuthToken())?.accessToken;
602
614
  if (!accessToken) {
@@ -605,10 +617,15 @@ const AI_PROVIDER_DEFINITIONS = {
605
617
  const env = createBaseEnvironment();
606
618
  env.ANTHROPIC_BASE_URL = getWpcomAiGatewayBaseUrl();
607
619
  env.ANTHROPIC_AUTH_TOKEN = accessToken;
608
- env.ANTHROPIC_CUSTOM_HEADERS = buildAnthropicCustomHeaders({
620
+ const customHeaders = {
609
621
  "X-WPCOM-AI-Feature": WPCOM_AI_FEATURE_HEADER
610
- });
622
+ };
623
+ if (options?.sessionId) {
624
+ customHeaders["X-WPCOM-Session-ID"] = options.sessionId;
625
+ }
626
+ env.ANTHROPIC_CUSTOM_HEADERS = buildAnthropicCustomHeaders(customHeaders);
611
627
  env.CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS = "1";
628
+ env.CLAUDE_CODE_MAX_RETRIES = "0";
612
629
  return env;
613
630
  }
614
631
  },
@@ -698,8 +715,8 @@ async function saveSelectedAiProvider(provider) {
698
715
  async function prepareAiProvider(provider, options) {
699
716
  await getAiProviderDefinition(provider).prepare(options);
700
717
  }
701
- async function resolveAiEnvironment(provider) {
702
- return getAiProviderDefinition(provider).resolveEnv();
718
+ async function resolveAiEnvironment(provider, options) {
719
+ return getAiProviderDefinition(provider).resolveEnv(options);
703
720
  }
704
721
  function emitEvent(event) {
705
722
  if (typeof process.send === "function") {
@@ -938,15 +955,6 @@ class AiSessionRecorder {
938
955
  wpcomSiteId: site.wpcomSiteId
939
956
  });
940
957
  }
941
- async recordEnvironmentSelected(payload) {
942
- await this.appendEvent({
943
- type: "environment.selected",
944
- timestamp: toIsoTimestamp(),
945
- environment: payload.environment,
946
- url: payload.url,
947
- wpcomSiteId: payload.wpcomSiteId
948
- });
949
- }
950
958
  async recordUserMessage(options) {
951
959
  await this.appendEvent({
952
960
  type: "user.message",
@@ -1033,25 +1041,6 @@ function replaySessionHistory(ui, events) {
1033
1041
  );
1034
1042
  continue;
1035
1043
  }
1036
- if (event.type === "environment.selected") {
1037
- const current = ui.activeSite;
1038
- if (!current) {
1039
- continue;
1040
- }
1041
- const isLive = event.environment === "live";
1042
- ui.setActiveSite(
1043
- {
1044
- name: current.name,
1045
- path: current.path,
1046
- running: current.running,
1047
- remote: isLive,
1048
- url: isLive ? event.url : void 0,
1049
- wpcomSiteId: isLive ? event.wpcomSiteId : void 0
1050
- },
1051
- { announce: true, emitEvent: false }
1052
- );
1053
- continue;
1054
- }
1055
1044
  if (event.type === "user.message") {
1056
1045
  if (!isVisibleUserMessage(event)) {
1057
1046
  continue;
@@ -1328,7 +1317,8 @@ const AI_CHAT_SLASH_COMMANDS = [
1328
1317
  handler: async () => "break"
1329
1318
  },
1330
1319
  { name: "taxonomist", description: __("Optimize category taxonomy with AI") },
1331
- { name: "need-for-speed", description: __("Run a performance audit on a site") }
1320
+ { name: "need-for-speed", description: __("Run a performance audit on a site") },
1321
+ { name: "rank-me-up", description: __("Run an on-page SEO audit on a site") }
1332
1322
  ];
1333
1323
  const THINKING_MESSAGES = [
1334
1324
  "Thinking…",
@@ -1792,6 +1782,7 @@ class AiChatUI {
1792
1782
  this.editorVisible = false;
1793
1783
  this.interruptCallback = null;
1794
1784
  this.wasInterrupted = false;
1785
+ this.usageCapReached = false;
1795
1786
  this.hasShownResponseMarker = false;
1796
1787
  this.turnStartTime = 0;
1797
1788
  this.toolStartTime = null;
@@ -2217,15 +2208,16 @@ ${chalk.dim(message)}
2217
2208
  this.refreshPromptChrome();
2218
2209
  const label = site.remote ? sprintf(
2219
2210
  /* translators: %s: site name */
2220
- __(" Selected site: %s (WordPress.com)"),
2211
+ __(" Selected site: %s (WordPress.com)"),
2221
2212
  site.name
2222
2213
  ) : sprintf(
2223
2214
  /* translators: %s: site name */
2224
- __(" Selected site: %s"),
2215
+ __(" Selected site: %s"),
2225
2216
  site.name
2226
2217
  );
2227
2218
  if (announce) {
2228
- this.messages.addChild(new Text(`${chalk.hex("#5b8db8")(label)}
2219
+ this.messages.addChild(new Text(`
2220
+ ${chalk.hex("#8839ef")(label)}
2229
2221
  `, 0, 0));
2230
2222
  }
2231
2223
  if (emitEvent2) {
@@ -2448,7 +2440,7 @@ ${chalk.dim(message)}
2448
2440
  this.tui.start();
2449
2441
  }
2450
2442
  showWelcome() {
2451
- const version = "1.7.10";
2443
+ const version = "1.7.11-beta1";
2452
2444
  const cwd = process.cwd();
2453
2445
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
2454
2446
  const displayCwd = home && cwd.startsWith(home) ? "~" + cwd.slice(home.length) : cwd;
@@ -2622,6 +2614,7 @@ ${chalk.dim(message)}
2622
2614
  this.currentResponseText = "";
2623
2615
  this.hasShownResponseMarker = false;
2624
2616
  this.wasInterrupted = false;
2617
+ this.usageCapReached = false;
2625
2618
  this.turnStartTime = this.nowMs();
2626
2619
  this.todoSnapshot = [];
2627
2620
  this.latestTodoSnapshot = [];
@@ -2648,6 +2641,15 @@ ${chalk.dim(message)}
2648
2641
  this.pendingTodoRenders.clear();
2649
2642
  this.pendingTodoRenderOrder = [];
2650
2643
  }
2644
+ /**
2645
+ * Returns true when the current/last turn surfaced the AI usage cap
2646
+ * message to the user. Lets callers suppress redundant downstream
2647
+ * errors (e.g. the SDK's "process exited with code 1" that follows
2648
+ * the upstream 429).
2649
+ */
2650
+ hasErrorBeenSurfaced() {
2651
+ return this.usageCapReached;
2652
+ }
2651
2653
  showOnboarding() {
2652
2654
  const text = " " + chalk.blue("⏺") + " " + sprintf(
2653
2655
  /* translators: %s: product name (WordPress Studio) */
@@ -3113,6 +3115,30 @@ ${chalk.dim(message)}
3113
3115
  handleMessage(message) {
3114
3116
  switch (message.type) {
3115
3117
  case "assistant": {
3118
+ let isCapMessage = false;
3119
+ if (message.error && this.currentProvider === "wpcom") {
3120
+ for (const block of message.message.content) {
3121
+ if (block.type === "text" && /API Error:\s*429/i.test(block.text ?? "")) {
3122
+ isCapMessage = true;
3123
+ break;
3124
+ }
3125
+ }
3126
+ }
3127
+ if (isCapMessage) {
3128
+ this.hideLoader();
3129
+ this.usageCapReached = true;
3130
+ this.showError(
3131
+ __(
3132
+ "AI usage cap reached. You can continue using Studio Code by switching to your own Anthropic API key."
3133
+ )
3134
+ );
3135
+ this.showInfo(
3136
+ __("Use /provider to switch to Anthropic · API key, or try again later.")
3137
+ );
3138
+ this.currentMarkdown = null;
3139
+ this.currentResponseText = "";
3140
+ return void 0;
3141
+ }
3116
3142
  for (const block of message.message.content) {
3117
3143
  if (block.type === "text") {
3118
3144
  this.hideLoader();
@@ -3195,6 +3221,9 @@ ${chalk.dim(message)}
3195
3221
  numTurns: message.num_turns
3196
3222
  };
3197
3223
  }
3224
+ if (this.usageCapReached) {
3225
+ return { type: "result", sessionId: message.session_id, success: false };
3226
+ }
3198
3227
  if (this.wasInterrupted) {
3199
3228
  const thinkingSec2 = Math.round((this.nowMs() - this.turnStartTime) / 1e3);
3200
3229
  this.messages.addChild(
@@ -3466,6 +3495,9 @@ async function runCommand(options) {
3466
3495
  }
3467
3496
  function handleAgentTurnError(error) {
3468
3497
  sessionId = void 0;
3498
+ if (ui instanceof AiChatUI && ui.hasErrorBeenSurfaced()) {
3499
+ return;
3500
+ }
3469
3501
  if (error instanceof LoggerError) {
3470
3502
  ui.showError(error.message);
3471
3503
  } else if (error instanceof Error) {
@@ -3588,7 +3620,10 @@ async function runCommand(options) {
3588
3620
  const MAX_RETRY_ATTEMPTS = 4;
3589
3621
  async function runAgentTurn(prompt, retryAttempt = 0) {
3590
3622
  await maybeAutoSwitchProvider();
3591
- const env = await resolveAiEnvironment(currentProvider);
3623
+ const recorder = await ensureSessionRecorder();
3624
+ const env = await resolveAiEnvironment(currentProvider, {
3625
+ sessionId: recorder?.sessionId
3626
+ });
3592
3627
  ui.beginAgentTurn();
3593
3628
  let enrichedPrompt = prompt;
3594
3629
  const site = ui.activeSite;
@@ -3608,7 +3643,7 @@ ${prompt}`;
3608
3643
  }
3609
3644
  await persistSessionContext();
3610
3645
  await persist(
3611
- (recorder) => recorder.recordUserMessage({
3646
+ (recorder2) => recorder2.recordUserMessage({
3612
3647
  text: prompt,
3613
3648
  source: "prompt",
3614
3649
  sitePath: site?.path
@@ -3633,10 +3668,10 @@ ${prompt}`;
3633
3668
  for await (const message of agentQuery) {
3634
3669
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
3635
3670
  const result = ui.handleMessage(message);
3636
- await persist((recorder) => recorder.recordSdkMessage(message, timestamp));
3671
+ await persist((recorder2) => recorder2.recordSdkMessage(message, timestamp));
3637
3672
  if (result) {
3638
3673
  sessionId = result.sessionId;
3639
- await persist((recorder) => recorder.recordAgentSessionId(result.sessionId));
3674
+ await persist((recorder2) => recorder2.recordAgentSessionId(result.sessionId));
3640
3675
  if (result.type === "max_turns") {
3641
3676
  maxTurnsResult = {
3642
3677
  numTurns: result.numTurns
@@ -3654,9 +3689,11 @@ ${prompt}`;
3654
3689
  if (isJsonMode) {
3655
3690
  throw error;
3656
3691
  }
3657
- ui.showError(getErrorMessage(error));
3692
+ if (!(ui instanceof AiChatUI && ui.hasErrorBeenSurfaced())) {
3693
+ ui.showError(getErrorMessage(error));
3694
+ }
3658
3695
  } finally {
3659
- await persist((recorder) => recorder.recordTurnClosed(turnStatus));
3696
+ await persist((recorder2) => recorder2.recordTurnClosed(turnStatus));
3660
3697
  ui.endAgentTurn();
3661
3698
  }
3662
3699
  if (maxTurnsResult) {
@@ -3682,7 +3719,8 @@ ${prompt}`;
3682
3719
  return runAgentTurn("Continue from where you left off.");
3683
3720
  }
3684
3721
  }
3685
- if (turnStatus === "error" && !isJsonMode) {
3722
+ const hasTerminalError = ui instanceof AiChatUI && ui.hasErrorBeenSurfaced();
3723
+ if (turnStatus === "error" && !isJsonMode && !hasTerminalError) {
3686
3724
  if (retryAttempt >= MAX_RETRY_ATTEMPTS) {
3687
3725
  ui.showInfo(
3688
3726
  __("The server has not recovered after multiple attempts. Please try again later.")
@@ -1,7 +1,7 @@
1
- import { l as listAiSessions, g as getAiSessionsRootDirectory } from "./paths-CqXGLB7R.mjs";
1
+ import { l as listAiSessions, g as getAiSessionsRootDirectory } from "./paths-D7DniT1Q.mjs";
2
2
  import { __ } from "@wordpress/i18n";
3
- import { d as displaySessionsCompact } from "./helpers-oQuItT8n.mjs";
4
- import { L as LoggerError, d as Logger } from "./well-known-paths-BYA1Bw5o.mjs";
3
+ import { d as displaySessionsCompact } from "./helpers-CBl4GQzX.mjs";
4
+ import { L as LoggerError, c as Logger } from "./certificate-manager-v-yNLDFJ.mjs";
5
5
  const logger = new Logger();
6
6
  async function runCommand(format) {
7
7
  const sessions = await listAiSessions(getAiSessionsRootDirectory());
@@ -1,7 +1,7 @@
1
1
  import { input } from "@inquirer/prompts";
2
- import { z as PROTOCOL_PREFIX, B as CLIENT_ID, d as Logger, L as LoggerError, E as DEFAULT_TOKEN_LIFETIME_MS } from "./well-known-paths-BYA1Bw5o.mjs";
3
- import { A as AUTH_EVENTS } from "./certificate-manager-SVYcCL_i.mjs";
4
- import { r as readAuthToken, A as AuthCommandLoggerAction, p as getAppLocale, o as openBrowser, g as getUserInfo, u as updateSharedConfig, q as emitCliEvent } from "./index-DRQnCQvM.mjs";
2
+ import { s as PROTOCOL_PREFIX, t as CLIENT_ID, u as DEFAULT_TOKEN_LIFETIME_MS } from "./well-known-paths-QcSJNi_l.mjs";
3
+ import { c as Logger, L as LoggerError, A as AUTH_EVENTS } from "./certificate-manager-v-yNLDFJ.mjs";
4
+ import { r as readAuthToken, A as AuthCommandLoggerAction, p as getAppLocale, o as openBrowser, g as getUserInfo, u as updateSharedConfig, q as emitCliEvent } from "./index-DYlrlauo.mjs";
5
5
  import { __, sprintf } from "@wordpress/i18n";
6
6
  const SCOPES = "global";
7
7
  const REDIRECT_URI = `${PROTOCOL_PREFIX}://auth`;
@@ -1,7 +1,6 @@
1
- import { A as AUTH_EVENTS } from "./certificate-manager-SVYcCL_i.mjs";
2
- import { A as AuthCommandLoggerAction, r as readAuthToken, s as revokeAuthToken, u as updateSharedConfig, q as emitCliEvent } from "./index-DRQnCQvM.mjs";
1
+ import { c as Logger, L as LoggerError, A as AUTH_EVENTS } from "./certificate-manager-v-yNLDFJ.mjs";
2
+ import { A as AuthCommandLoggerAction, r as readAuthToken, s as revokeAuthToken, u as updateSharedConfig, q as emitCliEvent } from "./index-DYlrlauo.mjs";
3
3
  import { __ } from "@wordpress/i18n";
4
- import { d as Logger, L as LoggerError } from "./well-known-paths-BYA1Bw5o.mjs";
5
4
  async function runCommand() {
6
5
  const logger = new Logger();
7
6
  logger.reportStart(AuthCommandLoggerAction.LOGOUT, __("Logging out…"));
package/dist/cli/main.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import "node:path";
3
- import "./index-DRQnCQvM.mjs";
3
+ import "./index-DYlrlauo.mjs";
4
4
  import "@wordpress/i18n";
5
5
  import "semver";
6
6
  import "yargs";
7
- import "./certificate-manager-SVYcCL_i.mjs";
7
+ import "./certificate-manager-v-yNLDFJ.mjs";
@@ -1,7 +1,7 @@
1
1
  import "crypto";
2
2
  import fs from "fs/promises";
3
3
  import path__default from "path";
4
- import { G as getAppdataDirectory } from "./rewrite-wp-cli-post-content-2zlfFnKT.mjs";
4
+ import { g as getAppdataDirectory } from "./appdata-D-luHxJU.mjs";
5
5
  const SESSION_FILE_EXTENSION = ".jsonl";
6
6
  const SESSION_ID_REGEX = /\b([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\b/i;
7
7
  function buildAiSessionFileName(startedAt, sessionId) {
@@ -35,6 +35,7 @@ async function readAiSessionSummaryFromEvents(filePath, events) {
35
35
  let ownerSiteName;
36
36
  let selectedSiteName;
37
37
  let activeEnvironment = "local";
38
+ let lastSelectedWpcomSiteId;
38
39
  let endReason;
39
40
  let eventCount = 0;
40
41
  for (const event of events) {
@@ -51,14 +52,13 @@ async function readAiSessionSummaryFromEvents(filePath, events) {
51
52
  }
52
53
  if (event.type === "site.selected") {
53
54
  selectedSiteName = event.siteName;
54
- if (ownerSitePath === void 0) {
55
+ const isLive = event.remote === true;
56
+ activeEnvironment = isLive ? "live" : "local";
57
+ lastSelectedWpcomSiteId = isLive ? event.wpcomSiteId : void 0;
58
+ if (ownerSitePath === void 0 && !isLive) {
55
59
  ownerSitePath = event.sitePath;
56
60
  ownerSiteName = event.siteName;
57
61
  }
58
- activeEnvironment = event.remote === true ? "live" : "local";
59
- }
60
- if (event.type === "environment.selected") {
61
- activeEnvironment = event.environment;
62
62
  }
63
63
  if (event.type === "user.message" && event.source === "prompt" && !firstPrompt) {
64
64
  firstPrompt = event.text;
@@ -85,6 +85,7 @@ async function readAiSessionSummaryFromEvents(filePath, events) {
85
85
  ownerSiteName,
86
86
  selectedSiteName,
87
87
  activeEnvironment,
88
+ lastSelectedWpcomSiteId,
88
89
  endReason,
89
90
  eventCount
90
91
  };
@@ -0,0 +1,166 @@
1
+ ---
2
+ name: rank-me-up
3
+ description: Run an on-page SEO audit on a WordPress site and get actionable recommendations to improve search visibility.
4
+ user-invokable: true
5
+ ---
6
+
7
+ # SEO Audit
8
+
9
+ Run an on-page SEO audit on a WordPress site to surface missing meta tags, broken heading structure, alt-text gaps, missing structured data, and indexing issues — then provide concrete, ordered fixes.
10
+
11
+ ## How to Run
12
+
13
+ 1. Determine which site to audit. If the user hasn't specified, ask them or use the site from the current context.
14
+ 2. Ensure the site is running (use `site_start` if needed).
15
+ 3. Call `rank_me_up` with the site name and path (defaults to `/`).
16
+ 4. **Inspect the SEO toolchain already on the site** (mandatory before suggesting plugin installs — see "Check Active Plugins First" below).
17
+ 5. Analyze the results using the interpretation guide.
18
+ 6. Present a prioritized list of fixes — most impactful first — with the specific change to make, routed through whatever plugin is already active.
19
+
20
+ ## Check Active Plugins First
21
+
22
+ The audit only sees rendered HTML — it can't tell *why* a tag is missing. Before recommending an install, find out what's already wired up. Skipping this step leads to dumb suggestions like "install Jetpack" when Jetpack is already active and just needs a module enabled.
23
+
24
+ **Step 1 — list active plugins:**
25
+
26
+ ```
27
+ wp_cli: plugin list --status=active --field=name
28
+ ```
29
+
30
+ Look for known SEO providers:
31
+
32
+ | Slug | Plugin |
33
+ |------|--------|
34
+ | `jetpack` | Jetpack (preferred — see below) |
35
+ | `wordpress-seo` | Yoast SEO |
36
+ | `seo-by-rank-math` | Rank Math |
37
+ | `all-in-one-seo-pack` | All in One SEO |
38
+ | `wp-seopress` | SEOPress |
39
+ | `the-seo-framework` | The SEO Framework |
40
+
41
+ **Step 2 — if Jetpack is active, list its enabled modules** (Jetpack is modular; SEO features live in the `seo-tools` module and don't run unless that specific module is on):
42
+
43
+ ```
44
+ wp_cli: jetpack module list
45
+ ```
46
+
47
+ Then map the audit findings:
48
+
49
+ - **Jetpack active + `seo-tools` module active** → don't install anything. Tell the user the issue is in *configuration* (e.g. empty default title format, missing per-post meta description). Point them at **Jetpack → Settings → Traffic → SEO Tools** in `wp-admin`.
50
+ - **Jetpack active + `seo-tools` module inactive** → enable the module instead of installing a new plugin:
51
+ ```
52
+ wp_cli: jetpack module activate seo-tools
53
+ ```
54
+ Then re-run `rank_me_up` to confirm the tags appear.
55
+ - **Another SEO plugin active** → don't recommend installing Jetpack on top of it (two SEO plugins fight over the same `<head>` tags). Surface the *settings page* for the active plugin instead — e.g. for Yoast: **SEO → Search Appearance**; for Rank Math: **Rank Math → Titles & Meta**.
56
+ - **No SEO plugin active** → recommend installing Jetpack. See "Recommended SEO Plugin" below.
57
+
58
+ **Also worth checking for non-plugin fixes:**
59
+
60
+ ```
61
+ wp_cli: option get blog_public
62
+ ```
63
+
64
+ If it returns `0`, the site has "Discourage search engines" enabled — no plugin can override that. Fix with `wp option update blog_public 1`.
65
+
66
+ ## Interpreting Results
67
+
68
+ ### Title and Meta Description
69
+
70
+ | Field | Recommended | Issue |
71
+ |-------|-------------|-------|
72
+ | `title` | 30–60 chars | Missing, too short (< 20), or too long (> 70) gets truncated in SERPs |
73
+ | `description` | 70–160 chars | Missing means Google generates one for you, often poorly |
74
+ | `canonical` | Self-referencing absolute URL | Missing causes duplicate-content risk |
75
+ | `robots` | absent or `index, follow` | `noindex` blocks the page from search results |
76
+ | `viewport` | `width=device-width, initial-scale=1` | Missing breaks mobile usability ranking signal |
77
+ | `htmlLang` | Present (e.g. `en-US`) | Missing hurts internationalization and accessibility |
78
+
79
+ ### Headings
80
+
81
+ - **Exactly 1 `h1`** per page. 0 means the page has no clear topic; 2+ dilutes the signal.
82
+ - **No skipped levels** (e.g. `h2 → h4`). Skipped levels confuse screen readers and weaken topical structure.
83
+ - The `h1` text should reflect the page's primary search intent.
84
+
85
+ ### Images
86
+
87
+ - Every `img` should have an `alt` attribute. `withoutAlt` (missing entirely) is worse than `emptyAlt` (decorative).
88
+ - Empty `alt=""` is correct only for decorative images. Content images need descriptive alt text.
89
+ - Use the `missingAltUrls` list to identify which images to fix first.
90
+
91
+ ### Social Tags (Open Graph & Twitter)
92
+
93
+ Required for good link previews on social media:
94
+ - **Open Graph**: `og:title`, `og:description`, `og:image`, `og:url`, `og:type`
95
+ - **Twitter**: `twitter:card` (typically `summary_large_image`), `twitter:title`, `twitter:description`, `twitter:image`
96
+
97
+ Missing OG tags don't hurt rankings directly but kill click-through from social shares.
98
+
99
+ ### Structured Data (JSON-LD)
100
+
101
+ - `jsonLdCount: 0` means no rich-result eligibility. Add at minimum `WebSite` and `Organization` schema sitewide.
102
+ - For specific page types, add `Article`, `Product`, `FAQPage`, `BreadcrumbList`, `LocalBusiness`, etc.
103
+
104
+ ### Resources
105
+
106
+ - **`robotsTxtFound: false`**: WordPress generates a virtual one — usually fine, but a real file gives more control.
107
+ - **`robotsTxtBlocksAll: true`**: CRITICAL — `Disallow: /` blocks all crawlers. Often a forgotten "Discourage search engines" setting in WP Admin → Reading.
108
+ - **`sitemapFound: false`**: Install Jetpack and enable its SEO Tools module, or enable WP core's sitemap (`/wp-sitemap.xml`). See "Recommended SEO Plugin" below.
109
+
110
+ ### Links
111
+
112
+ - Many `emptyText` links (anchors with no visible text, no `aria-label`, no alt-text image) hurt accessibility and SEO. Use the `emptyTextHrefs` list to fix.
113
+ - A high `nofollow` count on internal links wastes link equity — internal links should generally be `dofollow`.
114
+
115
+ ## Recommended SEO Plugin: Jetpack
116
+
117
+ **Only reach this section after running the "Check Active Plugins First" step and confirming no SEO plugin is active.**
118
+
119
+ **Install, activate, and enable the SEO module:**
120
+
121
+ ```
122
+ wp_cli: plugin install jetpack --activate
123
+ wp_cli: jetpack module activate seo-tools
124
+ ```
125
+
126
+ **What the `seo-tools` module covers:**
127
+
128
+ - Custom page title formats (front page, post, page, archive, etc.)
129
+ - Custom meta descriptions per post/page
130
+ - Open Graph + Twitter Card tags (including `twitter:card`, `og:image`, etc.)
131
+ - XML sitemap at `/sitemap.xml` (auto-submitted to WordPress.com)
132
+ - Verification tag support (Google, Bing, Pinterest, Yandex)
133
+
134
+ **Plan note:** Jetpack's *Advanced SEO Tools* (custom title formats + per-post meta descriptions) require a paid Jetpack plan (Security, Complete, or the standalone Jetpack SEO add-on). Basic social meta + sitemap are available on the free tier. If the user is on free and needs per-post meta descriptions, tell them upfront so they can decide whether to upgrade.
135
+
136
+ **Fallback:** Only suggest alternatives (Yoast SEO, Rank Math, AIOSEO) if the user explicitly rejects Jetpack.
137
+
138
+ ## Common WordPress Recommendations
139
+
140
+ Based on the metrics, suggest specific actions. Route each fix through whatever you discovered in "Check Active Plugins First" — don't recommend installing a plugin that's already active or telling the user to configure one that isn't.
141
+
142
+ - **Missing title/description**:
143
+ - *Jetpack active + `seo-tools` on*: user needs to *configure* title formats at **Jetpack → Settings → Traffic → SEO Tools** and fill meta descriptions per-post (paid plan required for per-post).
144
+ - *Jetpack active + `seo-tools` off*: `wp_cli: jetpack module activate seo-tools`.
145
+ - *Another SEO plugin active*: point to its title/meta settings page — don't install Jetpack on top.
146
+ - *Nothing active*: install Jetpack (see "Recommended SEO Plugin" above).
147
+ - *Plugins refused*: set the title via theme `wp_title()` and description via a `<meta>` tag in `header.php` / a block theme template part.
148
+ - **Missing canonical**: Any SEO plugin (Jetpack, Yoast, Rank Math, AIOSEO) handles this automatically once active. Without one, add `<link rel="canonical" href="<?php echo esc_url( wp_get_canonical_url() ); ?>">` to the head.
149
+ - **Wrong h1 count**: Audit the active block theme's templates — site title is often wrapped in an `h1` on every page. Use `h2` for the site title on inner pages, reserve `h1` for the page title. This is a theme fix; no SEO plugin will correct it.
150
+ - **Missing alt text**: Content fix, not a plugin fix. Run `wp media list --field=ID` and update via `wp post update`, or train content editors to add alt text on upload.
151
+ - **Missing OG / Twitter tags**:
152
+ - *Jetpack active + `seo-tools` on*: OG/Twitter should already be emitted — re-check the HTML; if still missing, inspect for a conflicting plugin stripping them.
153
+ - *Jetpack active + `seo-tools` off*: enable the module.
154
+ - *Another SEO plugin active*: enable its social-meta feature (Yoast: **SEO → Social**; Rank Math: **Rank Math → Titles & Meta → Social Meta**).
155
+ - *Nothing active*: install Jetpack, or hook into `wp_head` to emit tags manually.
156
+ - **No JSON-LD**: WordPress core does not emit JSON-LD. Jetpack adds basic `WebSite` / `SearchAction` markup when the `seo-tools` module is on. For richer schemas (Product, FAQ, Article, BreadcrumbList) use a dedicated schema plugin (e.g. Yoast/Rank Math handle these) or custom `wp_head` output.
157
+ - **`robotsTxtBlocksAll: true`**: Run `wp_cli: option update blog_public 1` to undo the "Discourage search engines" setting. No plugin can override this.
158
+ - **Missing sitemap**: Verify `blog_public` is `1` first. Then check `/wp-sitemap.xml` (WP core since 5.5) or `/sitemap.xml` (Jetpack). If Jetpack is active but the sitemap is missing, enable the `seo-tools` module. If another SEO plugin is active, it may have disabled core sitemaps in favor of its own — check its sitemap settings.
159
+ - **Missing viewport meta**: Theme bug — add `<meta name="viewport" content="width=device-width, initial-scale=1">` in the `<head>`. Not a plugin concern.
160
+ - **Missing `htmlLang`**: Theme should output `<html <?php language_attributes(); ?>>` (block themes do this automatically; classic themes need it added). Not a plugin concern.
161
+
162
+ ## Important Notes
163
+
164
+ - This is an **on-page audit of a local dev site**. It does NOT measure rankings, traffic, backlinks, or Core Web Vitals (use `need_for_speed` for performance signals).
165
+ - Search engines won't crawl a local Studio site, so this is purely about preparing the site for production. Pair fixes with `site_push` or `preview_create` to validate on a public URL.
166
+ - For content-quality SEO (keyword targeting, search intent, content depth), the audit can only flag structural gaps — not whether the content is *good*. Combine with editorial review.
@@ -27,6 +27,10 @@ After the user provides the name, use AskUserQuestion for:
27
27
 
28
28
  Call `site_create` with the provided name and use the layout preference to guide all subsequent design decisions.
29
29
 
30
+ ## After site_create returns
31
+
32
+ The turn immediately after `site_create` is the biggest source of perceived hangs. Acknowledge the site in ≤2 lines of prose, then make your next tool call a small one — `site_info`, or a single ≤50-line first `Write`. Do NOT scaffold the theme, chain multiple Writes, or write a long design-plan essay in this turn. Grow the build across many small turns (see the "Working cadence" section of the system prompt).
33
+
30
34
  ## When to Skip Discovery
31
35
 
32
36
  Do NOT ask questions if: