wp-studio 1.7.10 → 1.7.11

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-B8xQ_baD.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-BG-E-HsW.mjs} +3 -3
  5. package/dist/cli/{helpers-oQuItT8n.mjs → helpers-CIAgfdq8.mjs} +2 -2
  6. package/dist/cli/{index-4lan3TI_.mjs → index-Bej4fL6n.mjs} +31 -4
  7. package/dist/cli/{index-BjzOJKPi.mjs → index-Dhun0W1n.mjs} +90 -52
  8. package/dist/cli/{index-DRQnCQvM.mjs → index-ul3DeWvy.mjs} +1308 -1109
  9. package/dist/cli/{list-DOFyyV1f.mjs → list-ck0oK4vb.mjs} +3 -3
  10. package/dist/cli/{login-BtPZeZ4G.mjs → login-Dz63Zdfn.mjs} +3 -3
  11. package/dist/cli/{logout-Cr631QzG.mjs → logout-CLUKQeZh.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-BSIOJnyM.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-CtDZnARG.mjs} +2 -3
  23. package/dist/cli/{set-DYnzUz_G.mjs → set-PJvs-Yw5.mjs} +4 -5
  24. package/dist/cli/{status-DNvMZBqD.mjs → status-DU07aAtD.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-_X-h-yuW.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,11 +1,10 @@
1
1
  import fs__default from "fs";
2
- import { o as socketEventSchema, j as SITE_EVENTS, S as SNAPSHOT_EVENTS, A as AUTH_EVENTS, r as readCliConfig, p as siteDetailsSchema } from "./certificate-manager-SVYcCL_i.mjs";
3
- import { v as isEmptyDir } from "./rewrite-wp-cli-post-content-2zlfFnKT.mjs";
2
+ import { L as LoggerError, c as Logger, x as socketEventSchema, S as SITE_EVENTS, f as SNAPSHOT_EVENTS, A as AUTH_EVENTS, r as readCliConfig, y as siteDetailsSchema } from "./certificate-manager-v-yNLDFJ.mjs";
3
+ import { o as isEmptyDir } from "./rewrite-wp-cli-post-content-Beo5_Ojo.mjs";
4
4
  import { s as sequential } from "./sequential-BQFuixXz.mjs";
5
- import { M as SITE_EVENTS_SOCKET_PATH, w as SiteCommandLoggerAction, B as connectToDaemon, N as getDaemonBus, O as SITE_PROCESS_PREFIX, J as disconnectFromDaemon, Q as removeSiteFromConfig, m as getSiteUrl, l as isSiteRunning } from "./index-DRQnCQvM.mjs";
5
+ import { M as SITE_EVENTS_SOCKET_PATH, w as SiteCommandLoggerAction, B as connectToDaemon, N as getDaemonBus, O as SITE_PROCESS_PREFIX, J as disconnectFromDaemon, Q as removeSiteFromConfig, m as getSiteUrl, l as isSiteRunning } from "./index-ul3DeWvy.mjs";
6
6
  import { __ } from "@wordpress/i18n";
7
- import { g as SocketServer } from "./process-manager-ipc-BisO0qtU.mjs";
8
- import { L as LoggerError, d as Logger } from "./well-known-paths-BYA1Bw5o.mjs";
7
+ import { g as SocketServer } from "./process-manager-ipc-GCdebuBH.mjs";
9
8
  const logger = new Logger();
10
9
  function toSiteDetails(site) {
11
10
  return siteDetailsSchema.parse({
@@ -0,0 +1,19 @@
1
+ import os from "os";
2
+ import path__default from "path";
3
+ import { __ } from "@wordpress/i18n";
4
+ import { L as LoggerError } from "./certificate-manager-v-yNLDFJ.mjs";
5
+ function getAppdataDirectory() {
6
+ if (process.env.E2E && process.env.E2E_APP_DATA_PATH) {
7
+ return path__default.join(process.env.E2E_APP_DATA_PATH, "Studio");
8
+ }
9
+ if (process.platform === "win32") {
10
+ if (!process.env.APPDATA) {
11
+ throw new LoggerError(__("Studio config file path not found."));
12
+ }
13
+ return path__default.join(process.env.APPDATA, "Studio");
14
+ }
15
+ return path__default.join(os.homedir(), "Library", "Application Support", "Studio");
16
+ }
17
+ export {
18
+ getAppdataDirectory as g
19
+ };
@@ -1,6 +1,6 @@
1
1
  import fs__default from "fs";
2
2
  import path__default from "path";
3
- import { i as commonjsGlobal, g as getDefaultExportFromCjs, k as getConfigDirectory, C as CLI_CONFIG_LOCKFILE_NAME, u as getCliConfigPath, L as LoggerError, a as LOCKFILE_STALE_TIME, b as LOCKFILE_WAIT_TIME, v as getCertificatesPath, w as CERT_UNTRUSTED_ROOT, x as SERVER_AUTH_OID } from "./well-known-paths-BYA1Bw5o.mjs";
3
+ import { f as getConfigDirectory, C as CLI_CONFIG_LOCKFILE_NAME, n as getCliConfigPath, L as LOCKFILE_STALE_TIME, a as LOCKFILE_WAIT_TIME, o as getCertificatesPath, p as CERT_UNTRUSTED_ROOT, q as SERVER_AUTH_OID } from "./well-known-paths-QcSJNi_l.mjs";
4
4
  import { z } from "zod";
5
5
  import { execFile } from "child_process";
6
6
  import require$$1 from "constants";
@@ -10,6 +10,7 @@ import require$$0 from "assert";
10
10
  import require$$2 from "events";
11
11
  import { __ } from "@wordpress/i18n";
12
12
  import { readFile, writeFile } from "atomically";
13
+ import { Spinner } from "picospinner";
13
14
  import { execFile as execFile$1 } from "node:child_process";
14
15
  import crypto from "node:crypto";
15
16
  import fs from "node:fs";
@@ -18,6 +19,113 @@ import { domainToASCII } from "node:url";
18
19
  import { promisify } from "node:util";
19
20
  import sudo from "@vscode/sudo-prompt";
20
21
  import forge from "node-forge";
22
+ const isIpcMode = Boolean(process.send);
23
+ let progressCallback = null;
24
+ function setProgressCallback(callback) {
25
+ progressCallback = callback;
26
+ }
27
+ function getProgressCallback() {
28
+ return progressCallback;
29
+ }
30
+ function emitProgress(message) {
31
+ progressCallback?.(message);
32
+ }
33
+ function canSend() {
34
+ return isIpcMode && !!process.send && process.connected;
35
+ }
36
+ class LoggerError extends Error {
37
+ constructor(message, previousError) {
38
+ super();
39
+ this.name = "LoggerError";
40
+ this.errorMessage = message;
41
+ if (previousError instanceof Error) {
42
+ this.previousError = previousError;
43
+ }
44
+ }
45
+ get message() {
46
+ if (this.previousError) {
47
+ return `${this.errorMessage}: ${this.previousError.message}`;
48
+ }
49
+ return this.errorMessage;
50
+ }
51
+ }
52
+ class Logger {
53
+ constructor() {
54
+ this.currentAction = null;
55
+ this.spinner = new Spinner();
56
+ }
57
+ reportStart(action, message) {
58
+ this.currentAction = action;
59
+ if (canSend()) {
60
+ process.send({ action, status: "inprogress", message });
61
+ } else if (progressCallback) {
62
+ progressCallback(message);
63
+ } else {
64
+ this.spinner.setText(message);
65
+ if (!this.spinner.running) {
66
+ this.spinner.start();
67
+ }
68
+ }
69
+ }
70
+ reportProgress(message) {
71
+ if (canSend()) {
72
+ process.send({ action: this.currentAction, status: "inprogress", message });
73
+ } else if (progressCallback) {
74
+ progressCallback(message, true);
75
+ } else {
76
+ if (!this.spinner.running) {
77
+ this.spinner.start();
78
+ }
79
+ this.spinner.setText(message);
80
+ }
81
+ }
82
+ reportSuccess(message) {
83
+ if (canSend()) {
84
+ process.send({ action: this.currentAction, status: "success", message });
85
+ } else if (progressCallback) {
86
+ progressCallback(message);
87
+ } else {
88
+ if (!this.spinner.running) {
89
+ this.spinner.start();
90
+ }
91
+ this.spinner.succeed(message);
92
+ }
93
+ this.currentAction = null;
94
+ }
95
+ reportWarning(message) {
96
+ if (canSend()) {
97
+ process.send({ action: this.currentAction, status: "warning", message });
98
+ } else if (progressCallback) {
99
+ progressCallback(message);
100
+ } else {
101
+ if (!this.spinner.running) {
102
+ this.spinner.start();
103
+ }
104
+ this.spinner.warn(message);
105
+ }
106
+ }
107
+ reportError(error, isFatal = true) {
108
+ if (isFatal) {
109
+ process.exitCode = 1;
110
+ }
111
+ if (canSend()) {
112
+ process.send({ action: this.currentAction, status: "fail", message: error.message });
113
+ } else if (progressCallback) {
114
+ progressCallback(error.message);
115
+ } else {
116
+ if (!this.spinner.running) {
117
+ this.spinner.start();
118
+ }
119
+ this.spinner.fail(error.message);
120
+ }
121
+ this.currentAction = null;
122
+ }
123
+ reportKeyValuePair(key, value) {
124
+ if (canSend()) {
125
+ process.send({ action: "keyValuePair", key, value });
126
+ }
127
+ }
128
+ }
21
129
  const authTokenSchema = z.object({
22
130
  accessToken: z.string(),
23
131
  expiresIn: z.number(),
@@ -171,6 +279,10 @@ async function isDirectoryHiddenOnWindows(dirPath) {
171
279
  });
172
280
  });
173
281
  }
282
+ var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
283
+ function getDefaultExportFromCjs(x) {
284
+ return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
285
+ }
174
286
  var lockfile$1 = {};
175
287
  var signalExit = { exports: {} };
176
288
  var signals = { exports: {} };
@@ -1005,24 +1117,31 @@ function deleteSiteCertificate(domain) {
1005
1117
  }
1006
1118
  export {
1007
1119
  AUTH_EVENTS as A,
1008
- SNAPSHOT_EVENTS as S,
1120
+ LoggerError as L,
1121
+ SITE_EVENTS as S,
1009
1122
  lockFileAsync as a,
1010
1123
  unlockFileAsync as b,
1011
- authTokenSchema as c,
1012
- requireSignalExit as d,
1013
- StatsMetric as e,
1014
- StatsGroup as f,
1015
- updateCliConfigWithPartial as g,
1124
+ Logger as c,
1125
+ commonjsGlobal as d,
1126
+ authTokenSchema as e,
1127
+ SNAPSHOT_EVENTS as f,
1128
+ getDefaultExportFromCjs as g,
1016
1129
  hideDirectoryOnWindows as h,
1017
- generateSiteCertificate as i,
1018
- SITE_EVENTS as j,
1019
- deleteSiteCertificate as k,
1130
+ requireSignalExit as i,
1131
+ StatsMetric as j,
1132
+ StatsGroup as k,
1020
1133
  lockCliConfig as l,
1021
- updateCheckSchema as m,
1022
- isDirectoryHiddenOnWindows as n,
1023
- socketEventSchema as o,
1024
- siteDetailsSchema as p,
1134
+ updateCliConfigWithPartial as m,
1135
+ generateSiteCertificate as n,
1136
+ deleteSiteCertificate as o,
1137
+ emitProgress as p,
1138
+ setProgressCallback as q,
1025
1139
  readCliConfig as r,
1026
1140
  saveCliConfig as s,
1027
- unlockCliConfig as u
1141
+ getProgressCallback as t,
1142
+ unlockCliConfig as u,
1143
+ updateCheckSchema as v,
1144
+ isDirectoryHiddenOnWindows as w,
1145
+ socketEventSchema as x,
1146
+ siteDetailsSchema as y
1028
1147
  };
@@ -1,7 +1,7 @@
1
- import { l as listAiSessions, g as getAiSessionsRootDirectory, d as deleteAiSession } from "./paths-CqXGLB7R.mjs";
1
+ import { l as listAiSessions, g as getAiSessionsRootDirectory, d as deleteAiSession } from "./paths-D7DniT1Q.mjs";
2
2
  import { __ } from "@wordpress/i18n";
3
- import { c as chooseSessionForAction } from "./helpers-oQuItT8n.mjs";
4
- import { L as LoggerError, d as Logger } from "./well-known-paths-BYA1Bw5o.mjs";
3
+ import { c as chooseSessionForAction } from "./helpers-CIAgfdq8.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(sessionIdOrPrefix) {
7
7
  let resolvedSessionIdOrPrefix = sessionIdOrPrefix?.trim();
@@ -1,7 +1,7 @@
1
1
  import { select } from "@inquirer/prompts";
2
2
  import { visibleWidth, truncateToWidth } from "@mariozechner/pi-tui";
3
- import { l as listAiSessions, g as getAiSessionsRootDirectory } from "./paths-CqXGLB7R.mjs";
4
- import { h as chalk } from "./index-DRQnCQvM.mjs";
3
+ import { l as listAiSessions, g as getAiSessionsRootDirectory } from "./paths-D7DniT1Q.mjs";
4
+ import { h as chalk } from "./index-ul3DeWvy.mjs";
5
5
  import { __ } from "@wordpress/i18n";
6
6
  function formatSessionTimestamp(timestamp) {
7
7
  const parsed = Date.parse(timestamp);
@@ -2,11 +2,11 @@ import { execFile } from "child_process";
2
2
  import fs__default from "fs";
3
3
  import path__default from "path";
4
4
  import { confirm } from "@inquirer/prompts";
5
- import { y as getAppConfigPath, k as getConfigDirectory } from "./well-known-paths-BYA1Bw5o.mjs";
5
+ import { r as getAppConfigPath, f as getConfigDirectory, g as getServerFilesPath } from "./well-known-paths-QcSJNi_l.mjs";
6
6
  import { __ } from "@wordpress/i18n";
7
- import { G as getAppdataDirectory } from "./rewrite-wp-cli-post-content-2zlfFnKT.mjs";
7
+ import { g as getAppdataDirectory } from "./appdata-D-luHxJU.mjs";
8
8
  import fs from "node:fs";
9
- import { h as hideDirectoryOnWindows, n as isDirectoryHiddenOnWindows } from "./certificate-manager-SVYcCL_i.mjs";
9
+ import { h as hideDirectoryOnWindows, w as isDirectoryHiddenOnWindows } from "./certificate-manager-v-yNLDFJ.mjs";
10
10
  import os from "node:os";
11
11
  import path from "node:path";
12
12
  function isInstalledOnMacOs() {
@@ -134,10 +134,37 @@ const renameProcessManagerHome = {
134
134
  fs.renameSync(getLegacyPmHome(), getNewPmHome());
135
135
  }
136
136
  };
137
+ function getObsoletePaths() {
138
+ const serverFilesPath = getServerFilesPath();
139
+ return [
140
+ path.join(serverFilesPath, "skills"),
141
+ path.join(serverFilesPath, "sqlite-database-integration"),
142
+ path.join(serverFilesPath, "sqlite-command"),
143
+ path.join(serverFilesPath, "phpmyadmin"),
144
+ path.join(serverFilesPath, "wp-cli.phar"),
145
+ path.join(
146
+ serverFilesPath,
147
+ "wordpress-versions",
148
+ "latest",
149
+ "available-site-translations.json"
150
+ )
151
+ ];
152
+ }
153
+ const cleanupObsoleteServerFiles = {
154
+ needsToRun: async () => {
155
+ return getObsoletePaths().some((p) => fs.existsSync(p));
156
+ },
157
+ run: async () => {
158
+ for (const p of getObsoletePaths()) {
159
+ await fs.promises.rm(p, { recursive: true, force: true });
160
+ }
161
+ }
162
+ };
137
163
  const migrations = [
138
164
  checkStudioCompatibilityForInitialMigration,
139
165
  hideStudioDirWindows,
140
- renameProcessManagerHome
166
+ renameProcessManagerHome,
167
+ cleanupObsoleteServerFiles
141
168
  ];
142
169
  export {
143
170
  migrations
@@ -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-ul3DeWvy.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-Dz63Zdfn.mjs";
13
+ import { runCommand as runCommand$2 } from "./logout-CLUKQeZh.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";
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.")