@respira/wordpress-mcp-server 7.3.0 → 7.4.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.
- package/dist/acf-tools.d.ts +0 -10
- package/dist/acf-tools.d.ts.map +1 -1
- package/dist/acf-tools.js +0 -15
- package/dist/acf-tools.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +31 -36
- package/dist/config.js.map +1 -1
- package/dist/server.d.ts +0 -47
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +404 -462
- package/dist/server.js.map +1 -1
- package/dist/wordpress-client.d.ts +26 -29
- package/dist/wordpress-client.d.ts.map +1 -1
- package/dist/wordpress-client.js +115 -148
- package/dist/wordpress-client.js.map +1 -1
- package/package.json +2 -2
- package/skills/activity-report-composer/README.md +38 -0
- package/skills/activity-report-composer/SKILL.md +167 -0
- package/skills/activity-report-composer/metadata.json +42 -0
- package/skills/brand-voice-synthesizer/README.md +43 -0
- package/skills/brand-voice-synthesizer/SKILL.md +259 -0
- package/skills/brand-voice-synthesizer/metadata.json +46 -0
- package/skills/build-oxygen6-page/SKILL.md +59 -0
- package/skills/conversion-audit/README.md +37 -0
- package/skills/conversion-audit/SKILL.md +185 -0
- package/skills/conversion-audit/metadata.json +46 -0
- package/skills/custom-post-type-architect/README.md +34 -0
- package/skills/custom-post-type-architect/SKILL.md +158 -0
- package/skills/custom-post-type-architect/metadata.json +39 -0
- package/skills/design-system-synthesizer/README.md +46 -0
- package/skills/design-system-synthesizer/SKILL.md +395 -0
- package/skills/design-system-synthesizer/metadata.json +57 -0
- package/skills/figma-to-elementor/SKILL.md +204 -0
- package/skills/html-to-bricks/README.md +43 -0
- package/skills/html-to-bricks/SKILL.md +135 -0
- package/skills/html-to-bricks/metadata.json +41 -0
- package/skills/internal-link-builder/SKILL.md +258 -0
- package/skills/internal-link-builder/metadata.json +46 -0
- package/skills/migrate-beaver-builder-to-bricks/SKILL.md +251 -0
- package/skills/migrate-beaver-builder-to-gutenberg/SKILL.md +248 -0
- package/skills/migrate-brizy-to-gutenberg/SKILL.md +248 -0
- package/skills/migrate-divi-to-breakdance/SKILL.md +312 -0
- package/skills/migrate-divi-to-bricks/SKILL.md +321 -0
- package/skills/migrate-divi-to-gutenberg/SKILL.md +328 -0
- package/skills/migrate-elementor-to-breakdance/SKILL.md +287 -0
- package/skills/migrate-elementor-to-bricks/SKILL.md +289 -0
- package/skills/migrate-elementor-to-gutenberg/SKILL.md +327 -0
- package/skills/migrate-elementor-to-oxygen/SKILL.md +300 -0
- package/skills/migrate-oxygen-to-breakdance/SKILL.md +235 -0
- package/skills/migrate-oxygen-to-bricks/SKILL.md +237 -0
- package/skills/migrate-thrive-architect-to-gutenberg/SKILL.md +257 -0
- package/skills/migrate-visual-composer-to-gutenberg/SKILL.md +261 -0
- package/skills/migrate-wpbakery-to-bricks/SKILL.md +336 -0
- package/skills/migrate-wpbakery-to-gutenberg/SKILL.md +345 -0
- package/skills/mobile-experience-report/SKILL.md +719 -0
- package/skills/mobile-experience-report/telemetry.ts +137 -0
- package/skills/page-template-library/README.md +41 -0
- package/skills/page-template-library/SKILL.md +175 -0
- package/skills/page-template-library/metadata.json +44 -0
- package/skills/prime-the-agent/README.md +49 -0
- package/skills/prime-the-agent/SKILL.md +225 -0
- package/skills/prime-the-agent/metadata.json +46 -0
- package/skills/respira-setup-assistant/SKILL.md +386 -0
- package/skills/respira-setup-assistant/metadata.json +36 -0
- package/skills/seo-aeo-amplifier/README.md +61 -0
- package/skills/seo-aeo-amplifier/SKILL.md +277 -0
- package/skills/seo-aeo-amplifier/metadata.json +62 -0
- package/skills/stale-content-detector/README.md +41 -0
- package/skills/stale-content-detector/SKILL.md +156 -0
- package/skills/stale-content-detector/metadata.json +43 -0
- package/skills/technical-debt-audit/SKILL.md +691 -0
- package/skills/technical-debt-audit/telemetry.ts +142 -0
- package/skills/woocommerce-health-check/SKILL.md +719 -0
- package/skills/woocommerce-health-check/telemetry.ts +137 -0
- package/skills/wordpress-ai-image-optimizer/README.md +49 -0
- package/skills/wordpress-ai-image-optimizer/SKILL.md +232 -0
- package/skills/wordpress-ai-image-optimizer/metadata.json +63 -0
- package/skills/wordpress-site-dna/README.md +81 -0
- package/skills/wordpress-site-dna/SKILL.md +669 -0
- package/skills/wordpress-site-dna/telemetry.ts +137 -0
- package/dist/__tests__/acf-tool-routing.test.d.ts +0 -2
- package/dist/__tests__/acf-tool-routing.test.d.ts.map +0 -1
- package/dist/__tests__/acf-tool-routing.test.js +0 -37
- package/dist/__tests__/acf-tool-routing.test.js.map +0 -1
- package/dist/__tests__/cowork-bootstrap.test.d.ts +0 -2
- package/dist/__tests__/cowork-bootstrap.test.d.ts.map +0 -1
- package/dist/__tests__/cowork-bootstrap.test.js +0 -61
- package/dist/__tests__/cowork-bootstrap.test.js.map +0 -1
package/dist/server.js
CHANGED
|
@@ -15,61 +15,8 @@ import { WordPressClient } from './wordpress-client.js';
|
|
|
15
15
|
import { RespiraVersionChecker } from './version-checker.js';
|
|
16
16
|
import { getBricksTools, dispatchBricksTool } from './bricks-tools.js';
|
|
17
17
|
import { getElementorTools, dispatchElementorTool } from './elementor-tools.js';
|
|
18
|
-
import { getAcfTools,
|
|
18
|
+
import { getAcfTools, ACF_TOOL_NAMES } from './acf-tools.js';
|
|
19
19
|
import { getUsageEmitter, deriveToolKind } from './usage-emitter.js';
|
|
20
|
-
import { loadConfig, saveConfig } from './config.js';
|
|
21
|
-
/**
|
|
22
|
-
* Per-tool argument aliases. AI clients (especially Cowork / Claude desktop)
|
|
23
|
-
* frequently pass a natural-but-wrong key for a value: `pageId` / `page_id`
|
|
24
|
-
* instead of `id`, `filePath` instead of `file`. Because the dispatch never
|
|
25
|
-
* validated required params, a misnamed id silently produced a broken request
|
|
26
|
-
* like `/wp-json/respira/v2/pages/undefined`, and a misnamed file produced an
|
|
27
|
-
* empty upload that failed with "Could not connect — no response received".
|
|
28
|
-
*
|
|
29
|
-
* This map fills the canonical key a handler reads from the first present
|
|
30
|
-
* synonym, and ONLY when the canonical key is absent — an explicit value is
|
|
31
|
-
* never overwritten. Keyed by the canonical (wordpress_*) tool name, i.e.
|
|
32
|
-
* after normalizeToolName().
|
|
33
|
-
*/
|
|
34
|
-
const TOOL_ARG_ALIASES = {
|
|
35
|
-
wordpress_read_page: { id: ['page_id', 'pageId', 'post_id', 'postId'] },
|
|
36
|
-
wordpress_read_post: { id: ['post_id', 'postId', 'page_id', 'pageId'] },
|
|
37
|
-
wordpress_create_page_duplicate: {
|
|
38
|
-
original_id: ['page_id', 'pageId', 'id', 'original_page_id', 'originalId'],
|
|
39
|
-
},
|
|
40
|
-
wordpress_create_post_duplicate: {
|
|
41
|
-
original_id: ['post_id', 'postId', 'id', 'original_post_id', 'originalId'],
|
|
42
|
-
},
|
|
43
|
-
wordpress_get_snapshot: { snapshot_uuid: ['snapshot_id', 'snapshotId', 'uuid', 'id'] },
|
|
44
|
-
wordpress_find_element: { post_id: ['page_id', 'pageId', 'id', 'postId'] },
|
|
45
|
-
wordpress_upload_media: {
|
|
46
|
-
file: ['filePath', 'file_path', 'path', 'url', 'file_url', 'fileUrl'],
|
|
47
|
-
filename: ['file_name', 'fileName', 'name'],
|
|
48
|
-
},
|
|
49
|
-
};
|
|
50
|
-
/**
|
|
51
|
-
* Fill canonical argument keys from known synonyms, in place. No-op for tools
|
|
52
|
-
* without an alias entry and for args that already carry the canonical key.
|
|
53
|
-
*/
|
|
54
|
-
function normalizeToolArgs(canonicalName, args) {
|
|
55
|
-
if (!args || typeof args !== 'object')
|
|
56
|
-
return;
|
|
57
|
-
const aliases = TOOL_ARG_ALIASES[canonicalName];
|
|
58
|
-
if (!aliases)
|
|
59
|
-
return;
|
|
60
|
-
const record = args;
|
|
61
|
-
const isSet = (v) => v !== undefined && v !== null && v !== '';
|
|
62
|
-
for (const [canonical, synonyms] of Object.entries(aliases)) {
|
|
63
|
-
if (isSet(record[canonical]))
|
|
64
|
-
continue;
|
|
65
|
-
for (const syn of synonyms) {
|
|
66
|
-
if (isSet(record[syn])) {
|
|
67
|
-
record[canonical] = record[syn];
|
|
68
|
-
break;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
20
|
/**
|
|
74
21
|
* Read the server version from package.json at module load time so the
|
|
75
22
|
* MCP handshake, the `instructions` block, and the version-checker all
|
|
@@ -303,15 +250,6 @@ const TOOLS_WITH_PAGINATION_HINT = {
|
|
|
303
250
|
respira_list_users: 'Pass per_page (default 100, recommend 25) and page args plus a search filter to narrow results.',
|
|
304
251
|
respira_list_media: 'Pass per_page (default 100, recommend 25) and page args plus a mime_type filter to narrow results.',
|
|
305
252
|
respira_extract_builder_content: 'Use respira_get_page_outline first for a row-level summary, or pass node_id to extract just the subtree you care about.',
|
|
306
|
-
// capResponseSize is called with the canonical wordpress_* name, so the
|
|
307
|
-
// extract/read tools need wordpress_* keys too or they fall through to the
|
|
308
|
-
// generic hint. Read tools were missing entirely, which is why a large-page
|
|
309
|
-
// read gave no "use get_page_outline + node_id" guidance. (Jeff, 2026-07-09.)
|
|
310
|
-
wordpress_extract_builder_content: 'Use wordpress_get_page_outline first for a row-level summary, or pass node_id to extract just the subtree you care about.',
|
|
311
|
-
respira_read_page: 'Large page. Call respira_get_page_outline first for a row-level map, then respira_extract_builder_content with a node_id to pull one subtree at a time.',
|
|
312
|
-
wordpress_read_page: 'Large page. Call wordpress_get_page_outline first for a row-level map, then wordpress_extract_builder_content with a node_id to pull one subtree at a time.',
|
|
313
|
-
respira_read_post: 'Large post. Call respira_get_page_outline first for a row-level map, then respira_extract_builder_content with a node_id to pull one subtree at a time.',
|
|
314
|
-
wordpress_read_post: 'Large post. Call wordpress_get_page_outline first for a row-level map, then wordpress_extract_builder_content with a node_id to pull one subtree at a time.',
|
|
315
253
|
respira_list_plugins: 'No pagination arg today. The full plugin list is usually small; consider filing a feature request if your site hits the cap here.',
|
|
316
254
|
};
|
|
317
255
|
export class RespiraWordPressServer {
|
|
@@ -325,9 +263,6 @@ export class RespiraWordPressServer {
|
|
|
325
263
|
allowedSites = null;
|
|
326
264
|
/** Whether the plugin version warning has already been shown this session. */
|
|
327
265
|
versionWarningShown = false;
|
|
328
|
-
/** Epoch ms of the last account site-list refresh (throttle network calls). */
|
|
329
|
-
lastSiteRefreshAt = 0;
|
|
330
|
-
static SITE_REFRESH_MIN_INTERVAL_MS = 60_000;
|
|
331
266
|
static MCP_SERVER_VERSION = MCP_SERVER_VERSION;
|
|
332
267
|
/**
|
|
333
268
|
* Normalize a tool name: respira_* → wordpress_* for switch dispatch.
|
|
@@ -570,14 +505,9 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
|
|
|
570
505
|
- _nextNodeId tracking for proper element ID sequencing after injection
|
|
571
506
|
- respira_find_element and respira_update_element work via tree traversal with full element schema
|
|
572
507
|
|
|
573
|
-
### Oxygen
|
|
574
|
-
|
|
575
|
-
-
|
|
576
|
-
- **Oxygen 6 (codename Jenga, the Breakdance engine)** — a different builder, not Classic. Content is a node tree in the _oxygen_data meta, rendered as native OxygenElements\\* / EssentialElements\\* elements, NOT shortcodes. Author with simplified types via wordpress_inject_builder_content / build_page (pass type + settings, Respira maps them): section, heading (settings.text + level h1-h6), text, rich-text, button (text + url), image (url), icon, video, row, column. On an Oxygen 6 site, get_builder_info returns an \`oxygen6\` block with the full per-element schemas and a structure playbook — read it before building.
|
|
577
|
-
- Build with native elements. NEVER put a whole page or section into a single code/html block: it renders but is not editable in the Visual Builder and is the wrong way to build an Oxygen page.
|
|
578
|
-
- Header, footer, and templates are separate post types: oxygen_header, oxygen_footer, oxygen_template. Edit the existing header/footer posts; do not delete them and inline header/footer into every page.
|
|
579
|
-
- A template MUST use a Template Content Area element (OxygenElements\\TemplateContentArea) where the page body should render. Do NOT use a Post Content element on Oxygen 6 — it errors and the page cannot be edited. This is the most common Oxygen 6 template mistake.
|
|
580
|
-
- The richest building blocks (a real Section, Heading, Button with full design controls) come from the "Breakdance Elements for Oxygen" add-on; when it is absent Respira uses the core OxygenElements equivalents, still native and editable.
|
|
508
|
+
### Oxygen (Smart Defaults)
|
|
509
|
+
- Content stored in ct_builder_shortcodes and/or ct_builder_json meta
|
|
510
|
+
- Templates use ct_template post type
|
|
581
511
|
- respira_find_element works via tree traversal on the parsed structure
|
|
582
512
|
|
|
583
513
|
### WPBakery (Smart Defaults)
|
|
@@ -706,124 +636,6 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
706
636
|
getSiteSummary(site) {
|
|
707
637
|
return site.getSiteSummary(site.getSiteId() === this.defaultSiteId);
|
|
708
638
|
}
|
|
709
|
-
/**
|
|
710
|
-
* Re-pull the account's CURRENT site list from respira.press and merge in any
|
|
711
|
-
* sites that aren't in the running server yet.
|
|
712
|
-
*
|
|
713
|
-
* Why this exists: the server boots from whatever site list was in its config
|
|
714
|
-
* at startup. When the user adds a site on the dashboard afterwards, a
|
|
715
|
-
* long-lived server (Cowork especially) never learns about it and silently
|
|
716
|
-
* serves the stale list — Emil had 4 sites but Cowork only ever saw 3
|
|
717
|
-
* (2026-06-30). Rather than make the user re-paste config and restart, this
|
|
718
|
-
* lets list_sites self-heal: authenticate with a `respira_site_` token the
|
|
719
|
-
* server already holds, fetch the account's canonical config, and register
|
|
720
|
-
* any new sites in-memory (and persist to ~/.respira/config.json when that is
|
|
721
|
-
* the active source).
|
|
722
|
-
*
|
|
723
|
-
* Best-effort and throttled: a network/auth failure is swallowed and the
|
|
724
|
-
* existing list is returned unchanged. Servers with no site token (OAuth-only)
|
|
725
|
-
* or pinned to RESPIRA_CONFIG_B64 still get the in-session merge; only the
|
|
726
|
-
* on-disk persistence is skipped when the file is not the active source.
|
|
727
|
-
*/
|
|
728
|
-
async refreshSitesFromAccount(force = false) {
|
|
729
|
-
const result = { added: [] };
|
|
730
|
-
const now = Date.now();
|
|
731
|
-
if (!force && now - this.lastSiteRefreshAt < RespiraWordPressServer.SITE_REFRESH_MIN_INTERVAL_MS) {
|
|
732
|
-
return result;
|
|
733
|
-
}
|
|
734
|
-
// Pick a live dashboard site token to authenticate the refresh.
|
|
735
|
-
let authToken = '';
|
|
736
|
-
for (const client of this.sites.values()) {
|
|
737
|
-
const key = client.getApiKey();
|
|
738
|
-
if (typeof key === 'string' && key.startsWith('respira_site_')) {
|
|
739
|
-
authToken = key;
|
|
740
|
-
break;
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
if (!authToken) {
|
|
744
|
-
// OAuth-only or token-less server: nothing to authenticate with.
|
|
745
|
-
return result;
|
|
746
|
-
}
|
|
747
|
-
// Stamp before the network call so a hang/failure still respects the
|
|
748
|
-
// throttle and we don't retry on every single list_sites.
|
|
749
|
-
this.lastSiteRefreshAt = now;
|
|
750
|
-
const apiBase = process.env.RESPIRA_API_BASE || 'https://www.respira.press';
|
|
751
|
-
const url = `${apiBase.replace(/\/+$/, '')}/api/mcp/config/refresh`;
|
|
752
|
-
let payload = null;
|
|
753
|
-
try {
|
|
754
|
-
const response = await fetch(url, {
|
|
755
|
-
method: 'POST',
|
|
756
|
-
headers: { 'Content-Type': 'application/json' },
|
|
757
|
-
body: JSON.stringify({ token: authToken }),
|
|
758
|
-
signal: AbortSignal.timeout(8000),
|
|
759
|
-
});
|
|
760
|
-
if (!response.ok) {
|
|
761
|
-
return result;
|
|
762
|
-
}
|
|
763
|
-
payload = await response.json().catch(() => null);
|
|
764
|
-
}
|
|
765
|
-
catch {
|
|
766
|
-
return result;
|
|
767
|
-
}
|
|
768
|
-
const accountSites = payload?.config?.sites;
|
|
769
|
-
if (!Array.isArray(accountSites)) {
|
|
770
|
-
return result;
|
|
771
|
-
}
|
|
772
|
-
for (const site of accountSites) {
|
|
773
|
-
if (!site?.id || !site?.url || !site?.apiKey)
|
|
774
|
-
continue;
|
|
775
|
-
if (this.sites.has(site.id))
|
|
776
|
-
continue;
|
|
777
|
-
const cfg = {
|
|
778
|
-
id: site.id,
|
|
779
|
-
name: site.name || site.id,
|
|
780
|
-
url: site.url,
|
|
781
|
-
apiKey: site.apiKey,
|
|
782
|
-
default: false,
|
|
783
|
-
};
|
|
784
|
-
const client = new WordPressClient(cfg);
|
|
785
|
-
this.sites.set(cfg.id, client);
|
|
786
|
-
if (this.currentSite === null) {
|
|
787
|
-
this.currentSite = client;
|
|
788
|
-
this.defaultSiteId = cfg.id;
|
|
789
|
-
}
|
|
790
|
-
try {
|
|
791
|
-
getUsageEmitter().registerSiteToken(cfg.url, cfg.apiKey);
|
|
792
|
-
}
|
|
793
|
-
catch {
|
|
794
|
-
// Best-effort; usage telemetry never blocks.
|
|
795
|
-
}
|
|
796
|
-
result.added.push(cfg.name);
|
|
797
|
-
}
|
|
798
|
-
// Persist new sites to disk so a restart keeps them — only when the file is
|
|
799
|
-
// the active config source. A RESPIRA_CONFIG_B64- or RESPIRA_CONFIG_FILE-
|
|
800
|
-
// pinned server still has the correct in-memory list this session; writing
|
|
801
|
-
// the default file would not be read on its next boot and could orphan a
|
|
802
|
-
// stray file, so skip it there.
|
|
803
|
-
const fileIsActiveSource = !process.env.RESPIRA_CONFIG_B64?.trim() && !process.env.RESPIRA_CONFIG_FILE?.trim();
|
|
804
|
-
if (result.added.length > 0 && fileIsActiveSource) {
|
|
805
|
-
try {
|
|
806
|
-
const onDisk = loadConfig();
|
|
807
|
-
const known = new Set(onDisk.sites.map((s) => s.id));
|
|
808
|
-
for (const site of accountSites) {
|
|
809
|
-
if (site?.id && site?.url && site?.apiKey && !known.has(site.id)) {
|
|
810
|
-
onDisk.sites.push({
|
|
811
|
-
id: site.id,
|
|
812
|
-
name: site.name || site.id,
|
|
813
|
-
url: site.url,
|
|
814
|
-
apiKey: site.apiKey,
|
|
815
|
-
default: false,
|
|
816
|
-
});
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
saveConfig(onDisk);
|
|
820
|
-
}
|
|
821
|
-
catch {
|
|
822
|
-
// Best-effort; the in-memory merge above is what unblocks this session.
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
return result;
|
|
826
|
-
}
|
|
827
639
|
/**
|
|
828
640
|
* N6 fix (v6.19.0): the response envelope `site` field must reflect the
|
|
829
641
|
* client that ACTUALLY serviced the call, not the global default. Pre-fix,
|
|
@@ -915,14 +727,9 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
915
727
|
const apiBase = process.env.RESPIRA_API_BASE || 'https://www.respira.press';
|
|
916
728
|
const url = `${apiBase.replace(/\/+$/, '')}/docs-search?q=${encodeURIComponent(query)}&limit=${limit}`;
|
|
917
729
|
try {
|
|
918
|
-
// Bound the docs fetch (20s) so a slow or unreachable respira.press
|
|
919
|
-
// (a QUIC-hostile network, a proxy, a stalled link) fails fast instead
|
|
920
|
-
// of hanging the tool to the MCP client's multi-minute ceiling.
|
|
921
|
-
// search_docs is a convenience lookup, never worth blocking triage on.
|
|
922
730
|
const response = await fetch(url, {
|
|
923
731
|
method: 'GET',
|
|
924
732
|
headers: { 'User-Agent': `respira-mcp/${MCP_SERVER_VERSION}` },
|
|
925
|
-
signal: AbortSignal.timeout(20_000),
|
|
926
733
|
});
|
|
927
734
|
if (!response.ok) {
|
|
928
735
|
return {
|
|
@@ -1224,39 +1031,17 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
1224
1031
|
url: s.url,
|
|
1225
1032
|
}));
|
|
1226
1033
|
const hasConfigB64 = Boolean(process.env.RESPIRA_CONFIG_B64);
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
// success with the caveat appended to the end of the message; agents glossed
|
|
1232
|
-
// over it and reported "no output", so the user reconnected forever with a
|
|
1233
|
-
// stale token (Sara / crediso.io: repeated redeems, token never updated,
|
|
1234
|
-
// every authenticated call rejected). Returning success:false with the fix
|
|
1235
|
-
// and the exact replacement value up front makes it impossible to miss.
|
|
1236
|
-
if (hasConfigB64) {
|
|
1237
|
-
const newConfigB64 = Buffer.from(JSON.stringify(config)).toString('base64');
|
|
1238
|
-
return {
|
|
1239
|
-
success: false,
|
|
1240
|
-
error: 'config_pinned_to_env',
|
|
1241
|
-
config_b64_active: true,
|
|
1242
|
-
action_required: true,
|
|
1243
|
-
config_path: configPath,
|
|
1244
|
-
sites_count: sites.length,
|
|
1245
|
-
sites,
|
|
1246
|
-
new_config_b64: newConfigB64,
|
|
1247
|
-
message: 'The token was redeemed and written to ~/.respira/config.json, but it will NOT take effect yet: this MCP server is pinned to the RESPIRA_CONFIG_B64 environment variable, which overrides the config file. That is exactly why reconnecting never updates the token and authenticated calls keep failing. ' +
|
|
1248
|
-
'Fix it one of two ways, then FULLY restart the agent: ' +
|
|
1249
|
-
'(1) remove RESPIRA_CONFIG_B64 from this server\'s MCP config so the file just written is used, or ' +
|
|
1250
|
-
'(2) replace the value of RESPIRA_CONFIG_B64 with this updated base64:\n\n' + newConfigB64,
|
|
1251
|
-
};
|
|
1252
|
-
}
|
|
1034
|
+
const baseMessage = `Connected ${sites.length} site${sites.length === 1 ? '' : 's'} via the Respira Cowork token. The config was written to ~/.respira/config.json. Restart this Cowork chat (or open a new one) so the MCP server picks up the new sites.`;
|
|
1035
|
+
const message = hasConfigB64
|
|
1036
|
+
? `${baseMessage} IMPORTANT: your MCP server is currently configured via the RESPIRA_CONFIG_B64 environment variable, which takes precedence over the file on disk. To activate the new site, update RESPIRA_CONFIG_B64 in your agent's MCP server config with the base64-encoded contents of the new ~/.respira/config.json file, then restart the agent.`
|
|
1037
|
+
: baseMessage;
|
|
1253
1038
|
return {
|
|
1254
1039
|
success: true,
|
|
1255
1040
|
sites_count: sites.length,
|
|
1256
1041
|
sites,
|
|
1257
1042
|
config_path: configPath,
|
|
1258
|
-
config_b64_active:
|
|
1259
|
-
message
|
|
1043
|
+
config_b64_active: hasConfigB64,
|
|
1044
|
+
message,
|
|
1260
1045
|
};
|
|
1261
1046
|
}
|
|
1262
1047
|
/**
|
|
@@ -1368,12 +1153,6 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
1368
1153
|
// (wordpress_redeem_token) because normalizeToolName rewrites the
|
|
1369
1154
|
// respira_* alias to wordpress_* before dispatch.
|
|
1370
1155
|
const canonical = this.normalizeToolName(name).canonical;
|
|
1371
|
-
// Tolerate natural-but-wrong arg names from AI clients (pageId/page_id
|
|
1372
|
-
// vs id, filePath vs file). Fills canonical keys from synonyms in place
|
|
1373
|
-
// so a misnamed required param no longer yields /pages/undefined or an
|
|
1374
|
-
// empty "no response received" upload. No-op for tools without aliases;
|
|
1375
|
-
// an explicit canonical value is never overwritten.
|
|
1376
|
-
normalizeToolArgs(canonical, args);
|
|
1377
1156
|
if (canonical === 'wordpress_redeem_token') {
|
|
1378
1157
|
// redeemInstallToken returns a plain object. Wrap it in the standard
|
|
1379
1158
|
// MCP content envelope — the early-exit path bypasses the normal
|
|
@@ -1475,18 +1254,6 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
1475
1254
|
timeout_ms: error.timeoutMs,
|
|
1476
1255
|
message: error.message,
|
|
1477
1256
|
hint: 'Other tools should continue to work. ' +
|
|
1478
|
-
// For a read on a large page, the timeout is almost
|
|
1479
|
-
// always a SIZE problem wearing a timeout costume: a big
|
|
1480
|
-
// page takes longer to generate/transfer than the
|
|
1481
|
-
// per-request HTTP timeout, so the request aborts before
|
|
1482
|
-
// the response-size cap can return its truncation
|
|
1483
|
-
// envelope. Without this line the user only sees "timeout"
|
|
1484
|
-
// and keeps raising RESPIRA_MAX_TOOL_TIMEOUT_MS or
|
|
1485
|
-
// restarting the client, never learning it was the cap.
|
|
1486
|
-
// (Jeff Imperial, 2026-07-09.)
|
|
1487
|
-
(/read_page|read_post|read_custom_post|extract_builder_content|find_builder_targets|get_builder_info|get_page/.test(String(error.toolName || ''))
|
|
1488
|
-
? 'This was a read on a page or site that may be large. The most likely cause is response SIZE, not a dead server: a big page can exceed the per-request HTTP timeout before the response-size cap returns a truncation envelope, so it surfaces as a plain timeout. Recover by calling respira_get_page_outline first, then respira_extract_builder_content with a node_id to pull one row or subtree at a time (or add a search / per_page filter). '
|
|
1489
|
-
: '') +
|
|
1490
1257
|
'If this operation genuinely needs more time, raise RESPIRA_MAX_TOOL_TIMEOUT_MS. ' +
|
|
1491
1258
|
'For uploads specifically, also see RESPIRA_UPLOAD_TIMEOUT_MS and RESPIRA_MAX_UPLOAD_MB. ' +
|
|
1492
1259
|
'To triangulate which network layer (origin / Cloudflare / Wordfence) is slow, run respira_diagnose_connection — it probes the same site with HEAD/GET/OPTIONS and reports per-layer latency + edge-layer presence. ' +
|
|
@@ -2284,7 +2051,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
2284
2051
|
},
|
|
2285
2052
|
{
|
|
2286
2053
|
name: 'wordpress_inject_builder_content',
|
|
2287
|
-
description: 'REPLACE (or append to) the entire page builder layout. WARNING: By default this REPLACES all existing content (use mode:"append" to add content without destroying existing elements). For editing a single module, use wordpress_update_module instead. Use exactly: gutenberg, divi, elementor, bricks, beaver, oxygen, breakdance, brizy, thrive, visual-composer, wpbakery. For Divi, divi_version is required ("4" or "5"). Replacing a page that already has content with mode="replace" requires confirm_replace=true, else returns 409 respira_replace_confirmation_required. Alternatives: mode="append" to add to existing content, or edit_target="live" to overwrite the live page directly. The gate prevents silent data loss.\n\nCanonical Divi 5 nested shape: `content: [{ type: "section", children: [{ type: "row", children: [{ type: "column", children: [{ type: "heading", settings: {...} }] }] }] }]` (rows/cols/columns/modules/elements/innerBlocks also accepted as `children` aliases). Each Divi module needs its render-critical attr in `settings` (e.g. heading→title, text→content, button→button_text); the plugin names any missing one via `respira_divi_required_attrs_missing
|
|
2054
|
+
description: 'REPLACE (or append to) the entire page builder layout. WARNING: By default this REPLACES all existing content (use mode:"append" to add content without destroying existing elements). For editing a single module, use wordpress_update_module instead. Use exactly: gutenberg, divi, elementor, bricks, beaver, oxygen, breakdance, brizy, thrive, visual-composer, wpbakery. For Divi, divi_version is required ("4" or "5"). Replacing a page that already has content with mode="replace" requires confirm_replace=true, else returns 409 respira_replace_confirmation_required. Alternatives: mode="append" to add to existing content, or edit_target="live" to overwrite the live page directly. The gate prevents silent data loss.\n\nCanonical Divi 5 nested shape: `content: [{ type: "section", children: [{ type: "row", children: [{ type: "column", children: [{ type: "heading", settings: {...} }] }] }] }]` (rows/cols/columns/modules/elements/innerBlocks also accepted as `children` aliases). Each Divi module needs its render-critical attr in `settings` (e.g. heading→title, text→content, button→button_text); the plugin names any missing one via `respira_divi_required_attrs_missing`.',
|
|
2288
2055
|
inputSchema: {
|
|
2289
2056
|
type: 'object',
|
|
2290
2057
|
properties: {
|
|
@@ -2319,14 +2086,6 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
2319
2086
|
description: 'Divi generation hint ("4" for shortcode format, "5" for block format). Required for all Divi inject calls.',
|
|
2320
2087
|
enum: ['4', '5'],
|
|
2321
2088
|
},
|
|
2322
|
-
target_path: {
|
|
2323
|
-
type: 'string',
|
|
2324
|
-
description: 'Insert the content as children of the container at this bracket path (from extract_builder_content), e.g. "sections[0].rows[1].columns[0]", instead of at page top level. Use this to build a large section in small calls when the whole tree exceeds the AI client payload cap: create the section frame first, then append each module by target_path. Omit for normal whole-page inject. Supported on Divi 5 today; other builders return respira_target_path_unsupported.',
|
|
2325
|
-
},
|
|
2326
|
-
position: {
|
|
2327
|
-
type: 'number',
|
|
2328
|
-
description: 'Where to insert within the target_path container: -1 (default) appends to the end, 0 inserts first, N inserts at index N. Only used when target_path is set.',
|
|
2329
|
-
},
|
|
2330
2089
|
},
|
|
2331
2090
|
required: ['builder', 'page_id', 'content'],
|
|
2332
2091
|
},
|
|
@@ -2409,10 +2168,6 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
2409
2168
|
description: 'When editing an original, choose ask, live, or duplicate. Defaults to ask when direct editing is enabled.',
|
|
2410
2169
|
enum: ['ask', 'live', 'duplicate'],
|
|
2411
2170
|
},
|
|
2412
|
-
confirm_live_edit: {
|
|
2413
|
-
type: 'boolean',
|
|
2414
|
-
description: 'Set true together with edit_target:"live" to acknowledge a direct write to the published original. Exposed at the top level so autonomous flows can complete the confirmation_required handshake without polluting the updates object.',
|
|
2415
|
-
},
|
|
2416
2171
|
},
|
|
2417
2172
|
required: ['builder', 'page_id', 'module_identifier', 'updates'],
|
|
2418
2173
|
},
|
|
@@ -2573,10 +2328,6 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
2573
2328
|
description: 'When editing an original, choose ask, live, or duplicate. Defaults to ask when direct editing is enabled.',
|
|
2574
2329
|
enum: ['ask', 'live', 'duplicate'],
|
|
2575
2330
|
},
|
|
2576
|
-
confirm_live_edit: {
|
|
2577
|
-
type: 'boolean',
|
|
2578
|
-
description: 'Set true together with edit_target:"live" to acknowledge a direct write to the published original. Exposed at the top level so autonomous flows can complete the confirmation_required handshake without polluting the operations.',
|
|
2579
|
-
},
|
|
2580
2331
|
},
|
|
2581
2332
|
required: ['builder', 'post_id', 'operations'],
|
|
2582
2333
|
},
|
|
@@ -2647,35 +2398,6 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
2647
2398
|
required: ['relative_path', 'content'],
|
|
2648
2399
|
},
|
|
2649
2400
|
},
|
|
2650
|
-
{
|
|
2651
|
-
name: 'wordpress_read_debug_log',
|
|
2652
|
-
description: 'Tail wp-content/debug.log (or a custom WP_DEBUG_LOG path) to diagnose PHP fatals, warnings and notices without SSH. Read-only: never writes, rotates, or deletes the log. Only reads the last few MB of the file from the end, so this is safe on large logs. Requires plugin v7.4.21+, WP_DEBUG_LOG must be enabled on the site, and the user behind the API key must have the WP `edit_themes` capability (same gate as wordpress_read_theme_file). Lines are grouped into log entries (a stack trace stays attached to its originating error) and obvious secrets (Authorization: Bearer tokens, api_key=/password= style params, long hex/base64 blobs) are redacted before the response leaves the server. Use `level` to filter to fatals/errors/warnings, `since` to scope to recent activity, and `grep` to search for a specific string or pattern. Pairs well with wordpress_diagnose_connection: when a route returns a 5xx, pull the matching debug.log lines from around that time.',
|
|
2653
|
-
inputSchema: {
|
|
2654
|
-
type: 'object',
|
|
2655
|
-
properties: {
|
|
2656
|
-
lines: {
|
|
2657
|
-
type: 'number',
|
|
2658
|
-
description: 'Maximum number of matching log entries to return, most recent first is NOT applied (returned in chronological order). Default 200, capped at 2000.',
|
|
2659
|
-
minimum: 1,
|
|
2660
|
-
maximum: 2000,
|
|
2661
|
-
},
|
|
2662
|
-
level: {
|
|
2663
|
-
type: 'string',
|
|
2664
|
-
description: 'Filter by severity: "fatal" (PHP Fatal error only), "error" (fatal + parse + recoverable + warning-class PHP errors), "warning" (PHP Warning / Notice / Deprecated), or "all" (default, no filtering).',
|
|
2665
|
-
enum: ['fatal', 'error', 'warning', 'all'],
|
|
2666
|
-
},
|
|
2667
|
-
since: {
|
|
2668
|
-
type: 'string',
|
|
2669
|
-
description: 'Only return entries whose leading timestamp is after this point. Accepts an ISO-8601 timestamp or a relative expression like "-1 hour", "-30 minutes", "yesterday".',
|
|
2670
|
-
},
|
|
2671
|
-
grep: {
|
|
2672
|
-
type: 'string',
|
|
2673
|
-
description: 'Filter to entries containing this plain substring (case-insensitive), or a `/pattern/` regex (also matched case-insensitively; malformed patterns fall back to a plain substring match instead of erroring).',
|
|
2674
|
-
},
|
|
2675
|
-
},
|
|
2676
|
-
},
|
|
2677
|
-
readOnlyHint: true,
|
|
2678
|
-
},
|
|
2679
2401
|
{
|
|
2680
2402
|
name: 'wordpress_switch_site',
|
|
2681
2403
|
description: 'Switch to a different WordPress site in the active Respira multi-site configuration.',
|
|
@@ -4061,21 +3783,6 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
4061
3783
|
},
|
|
4062
3784
|
destructiveHint: true,
|
|
4063
3785
|
},
|
|
4064
|
-
{
|
|
4065
|
-
name: 'wordpress_detach_duplicate',
|
|
4066
|
-
description: 'Clear the is_duplicate/original_id link on a post Respira still tracks as a pending duplicate, WITHOUT deleting it or overwriting the original. Use this instead of approving or rejecting when the "duplicate" turned out to be its own independent live page (for example it was cloned from the original once, long ago, as a starting template and later rebuilt into unrelated content). This is the fix for "Cannot edit original ... A duplicate already exists ..." errors caused by a stale/phantom duplicate: detach the duplicate_id from the error response, then retry the original edit. Works for pages, posts, and any custom post type since the relationship is tracked by post meta, not post type.',
|
|
4067
|
-
inputSchema: {
|
|
4068
|
-
type: 'object',
|
|
4069
|
-
properties: {
|
|
4070
|
-
duplicate_id: {
|
|
4071
|
-
type: 'number',
|
|
4072
|
-
description: 'ID of the post currently marked as a Respira duplicate to detach (the duplicate_id returned in the "A duplicate already exists" error, NOT the original id).',
|
|
4073
|
-
},
|
|
4074
|
-
},
|
|
4075
|
-
required: ['duplicate_id'],
|
|
4076
|
-
},
|
|
4077
|
-
idempotentHint: true,
|
|
4078
|
-
},
|
|
4079
3786
|
// Options
|
|
4080
3787
|
{
|
|
4081
3788
|
name: 'wordpress_list_options',
|
|
@@ -4528,56 +4235,6 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
4528
4235
|
/** Cached site context for tool filtering (refreshed on site switch). */
|
|
4529
4236
|
cachedFilterContext = null;
|
|
4530
4237
|
static FILTER_CACHE_TTL = 60_000; // 1 minute
|
|
4531
|
-
/**
|
|
4532
|
-
* Capability negotiation (7.2.6): tools whose plugin REST route only exists
|
|
4533
|
-
* from a given plugin version. On older plugins these 404 with
|
|
4534
|
-
* respira_rest_no_route — the biggest non-element failure class in the
|
|
4535
|
-
* MCP-quality telemetry (656 errors across 97 tools), pure version skew since
|
|
4536
|
-
* only ~half the connected sites run a current plugin. We stop advertising
|
|
4537
|
-
* these tools when the connected plugin predates the route, so the agent is
|
|
4538
|
-
* never offered something that will 404. @since values are verified against
|
|
4539
|
-
* the plugin source (the first release tag that contains each route).
|
|
4540
|
-
*/
|
|
4541
|
-
static TOOL_MIN_PLUGIN_VERSION = {
|
|
4542
|
-
wordpress_run_pagespeed_audit: '7.1.0', // /pagespeed/audit — first in v7.1.0
|
|
4543
|
-
wordpress_analyze_pagespeed: '7.1.0', // /analyze/pagespeed — first in v7.1.0
|
|
4544
|
-
wordpress_abilities_gap_report: '7.1.0', // /abilities/gap-report — first in v7.1.0
|
|
4545
|
-
wordpress_invoke_ability: '7.1.0', // /abilities/invoke — first in v7.1.0
|
|
4546
|
-
wordpress_read_theme_file: '7.0.42', // /theme-files/* — @since 7.0.42
|
|
4547
|
-
wordpress_write_theme_file: '7.0.42',
|
|
4548
|
-
wordpress_append_theme_file: '7.0.42',
|
|
4549
|
-
};
|
|
4550
|
-
/**
|
|
4551
|
-
* Capability negotiation Phase 2 (7.2.7): the feature flag each version-skewed
|
|
4552
|
-
* tool needs. From plugin 7.4.20 the compatibility endpoint declares these
|
|
4553
|
-
* flags from its LIVE registered REST routes, so this is an exact "does the
|
|
4554
|
-
* route exist on this install" signal — more precise than the version map
|
|
4555
|
-
* above and self-maintaining. The version map stays as the fallback for
|
|
4556
|
-
* plugins older than 7.4.20, which do not declare per-capability features yet.
|
|
4557
|
-
*/
|
|
4558
|
-
static TOOL_REQUIRED_FEATURE = {
|
|
4559
|
-
wordpress_run_pagespeed_audit: 'pagespeed',
|
|
4560
|
-
wordpress_analyze_pagespeed: 'pagespeed',
|
|
4561
|
-
wordpress_abilities_gap_report: 'abilities',
|
|
4562
|
-
wordpress_invoke_ability: 'abilities',
|
|
4563
|
-
wordpress_read_theme_file: 'theme_files',
|
|
4564
|
-
wordpress_write_theme_file: 'theme_files',
|
|
4565
|
-
wordpress_append_theme_file: 'theme_files',
|
|
4566
|
-
};
|
|
4567
|
-
/** First plugin version that declares per-capability features (Phase 2). */
|
|
4568
|
-
static FEATURE_DECL_MIN_PLUGIN_VERSION = '7.4.20';
|
|
4569
|
-
/** True when version `a` is strictly older than `b` (both "x.y.z"). */
|
|
4570
|
-
static versionLt(a, b) {
|
|
4571
|
-
const pa = a.split('.').map((n) => parseInt(n, 10) || 0);
|
|
4572
|
-
const pb = b.split('.').map((n) => parseInt(n, 10) || 0);
|
|
4573
|
-
for (let i = 0; i < 3; i++) {
|
|
4574
|
-
const x = pa[i] || 0;
|
|
4575
|
-
const y = pb[i] || 0;
|
|
4576
|
-
if (x !== y)
|
|
4577
|
-
return x < y;
|
|
4578
|
-
}
|
|
4579
|
-
return false;
|
|
4580
|
-
}
|
|
4581
4238
|
async filterToolsByContext(tools) {
|
|
4582
4239
|
if (!this.currentSite) {
|
|
4583
4240
|
return tools; // No site connected — show everything.
|
|
@@ -4596,22 +4253,6 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
4596
4253
|
return tools; // Can't fetch context — show everything.
|
|
4597
4254
|
}
|
|
4598
4255
|
}
|
|
4599
|
-
// Capability negotiation: make sure the plugin version is known (cheap and
|
|
4600
|
-
// memoized in the client) so the per-tool gate below can hide routes the
|
|
4601
|
-
// connected plugin is too old to have. Never blocks the filter on failure.
|
|
4602
|
-
try {
|
|
4603
|
-
await this.currentSite.checkCompatibility();
|
|
4604
|
-
}
|
|
4605
|
-
catch {
|
|
4606
|
-
/* non-fatal — fall through with whatever version we have (possibly null) */
|
|
4607
|
-
}
|
|
4608
|
-
const pluginVersion = this.currentSite.pluginVersion ?? null;
|
|
4609
|
-
// Phase 2 (7.2.7): plugin 7.4.20+ declares per-capability features from its
|
|
4610
|
-
// live REST routes; trust those exactly. Older plugins do not, so they fall
|
|
4611
|
-
// back to the version map in the per-tool gate below.
|
|
4612
|
-
const pluginFeatures = this.currentSite.pluginFeatures ?? [];
|
|
4613
|
-
const declaresFeatures = !!pluginVersion &&
|
|
4614
|
-
!RespiraWordPressServer.versionLt(pluginVersion, RespiraWordPressServer.FEATURE_DECL_MIN_PLUGIN_VERSION);
|
|
4615
4256
|
const detectedBuilder = (context?.page_builder?.name || '').toLowerCase();
|
|
4616
4257
|
const hasWooCommerce = Boolean(context?.woocommerce?.active || context?.addons?.woocommerce?.installed);
|
|
4617
4258
|
return tools.filter((tool) => {
|
|
@@ -4636,20 +4277,6 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
4636
4277
|
if (name.startsWith('woocommerce_')) {
|
|
4637
4278
|
return hasWooCommerce;
|
|
4638
4279
|
}
|
|
4639
|
-
// Capability negotiation: hide tools whose plugin route the connected site
|
|
4640
|
-
// does not expose (avoids rest_no_route 404s on version-skewed sites).
|
|
4641
|
-
// Prefer the plugin's declared per-capability features (7.4.20+, computed
|
|
4642
|
-
// from its live routes — exact), and fall back to the verified version map
|
|
4643
|
-
// for older plugins that do not declare them. Both are conservative: a tool
|
|
4644
|
-
// is hidden only on a positive signal that its route is absent.
|
|
4645
|
-
const reqFeature = RespiraWordPressServer.TOOL_REQUIRED_FEATURE[name];
|
|
4646
|
-
if (reqFeature && declaresFeatures) {
|
|
4647
|
-
return pluginFeatures.includes(reqFeature);
|
|
4648
|
-
}
|
|
4649
|
-
const minPv = RespiraWordPressServer.TOOL_MIN_PLUGIN_VERSION[name];
|
|
4650
|
-
if (minPv && pluginVersion && RespiraWordPressServer.versionLt(pluginVersion, minPv)) {
|
|
4651
|
-
return false;
|
|
4652
|
-
}
|
|
4653
4280
|
// Everything else: always show.
|
|
4654
4281
|
return true;
|
|
4655
4282
|
});
|
|
@@ -4767,6 +4394,15 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
4767
4394
|
items: { type: 'object' },
|
|
4768
4395
|
description: 'Alternative tag payload (IDs, names, slugs, or objects with id/name/slug)',
|
|
4769
4396
|
},
|
|
4397
|
+
global_unique_id: {
|
|
4398
|
+
type: 'string',
|
|
4399
|
+
description: 'GTIN/UPC/EAN (WooCommerce 9.2+). The strongest identifier for feeds and AI shopping.',
|
|
4400
|
+
},
|
|
4401
|
+
gallery_image_ids: {
|
|
4402
|
+
type: 'array',
|
|
4403
|
+
items: { type: 'number' },
|
|
4404
|
+
description: 'Gallery image attachment IDs; empty array clears the gallery',
|
|
4405
|
+
},
|
|
4770
4406
|
},
|
|
4771
4407
|
required: ['id'],
|
|
4772
4408
|
},
|
|
@@ -5487,6 +5123,320 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
5487
5123
|
},
|
|
5488
5124
|
destructiveHint: true,
|
|
5489
5125
|
},
|
|
5126
|
+
// --- Subscriptions / Bookings / Memberships (addon v3.1, extensions) ---
|
|
5127
|
+
{
|
|
5128
|
+
name: 'woocommerce_list_subscriptions',
|
|
5129
|
+
description: 'List WooCommerce Subscriptions, filterable by status, customer, or product. Requires the WooCommerce Subscriptions extension on the site.',
|
|
5130
|
+
inputSchema: {
|
|
5131
|
+
type: 'object',
|
|
5132
|
+
properties: {
|
|
5133
|
+
status: { type: 'string', description: 'active, on-hold, cancelled, pending-cancel, expired' },
|
|
5134
|
+
customer_id: { type: 'number' },
|
|
5135
|
+
product_id: { type: 'number' },
|
|
5136
|
+
per_page: { type: 'number' },
|
|
5137
|
+
page: { type: 'number' },
|
|
5138
|
+
},
|
|
5139
|
+
},
|
|
5140
|
+
readOnlyHint: true,
|
|
5141
|
+
},
|
|
5142
|
+
{
|
|
5143
|
+
name: 'woocommerce_get_subscription',
|
|
5144
|
+
description: 'Get one subscription with items, dates, and payment method.',
|
|
5145
|
+
inputSchema: {
|
|
5146
|
+
type: 'object',
|
|
5147
|
+
properties: {
|
|
5148
|
+
id: { type: 'number', description: 'Subscription ID' },
|
|
5149
|
+
},
|
|
5150
|
+
required: ['id'],
|
|
5151
|
+
},
|
|
5152
|
+
readOnlyHint: true,
|
|
5153
|
+
},
|
|
5154
|
+
{
|
|
5155
|
+
name: 'woocommerce_update_subscription_status',
|
|
5156
|
+
description: 'Pause (on-hold), reactivate, or cancel a subscription. DRY-RUN BY DEFAULT: pass dry_run: false to apply. Transitions are validated against Subscriptions rules; snapshots taken.',
|
|
5157
|
+
inputSchema: {
|
|
5158
|
+
type: 'object',
|
|
5159
|
+
properties: {
|
|
5160
|
+
id: { type: 'number', description: 'Subscription ID' },
|
|
5161
|
+
status: { type: 'string', description: 'active, on-hold, cancelled, pending-cancel' },
|
|
5162
|
+
note: { type: 'string' },
|
|
5163
|
+
dry_run: { type: 'boolean', description: 'Default TRUE: preview only' },
|
|
5164
|
+
},
|
|
5165
|
+
required: ['id', 'status'],
|
|
5166
|
+
},
|
|
5167
|
+
destructiveHint: true,
|
|
5168
|
+
},
|
|
5169
|
+
{
|
|
5170
|
+
name: 'woocommerce_update_subscription_dates',
|
|
5171
|
+
description: 'Move a subscription\'s next_payment, end, or trial_end dates (empty string clears). DRY-RUN BY DEFAULT: moving next_payment changes when the customer is charged.',
|
|
5172
|
+
inputSchema: {
|
|
5173
|
+
type: 'object',
|
|
5174
|
+
properties: {
|
|
5175
|
+
id: { type: 'number', description: 'Subscription ID' },
|
|
5176
|
+
next_payment: { type: 'string', description: 'ISO 8601' },
|
|
5177
|
+
end: { type: 'string' },
|
|
5178
|
+
trial_end: { type: 'string' },
|
|
5179
|
+
dry_run: { type: 'boolean', description: 'Default TRUE: preview only' },
|
|
5180
|
+
},
|
|
5181
|
+
required: ['id'],
|
|
5182
|
+
},
|
|
5183
|
+
destructiveHint: true,
|
|
5184
|
+
},
|
|
5185
|
+
{
|
|
5186
|
+
name: 'woocommerce_add_subscription_note',
|
|
5187
|
+
description: 'Add a note to a subscription. Private by default; customer_note: true emails the customer.',
|
|
5188
|
+
inputSchema: {
|
|
5189
|
+
type: 'object',
|
|
5190
|
+
properties: {
|
|
5191
|
+
id: { type: 'number', description: 'Subscription ID' },
|
|
5192
|
+
note: { type: 'string' },
|
|
5193
|
+
customer_note: { type: 'boolean', description: 'Default false (private note)' },
|
|
5194
|
+
},
|
|
5195
|
+
required: ['id', 'note'],
|
|
5196
|
+
},
|
|
5197
|
+
},
|
|
5198
|
+
{
|
|
5199
|
+
name: 'woocommerce_list_bookings',
|
|
5200
|
+
description: 'List WooCommerce Bookings, filterable by status or product. Requires the WooCommerce Bookings extension on the site.',
|
|
5201
|
+
inputSchema: {
|
|
5202
|
+
type: 'object',
|
|
5203
|
+
properties: {
|
|
5204
|
+
status: { type: 'string', description: 'unpaid, pending-confirmation, confirmed, paid, cancelled, complete' },
|
|
5205
|
+
product_id: { type: 'number' },
|
|
5206
|
+
per_page: { type: 'number' },
|
|
5207
|
+
page: { type: 'number' },
|
|
5208
|
+
},
|
|
5209
|
+
},
|
|
5210
|
+
readOnlyHint: true,
|
|
5211
|
+
},
|
|
5212
|
+
{
|
|
5213
|
+
name: 'woocommerce_get_booking',
|
|
5214
|
+
description: 'Get one booking with times, persons, resource, and order linkage.',
|
|
5215
|
+
inputSchema: {
|
|
5216
|
+
type: 'object',
|
|
5217
|
+
properties: {
|
|
5218
|
+
id: { type: 'number', description: 'Booking ID' },
|
|
5219
|
+
},
|
|
5220
|
+
required: ['id'],
|
|
5221
|
+
},
|
|
5222
|
+
readOnlyHint: true,
|
|
5223
|
+
},
|
|
5224
|
+
{
|
|
5225
|
+
name: 'woocommerce_update_booking_status',
|
|
5226
|
+
description: 'Confirm, mark paid, complete, or cancel a booking. DRY-RUN BY DEFAULT: status changes can trigger customer emails.',
|
|
5227
|
+
inputSchema: {
|
|
5228
|
+
type: 'object',
|
|
5229
|
+
properties: {
|
|
5230
|
+
id: { type: 'number', description: 'Booking ID' },
|
|
5231
|
+
status: { type: 'string', description: 'unpaid, pending-confirmation, confirmed, paid, cancelled, complete' },
|
|
5232
|
+
dry_run: { type: 'boolean', description: 'Default TRUE: preview only' },
|
|
5233
|
+
},
|
|
5234
|
+
required: ['id', 'status'],
|
|
5235
|
+
},
|
|
5236
|
+
destructiveHint: true,
|
|
5237
|
+
},
|
|
5238
|
+
{
|
|
5239
|
+
name: 'woocommerce_reschedule_booking',
|
|
5240
|
+
description: 'Move a booking to a new time window. DRY-RUN BY DEFAULT with a conflict scan against other bookings on the same product; overlapping windows need force: true.',
|
|
5241
|
+
inputSchema: {
|
|
5242
|
+
type: 'object',
|
|
5243
|
+
properties: {
|
|
5244
|
+
id: { type: 'number', description: 'Booking ID' },
|
|
5245
|
+
start: { type: 'string', description: 'ISO 8601' },
|
|
5246
|
+
end: { type: 'string', description: 'ISO 8601' },
|
|
5247
|
+
force: { type: 'boolean', description: 'Apply despite conflicts' },
|
|
5248
|
+
dry_run: { type: 'boolean', description: 'Default TRUE: preview only' },
|
|
5249
|
+
},
|
|
5250
|
+
required: ['id', 'start', 'end'],
|
|
5251
|
+
},
|
|
5252
|
+
destructiveHint: true,
|
|
5253
|
+
},
|
|
5254
|
+
{
|
|
5255
|
+
name: 'woocommerce_list_membership_plans',
|
|
5256
|
+
description: 'All membership plans with member counts. Requires the WooCommerce Memberships extension on the site.',
|
|
5257
|
+
inputSchema: {
|
|
5258
|
+
type: 'object',
|
|
5259
|
+
properties: {},
|
|
5260
|
+
},
|
|
5261
|
+
readOnlyHint: true,
|
|
5262
|
+
},
|
|
5263
|
+
{
|
|
5264
|
+
name: 'woocommerce_list_memberships',
|
|
5265
|
+
description: 'List user memberships, filterable by plan, customer, or status.',
|
|
5266
|
+
inputSchema: {
|
|
5267
|
+
type: 'object',
|
|
5268
|
+
properties: {
|
|
5269
|
+
plan_id: { type: 'number' },
|
|
5270
|
+
customer_id: { type: 'number' },
|
|
5271
|
+
status: { type: 'string', description: 'active, paused, cancelled, expired, pending' },
|
|
5272
|
+
per_page: { type: 'number' },
|
|
5273
|
+
page: { type: 'number' },
|
|
5274
|
+
},
|
|
5275
|
+
},
|
|
5276
|
+
readOnlyHint: true,
|
|
5277
|
+
},
|
|
5278
|
+
{
|
|
5279
|
+
name: 'woocommerce_get_membership',
|
|
5280
|
+
description: 'Get one user membership with plan, dates, and order linkage.',
|
|
5281
|
+
inputSchema: {
|
|
5282
|
+
type: 'object',
|
|
5283
|
+
properties: {
|
|
5284
|
+
id: { type: 'number', description: 'Membership ID' },
|
|
5285
|
+
},
|
|
5286
|
+
required: ['id'],
|
|
5287
|
+
},
|
|
5288
|
+
readOnlyHint: true,
|
|
5289
|
+
},
|
|
5290
|
+
{
|
|
5291
|
+
name: 'woocommerce_update_membership_status',
|
|
5292
|
+
description: 'Pause, resume, or cancel a member\'s access. DRY-RUN BY DEFAULT: the change takes effect immediately for the member.',
|
|
5293
|
+
inputSchema: {
|
|
5294
|
+
type: 'object',
|
|
5295
|
+
properties: {
|
|
5296
|
+
id: { type: 'number', description: 'Membership ID' },
|
|
5297
|
+
membership_action: { type: 'string', description: 'pause, resume, or cancel' },
|
|
5298
|
+
note: { type: 'string' },
|
|
5299
|
+
dry_run: { type: 'boolean', description: 'Default TRUE: preview only' },
|
|
5300
|
+
},
|
|
5301
|
+
required: ['id', 'membership_action'],
|
|
5302
|
+
},
|
|
5303
|
+
destructiveHint: true,
|
|
5304
|
+
},
|
|
5305
|
+
{
|
|
5306
|
+
name: 'woocommerce_set_membership_end_date',
|
|
5307
|
+
description: 'Move or clear a membership\'s expiry date (empty string = never expires). DRY-RUN BY DEFAULT.',
|
|
5308
|
+
inputSchema: {
|
|
5309
|
+
type: 'object',
|
|
5310
|
+
properties: {
|
|
5311
|
+
id: { type: 'number', description: 'Membership ID' },
|
|
5312
|
+
end_date: { type: 'string', description: 'ISO 8601; empty string = never expires' },
|
|
5313
|
+
dry_run: { type: 'boolean', description: 'Default TRUE: preview only' },
|
|
5314
|
+
},
|
|
5315
|
+
required: ['id', 'end_date'],
|
|
5316
|
+
},
|
|
5317
|
+
destructiveHint: true,
|
|
5318
|
+
},
|
|
5319
|
+
// --- Assisted checkout (addon v3.1) ---
|
|
5320
|
+
{
|
|
5321
|
+
name: 'woocommerce_create_cart_link',
|
|
5322
|
+
description: 'Create a signed multi-item cart URL for a shopper: their cart fills server-side and they land on this store\'s own checkout, with the order attributed to the agent source. The assisted-checkout layer that works while native agent checkout programs are gated.',
|
|
5323
|
+
inputSchema: {
|
|
5324
|
+
type: 'object',
|
|
5325
|
+
properties: {
|
|
5326
|
+
items: {
|
|
5327
|
+
type: 'array',
|
|
5328
|
+
items: { type: 'object' },
|
|
5329
|
+
description: 'Each: {product_id or variation_id, quantity}. Variable products need a variation_id.',
|
|
5330
|
+
},
|
|
5331
|
+
utm_source: { type: 'string', description: 'Attribution source, e.g. chatgpt.com (default ai-agent)' },
|
|
5332
|
+
replace_cart: { type: 'boolean', description: 'Empty the shopper\'s cart first (default false)' },
|
|
5333
|
+
expires_in_days: { type: 'number', description: 'Default 30; 0 = never expires' },
|
|
5334
|
+
},
|
|
5335
|
+
required: ['items'],
|
|
5336
|
+
},
|
|
5337
|
+
},
|
|
5338
|
+
{
|
|
5339
|
+
name: 'woocommerce_agent_orders_report',
|
|
5340
|
+
description: 'Orders and revenue that arrived through agent cart links over a period, with a per-source breakdown.',
|
|
5341
|
+
inputSchema: {
|
|
5342
|
+
type: 'object',
|
|
5343
|
+
properties: {
|
|
5344
|
+
days: { type: 'number', description: 'Period in days (default 30, max 365)' },
|
|
5345
|
+
},
|
|
5346
|
+
},
|
|
5347
|
+
readOnlyHint: true,
|
|
5348
|
+
},
|
|
5349
|
+
// --- Readiness autofix (addon v3.1) ---
|
|
5350
|
+
{
|
|
5351
|
+
name: 'woocommerce_readiness_fixlist',
|
|
5352
|
+
description: 'The catalog AI-readiness scan as an ordered work queue: weakest products first, each failing check paired with the tool and params that fix it. Work it top-down to raise the catalog score.',
|
|
5353
|
+
inputSchema: {
|
|
5354
|
+
type: 'object',
|
|
5355
|
+
properties: {
|
|
5356
|
+
limit: { type: 'number', description: 'Products to scan (default 100, max 500)' },
|
|
5357
|
+
target_score: { type: 'number', description: 'Skip products at or above this score (default 90)' },
|
|
5358
|
+
},
|
|
5359
|
+
},
|
|
5360
|
+
readOnlyHint: true,
|
|
5361
|
+
},
|
|
5362
|
+
{
|
|
5363
|
+
name: 'woocommerce_set_image_alt',
|
|
5364
|
+
description: 'Set alt text on a product\'s featured image (default) or a specific gallery image. The readiness check that fails most often.',
|
|
5365
|
+
inputSchema: {
|
|
5366
|
+
type: 'object',
|
|
5367
|
+
properties: {
|
|
5368
|
+
id: { type: 'number', description: 'Product ID' },
|
|
5369
|
+
alt: { type: 'string' },
|
|
5370
|
+
attachment_id: { type: 'number', description: 'Optional gallery image; defaults to the featured image' },
|
|
5371
|
+
},
|
|
5372
|
+
required: ['id', 'alt'],
|
|
5373
|
+
},
|
|
5374
|
+
idempotentHint: true,
|
|
5375
|
+
},
|
|
5376
|
+
// --- Product feeds + llms.txt (addon v3.1) ---
|
|
5377
|
+
{
|
|
5378
|
+
name: 'woocommerce_configure_feed',
|
|
5379
|
+
description: 'Read or update product feed settings (formats, cadence, countries, llms.txt) and get the public feed URLs. Call with no params to read.',
|
|
5380
|
+
inputSchema: {
|
|
5381
|
+
type: 'object',
|
|
5382
|
+
properties: {
|
|
5383
|
+
enabled_formats: {
|
|
5384
|
+
type: 'array',
|
|
5385
|
+
items: { type: 'string' },
|
|
5386
|
+
description: 'Any of: google, openai, meta, pinterest, tiktok, csv',
|
|
5387
|
+
},
|
|
5388
|
+
cadence: { type: 'string', description: 'hourly, twicedaily, or daily' },
|
|
5389
|
+
include_out_of_stock: { type: 'boolean' },
|
|
5390
|
+
target_countries: { type: 'array', items: { type: 'string' }, description: 'ISO 3166-1 alpha-2' },
|
|
5391
|
+
store_country: { type: 'string' },
|
|
5392
|
+
llms_txt: { type: 'boolean', description: 'Serve a store llms.txt at the site root' },
|
|
5393
|
+
rotate_token: { type: 'boolean', description: 'Rotate the secret feed URLs' },
|
|
5394
|
+
},
|
|
5395
|
+
},
|
|
5396
|
+
idempotentHint: true,
|
|
5397
|
+
},
|
|
5398
|
+
{
|
|
5399
|
+
name: 'woocommerce_generate_feed',
|
|
5400
|
+
description: 'Build the enabled product feeds now (batched in the background; the first batch runs immediately, so this works even when wp-cron is dead). Idempotent while a build is running.',
|
|
5401
|
+
inputSchema: {
|
|
5402
|
+
type: 'object',
|
|
5403
|
+
properties: {
|
|
5404
|
+
format: { type: 'string', description: 'Optional: enable and build one specific format' },
|
|
5405
|
+
},
|
|
5406
|
+
},
|
|
5407
|
+
},
|
|
5408
|
+
{
|
|
5409
|
+
name: 'woocommerce_get_feed_status',
|
|
5410
|
+
description: 'Per-format feed URLs, row counts, generation times, running build progress, and wp-cron health warnings.',
|
|
5411
|
+
inputSchema: {
|
|
5412
|
+
type: 'object',
|
|
5413
|
+
properties: {},
|
|
5414
|
+
},
|
|
5415
|
+
readOnlyHint: true,
|
|
5416
|
+
},
|
|
5417
|
+
{
|
|
5418
|
+
name: 'woocommerce_validate_feed',
|
|
5419
|
+
description: 'Check a catalog sample against one feed format\'s requirements (identifiers, images, descriptions, titles, category mapping) with per-check failing product IDs, so the catalog tools can fix them.',
|
|
5420
|
+
inputSchema: {
|
|
5421
|
+
type: 'object',
|
|
5422
|
+
properties: {
|
|
5423
|
+
format: { type: 'string', description: 'google (default), openai, meta, pinterest, tiktok, csv' },
|
|
5424
|
+
limit: { type: 'number', description: 'Parent products to sample (default 100, max 200)' },
|
|
5425
|
+
},
|
|
5426
|
+
},
|
|
5427
|
+
readOnlyHint: true,
|
|
5428
|
+
},
|
|
5429
|
+
{
|
|
5430
|
+
name: 'woocommerce_set_feed_category_mapping',
|
|
5431
|
+
description: 'Map WooCommerce product categories to Google product categories for the feeds. Call with no params to list mapped and unmapped categories with product counts.',
|
|
5432
|
+
inputSchema: {
|
|
5433
|
+
type: 'object',
|
|
5434
|
+
properties: {
|
|
5435
|
+
mappings: { type: 'object', description: '{"<term_id>": "<google category id or full path>"}; empty string removes a mapping' },
|
|
5436
|
+
},
|
|
5437
|
+
},
|
|
5438
|
+
idempotentHint: true,
|
|
5439
|
+
},
|
|
5490
5440
|
// --- AI readiness (addon v3.0) ---
|
|
5491
5441
|
{
|
|
5492
5442
|
name: 'woocommerce_product_ai_readiness',
|
|
@@ -5755,10 +5705,6 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
5755
5705
|
? await client.getSiteContext()
|
|
5756
5706
|
: await client.getCompactSiteContext();
|
|
5757
5707
|
case 'wordpress_list_sites': {
|
|
5758
|
-
// Self-heal a stale list: re-pull the account's current sites so a site
|
|
5759
|
-
// added on the dashboard after this server booted shows up without a
|
|
5760
|
-
// re-paste or restart. Best-effort and throttled; never blocks the list.
|
|
5761
|
-
const refresh = await this.refreshSitesFromAccount().catch(() => ({ added: [] }));
|
|
5762
5708
|
const allSites = Array.from(this.sites.values());
|
|
5763
5709
|
const visibleSites = allSites.filter((site) => this.isSiteAllowed(site));
|
|
5764
5710
|
// v6.17.2: surface hidden sites so the AI can explain
|
|
@@ -5771,13 +5717,6 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
5771
5717
|
return {
|
|
5772
5718
|
sites: visibleSites.map((site) => this.getSiteSummary(site)),
|
|
5773
5719
|
active_site: this.getActiveSiteSummary(),
|
|
5774
|
-
...(refresh.added.length > 0
|
|
5775
|
-
? {
|
|
5776
|
-
refreshed_note: `Picked up ${refresh.added.length} newly added site${refresh.added.length === 1 ? '' : 's'} from your respira.press account: ${refresh.added.join(', ')}.${process.env.RESPIRA_CONFIG_B64?.trim()
|
|
5777
|
-
? ' This server is pinned to the RESPIRA_CONFIG_B64 env var, so update that env to keep these sites after a restart.'
|
|
5778
|
-
: ''}`,
|
|
5779
|
-
}
|
|
5780
|
-
: {}),
|
|
5781
5720
|
...(hiddenSites.length > 0
|
|
5782
5721
|
? {
|
|
5783
5722
|
hidden_by_filter: hiddenSites,
|
|
@@ -5919,14 +5858,14 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
5919
5858
|
return await client.getBuilderInlineSchemas(args.builder, args.types);
|
|
5920
5859
|
case 'wordpress_inject_builder_content':
|
|
5921
5860
|
return hoistDroppedStylingWarnings({
|
|
5922
|
-
...(await client.injectBuilderContent(args.builder, args.page_id, args.content, args.divi_version, args.edit_target, args.mode, args.confirm_replace
|
|
5861
|
+
...(await client.injectBuilderContent(args.builder, args.page_id, args.content, args.divi_version, args.edit_target, args.mode, args.confirm_replace)),
|
|
5923
5862
|
respira_approvals_url: client.getApprovalsUrl(),
|
|
5924
5863
|
});
|
|
5925
5864
|
case 'wordpress_make_responsive':
|
|
5926
5865
|
return await client.makeResponsive(args.page_id, args.dry_run || false);
|
|
5927
5866
|
case 'wordpress_update_module':
|
|
5928
5867
|
return hoistDroppedStylingWarnings({
|
|
5929
|
-
...(await client.updateModule(args.builder, args.page_id, args.module_identifier, args.updates, args.edit_target
|
|
5868
|
+
...(await client.updateModule(args.builder, args.page_id, args.module_identifier, args.updates, args.edit_target)),
|
|
5930
5869
|
respira_approvals_url: client.getApprovalsUrl(),
|
|
5931
5870
|
});
|
|
5932
5871
|
case 'wordpress_validate_security':
|
|
@@ -5943,13 +5882,6 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
5943
5882
|
...(await client.appendThemeFile(args.relative_path, args.content)),
|
|
5944
5883
|
respira_approvals_url: client.getApprovalsUrl(),
|
|
5945
5884
|
};
|
|
5946
|
-
case 'wordpress_read_debug_log':
|
|
5947
|
-
return await client.readDebugLog({
|
|
5948
|
-
lines: args.lines,
|
|
5949
|
-
level: args.level,
|
|
5950
|
-
since: args.since,
|
|
5951
|
-
grep: args.grep,
|
|
5952
|
-
});
|
|
5953
5885
|
case 'wordpress_switch_site': {
|
|
5954
5886
|
const newSite = this.sites.get(args.site_id);
|
|
5955
5887
|
if (!newSite) {
|
|
@@ -6176,8 +6108,6 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
6176
6108
|
confirm_live_edit: args.confirm_live_edit,
|
|
6177
6109
|
approval_token: args.approval_token,
|
|
6178
6110
|
});
|
|
6179
|
-
case 'wordpress_detach_duplicate':
|
|
6180
|
-
return await client.callRestV1('POST', `/duplicates/${args.duplicate_id}/detach`);
|
|
6181
6111
|
// Options
|
|
6182
6112
|
case 'wordpress_list_options':
|
|
6183
6113
|
return await client.listOptions(args.search);
|
|
@@ -6238,7 +6168,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
6238
6168
|
case 'wordpress_end_session':
|
|
6239
6169
|
return await client.callRestV2('POST', '/snapshots/session/end', {});
|
|
6240
6170
|
case 'wordpress_apply_builder_patch':
|
|
6241
|
-
return await client.applyBuilderPatch(args.builder, args.post_id, args.operations, args.include, args.edit_target
|
|
6171
|
+
return await client.applyBuilderPatch(args.builder, args.post_id, args.operations, args.include, args.edit_target);
|
|
6242
6172
|
case 'woocommerce_list_products':
|
|
6243
6173
|
return await client.woocommerceListProducts(args);
|
|
6244
6174
|
case 'woocommerce_get_product':
|
|
@@ -6374,6 +6304,68 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
6374
6304
|
const { id, ...payload } = args;
|
|
6375
6305
|
return await client.woocommerceCreateOrderRefund(id, payload);
|
|
6376
6306
|
}
|
|
6307
|
+
case 'woocommerce_list_subscriptions':
|
|
6308
|
+
return await client.woocommerceListSubscriptions(args);
|
|
6309
|
+
case 'woocommerce_get_subscription':
|
|
6310
|
+
return await client.woocommerceGetSubscription(args.id);
|
|
6311
|
+
case 'woocommerce_update_subscription_status': {
|
|
6312
|
+
const { id, ...payload } = args;
|
|
6313
|
+
return await client.woocommerceUpdateSubscriptionStatus(id, payload);
|
|
6314
|
+
}
|
|
6315
|
+
case 'woocommerce_update_subscription_dates': {
|
|
6316
|
+
const { id, ...payload } = args;
|
|
6317
|
+
return await client.woocommerceUpdateSubscriptionDates(id, payload);
|
|
6318
|
+
}
|
|
6319
|
+
case 'woocommerce_add_subscription_note': {
|
|
6320
|
+
const { id, ...payload } = args;
|
|
6321
|
+
return await client.woocommerceAddSubscriptionNote(id, payload);
|
|
6322
|
+
}
|
|
6323
|
+
case 'woocommerce_list_bookings':
|
|
6324
|
+
return await client.woocommerceListBookings(args);
|
|
6325
|
+
case 'woocommerce_get_booking':
|
|
6326
|
+
return await client.woocommerceGetBooking(args.id);
|
|
6327
|
+
case 'woocommerce_update_booking_status': {
|
|
6328
|
+
const { id, ...payload } = args;
|
|
6329
|
+
return await client.woocommerceUpdateBookingStatus(id, payload);
|
|
6330
|
+
}
|
|
6331
|
+
case 'woocommerce_reschedule_booking': {
|
|
6332
|
+
const { id, ...payload } = args;
|
|
6333
|
+
return await client.woocommerceRescheduleBooking(id, payload);
|
|
6334
|
+
}
|
|
6335
|
+
case 'woocommerce_list_membership_plans':
|
|
6336
|
+
return await client.woocommerceListMembershipPlans(args);
|
|
6337
|
+
case 'woocommerce_list_memberships':
|
|
6338
|
+
return await client.woocommerceListMemberships(args);
|
|
6339
|
+
case 'woocommerce_get_membership':
|
|
6340
|
+
return await client.woocommerceGetMembership(args.id);
|
|
6341
|
+
case 'woocommerce_update_membership_status': {
|
|
6342
|
+
const { id, ...payload } = args;
|
|
6343
|
+
return await client.woocommerceUpdateMembershipStatus(id, payload);
|
|
6344
|
+
}
|
|
6345
|
+
case 'woocommerce_set_membership_end_date': {
|
|
6346
|
+
const { id, ...payload } = args;
|
|
6347
|
+
return await client.woocommerceSetMembershipEndDate(id, payload);
|
|
6348
|
+
}
|
|
6349
|
+
case 'woocommerce_create_cart_link':
|
|
6350
|
+
return await client.woocommerceCreateCartLink(args);
|
|
6351
|
+
case 'woocommerce_agent_orders_report':
|
|
6352
|
+
return await client.woocommerceAgentOrdersReport(args);
|
|
6353
|
+
case 'woocommerce_readiness_fixlist':
|
|
6354
|
+
return await client.woocommerceReadinessFixlist(args);
|
|
6355
|
+
case 'woocommerce_set_image_alt': {
|
|
6356
|
+
const { id, ...payload } = args;
|
|
6357
|
+
return await client.woocommerceSetImageAlt(id, payload);
|
|
6358
|
+
}
|
|
6359
|
+
case 'woocommerce_configure_feed':
|
|
6360
|
+
return await client.woocommerceConfigureFeed(args);
|
|
6361
|
+
case 'woocommerce_generate_feed':
|
|
6362
|
+
return await client.woocommerceGenerateFeed(args);
|
|
6363
|
+
case 'woocommerce_get_feed_status':
|
|
6364
|
+
return await client.woocommerceGetFeedStatus();
|
|
6365
|
+
case 'woocommerce_validate_feed':
|
|
6366
|
+
return await client.woocommerceValidateFeed(args);
|
|
6367
|
+
case 'woocommerce_set_feed_category_mapping':
|
|
6368
|
+
return await client.woocommerceSetFeedCategoryMapping(args);
|
|
6377
6369
|
case 'woocommerce_product_ai_readiness':
|
|
6378
6370
|
return await client.woocommerceProductAiReadiness(args.id);
|
|
6379
6371
|
case 'woocommerce_catalog_ai_readiness':
|
|
@@ -6477,16 +6469,8 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
6477
6469
|
if (elementorResult !== null) {
|
|
6478
6470
|
return elementorResult;
|
|
6479
6471
|
}
|
|
6480
|
-
// Check if this is an ACF tool.
|
|
6481
|
-
|
|
6482
|
-
// are keyed by respira_acf_*), but normalizeToolName blindly rewrites
|
|
6483
|
-
// respira_* -> wordpress_*, so by the time dispatchToolCall runs an ACF
|
|
6484
|
-
// call arrives as wordpress_acf_*. Map it back so the guard matches and
|
|
6485
|
-
// the plugin dispatch gets the name it expects. Without this every ACF
|
|
6486
|
-
// tool fell through to "Unknown tool: wordpress_acf_*" (kenbi/Pedro,
|
|
6487
|
-
// 2026-07-14; same class as the v7.1.0 fix that regressed).
|
|
6488
|
-
const acfName = resolveAcfToolName(name);
|
|
6489
|
-
if (acfName) {
|
|
6472
|
+
// Check if this is an ACF tool.
|
|
6473
|
+
if (ACF_TOOL_NAMES.has(name)) {
|
|
6490
6474
|
if (!(await this.isAcfAvailable())) {
|
|
6491
6475
|
return {
|
|
6492
6476
|
__respira_is_error: true,
|
|
@@ -6494,7 +6478,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
6494
6478
|
hint: 'Install Advanced Custom Fields on the target site, then retry. ACF Pro is required for repeaters, flexible content, galleries, and options pages.'
|
|
6495
6479
|
};
|
|
6496
6480
|
}
|
|
6497
|
-
return await this.currentSite.dispatchAcfTool(
|
|
6481
|
+
return await this.currentSite.dispatchAcfTool(name, args);
|
|
6498
6482
|
}
|
|
6499
6483
|
// Check if this is a widget shortcut (wordpress_add_*).
|
|
6500
6484
|
if (name.startsWith('wordpress_add_')) {
|
|
@@ -6602,20 +6586,6 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
6602
6586
|
type: 'number',
|
|
6603
6587
|
description: 'Position index within the target container (0-based). Pass -1 to append.',
|
|
6604
6588
|
},
|
|
6605
|
-
edit_target: {
|
|
6606
|
-
type: 'string',
|
|
6607
|
-
enum: ['live', 'duplicate'],
|
|
6608
|
-
description: 'Where to apply the write. "duplicate" (default) routes the edit to the Respira duplicate of the original (or auto-creates one) so changes go through the Respira → Changes approval flow before they reach the public page. "live" writes straight to the published original (requires Respira → Settings → Allow direct edit, or the original to be a draft / existing Respira duplicate). The response always reports the resolved target so you know exactly which post received the write.',
|
|
6609
|
-
},
|
|
6610
|
-
editTarget: {
|
|
6611
|
-
type: 'string',
|
|
6612
|
-
enum: ['live', 'duplicate'],
|
|
6613
|
-
description: 'Deprecated camelCase alias of edit_target. Prefer edit_target. Kept for back-compat with pre-v6.19.5 callers.',
|
|
6614
|
-
},
|
|
6615
|
-
confirm_live_edit: {
|
|
6616
|
-
type: 'boolean',
|
|
6617
|
-
description: 'Required when responding to a `confirmation_required` reply on the live path. Pass `true` alongside `edit_target: "live"` to acknowledge the published-original write. Has no effect when `edit_target` is `duplicate` or omitted.',
|
|
6618
|
-
},
|
|
6619
6589
|
},
|
|
6620
6590
|
required: ['post_id', 'identifier_type', 'identifier_value', 'target_container_path', 'position'],
|
|
6621
6591
|
},
|
|
@@ -6653,27 +6623,13 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
6653
6623
|
post_id: { type: 'number', description: 'Page/post ID' },
|
|
6654
6624
|
identifier_type: {
|
|
6655
6625
|
type: 'string',
|
|
6656
|
-
enum: ['id', 'css_class', 'text', 'widget_type', 'global_id'
|
|
6657
|
-
description: 'How to locate the element: "id" (element ID), "css_class" (CSS class name), "text" (visible text content), "widget_type" (widget/module type), "global_id" (cross-page global element ID)
|
|
6626
|
+
enum: ['id', 'css_class', 'text', 'widget_type', 'global_id'],
|
|
6627
|
+
description: 'How to locate the element: "id" (element ID), "css_class" (CSS class name), "text" (visible text content), "widget_type" (widget/module type), or "global_id" (cross-page global element ID).',
|
|
6658
6628
|
},
|
|
6659
6629
|
identifier_value: {
|
|
6660
6630
|
type: 'string',
|
|
6661
6631
|
description: 'Value matching the chosen identifier_type (e.g. the element ID, the class name, the text to match).',
|
|
6662
6632
|
},
|
|
6663
|
-
edit_target: {
|
|
6664
|
-
type: 'string',
|
|
6665
|
-
enum: ['live', 'duplicate'],
|
|
6666
|
-
description: 'Where to apply the write. "duplicate" (default) routes the edit to the Respira duplicate of the original (or auto-creates one) so changes go through the Respira → Changes approval flow before they reach the public page. "live" writes straight to the published original (requires Respira → Settings → Allow direct edit, or the original to be a draft / existing Respira duplicate). The response always reports the resolved target so you know exactly which post received the write.',
|
|
6667
|
-
},
|
|
6668
|
-
editTarget: {
|
|
6669
|
-
type: 'string',
|
|
6670
|
-
enum: ['live', 'duplicate'],
|
|
6671
|
-
description: 'Deprecated camelCase alias of edit_target. Prefer edit_target. Kept for back-compat with pre-v6.19.5 callers.',
|
|
6672
|
-
},
|
|
6673
|
-
confirm_live_edit: {
|
|
6674
|
-
type: 'boolean',
|
|
6675
|
-
description: 'Required when responding to a `confirmation_required` reply on the live path. Pass `true` alongside `edit_target: "live"` to acknowledge the published-original write. Has no effect when `edit_target` is `duplicate` or omitted.',
|
|
6676
|
-
},
|
|
6677
6633
|
},
|
|
6678
6634
|
required: ['post_id', 'identifier_type', 'identifier_value'],
|
|
6679
6635
|
},
|
|
@@ -6723,20 +6679,6 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
6723
6679
|
description: 'Array of element IDs in the desired order.',
|
|
6724
6680
|
items: { type: 'string' },
|
|
6725
6681
|
},
|
|
6726
|
-
edit_target: {
|
|
6727
|
-
type: 'string',
|
|
6728
|
-
enum: ['live', 'duplicate'],
|
|
6729
|
-
description: 'Where to apply the write. "duplicate" (default) routes the edit to the Respira duplicate of the original (or auto-creates one) so changes go through the Respira → Changes approval flow before they reach the public page. "live" writes straight to the published original (requires Respira → Settings → Allow direct edit, or the original to be a draft / existing Respira duplicate). The response always reports the resolved target so you know exactly which post received the write.',
|
|
6730
|
-
},
|
|
6731
|
-
editTarget: {
|
|
6732
|
-
type: 'string',
|
|
6733
|
-
enum: ['live', 'duplicate'],
|
|
6734
|
-
description: 'Deprecated camelCase alias of edit_target. Prefer edit_target. Kept for back-compat with pre-v6.19.5 callers.',
|
|
6735
|
-
},
|
|
6736
|
-
confirm_live_edit: {
|
|
6737
|
-
type: 'boolean',
|
|
6738
|
-
description: 'Required when responding to a `confirmation_required` reply on the live path. Pass `true` alongside `edit_target: "live"` to acknowledge the published-original write. Has no effect when `edit_target` is `duplicate` or omitted.',
|
|
6739
|
-
},
|
|
6740
6682
|
},
|
|
6741
6683
|
required: ['post_id', 'container_path', 'new_order'],
|
|
6742
6684
|
},
|