@cmo-so/mcp 0.12.23 → 0.12.25

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 (2) hide show
  1. package/dist/cmo-mcp.mjs +354 -4
  2. package/package.json +1 -1
package/dist/cmo-mcp.mjs CHANGED
@@ -24489,7 +24489,7 @@ var require_dist3 = __commonJS({
24489
24489
  // package.json
24490
24490
  var package_default = {
24491
24491
  name: "@cmo-so/cli",
24492
- version: "0.12.23",
24492
+ version: "0.12.25",
24493
24493
  description: "CMO.so on the command line: your websites, ideas, blog posts, LinkedIn queue and results, plus a local MCP server for Claude Desktop, Cursor and other MCP clients.",
24494
24494
  type: "module",
24495
24495
  license: "MIT",
@@ -44525,7 +44525,7 @@ var childTools = [
44525
44525
  }),
44526
44526
  tool({
44527
44527
  name: "wp_child_theme_update",
44528
- description: "Change a Maggie child theme's files (default: the active child): brand colours (by name, hex) and fonts (by name, CSS font-family of fonts already in it) in theme.json, extra theme.json styles, plain CSS (replace or append), the site's own JavaScript (assets/js/site.js, replace or append; runs for every visitor), the header or footer (block markup), templates, and the site's own patterns (shown in the editor's inserter). No PHP or server code. Each change keeps a backup (wp_child_theme_restore). Changes made with wp_style_set / wp_header_set are stored in the database and win over the child's files.",
44528
+ description: "Change a Maggie child theme's files (default: the active child): brand colours (by name, hex) and fonts (by name, CSS font-family of fonts already in it) in theme.json, extra theme.json styles, plain CSS (replace or append), the site's own JavaScript (assets/js/site.js, replace or append; runs for every visitor), the child's own Tailwind CSS (tailwind/child.css: extra @theme tokens, @utility rules; Maggie Press and the native build import it after Maggie Core's bridge; needs Maggie Core 0.20+), the header or footer (block markup), templates, and the site's own patterns (shown in the editor's inserter). No PHP or server code. Each change keeps a backup (wp_child_theme_restore). Changes made with wp_style_set / wp_header_set are stored in the database and win over the child's files.",
44529
44529
  parameters: {
44530
44530
  type: "object",
44531
44531
  properties: {
@@ -44546,6 +44546,10 @@ var childTools = [
44546
44546
  styles: obj('theme.json styles to merge in (e.g. { "elements": { "button": { \u2026 } } }).'),
44547
44547
  css: str2("Replace the child's CSS (plain CSS)."),
44548
44548
  css_append: str2("Add CSS at the end."),
44549
+ tailwind: str2(
44550
+ "Replace the child's own Tailwind CSS (tailwind/child.css): @theme tokens, @utility and @layer rules that travel with the child theme. Tailwind CSS v4 syntax; no @plugin, @config or remote @import. Needs Maggie Core 0.20+."
44551
+ ),
44552
+ tailwind_append: str2("Add Tailwind CSS at the end of the child's tailwind/child.css."),
44549
44553
  js: str2(
44550
44554
  `Replace the site's own script (assets/js/site.js, plain JavaScript loaded deferred on every page; "" turns it off). Needs Maggie Core 0.12+ and a login allowed to add scripts. No eval or new Function; addresses only on the site or cdn.jsdelivr.net, unpkg.com, cdnjs.cloudflare.com.`
44551
44555
  ),
@@ -44658,7 +44662,7 @@ var childTools = [
44658
44662
  tj.styles = deepMerge(tj.styles ?? {}, a3.styles);
44659
44663
  files["theme.json"] = JSON.stringify(tj, null, " ");
44660
44664
  }
44661
- const touchesCredit = [a3.css, a3.css_append, a3.js, a3.js_append].some(
44665
+ const touchesCredit = [a3.css, a3.css_append, a3.js, a3.js_append, a3.tailwind, a3.tailwind_append].some(
44662
44666
  (t) => typeof t === "string" && /maggie-credit/i.test(t)
44663
44667
  );
44664
44668
  const creditOk = touchesCredit && await creditStyleAllowed(ctx);
@@ -44668,6 +44672,24 @@ var childTools = [
44668
44672
  if (typeof a3.css_append === "string")
44669
44673
  files["style.css"] = `${(files["style.css"] ?? cur["style.css"] ?? "").replace(/\n*$/, "\n")}${a3.css_append}
44670
44674
  `;
44675
+ if (typeof a3.tailwind === "string" || typeof a3.tailwind_append === "string") {
44676
+ const parentV = String(info.parent?.version ?? "0");
44677
+ const [maj, min] = parentV.split(".").map(Number);
44678
+ if ((maj ?? 0) === 0 && (min ?? 0) < 20)
44679
+ throw new Error(
44680
+ `The child's Tailwind CSS needs Maggie Core 0.20 or later (the site has ${parentV}): update it with wp_theme_update first.`
44681
+ );
44682
+ const tw = typeof a3.tailwind === "string" ? a3.tailwind : `${(cur["tailwind/child.css"] ?? "").replace(/\n*$/, "\n")}${String(a3.tailwind_append)}
44683
+ `.trimStart();
44684
+ if (/<\/?(script|style)\b|<\?(php|=)?/i.test(tw)) throw new Error("tailwind: CSS only (no markup or PHP).");
44685
+ if (/@(plugin|config)\b/.test(tw))
44686
+ throw new Error("tailwind: @plugin and @config load JavaScript modules; write tokens and utilities in CSS.");
44687
+ refuseCreditOverride("tailwind", tw, creditOk);
44688
+ files["tailwind/child.css"] = tw;
44689
+ notes.push(
44690
+ "tailwind/child.css is imported after Maggie Core's bridge (maggie-core.css); Maggie Press shows it read-only as child-theme.css."
44691
+ );
44692
+ }
44671
44693
  if (typeof a3.js === "string" || typeof a3.js_append === "string") {
44672
44694
  const parentV = String(info.parent?.version ?? "0");
44673
44695
  const [maj, min] = parentV.split(".").map(Number);
@@ -46161,7 +46183,33 @@ var MB_RESOURCES = {
46161
46183
  what: "email campaigns (subject, body, button, segment) \u2014 send with wp_booking_campaign_send"
46162
46184
  },
46163
46185
  automations: { path: "/automations", write: false, what: "automated emails: we miss you, time to rebook, birthday" },
46164
- messages: { path: "/messages", write: false, what: "conversations with customers; id = customer id \u2192 the thread" }
46186
+ messages: { path: "/messages", write: false, what: "conversations with customers; id = customer id \u2192 the thread" },
46187
+ portfolio: {
46188
+ path: "/portfolio",
46189
+ write: true,
46190
+ what: "portfolio photos of finished work (image_id, staff_id, service_id, caption)"
46191
+ },
46192
+ "form-responses": {
46193
+ path: "/form-responses",
46194
+ write: false,
46195
+ what: "customers' answers to intake / consultation forms"
46196
+ }
46197
+ };
46198
+ var MB_OWNER_ONLY = /^\/(gateways|payments\/settings|stripe|terminal|pay-links|pin|sms$|sms\/test|webhooks)|\/cards(\/|$)/;
46199
+ var mbPath = (raw) => {
46200
+ const p2 = `/${String(raw ?? "").trim().replace(/^\/+/, "").replace(/^maggie-booking\/v1\/admin\/?/, "")}`.replace(/\/+$/, "");
46201
+ if (p2 === "/" || /\.\.|[?#\s]/.test(p2) || !/^[\w\-/.]+$/.test(p2))
46202
+ throw new Error("path: a route under /admin, such as /services or /sales/12/close (see wp_booking_routes).");
46203
+ return p2;
46204
+ };
46205
+ var TILL_METHODS = ["cash", "card_reader", "gift_card", "credit", "pay_link", "other"];
46206
+ var escHtml = (t) => String(t ?? "").replace(/[&<>"]/g, (c2) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c2] ?? c2);
46207
+ var mbBlock = (name, attrs) => {
46208
+ const n2 = name.startsWith("maggie/") ? name : `maggie/${name}`;
46209
+ if (!/^maggie\/[a-z0-9-]+$/.test(n2))
46210
+ throw new Error(`block: ${name} is not a booking block (see wp_booking_blocks).`);
46211
+ const json = attrs && Object.keys(attrs).length ? ` ${JSON.stringify(attrs).replace(/--/g, "\\u002d\\u002d")}` : "";
46212
+ return `<!-- wp:${n2}${json} /-->`;
46165
46213
  };
46166
46214
  var bookingTools = [
46167
46215
  tool({
@@ -46705,6 +46753,308 @@ var bookingTools = [
46705
46753
  gates: [...GATES],
46706
46754
  confirm: (a3, lang) => say(lang, `Save the ${a3.lang} translation of ${a3.type} #${a3.id}.`, `\u5132\u5B58 ${a3.type} #${a3.id} \u7684 ${a3.lang} \u7FFB\u8B6F\u3002`),
46707
46755
  run: (ctx, a3) => a3.fields ? mb(ctx, "POST", "/translations", { type: a3.type, id: a3.id, lang: a3.lang, fields: a3.fields }) : mb(ctx, "GET", "/translations", void 0, { type: a3.type, id: a3.id })
46756
+ }),
46757
+ tool({
46758
+ name: "wp_booking_routes",
46759
+ description: "Every Maggie Booking admin route on the site (the whole wp-admin \u2192 Bookings dashboard: calendar, till, team, roles, clock, pay runs, goals, stock, finance, loyalty, automations, upsells, safety, client files\u2026): path under /admin, methods, arguments, and owner_only for the payment / SMS / card set-up that only the owner does in wp-admin. filter: text to match in the path. Use with wp_booking_get and wp_booking_post for anything the other wp_booking_* tools don't cover. Read-only.",
46760
+ parameters: { type: "object", properties: { filter: str2("Text in the path, e.g. sales, team, loyalty.") } },
46761
+ group: "site",
46762
+ need: READ,
46763
+ risk: "R0",
46764
+ gates: ["WP"],
46765
+ run: async (ctx, a3) => {
46766
+ const out = await mb(ctx, "GET", "/routes");
46767
+ const f2 = String(a3.filter ?? "").toLowerCase();
46768
+ const routes = f2 ? out.routes.filter((r) => r.path.toLowerCase().includes(f2)) : out.routes;
46769
+ return {
46770
+ count: routes.length,
46771
+ routes,
46772
+ note: "Write with wp_booking_post; owner_only routes are set up by the owner in wp-admin."
46773
+ };
46774
+ }
46775
+ }),
46776
+ tool({
46777
+ name: "wp_booking_get",
46778
+ description: "Read any Maggie Booking admin route (GET path under /admin, e.g. /overview, /sales/today, /sales/close?date=, /team/members, /clock, /pay-runs, /goals, /stock, /loyalty, /automations/rebook, /upsells, /patch-tests, /customers/12/wallet, /finance/accounts, /tools/settings) with an optional query. Find paths with wp_booking_routes. Money in minor units. Read-only.",
46779
+ parameters: {
46780
+ type: "object",
46781
+ properties: { path: str2("Route under /admin."), query: obj("Query parameters, e.g. { date: '2026-11-06' }.") },
46782
+ required: ["path"]
46783
+ },
46784
+ group: "site",
46785
+ need: READ,
46786
+ risk: "R0",
46787
+ gates: ["WP"],
46788
+ run: (ctx, a3) => mb(ctx, "GET", mbPath(a3.path), void 0, a3.query ?? void 0)
46789
+ }),
46790
+ tool({
46791
+ name: "wp_booking_post",
46792
+ description: "Change anything in the Maggie Booking dashboard that the other wp_booking_* tools don't cover: method POST | PATCH | DELETE, path under /admin (from wp_booking_routes), body. Examples: team roles and permissions (/team/roles), clock in/out (/clock), pay runs (/pay-runs), goals (/goals), stock moves (/stock), loyalty programme (/loyalty), automated emails (/automations/{kind}), upsells (/upsells), patch tests (/patch-tests), client files (/customers/{id}/files), finance accounts (/finance/accounts), AI polish (/ai/polish), tool settings (/tools/settings), till settings (/sales/settings). Read the item first with wp_booking_get. Refused: owner_only routes (payment, card reader, saved card, SMS and PIN set-up) and charging a saved card.",
46793
+ parameters: {
46794
+ type: "object",
46795
+ properties: {
46796
+ method: str2("POST | PATCH | DELETE"),
46797
+ path: str2("Route under /admin."),
46798
+ body: obj("Fields to send.")
46799
+ },
46800
+ required: ["method", "path"]
46801
+ },
46802
+ group: "site",
46803
+ need: WRITE,
46804
+ risk: "W3",
46805
+ gates: [...GATES],
46806
+ confirm: (a3, lang) => {
46807
+ const m = String(a3.method ?? "").toUpperCase();
46808
+ const verb = m === "DELETE" ? ["Delete", "\u522A\u9664"] : m === "PATCH" ? ["Change", "\u4FEE\u6539"] : ["Send", "\u9001\u51FA"];
46809
+ const body = a3.body ? ` ${JSON.stringify(a3.body).slice(0, 300)}` : "";
46810
+ return say(lang, `${verb[0]} booking ${String(a3.path)}${body}.`, `${verb[1]}\u9810\u7D04 ${String(a3.path)}${body}\u3002`);
46811
+ },
46812
+ run: (ctx, a3) => {
46813
+ const m = String(a3.method ?? "").toUpperCase();
46814
+ if (!["POST", "PATCH", "DELETE"].includes(m)) throw new Error("method: POST, PATCH or DELETE.");
46815
+ const p2 = mbPath(a3.path);
46816
+ if (MB_OWNER_ONLY.test(p2))
46817
+ throw new Error(
46818
+ "Payment, card reader, saved card, SMS and PIN set-up is the owner's, in wp-admin \u2192 Bookings. Nothing was changed."
46819
+ );
46820
+ if (/^\/sales\/\d+\/payments$/.test(p2) && !TILL_METHODS.includes(String(a3.body?.method ?? "")))
46821
+ throw new Error(`method: ${TILL_METHODS.join(", ")} (a saved card is charged by a person at the till).`);
46822
+ if (/^\/(routes|blocks)$/.test(p2)) throw new Error("That route is read-only.");
46823
+ return mb(ctx, m, p2, m === "DELETE" ? void 0 : a3.body ?? {});
46824
+ }
46825
+ }),
46826
+ tool({
46827
+ name: "wp_booking_till",
46828
+ description: "The till (point of sale): action start (new sale: customer_id, items, note) | from_booking (booking_id \u2192 its sale with treatments and what was already paid) | items (sale id, items [{kind service|product|gift_card|custom, service_id, variant_id, product_id, name, quantity, unit_minor, staff_id}], discount, tips \u2014 replaces the lines) | pay (sale id, method cash|card_reader|gift_card|credit|pay_link|other, amount_minor (default: what is due), tendered_minor for cash, reference) | close (sale id) | receipt (sale id, email) | refund (sale id, all true or payments [{id, amount_minor}], reason, restock) | void (sale id, reason). Read sales with wp_booking_list resource sales and today's takings with wp_booking_daily_close. A saved card is never charged by an agent.",
46829
+ parameters: {
46830
+ type: "object",
46831
+ properties: {
46832
+ action: str2("start | from_booking | items | pay | close | receipt | refund | void"),
46833
+ id: int2("Sale id."),
46834
+ booking_id: int2("For from_booking."),
46835
+ customer_id: int2("For start."),
46836
+ items: arr("Sale lines.", obj("")),
46837
+ discount: obj("For items: { percent } or { amount_minor }."),
46838
+ tips: arr("For items: tips per team member.", obj("")),
46839
+ note: str2("For start."),
46840
+ method: str2("For pay."),
46841
+ amount_minor: int2("For pay."),
46842
+ tendered_minor: int2("For pay in cash."),
46843
+ reference: str2("For pay."),
46844
+ email: str2("For receipt (default: the customer's)."),
46845
+ all: bool("For refund: everything."),
46846
+ payments: arr("For refund: [{ id, amount_minor }].", obj("")),
46847
+ restock: bool("For refund: put products back in stock."),
46848
+ reason: str2("For refund / void.")
46849
+ },
46850
+ required: ["action"]
46851
+ },
46852
+ group: "site",
46853
+ need: WRITE,
46854
+ risk: "W3",
46855
+ gates: [...GATES],
46856
+ confirm: (a3, lang) => {
46857
+ const money = (n2) => typeof n2 === "number" ? (n2 / 100).toFixed(2) : "";
46858
+ switch (a3.action) {
46859
+ case "pay":
46860
+ return say(
46861
+ lang,
46862
+ `Take ${a3.amount_minor ? money(a3.amount_minor) : "what is due"} by ${a3.method} on sale #${a3.id}.`,
46863
+ `\u5728\u92B7\u552E #${a3.id} \u4EE5 ${a3.method} \u6536\u53D6${a3.amount_minor ? ` ${money(a3.amount_minor)}` : "\u61C9\u4ED8\u91D1\u984D"}\u3002`
46864
+ );
46865
+ case "refund":
46866
+ return say(
46867
+ lang,
46868
+ `Refund sale #${a3.id}${a3.all ? " in full" : ""}: money goes back to the customer.`,
46869
+ `\u9000\u6B3E\u92B7\u552E #${a3.id}${a3.all ? "\uFF08\u5168\u984D\uFF09" : ""}\uFF1A\u6B3E\u9805\u6703\u9000\u56DE\u7D66\u5BA2\u4EBA\u3002`
46870
+ );
46871
+ case "void":
46872
+ return say(lang, `Void sale #${a3.id}.`, `\u4F5C\u5EE2\u92B7\u552E #${a3.id}\u3002`);
46873
+ case "close":
46874
+ return say(lang, `Close sale #${a3.id}.`, `\u7D50\u675F\u92B7\u552E #${a3.id}\u3002`);
46875
+ case "receipt":
46876
+ return say(lang, `Email the receipt for sale #${a3.id}.`, `\u5BC4\u51FA\u92B7\u552E #${a3.id} \u7684\u6536\u64DA\u3002`);
46877
+ case "from_booking":
46878
+ return say(lang, `Open the till for booking #${a3.booking_id}.`, `\u70BA\u9810\u7D04 #${a3.booking_id} \u958B\u555F\u6536\u9280\u3002`);
46879
+ case "items":
46880
+ return say(lang, `Change the lines of sale #${a3.id}.`, `\u4FEE\u6539\u92B7\u552E #${a3.id} \u7684\u9805\u76EE\u3002`);
46881
+ default:
46882
+ return say(lang, "Start a new till sale.", "\u958B\u59CB\u4E00\u7B46\u65B0\u7684\u6536\u9280\u92B7\u552E\u3002");
46883
+ }
46884
+ },
46885
+ run: (ctx, a3) => {
46886
+ const id = Number(a3.id);
46887
+ const need = () => {
46888
+ if (!id) throw new Error("id: the sale id.");
46889
+ };
46890
+ switch (a3.action) {
46891
+ case "start":
46892
+ return mb(ctx, "POST", "/sales", { customer_id: a3.customer_id, items: a3.items, note: a3.note });
46893
+ case "from_booking":
46894
+ if (!a3.booking_id) throw new Error("booking_id is needed.");
46895
+ return mb(ctx, "POST", `/sales/from-booking/${Number(a3.booking_id)}`, {});
46896
+ case "items":
46897
+ need();
46898
+ return mb(ctx, "PATCH", `/sales/${id}`, {
46899
+ ...a3.items ? { items: a3.items } : {},
46900
+ ...a3.discount ? { discount: a3.discount } : {},
46901
+ ...a3.tips ? { tips: a3.tips } : {}
46902
+ });
46903
+ case "pay":
46904
+ need();
46905
+ if (!TILL_METHODS.includes(String(a3.method)))
46906
+ throw new Error(`method: ${TILL_METHODS.join(", ")} (a saved card is charged by a person at the till).`);
46907
+ return mb(ctx, "POST", `/sales/${id}/payments`, {
46908
+ method: a3.method,
46909
+ amount_minor: a3.amount_minor,
46910
+ tendered_minor: a3.tendered_minor,
46911
+ reference: a3.reference,
46912
+ key: `mcp-${id}-${a3.method}-${a3.amount_minor ?? "due"}-${Date.now()}`
46913
+ });
46914
+ case "close":
46915
+ need();
46916
+ return mb(ctx, "POST", `/sales/${id}/close`, {});
46917
+ case "receipt":
46918
+ need();
46919
+ return mb(ctx, "POST", `/sales/${id}/receipt`, a3.email ? { email: a3.email } : {});
46920
+ case "refund":
46921
+ need();
46922
+ return mb(ctx, "POST", `/sales/${id}/refund`, {
46923
+ all: !!a3.all,
46924
+ payments: a3.payments,
46925
+ reason: a3.reason ?? "",
46926
+ restock: a3.restock,
46927
+ key: `mcp-refund-${id}-${Date.now()}`
46928
+ });
46929
+ case "void":
46930
+ need();
46931
+ return mb(ctx, "POST", `/sales/${id}/void`, { reason: a3.reason ?? "" });
46932
+ default:
46933
+ throw new Error("action: start, from_booking, items, pay, close, receipt, refund or void.");
46934
+ }
46935
+ }
46936
+ }),
46937
+ tool({
46938
+ name: "wp_booking_daily_close",
46939
+ description: "The day's till close (end-of-day report): takings by payment method, cash expected in the drawer, refunds, tips, open sales still to settle. date (Y-m-d, venue time; default today). Read-only.",
46940
+ parameters: { type: "object", properties: { date: str2("Y-m-d") } },
46941
+ group: "site",
46942
+ need: READ,
46943
+ risk: "R0",
46944
+ gates: ["WP"],
46945
+ run: (ctx, a3) => mb(ctx, "GET", "/sales/close", void 0, { date: a3.date })
46946
+ }),
46947
+ tool({
46948
+ name: "wp_booking_blocks",
46949
+ description: "The booking blocks visitors see (services menu, Book button, team, gift cards and balance check, manage booking, reviews, my bookings, products, plans and memberships, portfolio, AI receptionist chat) with their attributes, the Book page, booking links (#book, #book:<service_id>:<variant_id>) and the current visitor design. Use before wp_booking_page, wp_booking_design or adding a block to a page with wp_section_add / wp_page_markup_set. Read-only.",
46950
+ parameters: { type: "object", properties: {} },
46951
+ group: "site",
46952
+ need: READ,
46953
+ risk: "R0",
46954
+ gates: ["WP"],
46955
+ run: (ctx) => mb(ctx, "GET", "/blocks")
46956
+ }),
46957
+ tool({
46958
+ name: "wp_booking_page",
46959
+ description: "Make or rebuild a booking page from sections, in the theme's full width (template page-wide): sections in order, each { heading, text } (a heading and/or a paragraph) or { block, attrs } with a booking block from wp_booking_blocks (e.g. booking-services {layout: cards, category}, booking-team, gift-cards, gift-card-check, plans {kind}, portfolio, booking-reviews, my-bookings, products, booking-manage). page_id: rebuild that page (it keeps its address; the earlier version stays in revisions); else a new page (title, slug). book_page true: make it the page the booking links open. status draft (default) or publish. preview true: show the markup only.",
46960
+ parameters: {
46961
+ type: "object",
46962
+ properties: {
46963
+ title: str2("Page title (new page)."),
46964
+ slug: str2("Address (new page)."),
46965
+ page_id: int2("Page to rebuild."),
46966
+ sections: arr(
46967
+ "Sections in order.",
46968
+ obj("", { heading: str2(""), text: str2(""), block: str2(""), attrs: obj("") })
46969
+ ),
46970
+ book_page: bool("Use it as the Book page."),
46971
+ status: str2("draft | publish"),
46972
+ preview: bool("Markup only.")
46973
+ },
46974
+ required: ["sections"]
46975
+ },
46976
+ group: "site",
46977
+ need: WRITE,
46978
+ risk: "W2",
46979
+ gates: [...GATES],
46980
+ confirm: (a3, lang) => a3.page_id ? say(lang, `Rebuild page #${a3.page_id} as a booking page.`, `\u628A\u9801\u9762 #${a3.page_id} \u91CD\u5EFA\u70BA\u9810\u7D04\u9801\u9762\u3002`) : say(lang, `Create the booking page "${a3.title}".`, `\u5EFA\u7ACB\u9810\u7D04\u9801\u9762\u300C${a3.title}\u300D\u3002`),
46981
+ run: async (ctx, a3) => {
46982
+ const site = siteOf(ctx);
46983
+ const sections = Array.isArray(a3.sections) ? a3.sections : [];
46984
+ if (!sections.length) throw new Error("sections: at least one.");
46985
+ const parts = [];
46986
+ for (const s of sections) {
46987
+ if (s.heading)
46988
+ parts.push(
46989
+ `<!-- wp:heading -->
46990
+ <h2 class="wp-block-heading">${escHtml(s.heading)}</h2>
46991
+ <!-- /wp:heading -->`
46992
+ );
46993
+ if (s.text) parts.push(`<!-- wp:paragraph -->
46994
+ <p>${escHtml(s.text)}</p>
46995
+ <!-- /wp:paragraph -->`);
46996
+ if (s.block) parts.push(mbBlock(String(s.block), s.attrs ?? void 0));
46997
+ }
46998
+ const content = parts.join("\n\n");
46999
+ if (a3.preview) return { preview: true, template: "page-wide", content };
47000
+ if (!a3.page_id && !a3.title) throw new Error("title: needed for a new page.");
47001
+ const page = await call(site, {
47002
+ method: "POST",
47003
+ path: a3.page_id ? `/wp/v2/pages/${Number(a3.page_id)}` : "/wp/v2/pages",
47004
+ query: { _fields: "id,link,status" },
47005
+ body: {
47006
+ ...a3.title ? { title: a3.title } : {},
47007
+ ...a3.slug && !a3.page_id ? { slug: a3.slug } : {},
47008
+ ...a3.status ? { status: a3.status === "publish" ? "publish" : "draft" } : a3.page_id ? {} : { status: "draft" },
47009
+ template: "page-wide",
47010
+ content
47011
+ }
47012
+ });
47013
+ if (!page || typeof page.id !== "number") throw new Error("WordPress didn't save the page; nothing was changed.");
47014
+ const book = a3.book_page ? await mb(ctx, "POST", "/settings", { settings: { booking_page_id: page.id } }).then(
47015
+ () => true,
47016
+ (e2) => ({ error: e2.message })
47017
+ ) : false;
47018
+ return {
47019
+ saved: true,
47020
+ id: page.id,
47021
+ path: path(page.link),
47022
+ status: page.status,
47023
+ book_page: book,
47024
+ sections: sections.length
47025
+ };
47026
+ }
47027
+ }),
47028
+ tool({
47029
+ name: "wp_booking_design",
47030
+ description: "The look of the booking blocks, booking sheet, gift cards, plans and account pages for visitors (Maggie Core 0.20.1+): design { accent, accent_deep, ink, muted, line, surface, panel (hex colours), radius 0-40 (px, cards and sheet), button_radius 0-99 (px), density comfortable | compact }. Send only what changes; reset true goes back to the theme's colours. Without design or reset: read the current design. The footer credit is not part of it.",
47031
+ parameters: {
47032
+ type: "object",
47033
+ properties: { design: obj("Design values."), reset: bool("Back to the theme look.") }
47034
+ },
47035
+ group: "site",
47036
+ need: WRITE,
47037
+ risk: "W2",
47038
+ gates: [...GATES],
47039
+ confirm: (a3, lang) => a3.reset ? say(lang, "Reset the booking look to the theme's.", "\u628A\u9810\u7D04\u5916\u89C0\u91CD\u8A2D\u70BA\u4E3B\u984C\u9810\u8A2D\u3002") : say(
47040
+ lang,
47041
+ `Change the booking look: ${JSON.stringify(a3.design ?? {})}.`,
47042
+ `\u4FEE\u6539\u9810\u7D04\u5916\u89C0\uFF1A${JSON.stringify(a3.design ?? {})}\u3002`
47043
+ ),
47044
+ run: async (ctx, a3) => {
47045
+ if (!a3.design && !a3.reset) {
47046
+ const b2 = await mb(ctx, "GET", "/blocks");
47047
+ return { design: b2.design ?? {} };
47048
+ }
47049
+ let next = {};
47050
+ if (!a3.reset) {
47051
+ const b2 = await mb(ctx, "GET", "/blocks");
47052
+ const cur = b2.design && !Array.isArray(b2.design) ? b2.design : {};
47053
+ next = { ...cur, ...a3.design };
47054
+ }
47055
+ await mb(ctx, "POST", "/settings", { settings: { design: next } });
47056
+ return { saved: true, design: next };
47057
+ }
46708
47058
  })
46709
47059
  ];
46710
47060
  var RESTYLE = /* @__PURE__ */ new Set([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmo-so/mcp",
3
- "version": "0.12.23",
3
+ "version": "0.12.25",
4
4
  "description": "CMO.so's MCP server for Claude Desktop, Cursor, VS Code and other MCP clients: manage your websites, blog posts, LinkedIn queue and results with Maggie.",
5
5
  "type": "module",
6
6
  "license": "MIT",