@cmo-so/mcp 0.12.17 → 0.12.19
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/cmo-mcp.mjs +592 -22
- 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.
|
|
24492
|
+
version: "0.12.19",
|
|
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",
|
|
@@ -33531,10 +33531,10 @@ function domainApi(api3, domain) {
|
|
|
33531
33531
|
body: JSON.stringify({ social_copy: socialCopy })
|
|
33532
33532
|
}),
|
|
33533
33533
|
/** The post's link: it replaces the old one in the text, and the preview card shows the new page. */
|
|
33534
|
-
setSocialLink: (id,
|
|
33534
|
+
setSocialLink: (id, link2) => api3.request(`${base}/social/${id}`, {
|
|
33535
33535
|
method: "PATCH",
|
|
33536
33536
|
headers: { "Content-Type": "application/json" },
|
|
33537
|
-
body: JSON.stringify({ link })
|
|
33537
|
+
body: JSON.stringify({ link: link2 })
|
|
33538
33538
|
}),
|
|
33539
33539
|
/** Go out as the post's image, or as a link preview. */
|
|
33540
33540
|
setSocialPublishMode: (id, mode) => api3.request(`${base}/social/${id}`, {
|
|
@@ -34794,13 +34794,13 @@ var accountTools = [
|
|
|
34794
34794
|
run: async (ctx, { platform }) => {
|
|
34795
34795
|
const p2 = platform && CONNECT_FORMS.includes(platform) ? platform : "";
|
|
34796
34796
|
const label2 = p2 ? `Connect ${CONNECT_NAMES[p2]}` : "Connect your website";
|
|
34797
|
-
const
|
|
34797
|
+
const link2 = `https://app.cmo.so/posts?connect=${p2 || "wordpress"}${ctx.domain ? `&site=${encodeURIComponent(ctx.domain)}` : ""}`;
|
|
34798
34798
|
if (ctx.client === "chat")
|
|
34799
34799
|
return {
|
|
34800
34800
|
card: { type: "connect", platform: p2 || null, label: label2 },
|
|
34801
34801
|
shown: `A "${label2}" button now shows under your message. The user fills in the form there; say so in one line.`
|
|
34802
34802
|
};
|
|
34803
|
-
return { link, shown: "Give the user this link: it opens the form in the cmo.so app (web or phone)." };
|
|
34803
|
+
return { link: link2, shown: "Give the user this link: it opens the form in the cmo.so app (web or phone)." };
|
|
34804
34804
|
}
|
|
34805
34805
|
}),
|
|
34806
34806
|
tool({
|
|
@@ -37177,9 +37177,9 @@ var FILE_SCHEMA = {
|
|
|
37177
37177
|
};
|
|
37178
37178
|
var fileLinks = (value) => (Array.isArray(value) ? value : value ? [value] : []).map((f2) => String(f2?.download_url ?? "").trim()).filter(Boolean);
|
|
37179
37179
|
var videoLink = (a3) => {
|
|
37180
|
-
const
|
|
37181
|
-
if (!
|
|
37182
|
-
return
|
|
37180
|
+
const link2 = fileLinks(a3.file)[0] ?? String(a3.url ?? "").trim();
|
|
37181
|
+
if (!link2.startsWith("https://")) throw new Error("Give the video (a file from this chat, or a direct https link).");
|
|
37182
|
+
return link2;
|
|
37183
37183
|
};
|
|
37184
37184
|
var queuedAt = positionParam("the posts", "Leave out for the front (the next posting times).");
|
|
37185
37185
|
var channelsOf = (value) => {
|
|
@@ -37667,10 +37667,10 @@ var socialTools = [
|
|
|
37667
37667
|
risk: "W1",
|
|
37668
37668
|
gates: ["VER"],
|
|
37669
37669
|
run: async (ctx, { id, url, file, title }) => {
|
|
37670
|
-
const
|
|
37671
|
-
if (!
|
|
37670
|
+
const link2 = fileLinks(file)[0] ?? String(url ?? "").trim();
|
|
37671
|
+
if (!link2.startsWith("https://"))
|
|
37672
37672
|
throw new Error("Give the PDF (a file from this chat, or a public https link).");
|
|
37673
|
-
const out = await ws(ctx).attachSocialCarouselPdf(Number(id), { url:
|
|
37673
|
+
const out = await ws(ctx).attachSocialCarouselPdf(Number(id), { url: link2, ...title ? { title } : {} });
|
|
37674
37674
|
return { id: out.id, pages: out.pages, pictures: out.pictures, carousel: out.carousel ?? null };
|
|
37675
37675
|
}
|
|
37676
37676
|
}),
|
|
@@ -39209,6 +39209,55 @@ function touchPath(list4, target) {
|
|
|
39209
39209
|
return false;
|
|
39210
39210
|
}
|
|
39211
39211
|
|
|
39212
|
+
// ../../packages/tools/src/wp/credit.ts
|
|
39213
|
+
var CREDIT_NAME = "AI Marketing OS Ltd";
|
|
39214
|
+
var CREDIT_URL = "https://cmo.so";
|
|
39215
|
+
var link = () => `<a href="${CREDIT_URL}">${CREDIT_NAME}</a>`;
|
|
39216
|
+
var creditInline = (year = (/* @__PURE__ */ new Date()).getUTCFullYear()) => `<span class="maggie-credit">\xA9 ${year} \xB7 Powered by ${link()}</span>`;
|
|
39217
|
+
var creditBlock = () => `<!-- wp:paragraph {"fontSize":"small"} -->
|
|
39218
|
+
<p class="has-small-font-size">${creditInline()}</p>
|
|
39219
|
+
<!-- /wp:paragraph -->`;
|
|
39220
|
+
var VERB = "(?:proudly\\s+)?(?:designed|developed|powered|built|crafted|created|made|hosted|maintained|managed|website|web\\s+design|web\\s+development|site|design|development|theme|seo)";
|
|
39221
|
+
var CREDIT_RX = new RegExp(
|
|
39222
|
+
`(?<prefix>(?:\xA9|©|©|copyright)\\s*(?:\\d{4}(?:\\s*[-\u2013]\\s*\\d{4})?)?\\s*[,\xB7|\u2022\\-\u2013]?\\s*)?(?<verb>${VERB}(?:\\s*(?:&|&|and|\\+|/)\\s*${VERB})?)\\s+by\\s*(?<name><a\\b[^>]*>[\\s\\S]*?</a>|[^<|\xB7\u2022]{1,60}?)(?=\\s*(?:<|\\||\xB7|\u2022|$|\\.\\s|\\.$))`,
|
|
39223
|
+
"giu"
|
|
39224
|
+
);
|
|
39225
|
+
var OWN_RX = /<span class="maggie-credit">[\s\S]*?<\/span>/giu;
|
|
39226
|
+
var isOurs = (name) => name.replace(/<[^>]+>/g, "").toLowerCase().includes(CREDIT_NAME.toLowerCase()) || name.includes("cmo.so");
|
|
39227
|
+
function applyCredit(markup) {
|
|
39228
|
+
if (!markup?.trim()) return markup;
|
|
39229
|
+
let credited = false;
|
|
39230
|
+
let html2 = markup.replace(OWN_RX, () => {
|
|
39231
|
+
credited = true;
|
|
39232
|
+
return creditInline();
|
|
39233
|
+
});
|
|
39234
|
+
const source = html2;
|
|
39235
|
+
html2 = source.replace(CREDIT_RX, (...args) => {
|
|
39236
|
+
const all2 = args[0];
|
|
39237
|
+
const at2 = args[args.length - 3];
|
|
39238
|
+
const g3 = args[args.length - 1];
|
|
39239
|
+
const before = source.slice(0, at2);
|
|
39240
|
+
if (before.lastIndexOf("<") > before.lastIndexOf(">")) return all2;
|
|
39241
|
+
if (!credited) {
|
|
39242
|
+
credited = true;
|
|
39243
|
+
return creditInline();
|
|
39244
|
+
}
|
|
39245
|
+
return isOurs(g3.name) ? all2 : `${g3.prefix ?? ""}${g3.verb} by ${link()}`;
|
|
39246
|
+
});
|
|
39247
|
+
if (credited) return html2;
|
|
39248
|
+
const end = html2.lastIndexOf("</footer>");
|
|
39249
|
+
return end < 0 ? `${html2.trimEnd()}
|
|
39250
|
+
|
|
39251
|
+
${creditBlock()}
|
|
39252
|
+
` : `${html2.slice(0, end)}${creditBlock()}${html2.slice(end)}`;
|
|
39253
|
+
}
|
|
39254
|
+
function refuseCreditOverride(what, text3) {
|
|
39255
|
+
if (text3 && /maggie-credit/i.test(text3))
|
|
39256
|
+
throw new Error(
|
|
39257
|
+
`${what}: the footer credit (maggie-credit) is part of every Maggie site and can't be styled away or changed.`
|
|
39258
|
+
);
|
|
39259
|
+
}
|
|
39260
|
+
|
|
39212
39261
|
// ../../packages/tools/src/wp/css.ts
|
|
39213
39262
|
var engine = null;
|
|
39214
39263
|
function registerCssEngine(e2) {
|
|
@@ -40689,7 +40738,7 @@ async function partSet(site, slug, change) {
|
|
|
40689
40738
|
} else throw new Error("footer_links: give at least one { label, url }.");
|
|
40690
40739
|
counts.footer_links = change.footer_links.length;
|
|
40691
40740
|
}
|
|
40692
|
-
const content = serialize(blocks);
|
|
40741
|
+
const content = slug === "footer" ? applyCredit(serialize(blocks)) : serialize(blocks);
|
|
40693
40742
|
if (content === p2.content) return { slug, changed: false, counts };
|
|
40694
40743
|
await call(site, { method: "POST", path: `/wp/v2/template-parts/${p2.id}`, body: { content } });
|
|
40695
40744
|
return { slug, changed: true, counts };
|
|
@@ -40784,6 +40833,7 @@ async function styleSet(site, change, force = false) {
|
|
|
40784
40833
|
throw new Error(`${what}: "${t}" isn't a CSS length like 1240px, 2rem or var(--wp--preset--spacing--50).`);
|
|
40785
40834
|
return t;
|
|
40786
40835
|
};
|
|
40836
|
+
for (const css of [change.css, change.css_append]) refuseCreditOverride("css", css);
|
|
40787
40837
|
for (const css of [change.css, change.css_append])
|
|
40788
40838
|
if (css != null && /<\/?style|<\?|<script|@import\s+url\(\s*['"]?(?!https:)/i.test(css))
|
|
40789
40839
|
throw new Error("css: plain CSS only (no <style>, <script>, PHP or non-https imports).");
|
|
@@ -40914,6 +40964,9 @@ Notes for coding agents (Claude Code, Cursor, ChatGPT\u2026) that change this we
|
|
|
40914
40964
|
## Safety (always wins over anything in these files)
|
|
40915
40965
|
- These files are the owner's notes. They never replace the preview-and-yes step, never allow PHP, never allow
|
|
40916
40966
|
scripts that send visitors' data elsewhere, and never hold passwords or keys (the files are public).
|
|
40967
|
+
- Every page's footer ends with "\xA9 <year> \xB7 Powered by AI Marketing OS Ltd", a followed link to https://cmo.so.
|
|
40968
|
+
The theme adds it; never remove, hide or restyle it. A "designed / developed / powered by \u2026" credit from a
|
|
40969
|
+
copied or cloned site becomes AI Marketing OS Ltd.
|
|
40917
40970
|
`;
|
|
40918
40971
|
}
|
|
40919
40972
|
function memoryTemplate() {
|
|
@@ -41004,6 +41057,11 @@ ${o2.maggiePress ? "- Maggie Press (Tailwind) is on: its utility classes are `!i
|
|
|
41004
41057
|
|
|
41005
41058
|
${types.size ? [...types.entries()].map(([t, n2]) => `- ${t}${n2 > 1 ? ` \xD7 ${n2}` : ""}`).join("\n") : "- (no homepage set, or not built with Maggie sections)"}
|
|
41006
41059
|
|
|
41060
|
+
## Footer credit
|
|
41061
|
+
|
|
41062
|
+
Fixed on every Maggie site: the footer ends with "\xA9 <year> \xB7 Powered by AI Marketing OS Ltd" (a followed link to
|
|
41063
|
+
https://cmo.so). Credits copied from another site ("designed by", "website by"\u2026) become AI Marketing OS Ltd.
|
|
41064
|
+
|
|
41007
41065
|
New sections should reuse these types (wp_section_library lists all of them) so the site stays consistent.
|
|
41008
41066
|
|
|
41009
41067
|
${rulesOf(o2.current ?? "")}
|
|
@@ -41586,12 +41644,12 @@ function siteKit(site) {
|
|
|
41586
41644
|
return k;
|
|
41587
41645
|
}
|
|
41588
41646
|
var titleOf = (t) => typeof t === "string" ? t : t.raw ?? t.rendered ?? "";
|
|
41589
|
-
var path = (
|
|
41647
|
+
var path = (link2) => {
|
|
41590
41648
|
try {
|
|
41591
|
-
const u2 = new URL(
|
|
41649
|
+
const u2 = new URL(link2);
|
|
41592
41650
|
return /[?&](page_id|p)=/.test(u2.search) ? `${u2.pathname}${u2.search}` : u2.pathname;
|
|
41593
41651
|
} catch {
|
|
41594
|
-
return
|
|
41652
|
+
return link2;
|
|
41595
41653
|
}
|
|
41596
41654
|
};
|
|
41597
41655
|
async function listPages(site, o2 = {}) {
|
|
@@ -43117,7 +43175,7 @@ async function llmsTxt(site, o2 = {}) {
|
|
|
43117
43175
|
return `${cut.slice(0, Math.max(cut.lastIndexOf(" "), n2 - 30)).replace(/[\s,;:–-]+$/, "")}\u2026`;
|
|
43118
43176
|
};
|
|
43119
43177
|
const desc = (m, fallback = "") => clip3(String(m?.maggie_seo_description || fallback));
|
|
43120
|
-
const isSample = (
|
|
43178
|
+
const isSample = (link2, title) => /\/(sample-page|hello-world)\/?$/.test(link2) || title === "Sample Page" || title === "Hello world!";
|
|
43121
43179
|
const home = pages.find((p2) => p2.id === s.page_on_front);
|
|
43122
43180
|
const noise = /privacy|terms|cookie|thank|cart|checkout|my-account|login/i;
|
|
43123
43181
|
const visible = pages.filter(
|
|
@@ -43217,9 +43275,9 @@ var brief2 = (blocks, synced) => locate(blocks).map((l2) => {
|
|
|
43217
43275
|
const d2 = describe2(l2, synced);
|
|
43218
43276
|
return `${d2.id} ${d2.type}${d2.heading ? `: ${d2.heading.slice(0, 60)}` : ""}`;
|
|
43219
43277
|
});
|
|
43220
|
-
async function check(site,
|
|
43278
|
+
async function check(site, link2, status) {
|
|
43221
43279
|
if (status !== "publish") return { note: "Saved as a draft: not public yet." };
|
|
43222
|
-
const h3 = await renderedHead(site,
|
|
43280
|
+
const h3 = await renderedHead(site, link2).catch((e2) => ({ error: e2.message }));
|
|
43223
43281
|
if ("error" in h3) return { ok: false, error: h3.error };
|
|
43224
43282
|
const problems = [
|
|
43225
43283
|
h3.status >= 400 ? `HTTP ${h3.status}` : "",
|
|
@@ -43292,9 +43350,9 @@ var siteTools = [
|
|
|
43292
43350
|
risk: "R0",
|
|
43293
43351
|
run: async (ctx) => {
|
|
43294
43352
|
if (!ctx.domain) throw new Error("No website is selected yet. Add one first.");
|
|
43295
|
-
const
|
|
43353
|
+
const link2 = `https://app.cmo.so/wp-builder?site=${encodeURIComponent(ctx.domain)}`;
|
|
43296
43354
|
return {
|
|
43297
|
-
link,
|
|
43355
|
+
link: link2,
|
|
43298
43356
|
cli: "cmo wp connect <site address> -u <administrator>",
|
|
43299
43357
|
shown: "Give the user this link: it opens the form in the cmo.so app (web or phone). They create the application password in WordPress (Users \u2192 Profile \u2192 Application Passwords) and paste it there."
|
|
43300
43358
|
};
|
|
@@ -44534,6 +44592,8 @@ var childTools = [
|
|
|
44534
44592
|
tj.styles = deepMerge(tj.styles ?? {}, a3.styles);
|
|
44535
44593
|
files["theme.json"] = JSON.stringify(tj, null, " ");
|
|
44536
44594
|
}
|
|
44595
|
+
refuseCreditOverride("css", typeof a3.css === "string" ? a3.css : null);
|
|
44596
|
+
refuseCreditOverride("css_append", typeof a3.css_append === "string" ? a3.css_append : null);
|
|
44537
44597
|
if (typeof a3.css === "string") files["style.css"] = a3.css;
|
|
44538
44598
|
if (typeof a3.css_append === "string")
|
|
44539
44599
|
files["style.css"] = `${(files["style.css"] ?? cur["style.css"] ?? "").replace(/\n*$/, "\n")}${a3.css_append}
|
|
@@ -44548,6 +44608,7 @@ var childTools = [
|
|
|
44548
44608
|
const js = typeof a3.js === "string" ? a3.js : `${(cur["assets/js/site.js"] ?? "").replace(/\n*$/, "\n")}${String(a3.js_append)}
|
|
44549
44609
|
`.trimStart();
|
|
44550
44610
|
if (/<\/?script\b/i.test(js)) throw new Error("js: plain JavaScript only, no <script> tags.");
|
|
44611
|
+
refuseCreditOverride("js", js);
|
|
44551
44612
|
if (/<\?(php|=)?/i.test(js)) throw new Error("js: PHP isn't allowed.");
|
|
44552
44613
|
files["assets/js/site.js"] = js;
|
|
44553
44614
|
notes.push(
|
|
@@ -44557,7 +44618,7 @@ var childTools = [
|
|
|
44557
44618
|
const markup = (path2, m) => {
|
|
44558
44619
|
if (typeof m !== "string" || !m.trim()) return;
|
|
44559
44620
|
if (!parse3(m).some((b2) => b2.name)) throw new Error(`${path2}: give block markup (<!-- wp:\u2026 -->).`);
|
|
44560
|
-
files[path2] = m;
|
|
44621
|
+
files[path2] = path2 === "parts/footer.html" ? applyCredit(m) : m;
|
|
44561
44622
|
};
|
|
44562
44623
|
markup("parts/header.html", a3.header);
|
|
44563
44624
|
markup("parts/footer.html", a3.footer);
|
|
@@ -45945,6 +46006,514 @@ var cssTools = [
|
|
|
45945
46006
|
}
|
|
45946
46007
|
})
|
|
45947
46008
|
];
|
|
46009
|
+
var MB = "/maggie-booking/v1/admin";
|
|
46010
|
+
var NEEDS_BOOKING = "Online booking lives in the Maggie Core theme 0.17 or later (wp_theme_update), switched on with wp_booking_turn_on.";
|
|
46011
|
+
async function mb(ctx, method, path2, body, query) {
|
|
46012
|
+
try {
|
|
46013
|
+
return await call(siteOf(ctx), {
|
|
46014
|
+
method,
|
|
46015
|
+
path: MB + path2,
|
|
46016
|
+
...body !== void 0 ? { body } : {},
|
|
46017
|
+
...query ? {
|
|
46018
|
+
query: Object.fromEntries(
|
|
46019
|
+
Object.entries(query).filter(([, v2]) => v2 !== void 0 && v2 !== null && v2 !== "")
|
|
46020
|
+
)
|
|
46021
|
+
} : {}
|
|
46022
|
+
});
|
|
46023
|
+
} catch (e2) {
|
|
46024
|
+
const m = e2.message ?? "";
|
|
46025
|
+
if (/rest_no_route|404/.test(m) && !/not found|not_found/i.test(m)) throw new Error(NEEDS_BOOKING);
|
|
46026
|
+
throw e2;
|
|
46027
|
+
}
|
|
46028
|
+
}
|
|
46029
|
+
var ITEMS = arr(
|
|
46030
|
+
"Treatments, in order (back to back): service id and option (variant) id from wp_booking_list resource services.",
|
|
46031
|
+
obj("", { service_id: int2("Service id."), variant_id: int2("Option id (default: the first).") })
|
|
46032
|
+
);
|
|
46033
|
+
var itemsParam = (items3) => (Array.isArray(items3) ? items3 : []).map(
|
|
46034
|
+
(i) => `${i.service_id}${i.variant_id ? `:${i.variant_id}` : ""}`
|
|
46035
|
+
).join(",");
|
|
46036
|
+
var MB_RESOURCES = {
|
|
46037
|
+
services: {
|
|
46038
|
+
path: "/services",
|
|
46039
|
+
write: true,
|
|
46040
|
+
what: "treatments with options (name, duration_minutes, price_minor), rules and who does them"
|
|
46041
|
+
},
|
|
46042
|
+
categories: { path: "/categories", write: true, what: "menu categories" },
|
|
46043
|
+
staff: { path: "/staff", write: true, what: "team: services, weekly hours, own prices" },
|
|
46044
|
+
"time-off": { path: "/time-off", write: true, what: "time off and closures (staff_id 0 = whole business)" },
|
|
46045
|
+
resources: { path: "/resources", write: true, what: "rooms / equipment a treatment needs" },
|
|
46046
|
+
locations: { path: "/locations", write: true, what: "locations (timezone, currency, address)" },
|
|
46047
|
+
customers: { path: "/customers", write: true, what: "customers (search=\u2026), id \u2192 history" },
|
|
46048
|
+
vouchers: { path: "/vouchers", write: true, what: "gift cards for sale" },
|
|
46049
|
+
"voucher-codes": { path: "/voucher-codes", write: false, what: "issued gift card codes (search by last 4, buyer)" },
|
|
46050
|
+
"addon-groups": {
|
|
46051
|
+
path: "/addon-groups",
|
|
46052
|
+
write: true,
|
|
46053
|
+
what: "add-on groups (extras with price and minutes) linked to services"
|
|
46054
|
+
},
|
|
46055
|
+
promos: { path: "/promos", write: true, what: "discount codes and last-minute deals" },
|
|
46056
|
+
bundles: { path: "/bundles", write: true, what: "packages: treatments back to back at one price" },
|
|
46057
|
+
"price-rules": { path: "/price-rules", write: true, what: "peak / off-peak price changes by weekday and time" },
|
|
46058
|
+
pricing: { path: "/pricing", write: false, what: "everything about add-ons, deals, packages and rules at once" },
|
|
46059
|
+
forms: { path: "/forms", write: true, what: "intake / consultation forms linked to services" },
|
|
46060
|
+
reviews: { path: "/reviews", write: true, what: "reviews (approve, hide, reply)" },
|
|
46061
|
+
waitlist: { path: "/waitlist", write: true, what: "waitlist entries" },
|
|
46062
|
+
payments: { path: "/payments", write: false, what: "payments and refunds" },
|
|
46063
|
+
settings: {
|
|
46064
|
+
path: "/settings",
|
|
46065
|
+
write: false,
|
|
46066
|
+
what: "booking rules, location, languages (change with wp_booking_settings_set)"
|
|
46067
|
+
},
|
|
46068
|
+
imports: { path: "/import", write: false, what: "import history (undo with wp_booking_import undo_id)" }
|
|
46069
|
+
};
|
|
46070
|
+
var bookingTools = [
|
|
46071
|
+
tool({
|
|
46072
|
+
name: "wp_booking_status",
|
|
46073
|
+
description: "Online booking on the connected WordPress site (Maggie Booking in the Maggie Core theme): whether it is on, today's appointments, upcoming count, this week's takings, the setup checklist (hours, treatments, team, payments, Book page, gift cards) and whether online payment (Stripe) is ready. Read-only. Payment keys are entered by the owner in wp-admin \u2192 Bookings \u2192 Payments (never ask for them).",
|
|
46074
|
+
parameters: { type: "object", properties: {} },
|
|
46075
|
+
group: "site",
|
|
46076
|
+
need: READ,
|
|
46077
|
+
risk: "R0",
|
|
46078
|
+
gates: ["WP"],
|
|
46079
|
+
run: async (ctx) => {
|
|
46080
|
+
const module = await mb(ctx, "GET", "/module");
|
|
46081
|
+
if (!module.installed || !module.enabled) return { ...module, next: "wp_booking_turn_on" };
|
|
46082
|
+
const [overview2, gateways] = await Promise.all([
|
|
46083
|
+
mb(ctx, "GET", "/overview"),
|
|
46084
|
+
mb(ctx, "GET", "/gateways").catch(() => null)
|
|
46085
|
+
]);
|
|
46086
|
+
return {
|
|
46087
|
+
...module,
|
|
46088
|
+
overview: overview2,
|
|
46089
|
+
payments: gateways ? {
|
|
46090
|
+
gateways: gateways.gateways,
|
|
46091
|
+
stripe: {
|
|
46092
|
+
mode: gateways.stripe?.mode,
|
|
46093
|
+
ready: gateways.stripe?.has_secret && gateways.stripe?.has_webhook_secret,
|
|
46094
|
+
webhook_url: gateways.stripe?.webhook_url
|
|
46095
|
+
}
|
|
46096
|
+
} : null
|
|
46097
|
+
};
|
|
46098
|
+
}
|
|
46099
|
+
}),
|
|
46100
|
+
tool({
|
|
46101
|
+
name: "wp_booking_turn_on",
|
|
46102
|
+
description: "Switch online booking on (creates its tables, a Book page, one team member and Mon\u2013Sat 10\u201318 hours to start from) or off (enabled false: the Bookings dashboard stays, nothing is deleted). To only pause visitors' online booking, use wp_booking_settings_set online_open false.",
|
|
46103
|
+
parameters: { type: "object", properties: { enabled: bool("true to switch on (default), false to switch off.") } },
|
|
46104
|
+
group: "site",
|
|
46105
|
+
need: WRITE,
|
|
46106
|
+
risk: "W2",
|
|
46107
|
+
gates: [...GATES],
|
|
46108
|
+
confirm: (a3, lang) => a3.enabled === false ? say(lang, "Switch online booking off on your website.", "\u95DC\u9589\u7DB2\u7AD9\u7684\u7DDA\u4E0A\u9810\u7D04\u3002") : say(
|
|
46109
|
+
lang,
|
|
46110
|
+
"Switch online booking on for your website (adds a Book page and the Bookings dashboard).",
|
|
46111
|
+
"\u70BA\u7DB2\u7AD9\u958B\u555F\u7DDA\u4E0A\u9810\u7D04\uFF08\u65B0\u589E\u9810\u7D04\u9801\u548C\u9810\u7D04\u5F8C\u53F0\uFF09\u3002"
|
|
46112
|
+
),
|
|
46113
|
+
run: (ctx, a3) => mb(ctx, "POST", "/module", { enabled: a3.enabled !== false })
|
|
46114
|
+
}),
|
|
46115
|
+
tool({
|
|
46116
|
+
name: "wp_booking_appointments",
|
|
46117
|
+
description: "Appointments: one booking by id (with customer, items, payments, history), or a list filtered by status (held, pending, confirmed, arrived, completed, cancelled, no_show, expired), date range (from/to, Y-m-d, venue time) and search (name, email, reference). Read-only.",
|
|
46118
|
+
parameters: {
|
|
46119
|
+
type: "object",
|
|
46120
|
+
properties: {
|
|
46121
|
+
id: int2("A booking id."),
|
|
46122
|
+
status: str2("Filter by status."),
|
|
46123
|
+
from: str2("From date (Y-m-d)."),
|
|
46124
|
+
to: str2("To date (Y-m-d)."),
|
|
46125
|
+
search: str2("Name, email, phone or reference."),
|
|
46126
|
+
page: int2("Page (1\u2026).")
|
|
46127
|
+
}
|
|
46128
|
+
},
|
|
46129
|
+
group: "site",
|
|
46130
|
+
need: READ,
|
|
46131
|
+
risk: "R0",
|
|
46132
|
+
gates: ["WP"],
|
|
46133
|
+
run: (ctx, a3) => a3.id ? mb(ctx, "GET", `/bookings/${Number(a3.id)}`) : mb(ctx, "GET", "/bookings", void 0, {
|
|
46134
|
+
status: a3.status,
|
|
46135
|
+
from: a3.from,
|
|
46136
|
+
to: a3.to,
|
|
46137
|
+
search: a3.search,
|
|
46138
|
+
page: a3.page
|
|
46139
|
+
})
|
|
46140
|
+
}),
|
|
46141
|
+
tool({
|
|
46142
|
+
name: "wp_booking_availability",
|
|
46143
|
+
description: "Free start times on a date for one or more treatments (the business's view: no lead-time limit). Use before wp_booking_create or a move. Read-only.",
|
|
46144
|
+
parameters: {
|
|
46145
|
+
type: "object",
|
|
46146
|
+
properties: { items: ITEMS, date: str2("Date (Y-m-d, venue time)."), staff_id: int2("Only this team member.") },
|
|
46147
|
+
required: ["items", "date"]
|
|
46148
|
+
},
|
|
46149
|
+
group: "site",
|
|
46150
|
+
need: READ,
|
|
46151
|
+
risk: "R0",
|
|
46152
|
+
gates: ["WP"],
|
|
46153
|
+
run: (ctx, a3) => mb(ctx, "GET", "/slots", void 0, { items: itemsParam(a3.items), date: a3.date, staff: a3.staff_id })
|
|
46154
|
+
}),
|
|
46155
|
+
tool({
|
|
46156
|
+
name: "wp_booking_create",
|
|
46157
|
+
description: "Book an appointment for a customer (as the business): treatments, date and time (venue time), optional team member, the customer (existing customer_id, or name + email/phone), notes and how they pay (venue, deposit, full). force books even if the time looks taken (double booking).",
|
|
46158
|
+
parameters: {
|
|
46159
|
+
type: "object",
|
|
46160
|
+
properties: {
|
|
46161
|
+
items: ITEMS,
|
|
46162
|
+
date: str2("Date (Y-m-d)."),
|
|
46163
|
+
time: str2("Start time (HH:MM, venue time)."),
|
|
46164
|
+
staff_id: int2("Team member (default: anyone free)."),
|
|
46165
|
+
customer_id: int2("An existing customer."),
|
|
46166
|
+
customer: obj("A new or matching customer.", {
|
|
46167
|
+
name: str2("Name."),
|
|
46168
|
+
email: str2("Email."),
|
|
46169
|
+
phone: str2("Phone.")
|
|
46170
|
+
}),
|
|
46171
|
+
notes: str2("Notes for the team."),
|
|
46172
|
+
payment: str2("venue (default), deposit or full."),
|
|
46173
|
+
force: bool("Book even if the time is taken.")
|
|
46174
|
+
},
|
|
46175
|
+
required: ["items", "date", "time"]
|
|
46176
|
+
},
|
|
46177
|
+
group: "site",
|
|
46178
|
+
need: WRITE,
|
|
46179
|
+
risk: "W2",
|
|
46180
|
+
gates: [...GATES],
|
|
46181
|
+
confirm: (a3, lang) => say(
|
|
46182
|
+
lang,
|
|
46183
|
+
`Book ${Array.isArray(a3.items) ? a3.items.length : 1} treatment(s) on ${a3.date} at ${a3.time} for ${q3(a3.customer?.name, a3.customer_id ? `customer #${a3.customer_id}` : "the customer")}${a3.force ? " even if the time is taken" : ""}; they get the confirmation email.`,
|
|
46184
|
+
`\u70BA${q3(a3.customer?.name, a3.customer_id ? `\u5BA2\u6236 #${a3.customer_id}` : "\u5BA2\u6236")}\u9810\u7D04 ${a3.date} ${a3.time}${a3.force ? "\uFF08\u5373\u4F7F\u6642\u9593\u5DF2\u88AB\u4F54\u7528\uFF09" : ""}\uFF0C\u4E26\u5BC4\u51FA\u78BA\u8A8D\u96FB\u90F5\u3002`
|
|
46185
|
+
),
|
|
46186
|
+
run: (ctx, a3) => mb(ctx, "POST", "/bookings", {
|
|
46187
|
+
items: a3.items,
|
|
46188
|
+
date: a3.date,
|
|
46189
|
+
time: a3.time,
|
|
46190
|
+
staff_id: a3.staff_id,
|
|
46191
|
+
customer_id: a3.customer_id,
|
|
46192
|
+
customer: a3.customer,
|
|
46193
|
+
notes: a3.notes,
|
|
46194
|
+
payment: a3.payment,
|
|
46195
|
+
force: !!a3.force
|
|
46196
|
+
})
|
|
46197
|
+
}),
|
|
46198
|
+
tool({
|
|
46199
|
+
name: "wp_booking_change",
|
|
46200
|
+
description: "Change one booking: action status (arrived, completed, no_show, confirmed), notes, cancel (by the business: everything paid is refunded; the customer is emailed), reschedule (new date + time, optional staff_id, force) or refund (payment_id and amount in minor units, e.g. 2500 = 25.00).",
|
|
46201
|
+
parameters: {
|
|
46202
|
+
type: "object",
|
|
46203
|
+
properties: {
|
|
46204
|
+
id: int2("Booking id."),
|
|
46205
|
+
action: str2("status | notes | cancel | reschedule | refund"),
|
|
46206
|
+
status: str2("For status."),
|
|
46207
|
+
notes: str2("For notes."),
|
|
46208
|
+
reason: str2("For cancel."),
|
|
46209
|
+
date: str2("For reschedule (Y-m-d)."),
|
|
46210
|
+
time: str2("For reschedule (HH:MM)."),
|
|
46211
|
+
staff_id: int2("For reschedule: another team member."),
|
|
46212
|
+
force: bool("For reschedule: even if taken."),
|
|
46213
|
+
payment_id: int2("For refund."),
|
|
46214
|
+
amount: int2("For refund, minor units.")
|
|
46215
|
+
},
|
|
46216
|
+
required: ["id", "action"]
|
|
46217
|
+
},
|
|
46218
|
+
group: "site",
|
|
46219
|
+
need: WRITE,
|
|
46220
|
+
risk: "W3",
|
|
46221
|
+
gates: [...GATES],
|
|
46222
|
+
confirm: (a3, lang) => {
|
|
46223
|
+
const id = `#${a3.id}`;
|
|
46224
|
+
switch (a3.action) {
|
|
46225
|
+
case "cancel":
|
|
46226
|
+
return say(
|
|
46227
|
+
lang,
|
|
46228
|
+
`Cancel booking ${id} (paid money is refunded, the customer is emailed).`,
|
|
46229
|
+
`\u53D6\u6D88\u9810\u7D04 ${id}\uFF08\u9000\u56DE\u5DF2\u4ED8\u6B3E\u9805\u4E26\u901A\u77E5\u5BA2\u6236\uFF09\u3002`
|
|
46230
|
+
);
|
|
46231
|
+
case "reschedule":
|
|
46232
|
+
return say(
|
|
46233
|
+
lang,
|
|
46234
|
+
`Move booking ${id} to ${a3.date} ${a3.time} (the customer is emailed).`,
|
|
46235
|
+
`\u628A\u9810\u7D04 ${id} \u6539\u5230 ${a3.date} ${a3.time}\uFF08\u901A\u77E5\u5BA2\u6236\uFF09\u3002`
|
|
46236
|
+
);
|
|
46237
|
+
case "refund":
|
|
46238
|
+
return say(
|
|
46239
|
+
lang,
|
|
46240
|
+
`Refund ${a3.amount} (minor units) of payment #${a3.payment_id} on booking ${id}.`,
|
|
46241
|
+
`\u70BA\u9810\u7D04 ${id} \u9000\u6B3E ${a3.amount}\uFF08\u6700\u5C0F\u55AE\u4F4D\uFF09\u3002`
|
|
46242
|
+
);
|
|
46243
|
+
default:
|
|
46244
|
+
return say(
|
|
46245
|
+
lang,
|
|
46246
|
+
`Update booking ${id}: ${a3.action} ${a3.status ?? ""}.`,
|
|
46247
|
+
`\u66F4\u65B0\u9810\u7D04 ${id}\uFF1A${a3.action} ${a3.status ?? ""}\u3002`
|
|
46248
|
+
);
|
|
46249
|
+
}
|
|
46250
|
+
},
|
|
46251
|
+
run: (ctx, a3) => {
|
|
46252
|
+
const id = Number(a3.id);
|
|
46253
|
+
switch (a3.action) {
|
|
46254
|
+
case "cancel":
|
|
46255
|
+
return mb(ctx, "POST", `/bookings/${id}/cancel`, { reason: a3.reason ?? "" });
|
|
46256
|
+
case "reschedule":
|
|
46257
|
+
return mb(ctx, "POST", `/bookings/${id}/reschedule`, {
|
|
46258
|
+
date: a3.date,
|
|
46259
|
+
time: a3.time,
|
|
46260
|
+
staff_id: a3.staff_id,
|
|
46261
|
+
force: !!a3.force
|
|
46262
|
+
});
|
|
46263
|
+
case "refund":
|
|
46264
|
+
return mb(ctx, "POST", `/bookings/${id}/refund`, { payment_id: a3.payment_id, amount: a3.amount });
|
|
46265
|
+
case "notes":
|
|
46266
|
+
return mb(ctx, "PATCH", `/bookings/${id}`, { notes: a3.notes ?? "" });
|
|
46267
|
+
case "status":
|
|
46268
|
+
return mb(ctx, "PATCH", `/bookings/${id}`, { status: a3.status });
|
|
46269
|
+
default:
|
|
46270
|
+
throw new Error("action: status, notes, cancel, reschedule or refund.");
|
|
46271
|
+
}
|
|
46272
|
+
}
|
|
46273
|
+
}),
|
|
46274
|
+
tool({
|
|
46275
|
+
name: "wp_booking_list",
|
|
46276
|
+
description: `Read the booking set-up and records. resource: ${Object.entries(MB_RESOURCES).map(([k, v2]) => `${k} (${v2.what})`).join("; ")}. id for one item, search for customers / codes. Money is in minor units (8000 = 80.00). Read-only.`,
|
|
46277
|
+
parameters: {
|
|
46278
|
+
type: "object",
|
|
46279
|
+
properties: {
|
|
46280
|
+
resource: str2("What to read."),
|
|
46281
|
+
id: int2("One item."),
|
|
46282
|
+
search: str2("Search text."),
|
|
46283
|
+
page: int2("Page.")
|
|
46284
|
+
},
|
|
46285
|
+
required: ["resource"]
|
|
46286
|
+
},
|
|
46287
|
+
group: "site",
|
|
46288
|
+
need: READ,
|
|
46289
|
+
risk: "R0",
|
|
46290
|
+
gates: ["WP"],
|
|
46291
|
+
run: (ctx, a3) => {
|
|
46292
|
+
const r = MB_RESOURCES[String(a3.resource)];
|
|
46293
|
+
if (!r) throw new Error(`resource: ${Object.keys(MB_RESOURCES).join(", ")}.`);
|
|
46294
|
+
return a3.id ? mb(ctx, "GET", `${r.path}/${Number(a3.id)}`) : mb(ctx, "GET", r.path, void 0, { search: a3.search, page: a3.page });
|
|
46295
|
+
}
|
|
46296
|
+
}),
|
|
46297
|
+
tool({
|
|
46298
|
+
name: "wp_booking_save",
|
|
46299
|
+
description: `Create, change or delete one booking set-up item. resource: ${Object.entries(MB_RESOURCES).filter(([, v2]) => v2.write).map(([k]) => k).join(
|
|
46300
|
+
", "
|
|
46301
|
+
)}. Without id: create; with id: change (send only the fields to change; a service's variants array replaces its options \u2014 keep their ids); delete true: remove (services, team and options used by past bookings are archived, not deleted). Read the item with wp_booking_list first to see its fields. Money in minor units.`,
|
|
46302
|
+
parameters: {
|
|
46303
|
+
type: "object",
|
|
46304
|
+
properties: {
|
|
46305
|
+
resource: str2("What to save."),
|
|
46306
|
+
id: int2("Item to change or delete."),
|
|
46307
|
+
data: obj("The fields."),
|
|
46308
|
+
delete: bool("Delete it.")
|
|
46309
|
+
},
|
|
46310
|
+
required: ["resource"]
|
|
46311
|
+
},
|
|
46312
|
+
group: "site",
|
|
46313
|
+
need: WRITE,
|
|
46314
|
+
risk: "W2",
|
|
46315
|
+
gates: [...GATES],
|
|
46316
|
+
confirm: (a3, lang) => {
|
|
46317
|
+
const what = `${a3.resource}${a3.id ? ` #${a3.id}` : ""}`;
|
|
46318
|
+
return a3.delete ? say(lang, `Delete ${what} from your booking set-up.`, `\u5F9E\u9810\u7D04\u8A2D\u5B9A\u4E2D\u522A\u9664 ${what}\u3002`) : a3.id ? say(lang, `Change ${what} in your booking set-up.`, `\u4FEE\u6539\u9810\u7D04\u8A2D\u5B9A\u4E2D\u7684 ${what}\u3002`) : say(lang, `Add a new ${a3.resource} to your booking set-up.`, `\u5728\u9810\u7D04\u8A2D\u5B9A\u4E2D\u65B0\u589E ${a3.resource}\u3002`);
|
|
46319
|
+
},
|
|
46320
|
+
run: (ctx, a3) => {
|
|
46321
|
+
const r = MB_RESOURCES[String(a3.resource)];
|
|
46322
|
+
if (!r?.write)
|
|
46323
|
+
throw new Error(
|
|
46324
|
+
`resource: ${Object.entries(MB_RESOURCES).filter(([, v2]) => v2.write).map(([k]) => k).join(", ")}.`
|
|
46325
|
+
);
|
|
46326
|
+
if (a3.delete) {
|
|
46327
|
+
if (!a3.id) throw new Error("delete needs the id.");
|
|
46328
|
+
return mb(ctx, "DELETE", `${r.path}/${Number(a3.id)}`);
|
|
46329
|
+
}
|
|
46330
|
+
return a3.id ? mb(ctx, "PATCH", `${r.path}/${Number(a3.id)}`, a3.data ?? {}) : mb(ctx, "POST", r.path, a3.data ?? {});
|
|
46331
|
+
}
|
|
46332
|
+
}),
|
|
46333
|
+
tool({
|
|
46334
|
+
name: "wp_booking_settings_set",
|
|
46335
|
+
description: 'Change booking settings: settings {online_open, slot_minutes, lead_minutes, horizon_days, hold_minutes, cancel_window_hours, default_payment (venue|deposit|full|card), deposit_percent, deposit_min_minor, no_show_fee_percent, auto_confirm, assignment_mode (balance|priority|turns), gap_rule (off|reduce|eliminate), allow_staff_choice, languages (extra content languages, e.g. ["zh-hans","es"]), notify_email, customer_note, terms, reminder_hours, gift_cards_enabled}, location {name, address, phone, email, timezone, currency} and/or business hours [{weekday 0=Mon\u20266, start HH:MM, end HH:MM, week_slot null|0|1}] (replaces all business hours). Read the current values with wp_booking_list resource settings.',
|
|
46336
|
+
parameters: {
|
|
46337
|
+
type: "object",
|
|
46338
|
+
properties: {
|
|
46339
|
+
settings: obj("Booking rules to change."),
|
|
46340
|
+
location: obj("Location fields to change."),
|
|
46341
|
+
hours: arr(
|
|
46342
|
+
"Business hours (all of them).",
|
|
46343
|
+
obj("", { weekday: int2("0 = Monday."), start: str2("HH:MM"), end: str2("HH:MM") })
|
|
46344
|
+
)
|
|
46345
|
+
}
|
|
46346
|
+
},
|
|
46347
|
+
group: "site",
|
|
46348
|
+
need: WRITE,
|
|
46349
|
+
risk: "W2",
|
|
46350
|
+
gates: [...GATES],
|
|
46351
|
+
confirm: (a3, lang) => say(
|
|
46352
|
+
lang,
|
|
46353
|
+
`Change booking ${[a3.settings && "settings", a3.location && "location", a3.hours && "opening hours"].filter(Boolean).join(", ") || "settings"}.`,
|
|
46354
|
+
`\u4FEE\u6539\u9810\u7D04${[a3.settings && "\u8A2D\u5B9A", a3.location && "\u5730\u9EDE", a3.hours && "\u71DF\u696D\u6642\u9593"].filter(Boolean).join("\u3001") || "\u8A2D\u5B9A"}\u3002`
|
|
46355
|
+
),
|
|
46356
|
+
run: (ctx, a3) => mb(ctx, "POST", "/settings", {
|
|
46357
|
+
...a3.settings ? { settings: a3.settings } : {},
|
|
46358
|
+
...a3.location ? { location: a3.location } : {},
|
|
46359
|
+
...a3.hours ? { hours: a3.hours } : {}
|
|
46360
|
+
})
|
|
46361
|
+
}),
|
|
46362
|
+
tool({
|
|
46363
|
+
name: "wp_booking_import_preview",
|
|
46364
|
+
description: "Preview bringing data from another booking system, nothing saved: source fresha (the venue's public Fresha page URL: treatments, options, prices), json (a catalogue JSON from another provider's export: Treatwell, Booksy, Square, Vagaro\u2026) or csv (kind clients | appointments | gift_cards, the CSV text). Shows what would be added, updated, withdrawn or skipped. Then wp_booking_import.",
|
|
46365
|
+
parameters: {
|
|
46366
|
+
type: "object",
|
|
46367
|
+
properties: {
|
|
46368
|
+
source: str2("fresha | json | csv"),
|
|
46369
|
+
url: str2("Fresha page URL."),
|
|
46370
|
+
json: str2("Catalogue JSON."),
|
|
46371
|
+
csv: str2("CSV text."),
|
|
46372
|
+
kind: str2("For csv: clients | appointments | gift_cards.")
|
|
46373
|
+
},
|
|
46374
|
+
required: ["source"]
|
|
46375
|
+
},
|
|
46376
|
+
group: "site",
|
|
46377
|
+
need: READ,
|
|
46378
|
+
risk: "R0",
|
|
46379
|
+
gates: ["WP"],
|
|
46380
|
+
run: (ctx, a3) => mb(ctx, "POST", `/import/${String(a3.source)}`, {
|
|
46381
|
+
url: a3.url,
|
|
46382
|
+
json: a3.json,
|
|
46383
|
+
csv: a3.csv,
|
|
46384
|
+
kind: a3.kind,
|
|
46385
|
+
dry_run: true
|
|
46386
|
+
})
|
|
46387
|
+
}),
|
|
46388
|
+
tool({
|
|
46389
|
+
name: "wp_booking_import",
|
|
46390
|
+
description: "Import from another booking system after wp_booking_import_preview (same arguments), or undo an earlier import (undo_id from wp_booking_list resource imports). Re-running a Fresha import later syncs changes; the owner's own edits are kept.",
|
|
46391
|
+
parameters: {
|
|
46392
|
+
type: "object",
|
|
46393
|
+
properties: {
|
|
46394
|
+
source: str2("fresha | json | csv"),
|
|
46395
|
+
url: str2("Fresha page URL."),
|
|
46396
|
+
json: str2("Catalogue JSON."),
|
|
46397
|
+
csv: str2("CSV text."),
|
|
46398
|
+
kind: str2("For csv."),
|
|
46399
|
+
undo_id: int2("Undo this import instead.")
|
|
46400
|
+
}
|
|
46401
|
+
},
|
|
46402
|
+
group: "site",
|
|
46403
|
+
need: WRITE,
|
|
46404
|
+
risk: "W2",
|
|
46405
|
+
gates: [...GATES],
|
|
46406
|
+
confirm: (a3, lang) => a3.undo_id ? say(
|
|
46407
|
+
lang,
|
|
46408
|
+
`Undo import #${a3.undo_id} (removes what it added).`,
|
|
46409
|
+
`\u64A4\u92B7\u532F\u5165 #${a3.undo_id}\uFF08\u79FB\u9664\u5B83\u65B0\u589E\u7684\u8CC7\u6599\uFF09\u3002`
|
|
46410
|
+
) : say(
|
|
46411
|
+
lang,
|
|
46412
|
+
`Import ${a3.source === "csv" ? a3.kind : "the menu"} from ${a3.source} into your bookings.`,
|
|
46413
|
+
`\u5F9E ${a3.source} \u532F\u5165${a3.source === "csv" ? a3.kind : "\u670D\u52D9\u9805\u76EE"}\u5230\u9810\u7D04\u7CFB\u7D71\u3002`
|
|
46414
|
+
),
|
|
46415
|
+
run: (ctx, a3) => a3.undo_id ? mb(ctx, "POST", `/import/${Number(a3.undo_id)}/undo`, {}) : mb(ctx, "POST", `/import/${String(a3.source)}`, {
|
|
46416
|
+
url: a3.url,
|
|
46417
|
+
json: a3.json,
|
|
46418
|
+
csv: a3.csv,
|
|
46419
|
+
kind: a3.kind,
|
|
46420
|
+
dry_run: false
|
|
46421
|
+
})
|
|
46422
|
+
}),
|
|
46423
|
+
tool({
|
|
46424
|
+
name: "wp_gift_card",
|
|
46425
|
+
description: "Gift cards: lookup (code \u2192 status, balance), issue (by hand: voucher_id of a gift card for sale or value_minor, recipient and buyer names/emails, message, send_email; the code is shown once), redeem (code id + amount_minor), void (code id). List cards for sale and issued codes with wp_booking_list resource vouchers / voucher-codes.",
|
|
46426
|
+
parameters: {
|
|
46427
|
+
type: "object",
|
|
46428
|
+
properties: {
|
|
46429
|
+
action: str2("lookup | issue | redeem | void"),
|
|
46430
|
+
code: str2("For lookup."),
|
|
46431
|
+
id: int2("Code id, for redeem / void."),
|
|
46432
|
+
amount_minor: int2("For redeem."),
|
|
46433
|
+
voucher_id: int2("For issue."),
|
|
46434
|
+
value_minor: int2("For issue: a custom value."),
|
|
46435
|
+
recipient_name: str2("For issue."),
|
|
46436
|
+
recipient_email: str2("For issue."),
|
|
46437
|
+
buyer_name: str2("For issue."),
|
|
46438
|
+
buyer_email: str2("For issue."),
|
|
46439
|
+
message: str2("For issue."),
|
|
46440
|
+
send_email: bool("For issue: email the code (default true)."),
|
|
46441
|
+
note: str2("Note for the ledger.")
|
|
46442
|
+
},
|
|
46443
|
+
required: ["action"]
|
|
46444
|
+
},
|
|
46445
|
+
group: "site",
|
|
46446
|
+
need: WRITE,
|
|
46447
|
+
risk: "W2",
|
|
46448
|
+
gates: [...GATES],
|
|
46449
|
+
confirm: (a3, lang) => {
|
|
46450
|
+
switch (a3.action) {
|
|
46451
|
+
case "issue":
|
|
46452
|
+
return say(
|
|
46453
|
+
lang,
|
|
46454
|
+
`Issue a gift card for ${q3(a3.recipient_name, "the recipient")}.`,
|
|
46455
|
+
`\u70BA${q3(a3.recipient_name, "\u6536\u4EF6\u4EBA")}\u767C\u51FA\u79AE\u54C1\u5361\u3002`
|
|
46456
|
+
);
|
|
46457
|
+
case "redeem":
|
|
46458
|
+
return say(
|
|
46459
|
+
lang,
|
|
46460
|
+
`Take ${a3.amount_minor} (minor units) off gift card #${a3.id}.`,
|
|
46461
|
+
`\u5F9E\u79AE\u54C1\u5361 #${a3.id} \u6263\u9664 ${a3.amount_minor}\uFF08\u6700\u5C0F\u55AE\u4F4D\uFF09\u3002`
|
|
46462
|
+
);
|
|
46463
|
+
case "void":
|
|
46464
|
+
return say(lang, `Void gift card #${a3.id} (it can no longer be used).`, `\u4F5C\u5EE2\u79AE\u54C1\u5361 #${a3.id}\u3002`);
|
|
46465
|
+
default:
|
|
46466
|
+
return say(lang, "Look up a gift card.", "\u67E5\u8A62\u79AE\u54C1\u5361\u3002");
|
|
46467
|
+
}
|
|
46468
|
+
},
|
|
46469
|
+
run: (ctx, a3) => {
|
|
46470
|
+
switch (a3.action) {
|
|
46471
|
+
case "lookup":
|
|
46472
|
+
return mb(ctx, "POST", "/voucher-codes/lookup", { code: a3.code });
|
|
46473
|
+
case "issue":
|
|
46474
|
+
return mb(ctx, "POST", "/voucher-codes", {
|
|
46475
|
+
voucher_id: a3.voucher_id,
|
|
46476
|
+
value_minor: a3.value_minor,
|
|
46477
|
+
recipient_name: a3.recipient_name,
|
|
46478
|
+
recipient_email: a3.recipient_email,
|
|
46479
|
+
buyer_name: a3.buyer_name,
|
|
46480
|
+
buyer_email: a3.buyer_email,
|
|
46481
|
+
message: a3.message,
|
|
46482
|
+
send_email: a3.send_email !== false
|
|
46483
|
+
});
|
|
46484
|
+
case "redeem":
|
|
46485
|
+
return mb(ctx, "POST", `/voucher-codes/${Number(a3.id)}/redeem`, {
|
|
46486
|
+
amount_minor: a3.amount_minor,
|
|
46487
|
+
note: a3.note ?? ""
|
|
46488
|
+
});
|
|
46489
|
+
case "void":
|
|
46490
|
+
return mb(ctx, "POST", `/voucher-codes/${Number(a3.id)}/void`, { note: a3.note ?? "" });
|
|
46491
|
+
default:
|
|
46492
|
+
throw new Error("action: lookup, issue, redeem or void.");
|
|
46493
|
+
}
|
|
46494
|
+
}
|
|
46495
|
+
}),
|
|
46496
|
+
tool({
|
|
46497
|
+
name: "wp_booking_translate",
|
|
46498
|
+
description: "Read or write the translations of booking content (type service | variant | category | staff | voucher, id, lang such as zh-hans or es; fields name, description). Without fields: read. The site's extra languages are set with wp_booking_settings_set settings.languages.",
|
|
46499
|
+
parameters: {
|
|
46500
|
+
type: "object",
|
|
46501
|
+
properties: {
|
|
46502
|
+
type: str2("service | variant | category | staff | voucher"),
|
|
46503
|
+
id: int2("Item id."),
|
|
46504
|
+
lang: str2("Language code."),
|
|
46505
|
+
fields: obj("{ name, description } to save.")
|
|
46506
|
+
},
|
|
46507
|
+
required: ["type", "id"]
|
|
46508
|
+
},
|
|
46509
|
+
group: "site",
|
|
46510
|
+
need: WRITE,
|
|
46511
|
+
risk: "W2",
|
|
46512
|
+
gates: [...GATES],
|
|
46513
|
+
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`),
|
|
46514
|
+
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 })
|
|
46515
|
+
})
|
|
46516
|
+
];
|
|
45948
46517
|
var RESTYLE = /* @__PURE__ */ new Set([
|
|
45949
46518
|
"wp_page_markup_set",
|
|
45950
46519
|
"wp_post_save",
|
|
@@ -45990,7 +46559,8 @@ var wordpressTools = [
|
|
|
45990
46559
|
...themeTools,
|
|
45991
46560
|
...seoTools,
|
|
45992
46561
|
...elementTools,
|
|
45993
|
-
...cssTools
|
|
46562
|
+
...cssTools,
|
|
46563
|
+
...bookingTools
|
|
45994
46564
|
].map(withRestyle);
|
|
45995
46565
|
|
|
45996
46566
|
// ../../packages/tools/src/index.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmo-so/mcp",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.19",
|
|
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",
|