@respira/wordpress-mcp-server 8.3.19 → 8.3.21

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 (41) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/README.md +3 -2
  3. package/TOOL_CATALOG.md +38 -37
  4. package/dist/__tests__/0e6bd33c-redeem-preserves-per-site-settings.test.d.ts +2 -0
  5. package/dist/__tests__/0e6bd33c-redeem-preserves-per-site-settings.test.d.ts.map +1 -0
  6. package/dist/__tests__/0e6bd33c-redeem-preserves-per-site-settings.test.js +91 -0
  7. package/dist/__tests__/0e6bd33c-redeem-preserves-per-site-settings.test.js.map +1 -0
  8. package/dist/__tests__/139f7353-put-fallback-dedicated-methods.test.d.ts +30 -0
  9. package/dist/__tests__/139f7353-put-fallback-dedicated-methods.test.d.ts.map +1 -0
  10. package/dist/__tests__/139f7353-put-fallback-dedicated-methods.test.js +162 -0
  11. package/dist/__tests__/139f7353-put-fallback-dedicated-methods.test.js.map +1 -0
  12. package/dist/__tests__/44fcf6b5-basic-auth-is-not-a-blocked-verb.test.d.ts +2 -0
  13. package/dist/__tests__/44fcf6b5-basic-auth-is-not-a-blocked-verb.test.d.ts.map +1 -0
  14. package/dist/__tests__/44fcf6b5-basic-auth-is-not-a-blocked-verb.test.js +207 -0
  15. package/dist/__tests__/44fcf6b5-basic-auth-is-not-a-blocked-verb.test.js.map +1 -0
  16. package/dist/__tests__/e105ed25-report-issue-builder-context.test.d.ts +2 -0
  17. package/dist/__tests__/e105ed25-report-issue-builder-context.test.d.ts.map +1 -0
  18. package/dist/__tests__/e105ed25-report-issue-builder-context.test.js +115 -0
  19. package/dist/__tests__/e105ed25-report-issue-builder-context.test.js.map +1 -0
  20. package/dist/__tests__/respira-outage-blamed-on-the-site.test.d.ts +2 -0
  21. package/dist/__tests__/respira-outage-blamed-on-the-site.test.d.ts.map +1 -0
  22. package/dist/__tests__/respira-outage-blamed-on-the-site.test.js +83 -0
  23. package/dist/__tests__/respira-outage-blamed-on-the-site.test.js.map +1 -0
  24. package/dist/__tests__/v2-negotiation-ttl-retry.test.d.ts +2 -0
  25. package/dist/__tests__/v2-negotiation-ttl-retry.test.d.ts.map +1 -0
  26. package/dist/__tests__/v2-negotiation-ttl-retry.test.js +108 -0
  27. package/dist/__tests__/v2-negotiation-ttl-retry.test.js.map +1 -0
  28. package/dist/config.d.ts.map +1 -1
  29. package/dist/config.js +59 -4
  30. package/dist/config.js.map +1 -1
  31. package/dist/server.d.ts.map +1 -1
  32. package/dist/server.js +51 -4
  33. package/dist/server.js.map +1 -1
  34. package/dist/wordpress-client.d.ts +77 -1
  35. package/dist/wordpress-client.d.ts.map +1 -1
  36. package/dist/wordpress-client.js +385 -74
  37. package/dist/wordpress-client.js.map +1 -1
  38. package/package.json +2 -2
  39. package/skills/woo-marketing-campaigns/SKILL.md +128 -0
  40. package/skills/woo-pricing-promotions/SKILL.md +119 -0
  41. package/tool-capabilities.json +107 -347
package/CHANGELOG.md CHANGED
@@ -5,6 +5,76 @@ All notable changes to Respira WordPress MCP Server will be documented in this f
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [8.3.21] - 2026-09-11
9
+
10
+ A share link that lives as long as you need.
11
+
12
+ ### Added
13
+
14
+ - **`wordpress_create_share_link`.** Mints a share-grade preview link for any page, post,
15
+ draft or Respira duplicate, valid 1 to 90 days (14 by default), with no write. Until now
16
+ a long-lived link only came with a staged change, so for an existing draft the one link an
17
+ agent could mint was the dashboard's 15-minute design preview, and that is what got quoted
18
+ to people. Needs plugin 8.8.33 or later on the site.
19
+ - **`ttl_days` on `wordpress_mint_design_preview`.** The same token, minted for days instead
20
+ of minutes when a person will open it.
21
+
22
+ ### Changed
23
+
24
+ - Both tool descriptions now say what the link is for: put it in the reply with its expiry
25
+ and suggest sending it to the client or whoever signs off. The site's own MCP instructions
26
+ say the same on every staged write.
27
+
28
+ ## [8.3.20] - 2026-09-09
29
+
30
+ ### Fixed
31
+
32
+ - **One failed handshake made a site look broken for the rest of the session.** A
33
+ successful version negotiation is cached for the life of the client, which is right.
34
+ A failure was cached the same way, which is not: a single transient error from the
35
+ host meant every later call replayed the same verdict without sending a request, so
36
+ nothing appeared in the site's own logs either and the site looked dead for as long as
37
+ the session lasted. Two customers sat blocked for a quarter of an hour on sites that
38
+ were already healthy. Failures now expire after a minute, and a message that came from
39
+ cache says when the failure was first seen instead of implying a fresh check.
40
+ - **A Respira outage was described as a fault on the customer's site.** When the
41
+ account service could not answer, the resulting error was rendered as though the site
42
+ had produced it, ending with advice to go and read wp-content/debug.log. The failure
43
+ is now named for what it is, a problem reaching Respira rather than anything to fix on
44
+ the site, and the debug.log advice is gone from that path.
45
+ - **Editing a menu item failed on hosts that block PUT.** Those hosts already had a
46
+ fallback that reissues the request as a POST, but it was only wired into some paths,
47
+ and twenty five methods called PUT directly without it, menus among them. The fallback
48
+ could not have rescued them anyway: it decided whether to retry by reading a status
49
+ that the shared error handler discards, so it only ever fired for one specific case
50
+ and never for a real 405. Both halves are fixed, so every PUT write now falls back
51
+ where the host requires it. Deletes still need a change on the WordPress side and are
52
+ unchanged for now.
53
+ - **Redeeming an install token discarded local per-site settings.** The redeemed entry
54
+ replaced the stored one outright, and the dashboard only sends five fields, so
55
+ anything set by hand was dropped: the flag that forces the query-string REST route,
56
+ the forced write method, HTTP auth credentials, the TLS exception, and local tool
57
+ preferences. The site kept working until the next restart, which put the loss a long
58
+ way from its cause. A redeem now updates the fields it carries and leaves the rest
59
+ alone.
60
+ - **A password-protected site was reported as one with Respira uninstalled.** The check
61
+ for whether the Respira routes exist was the one request in the client that did not
62
+ send configured credentials, so on a site behind HTTP auth it was refused, and the
63
+ refusal was read as the routes being absent. The connector then said the plugin was
64
+ deactivated, deleted, or failing to load. It also called an authentication challenge a
65
+ firewall or maintenance mode, and told operators to add credentials they had already
66
+ added. The probe now authenticates, an unanswerable check says so instead of claiming
67
+ absence, and the connection diagnostic reports an authentication challenge as what it
68
+ is rather than as a blocked method.
69
+ - **Filed issue reports lost the builder they came from.** The report tool read the
70
+ builder name from a field the site does not return, so every report was filed with no
71
+ builder recorded even when the site could name it unambiguously.
72
+ - **Two commerce skills were never shipped.** The bundling step rebuilds the skills
73
+ directory from source on every build, and these two existed only in the built copy, so
74
+ each build deleted them. They were listed in the public catalogue throughout. Both now
75
+ ship, and the build fails loudly rather than quietly deleting a skill that is missing
76
+ from source.
77
+
8
78
  ## [8.3.19] - 2026-09-04
9
79
 
10
80
  ### Fixed
package/README.md CHANGED
@@ -398,7 +398,7 @@ These are Respira-native WordPress operations, not a copied generic tool catalog
398
398
  | `wordpress_update_site_navigation` | Target one link/submenu while preserving unknown extension blocks |
399
399
  | `wordpress_delete_site_navigation` | Delete through approval; referenced navigation is refused by default |
400
400
 
401
- ### Design Direction (12 tools)
401
+ ### Design Direction (13 tools)
402
402
 
403
403
  One saved design direction per site — identity, DTCG-subset tokens, dials, guidance, waivers — that agents read before building anything. Readiness is computed on every response: `ready` needs the bg + ink + accent color roles plus a heading/body font pair, and activation refuses a direction that is not ready. The direction document is site data, not instructions.
404
404
 
@@ -418,7 +418,8 @@ The design check closes the loop: `wordpress_check_design` runs deterministic sl
418
418
  | `wordpress_apply_design_direction` | Push the direction's tokens into the resolved builder's native store through approval; snapshot first, per-builder report persisted |
419
419
  | `wordpress_export_design_direction` | Export strict DTCG 2025.10 with roles + per-builder apply reports in `$extensions["press.respira"]` |
420
420
  | `wordpress_check_design` | Design check: deterministic slop-and-drift rules, plus `rendered: true` (plugin 8.6.20+) for contrast, structure and painted colors on the real page, screenshots attached |
421
- | `wordpress_mint_design_preview` | Mint a 15-minute signed preview URL (plugin 8.6.22+) that renders the real page, drafts and duplicates included, frameable by the respira.press dashboard only; stores nothing |
421
+ | `wordpress_mint_design_preview` | Mint a signed preview URL (plugin 8.6.22+) that renders the real page, drafts and duplicates included, frameable by the respira.press dashboard only; 15 minutes, or `ttl_days` (1 to 90) for a link a person will open; stores nothing |
422
+ | `wordpress_create_share_link` | Create a share link (plugin 8.8.33+) for any page, draft or duplicate: opens with no WordPress login for `ttl_days` (1 to 90, default 14), no write; send it to the client or whoever signs off |
422
423
  | `wordpress_get_design_apply_reports` | Read the stored per-builder apply reports for a direction: what apply actually wrote, straight from the site |
423
424
 
424
425
  ### Bricks Deep Intelligence (20 tools)
package/TOOL_CATALOG.md CHANGED
@@ -2,31 +2,31 @@
2
2
 
3
3
  <!-- Generated by scripts/check-tool-registry-parity.mjs. Do not edit by hand. -->
4
4
 
5
- Generated catalog: **193 core tools**, **105 WooCommerce tools**, **298 total**.
5
+ Generated catalog: **194 core tools**, **105 WooCommerce tools**, **299 total**.
6
6
 
7
7
  The runtime applies site context and per-site governance, so an individual connection may expose a smaller relevant subset.
8
8
 
9
9
  ## WordPress and Respira
10
10
 
11
11
  - `respira_duplicate_with_translations` — write; hosted/stdio
12
- - `wordpress_abilities_gap_report` — read; hosted/stdio
12
+ - `wordpress_abilities_gap_report` — read; native + hosted
13
13
  - `wordpress_activate_design_direction` — write; native + hosted
14
14
  - `wordpress_activate_plugin` — write; native + hosted
15
15
  - `wordpress_analyze_aeo` — read; native + hosted
16
16
  - `wordpress_analyze_images` — read; native + hosted
17
- - `wordpress_analyze_pagespeed` — read; hosted/stdio
17
+ - `wordpress_analyze_pagespeed` — read; native + hosted
18
18
  - `wordpress_analyze_performance` — read; native + hosted
19
19
  - `wordpress_analyze_rankmath` — read; native + hosted
20
20
  - `wordpress_analyze_readability` — read; native + hosted
21
21
  - `wordpress_analyze_seo` — read; native + hosted
22
- - `wordpress_append_theme_file` — read; hosted/stdio
23
- - `wordpress_apply_accessibility_fixes` — write; hosted/stdio
22
+ - `wordpress_append_theme_file` — read; native + hosted
23
+ - `wordpress_apply_accessibility_fixes` — write; native + hosted
24
24
  - `wordpress_apply_builder_patch` — write; native + hosted
25
25
  - `wordpress_apply_design_direction` — write; native + hosted
26
- - `wordpress_apply_divi_library_item` — write; hosted/stdio
26
+ - `wordpress_apply_divi_library_item` — write; native + hosted
27
27
  - `wordpress_assign_menu_location` — write; native + hosted
28
28
  - `wordpress_batch_update` — write; native + hosted
29
- - `wordpress_begin_session` — read; hosted/stdio
29
+ - `wordpress_begin_session` — read; native + hosted
30
30
  - `wordpress_build_mega_menu` — write; native + hosted
31
31
  - `wordpress_build_page` — write; native + hosted
32
32
  - `wordpress_bulk_pages_operation` — write; native + hosted
@@ -34,7 +34,7 @@ The runtime applies site context and per-site governance, so an individual conne
34
34
  - `wordpress_check_seo_issues` — read; native + hosted
35
35
  - `wordpress_check_structured_data` — read; native + hosted
36
36
  - `wordpress_convert_html_to_builder` — write; native + hosted
37
- - `wordpress_create_acf_field_group` — write; hosted/stdio
37
+ - `wordpress_create_acf_field_group` — write; native + hosted
38
38
  - `wordpress_create_comment` — write; native + hosted
39
39
  - `wordpress_create_custom_post` — write; native + hosted
40
40
  - `wordpress_create_design_token` — write; native + hosted
@@ -43,23 +43,24 @@ The runtime applies site context and per-site governance, so an individual conne
43
43
  - `wordpress_create_page_duplicate` — write; native + hosted
44
44
  - `wordpress_create_playbook` — write; hosted/stdio
45
45
  - `wordpress_create_post_duplicate` — write; native + hosted
46
- - `wordpress_create_post_type` — write; hosted/stdio
46
+ - `wordpress_create_post_type` — write; native + hosted
47
+ - `wordpress_create_share_link` — write; native + hosted
47
48
  - `wordpress_create_site_navigation` — write; native + hosted
48
49
  - `wordpress_create_site_pattern` — write; native + hosted
49
50
  - `wordpress_create_site_template` — write; native + hosted
50
- - `wordpress_create_taxonomy` — write; hosted/stdio
51
+ - `wordpress_create_taxonomy` — write; native + hosted
51
52
  - `wordpress_create_term` — write; native + hosted
52
53
  - `wordpress_create_theme_builder_template` — write; native + hosted
53
54
  - `wordpress_create_translation` — write; native + hosted
54
55
  - `wordpress_create_user` — write; native + hosted
55
56
  - `wordpress_deactivate_design_direction` — write; native + hosted
56
57
  - `wordpress_deactivate_plugin` — write; native + hosted
57
- - `wordpress_delete_acf_field_group` — write; hosted/stdio
58
+ - `wordpress_delete_acf_field_group` — write; native + hosted
58
59
  - `wordpress_delete_comment` — write; native + hosted
59
60
  - `wordpress_delete_custom_post` — write; native + hosted
60
61
  - `wordpress_delete_design_direction` — write; native + hosted
61
62
  - `wordpress_delete_design_token` — write; native + hosted
62
- - `wordpress_delete_divi_preset` — write; hosted/stdio
63
+ - `wordpress_delete_divi_preset` — write; native + hosted
63
64
  - `wordpress_delete_media` — write; native + hosted
64
65
  - `wordpress_delete_menu` — write; native + hosted
65
66
  - `wordpress_delete_menu_item` — write; native + hosted
@@ -68,37 +69,37 @@ The runtime applies site context and per-site governance, so an individual conne
68
69
  - `wordpress_delete_playbook` — write; hosted/stdio
69
70
  - `wordpress_delete_plugin` — write; native + hosted
70
71
  - `wordpress_delete_post` — write; native + hosted
71
- - `wordpress_delete_post_type` — write; hosted/stdio
72
+ - `wordpress_delete_post_type` — write; native + hosted
72
73
  - `wordpress_delete_site_navigation` — write; native + hosted
73
74
  - `wordpress_delete_site_pattern` — write; native + hosted
74
- - `wordpress_delete_taxonomy` — write; hosted/stdio
75
+ - `wordpress_delete_taxonomy` — write; native + hosted
75
76
  - `wordpress_delete_term` — write; native + hosted
76
77
  - `wordpress_delete_user` — write; native + hosted
77
78
  - `wordpress_diagnose_connection` — read; hosted/stdio
78
79
  - `wordpress_diff_snapshots` — read; native + hosted
79
80
  - `wordpress_duplicate_element` — write; native + hosted
80
- - `wordpress_end_session` — read; hosted/stdio
81
+ - `wordpress_end_session` — read; native + hosted
81
82
  - `wordpress_export_design_direction` — read; native + hosted
82
83
  - `wordpress_extract_builder_content` — read; native + hosted
83
84
  - `wordpress_find_builder_targets` — read; native + hosted
84
85
  - `wordpress_find_element` — read; native + hosted
85
86
  - `wordpress_forget` — read; native + hosted
86
- - `wordpress_get_accessibility_scan` — read; hosted/stdio
87
+ - `wordpress_get_accessibility_scan` — read; native + hosted
87
88
  - `wordpress_get_active_site` — read; hosted/stdio
88
89
  - `wordpress_get_activity` — read; native + hosted
89
90
  - `wordpress_get_builder_info` — read; native + hosted
90
- - `wordpress_get_builder_inline_schemas` — read; hosted/stdio
91
+ - `wordpress_get_builder_inline_schemas` — read; native + hosted
91
92
  - `wordpress_get_comment` — read; native + hosted
92
93
  - `wordpress_get_core_web_vitals` — read; native + hosted
93
94
  - `wordpress_get_custom_post` — read; native + hosted
94
95
  - `wordpress_get_design_apply_reports` — read; native + hosted
95
96
  - `wordpress_get_design_direction` — read; native + hosted
96
- - `wordpress_get_divi_migration_readiness` — read; hosted/stdio
97
+ - `wordpress_get_divi_migration_readiness` — read; native + hosted
97
98
  - `wordpress_get_media` — read; native + hosted
98
99
  - `wordpress_get_menu` — read; native + hosted
99
100
  - `wordpress_get_menu_item` — read; native + hosted
100
101
  - `wordpress_get_option` — read; native + hosted
101
- - `wordpress_get_page_outline` — read; hosted/stdio
102
+ - `wordpress_get_page_outline` — read; native + hosted
102
103
  - `wordpress_get_playbook` — read; hosted/stdio
103
104
  - `wordpress_get_post_type` — read; native + hosted
104
105
  - `wordpress_get_server_compatibility` — read; native + hosted
@@ -116,16 +117,16 @@ The runtime applies site context and per-site governance, so an individual conne
116
117
  - `wordpress_inject_builder_content` — write; native + hosted
117
118
  - `wordpress_install_plugin` — write; native + hosted
118
119
  - `wordpress_invoke_ability` — write; hosted/stdio
119
- - `wordpress_list_accessibility_scans` — read; hosted/stdio
120
- - `wordpress_list_acf_field_groups` — read; hosted/stdio
120
+ - `wordpress_list_accessibility_scans` — read; native + hosted
121
+ - `wordpress_list_acf_field_groups` — read; native + hosted
121
122
  - `wordpress_list_activity` — read; native + hosted
122
123
  - `wordpress_list_comments` — read; native + hosted
123
- - `wordpress_list_custom_post_types` — read; hosted/stdio
124
+ - `wordpress_list_custom_post_types` — read; native + hosted
124
125
  - `wordpress_list_custom_posts` — read; native + hosted
125
- - `wordpress_list_custom_taxonomies` — read; hosted/stdio
126
+ - `wordpress_list_custom_taxonomies` — read; native + hosted
126
127
  - `wordpress_list_design_directions` — read; native + hosted
127
128
  - `wordpress_list_design_tokens` — read; native + hosted
128
- - `wordpress_list_divi_presets` — read; hosted/stdio
129
+ - `wordpress_list_divi_presets` — read; native + hosted
129
130
  - `wordpress_list_media` — read; native + hosted
130
131
  - `wordpress_list_memory` — read; native + hosted
131
132
  - `wordpress_list_menu_items` — read; native + hosted
@@ -146,40 +147,40 @@ The runtime applies site context and per-site governance, so an individual conne
146
147
  - `wordpress_list_terms` — read; native + hosted
147
148
  - `wordpress_list_theme_builder_templates` — read; native + hosted
148
149
  - `wordpress_list_users` — read; native + hosted
149
- - `wordpress_make_responsive` — read; hosted/stdio
150
+ - `wordpress_make_responsive` — read; native + hosted
150
151
  - `wordpress_mint_design_preview` — read; native + hosted
151
152
  - `wordpress_move_element` — write; native + hosted
152
153
  - `wordpress_purge_cache` — write; native + hosted
153
154
  - `wordpress_read_page` — read; native + hosted
154
155
  - `wordpress_read_post` — read; native + hosted
155
- - `wordpress_read_theme_file` — read; hosted/stdio
156
+ - `wordpress_read_theme_file` — read; native + hosted
156
157
  - `wordpress_redeem_token` — write; hosted/stdio
157
158
  - `wordpress_remember` — read; native + hosted
158
159
  - `wordpress_remove_element` — write; native + hosted
159
160
  - `wordpress_reorder_elements` — write; native + hosted
160
161
  - `wordpress_report_issue` — read; hosted/stdio
161
162
  - `wordpress_reset_site_template` — write; native + hosted
162
- - `wordpress_resolve_divi_preset` — read; hosted/stdio
163
- - `wordpress_restore_session` — write; hosted/stdio
163
+ - `wordpress_resolve_divi_preset` — read; native + hosted
164
+ - `wordpress_restore_session` — write; native + hosted
164
165
  - `wordpress_restore_snapshot` — write; native + hosted
165
- - `wordpress_run_pagespeed_audit` — read; hosted/stdio
166
+ - `wordpress_run_pagespeed_audit` — read; native + hosted
166
167
  - `wordpress_run_security_audit` — read; native + hosted
167
168
  - `wordpress_save_design_direction` — write; native + hosted
168
- - `wordpress_save_divi_library_item` — write; hosted/stdio
169
- - `wordpress_scan_page_accessibility` — read; hosted/stdio
169
+ - `wordpress_save_divi_library_item` — write; native + hosted
170
+ - `wordpress_scan_page_accessibility` — read; native + hosted
170
171
  - `wordpress_search_abilities` — read; native + hosted
171
172
  - `wordpress_search_docs` — read; hosted/stdio
172
173
  - `wordpress_search_stock_images` — read; native + hosted
173
174
  - `wordpress_sideload_image` — write; native + hosted
174
175
  - `wordpress_switch_site` — write; native + hosted
175
- - `wordpress_update_acf_field_group` — write; hosted/stdio
176
+ - `wordpress_update_acf_field_group` — write; native + hosted
176
177
  - `wordpress_update_comment` — write; native + hosted
177
178
  - `wordpress_update_core_security` — write; native + hosted
178
179
  - `wordpress_update_custom_post` — write; native + hosted
179
180
  - `wordpress_update_design_token` — write; native + hosted
180
181
  - `wordpress_update_element` — write; native + hosted
181
182
  - `wordpress_update_media` — write; native + hosted
182
- - `wordpress_update_media_batch` — write; hosted/stdio
183
+ - `wordpress_update_media_batch` — write; native + hosted
183
184
  - `wordpress_update_menu` — write; native + hosted
184
185
  - `wordpress_update_menu_item` — write; native + hosted
185
186
  - `wordpress_update_module` — write; native + hosted
@@ -188,19 +189,19 @@ The runtime applies site context and per-site governance, so an individual conne
188
189
  - `wordpress_update_playbook` — write; hosted/stdio
189
190
  - `wordpress_update_plugin` — write; native + hosted
190
191
  - `wordpress_update_post` — write; native + hosted
191
- - `wordpress_update_post_type` — write; hosted/stdio
192
+ - `wordpress_update_post_type` — write; native + hosted
192
193
  - `wordpress_update_site_navigation` — write; native + hosted
193
194
  - `wordpress_update_site_pattern` — write; native + hosted
194
195
  - `wordpress_update_site_template` — write; native + hosted
195
- - `wordpress_update_taxonomy` — write; hosted/stdio
196
+ - `wordpress_update_taxonomy` — write; native + hosted
196
197
  - `wordpress_update_term` — write; native + hosted
197
198
  - `wordpress_update_theme_builder_template` — write; native + hosted
198
199
  - `wordpress_update_translation` — write; native + hosted
199
200
  - `wordpress_update_user` — write; native + hosted
200
201
  - `wordpress_upload_media` — write; native + hosted
201
- - `wordpress_upsert_divi_presets` — read; hosted/stdio
202
+ - `wordpress_upsert_divi_presets` — read; native + hosted
202
203
  - `wordpress_validate_security` — read; native + hosted
203
- - `wordpress_write_theme_file` — read; hosted/stdio
204
+ - `wordpress_write_theme_file` — read; native + hosted
204
205
 
205
206
  ## WooCommerce add-on
206
207
 
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=0e6bd33c-redeem-preserves-per-site-settings.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"0e6bd33c-redeem-preserves-per-site-settings.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/0e6bd33c-redeem-preserves-per-site-settings.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Ticket 0e6bd33c — redeeming a token must not silently discard the per-site
3
+ * settings the token does not own.
4
+ *
5
+ * A redeem token carries exactly five fields per site: id, name, url, apiKey
6
+ * and default (buildRespiraConfig, product-website/src/lib/mcp-config.ts).
7
+ * Everything else on a site object in ~/.respira/config.json was put there by
8
+ * the operator, usually on this connector's own advice:
9
+ *
10
+ * forceRestRoute — the only thing that makes hosts work where pretty
11
+ * /wp-json/ 404s and ?rest_route= returns JSON
12
+ * forceWriteMethod — the only thing that makes hosts work where PUT is
13
+ * blocked or dropped
14
+ * httpAuth — server-level Basic Auth for staging sites
15
+ * insecureTLS — the documented TLS escape hatch
16
+ *
17
+ * mergeRedeemedSites used to do `byKey.set(key, site)` on a URL collision:
18
+ * the freshly minted entry replaced the whole object. Redeem reported
19
+ * success, the file was rewritten, and every one of those flags was gone.
20
+ * The session kept working (the live clients were already built) so nothing
21
+ * failed until the next restart, at which point the site failed exactly the
22
+ * way it had failed before the flag was added.
23
+ *
24
+ * These assertions fail on unmodified main.
25
+ */
26
+ import { test } from 'node:test';
27
+ import assert from 'node:assert/strict';
28
+ import { mergeRedeemedSites } from '../config.js';
29
+ /** What the dashboard mints: five fields, nothing else. */
30
+ function tokenSite(id, url, extra = {}) {
31
+ return { id, name: id, url, apiKey: `respira_site_${id}`, ...extra };
32
+ }
33
+ test('a re-redeem keeps forceRestRoute on the site that needed it', () => {
34
+ const existing = [
35
+ tokenSite('a', 'https://a.example.com', { default: true, forceRestRoute: true }),
36
+ ];
37
+ const merged = mergeRedeemedSites(existing, [tokenSite('a', 'https://a.example.com', { default: true })]);
38
+ assert.equal(merged.length, 1);
39
+ assert.equal(merged[0].forceRestRoute, true, 'forceRestRoute was dropped by the redeem rewrite; the site 404s on every call after the next restart');
40
+ });
41
+ test('a re-redeem keeps every per-site key the token does not carry', () => {
42
+ const existing = [
43
+ tokenSite('a', 'https://a.example.com', {
44
+ default: true,
45
+ forceRestRoute: true,
46
+ forceWriteMethod: 'post',
47
+ insecureTLS: true,
48
+ httpAuth: { username: 'staging', password: 'hunter2' },
49
+ }),
50
+ ];
51
+ const merged = mergeRedeemedSites(existing, [tokenSite('a', 'https://a.example.com', { default: true })]);
52
+ assert.equal(merged[0].forceRestRoute, true, 'forceRestRoute dropped');
53
+ assert.equal(merged[0].forceWriteMethod, 'post', 'forceWriteMethod dropped');
54
+ assert.equal(merged[0].insecureTLS, true, 'insecureTLS dropped');
55
+ assert.deepEqual(merged[0].httpAuth, { username: 'staging', password: 'hunter2' }, 'httpAuth dropped');
56
+ });
57
+ test('an unrecognised per-site key added by hand also survives', () => {
58
+ // Deliberately not in WordPressSiteConfig: an allowlist-based fix would
59
+ // drop this, and would drop every future key added to the schema too.
60
+ const existing = [
61
+ { ...tokenSite('a', 'https://a.example.com', { default: true }), someFutureFlag: 'keep-me' },
62
+ ];
63
+ const merged = mergeRedeemedSites(existing, [tokenSite('a', 'https://a.example.com', { default: true })]);
64
+ assert.equal(merged[0].someFutureFlag, 'keep-me');
65
+ });
66
+ test('the token still wins for the fields it does carry', () => {
67
+ const existing = [
68
+ tokenSite('a', 'https://a.example.com', { default: true, forceRestRoute: true }),
69
+ ];
70
+ const merged = mergeRedeemedSites(existing, [
71
+ { id: 'a', name: 'Renamed', url: 'https://a.example.com', apiKey: 'respira_site_rotated', default: true },
72
+ ]);
73
+ assert.equal(merged[0].apiKey, 'respira_site_rotated', 'a rotated key must still land');
74
+ assert.equal(merged[0].name, 'Renamed', 'a renamed site must still land');
75
+ assert.equal(merged[0].forceRestRoute, true, 'and the local flag must still survive it');
76
+ });
77
+ test('preserving local keys does not leave two sites claiming default', () => {
78
+ const existing = [
79
+ tokenSite('a', 'https://a.example.com', { default: true, forceRestRoute: true }),
80
+ tokenSite('b', 'https://b.example.com'),
81
+ ];
82
+ // A full-account token re-mints both and marks b default.
83
+ const merged = mergeRedeemedSites(existing, [
84
+ tokenSite('b', 'https://b.example.com', { default: true }),
85
+ tokenSite('a', 'https://a.example.com'),
86
+ ]);
87
+ assert.equal(merged.filter((s) => s.default === true).length, 1, 'exactly one default must survive the merge');
88
+ assert.equal(merged.find((s) => s.default === true)?.url, 'https://b.example.com', "the just-redeemed token's default wins");
89
+ assert.equal(merged.find((s) => s.url === 'https://a.example.com')?.forceRestRoute, true);
90
+ });
91
+ //# sourceMappingURL=0e6bd33c-redeem-preserves-per-site-settings.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"0e6bd33c-redeem-preserves-per-site-settings.test.js","sourceRoot":"","sources":["../../src/__tests__/0e6bd33c-redeem-preserves-per-site-settings.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Ticket 139f7353 — the PUT-blocked-host fallback never reached the ~25
3
+ * dedicated client methods that call `client.put()` directly.
4
+ *
5
+ * Two earlier tickets fixed this in two places: e2d884cb wired
6
+ * `putWithPostFallback` into updatePost/updatePage/updateCustomPost, and
7
+ * 2b3a706b wired it into the shared `callRestV1`/`callRestV2` callers. Both
8
+ * left the dedicated methods alone. On a host whose edge blocks PUT (reported
9
+ * on Kinsta + Cloudflare, where `respira_diagnose_connection` shows
10
+ * `write_method_probe.put_status` 404/405) that produced a very specific
11
+ * shape of broken: `respira_create_menu_item` worked, because creating is a
12
+ * POST, and `respira_update_menu_item` returned 405, so a menu could be built
13
+ * and then never reordered.
14
+ *
15
+ * `respira_update_menu_item` is the one that got reported. It is not special:
16
+ * every menu, media, user, comment, term, WooCommerce and ACF update method
17
+ * had the identical gap.
18
+ *
19
+ * The fallback is safe here for the same reason it is safe for posts and
20
+ * pages: the plugin registers all of these routes with
21
+ * `WP_REST_Server::EDITABLE`, which is `'POST, PUT, PATCH'`, so the fallback
22
+ * POST lands on the identical handler. Verified against the route table in
23
+ * wordpress-plugin/respira-for-wordpress/includes/class-respira-api.php.
24
+ *
25
+ * A real HTTP server, not a mocked adapter, so the whole interceptor chain
26
+ * runs — including the codedError() step that strips `.response` and made an
27
+ * earlier status-only version of this predicate a no-op.
28
+ */
29
+ export {};
30
+ //# sourceMappingURL=139f7353-put-fallback-dedicated-methods.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"139f7353-put-fallback-dedicated-methods.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/139f7353-put-fallback-dedicated-methods.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG"}
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Ticket 139f7353 — the PUT-blocked-host fallback never reached the ~25
3
+ * dedicated client methods that call `client.put()` directly.
4
+ *
5
+ * Two earlier tickets fixed this in two places: e2d884cb wired
6
+ * `putWithPostFallback` into updatePost/updatePage/updateCustomPost, and
7
+ * 2b3a706b wired it into the shared `callRestV1`/`callRestV2` callers. Both
8
+ * left the dedicated methods alone. On a host whose edge blocks PUT (reported
9
+ * on Kinsta + Cloudflare, where `respira_diagnose_connection` shows
10
+ * `write_method_probe.put_status` 404/405) that produced a very specific
11
+ * shape of broken: `respira_create_menu_item` worked, because creating is a
12
+ * POST, and `respira_update_menu_item` returned 405, so a menu could be built
13
+ * and then never reordered.
14
+ *
15
+ * `respira_update_menu_item` is the one that got reported. It is not special:
16
+ * every menu, media, user, comment, term, WooCommerce and ACF update method
17
+ * had the identical gap.
18
+ *
19
+ * The fallback is safe here for the same reason it is safe for posts and
20
+ * pages: the plugin registers all of these routes with
21
+ * `WP_REST_Server::EDITABLE`, which is `'POST, PUT, PATCH'`, so the fallback
22
+ * POST lands on the identical handler. Verified against the route table in
23
+ * wordpress-plugin/respira-for-wordpress/includes/class-respira-api.php.
24
+ *
25
+ * A real HTTP server, not a mocked adapter, so the whole interceptor chain
26
+ * runs — including the codedError() step that strips `.response` and made an
27
+ * earlier status-only version of this predicate a no-op.
28
+ */
29
+ import { test, after } from 'node:test';
30
+ import assert from 'node:assert/strict';
31
+ import http from 'node:http';
32
+ import { WordPressClient, ORIGINAL_METHOD_HEADER, isRetryableAsPost } from '../wordpress-client.js';
33
+ /**
34
+ * The second half of the same ticket. PUT_FALLBACK_STATUSES has said
35
+ * {403, 404, 405} since 2b3a706b, but `handleError` rebuilds every non-2xx
36
+ * through `codedError`, which drops `.response`. So the status check in
37
+ * `isRetryableAsPost` could never fire on a real client error, and the
38
+ * name-based check that patched around that was hardcoded to 403 alone.
39
+ * A host that answers PUT with 405 and an HTML page — which is what the
40
+ * reporter's edge does — was therefore never retried, even at the call sites
41
+ * where the fallback WAS already wired in.
42
+ */
43
+ test('an HTML 405 from the edge is retryable as POST, not just an HTML 403', () => {
44
+ assert.equal(isRetryableAsPost({ name: 'http_403_non_wordpress' }), true);
45
+ assert.equal(isRetryableAsPost({ name: 'http_405_non_wordpress' }), true, '405 is the reported case');
46
+ assert.equal(isRetryableAsPost({ name: 'http_404_non_wordpress' }), true, '404 is the other reported case');
47
+ // A status raised BY WordPress keeps its WP_Error code and stays a decision.
48
+ assert.equal(isRetryableAsPost({ name: 'rest_forbidden' }), false);
49
+ assert.equal(isRetryableAsPost({ name: 'http_500_non_wordpress' }), false);
50
+ assert.equal(isRetryableAsPost({ name: 'http_401_non_wordpress' }), false);
51
+ });
52
+ const servers = [];
53
+ after(() => { for (const s of servers)
54
+ s.close(); });
55
+ /**
56
+ * A host that answers 405 Method Not Allowed to PUT before WordPress runs
57
+ * (Cloudflare / Kinsta edge rule on the verb) and 200 to POST on the same
58
+ * path. The 405 body is HTML, like a real edge rejection, so the client
59
+ * cannot mistake it for a WordPress decision.
60
+ */
61
+ async function startPutBlockedSite() {
62
+ const hits = [];
63
+ const server = http.createServer((req, res) => {
64
+ hits.push({
65
+ method: req.method || '',
66
+ url: req.url || '',
67
+ idempotencyKey: req.headers['idempotency-key'] || null,
68
+ originalMethod: req.headers[ORIGINAL_METHOD_HEADER.toLowerCase()] || null,
69
+ });
70
+ if (req.method === 'PUT') {
71
+ res.writeHead(405, { 'Content-Type': 'text/html', Allow: 'GET, POST, HEAD' });
72
+ res.end('<!DOCTYPE html><html><head><title>405 Not Allowed</title></head><body>405</body></html>');
73
+ return;
74
+ }
75
+ if (req.method === 'POST') {
76
+ let body = '';
77
+ req.on('data', (c) => { body += c; });
78
+ req.on('end', () => {
79
+ res.writeHead(200, { 'Content-Type': 'application/json' });
80
+ res.end(JSON.stringify({ success: true, via: 'POST', echo: body ? JSON.parse(body) : null }));
81
+ });
82
+ return;
83
+ }
84
+ res.writeHead(200, { 'Content-Type': 'application/json' });
85
+ res.end(JSON.stringify({ ok: true }));
86
+ });
87
+ servers.push(server);
88
+ await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
89
+ const { port } = server.address();
90
+ const client = new WordPressClient({
91
+ id: 'blocked',
92
+ name: 'blocked',
93
+ url: `http://127.0.0.1:${port}`,
94
+ apiKey: `respira_site_${'x'.repeat(24)}`,
95
+ });
96
+ return { client, hits };
97
+ }
98
+ test('update_menu_item survives a host that answers 405 to PUT', async () => {
99
+ const { client, hits } = await startPutBlockedSite();
100
+ const result = await client.updateMenuItem(42, { position: 2 });
101
+ assert.equal(result.success, true, 'the reorder must actually land, not surface a 405');
102
+ assert.deepEqual(hits.map((h) => h.method), ['PUT', 'POST'], 'exactly one PUT then exactly one POST');
103
+ assert.equal(hits[1].url, '/wp-json/respira/v1/menus/items/42');
104
+ assert.equal(hits[1].originalMethod, 'PUT', 'the fallback POST must declare the verb it replaces');
105
+ assert.equal(hits[0].idempotencyKey, hits[1].idempotencyKey, 'both attempts must share one Idempotency-Key or the plugin cannot dedupe them');
106
+ assert.ok(hits[0].idempotencyKey, 'an idempotency key must be present at all');
107
+ });
108
+ /**
109
+ * update_menu_item is the reported one. These are the sibling methods that had
110
+ * the identical gap — all of them EDITABLE routes, so all of them fixable the
111
+ * same way. Named individually so a future regression names the tool it broke.
112
+ */
113
+ const SIBLINGS = [
114
+ ['update_menu', (c) => c.updateMenu(7, { name: 'Main' }), '/wp-json/respira/v1/menus/7'],
115
+ ['assign_menu_location', (c) => c.assignMenuToLocation('primary', 7), '/wp-json/respira/v1/menus/locations/primary'],
116
+ ['update_media', (c) => c.updateMedia(3, { alt: 'a' }), '/wp-json/respira/v1/media/3'],
117
+ ['update_media_batch', (c) => c.updateMediaBatch([{ id: 3, alt: 'a' }]), '/wp-json/respira/v1/media/batch'],
118
+ ['update_user', (c) => c.updateUser(5, { display_name: 'A' }), '/wp-json/respira/v1/users/5'],
119
+ ['update_comment', (c) => c.updateComment(9, { content: 'x' }), '/wp-json/respira/v1/comments/9'],
120
+ ['update_term', (c) => c.updateTerm('category', 4, { name: 'n' }), '/wp-json/respira/v1/taxonomies/category/terms/4'],
121
+ ];
122
+ for (const [label, call, expectedPath] of SIBLINGS) {
123
+ test(`${label} survives a host that answers 405 to PUT`, async () => {
124
+ const { client, hits } = await startPutBlockedSite();
125
+ const result = await call(client);
126
+ assert.equal(result.success, true, `${label} must fall back to POST`);
127
+ assert.deepEqual(hits.map((h) => h.method), ['PUT', 'POST']);
128
+ assert.equal(hits[1].url, expectedPath);
129
+ assert.equal(hits[1].originalMethod, 'PUT');
130
+ assert.equal(hits[0].idempotencyKey, hits[1].idempotencyKey);
131
+ });
132
+ }
133
+ test('the ACF dispatcher falls back too', async () => {
134
+ const { client, hits } = await startPutBlockedSite();
135
+ const result = await client.dispatchAcfTool('respira_acf_update_field', {
136
+ post_id: 12,
137
+ field_key: 'field_1',
138
+ value: 'x',
139
+ });
140
+ assert.equal(result.success, true);
141
+ assert.deepEqual(hits.map((h) => h.method), ['PUT', 'POST']);
142
+ assert.equal(hits[1].originalMethod, 'PUT');
143
+ });
144
+ test('a WordPress-issued 405 on a POST is not retried into a loop', async () => {
145
+ // Guard against the fix turning one failed call into an unbounded ladder:
146
+ // the fallback is exactly one POST, and a POST that fails is final.
147
+ const hits = [];
148
+ const server = http.createServer((req, res) => {
149
+ hits.push(req.method || '');
150
+ res.writeHead(405, { 'Content-Type': 'text/html' });
151
+ res.end('<!DOCTYPE html><html><head><title>405</title></head><body>405</body></html>');
152
+ });
153
+ servers.push(server);
154
+ await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
155
+ const { port } = server.address();
156
+ const client = new WordPressClient({
157
+ id: 'x', name: 'x', url: `http://127.0.0.1:${port}`, apiKey: `respira_site_${'x'.repeat(24)}`,
158
+ });
159
+ await assert.rejects(() => client.updateMenuItem(1, { position: 1 }));
160
+ assert.deepEqual(hits, ['PUT', 'POST'], 'one PUT, one POST, then stop');
161
+ });
162
+ //# sourceMappingURL=139f7353-put-fallback-dedicated-methods.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"139f7353-put-fallback-dedicated-methods.test.js","sourceRoot":"","sources":["../../src/__tests__/139f7353-put-fallback-dedicated-methods.test.ts"],"names":[],"mappings":"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"}