@omniharmonic/openspace-cli 0.1.16 → 0.1.17
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/bin.js +1 -1
- package/dist/{chunk-JDVCOOGD.js → chunk-HUS7TQPS.js} +1119 -806
- package/dist/chunk-HUS7TQPS.js.map +1 -0
- package/dist/index.js +1 -1
- package/package.json +9 -9
- package/dist/chunk-JDVCOOGD.js.map +0 -1
|
@@ -7579,12 +7579,18 @@ var trimSlashes = (s2) => s2.replace(/^\/+|\/+$/g, "");
|
|
|
7579
7579
|
var LinkTargets = class {
|
|
7580
7580
|
constructor(doc) {
|
|
7581
7581
|
this.doc = doc;
|
|
7582
|
-
this.
|
|
7582
|
+
this.bySlug = new Map(Object.values(doc.pages).map((p) => [p.slug, p]));
|
|
7583
|
+
this.slugs = new Set(this.bySlug.keys());
|
|
7583
7584
|
this.redirectFroms = new Set((doc.site.redirects ?? []).map((r) => trimSlashes(r.from)));
|
|
7584
7585
|
}
|
|
7585
7586
|
doc;
|
|
7586
7587
|
slugs;
|
|
7587
7588
|
redirectFroms;
|
|
7589
|
+
bySlug;
|
|
7590
|
+
/** The page at a site-internal path (`/about/#team` → About), if it is one. */
|
|
7591
|
+
pageAt(raw2) {
|
|
7592
|
+
return this.bySlug.get(trimSlashes(raw2.split(/[?#]/)[0] ?? ""));
|
|
7593
|
+
}
|
|
7588
7594
|
hasCollection(name) {
|
|
7589
7595
|
return name in this.doc.collections || name in (this.doc.collectionConfigs ?? {});
|
|
7590
7596
|
}
|
|
@@ -7610,8 +7616,34 @@ var isObject = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
|
7610
7616
|
function findBrokenLinks(doc) {
|
|
7611
7617
|
const targets = new LinkTargets(doc);
|
|
7612
7618
|
const out = [];
|
|
7619
|
+
const fragments = /* @__PURE__ */ new Map();
|
|
7620
|
+
const fragmentsOf = (page) => {
|
|
7621
|
+
let f = fragments.get(page);
|
|
7622
|
+
if (!f) {
|
|
7623
|
+
f = pageFragments(doc, page);
|
|
7624
|
+
fragments.set(page, f);
|
|
7625
|
+
}
|
|
7626
|
+
return f;
|
|
7627
|
+
};
|
|
7628
|
+
const sectionIds = new Set(
|
|
7629
|
+
Object.values(doc.pages).flatMap((p) => p.sections.map((s2) => s2.id))
|
|
7630
|
+
);
|
|
7613
7631
|
const walk5 = (value, scope, path2, where2) => {
|
|
7614
7632
|
const add = (kind, target, message, severity = "error") => out.push({ scope, path: path2, ...where2, kind, target, severity, message });
|
|
7633
|
+
const checkAnchor = (o2) => {
|
|
7634
|
+
const ref = anchorRef(o2, targets, scope.kind === "page" ? scope.page : void 0);
|
|
7635
|
+
if (!ref || fragmentsOf(ref.page.id).has(ref.fragment)) return;
|
|
7636
|
+
out.push({
|
|
7637
|
+
scope,
|
|
7638
|
+
path: path2,
|
|
7639
|
+
...where2,
|
|
7640
|
+
kind: "anchor",
|
|
7641
|
+
target: ref.target,
|
|
7642
|
+
severity: "warning",
|
|
7643
|
+
message: `Link to ${ref.target}: no section on ${pageTitle(ref.page)} is called \u201C${ref.fragment}\u201D`,
|
|
7644
|
+
anchor: { page: ref.page.id, fragment: ref.fragment }
|
|
7645
|
+
});
|
|
7646
|
+
};
|
|
7615
7647
|
if (typeof value === "string") {
|
|
7616
7648
|
if (path2[path2.length - 1] === "body")
|
|
7617
7649
|
for (const m of value.matchAll(MARKDOWN_LINK_RE)) {
|
|
@@ -7630,6 +7662,10 @@ function findBrokenLinks(doc) {
|
|
|
7630
7662
|
if (o.type === "page" && typeof o.page === "string") {
|
|
7631
7663
|
if (!(o.page in doc.pages))
|
|
7632
7664
|
add("page", o.page, `Link to page ${o.page} but no such page exists`);
|
|
7665
|
+
else checkAnchor(o);
|
|
7666
|
+
} else if (o.type === "anchor" && typeof o.section === "string") {
|
|
7667
|
+
if (!sectionIds.has(o.section))
|
|
7668
|
+
add("anchor", o.section, "Link to a section that no longer exists", "warning");
|
|
7633
7669
|
} else if (o.type === "item" && typeof o.collection === "string" && typeof o.slug === "string") {
|
|
7634
7670
|
if (!doc.collections[o.collection]?.[o.slug])
|
|
7635
7671
|
add(
|
|
@@ -7647,10 +7683,12 @@ function findBrokenLinks(doc) {
|
|
|
7647
7683
|
} else if (o.type === "url" && typeof o.url === "string" && isInternalPath(o.url)) {
|
|
7648
7684
|
if (!targets.pathResolves(o.url))
|
|
7649
7685
|
add("url", o.url, `Link to ${o.url} does not lead to any page`);
|
|
7686
|
+
else checkAnchor(o);
|
|
7650
7687
|
} else if (o.type === "link" && isObject(o.attrs) && typeof o.attrs.href === "string") {
|
|
7651
7688
|
const href = o.attrs.href;
|
|
7652
7689
|
if (isInternalPath(href) && !targets.pathResolves(href))
|
|
7653
7690
|
add("richtext", href, `Text link to ${href} does not lead to any page`, "warning");
|
|
7691
|
+
else checkAnchor(o);
|
|
7654
7692
|
}
|
|
7655
7693
|
for (const [k, v] of Object.entries(o)) walk5(v, scope, [...path2, k], where2);
|
|
7656
7694
|
};
|
|
@@ -7788,6 +7826,121 @@ function withPages(doc, pages) {
|
|
|
7788
7826
|
for (const p of pages) all[p.id] = p;
|
|
7789
7827
|
return { ...doc, pages: all };
|
|
7790
7828
|
}
|
|
7829
|
+
var pageTitle = (p) => p.title || (p.slug === "" ? "Home" : p.slug);
|
|
7830
|
+
var LANDMARKS = ["main", "top"];
|
|
7831
|
+
var TEMPLATE_ID_RE = /\bid\s*=\s*["']([A-Za-z][\w-]*)["']/g;
|
|
7832
|
+
function pageFragments(doc, pageId) {
|
|
7833
|
+
const ids = new Set(LANDMARKS);
|
|
7834
|
+
const blocks2 = /* @__PURE__ */ new Set();
|
|
7835
|
+
const page = doc.pages[pageId];
|
|
7836
|
+
for (const s2 of [...page?.sections ?? [], doc.site.footer]) {
|
|
7837
|
+
ids.add(s2.anchor ?? s2.id);
|
|
7838
|
+
for (const b of s2.blocks) {
|
|
7839
|
+
blocks2.add(b.id);
|
|
7840
|
+
const name = /^component\/(.+)$/.exec(b.type)?.[1];
|
|
7841
|
+
const tpl = name ? doc.components?.[name]?.template : void 0;
|
|
7842
|
+
if (tpl) {
|
|
7843
|
+
for (const m of tpl.matchAll(TEMPLATE_ID_RE)) if (m[1]) ids.add(m[1]);
|
|
7844
|
+
}
|
|
7845
|
+
}
|
|
7846
|
+
}
|
|
7847
|
+
return {
|
|
7848
|
+
has: (f) => ids.has(f) || blocks2.has(f) || [...blocks2].some((b) => f.startsWith(`${b}-`))
|
|
7849
|
+
};
|
|
7850
|
+
}
|
|
7851
|
+
function anchorRef(o, targets, currentPage) {
|
|
7852
|
+
if (o.type === "page" && typeof o.page === "string" && typeof o.anchor === "string") {
|
|
7853
|
+
const page2 = targets.doc.pages[o.page];
|
|
7854
|
+
return page2 ? { page: page2, fragment: o.anchor, target: `${pagePath(page2)}#${o.anchor}` } : void 0;
|
|
7855
|
+
}
|
|
7856
|
+
const href = o.type === "url" && typeof o.url === "string" ? o.url : o.type === "link" && isObject(o.attrs) && typeof o.attrs.href === "string" ? o.attrs.href : void 0;
|
|
7857
|
+
if (!href) return void 0;
|
|
7858
|
+
const hash = /#([^#?]+)$/.exec(href)?.[1];
|
|
7859
|
+
if (!hash) return void 0;
|
|
7860
|
+
if (href.startsWith("#")) {
|
|
7861
|
+
if (o.type !== "link" || !currentPage) return void 0;
|
|
7862
|
+
const page2 = targets.doc.pages[currentPage];
|
|
7863
|
+
return page2 ? { page: page2, fragment: hash, target: href } : void 0;
|
|
7864
|
+
}
|
|
7865
|
+
if (!isInternalPath(href)) return void 0;
|
|
7866
|
+
const page = targets.pageAt(href);
|
|
7867
|
+
return page ? { page, fragment: hash, target: href } : void 0;
|
|
7868
|
+
}
|
|
7869
|
+
function readableStrings(value, out = []) {
|
|
7870
|
+
if (typeof value === "string") {
|
|
7871
|
+
if (!/^(?:\/|#|https?:|mailto:|tel:|media\/|data:)/.test(value)) out.push(value);
|
|
7872
|
+
} else if (Array.isArray(value)) for (const v of value) readableStrings(v, out);
|
|
7873
|
+
else if (isObject(value)) {
|
|
7874
|
+
for (const [k, v] of Object.entries(value))
|
|
7875
|
+
if (!["type", "id", "src", "url", "href", "page", "variant", "level", "preset"].includes(k))
|
|
7876
|
+
readableStrings(v, out);
|
|
7877
|
+
}
|
|
7878
|
+
return out;
|
|
7879
|
+
}
|
|
7880
|
+
function linksToAnchor(value, targets, pageId, fragment) {
|
|
7881
|
+
if (Array.isArray(value)) return value.some((v) => linksToAnchor(v, targets, pageId, fragment));
|
|
7882
|
+
if (!isObject(value)) return false;
|
|
7883
|
+
const ref = anchorRef(value, targets, pageId);
|
|
7884
|
+
if (ref && ref.page.id === pageId && ref.fragment === fragment) return true;
|
|
7885
|
+
return Object.values(value).some((v) => linksToAnchor(v, targets, pageId, fragment));
|
|
7886
|
+
}
|
|
7887
|
+
function suggestAnchorSection(doc, pageId, fragment) {
|
|
7888
|
+
const page = doc.pages[pageId];
|
|
7889
|
+
const wanted = words(fragment);
|
|
7890
|
+
if (!page || !wanted.length) return void 0;
|
|
7891
|
+
const targets = new LinkTargets(doc);
|
|
7892
|
+
const match = (s2) => {
|
|
7893
|
+
const own3 = words(s2);
|
|
7894
|
+
const hits = wanted.filter((w) => own3.some((x) => related(w, x))).length;
|
|
7895
|
+
return hits === wanted.length ? 2 : hits ? 1 : 0;
|
|
7896
|
+
};
|
|
7897
|
+
let best;
|
|
7898
|
+
let bestScore = 0;
|
|
7899
|
+
for (const s2 of page.sections) {
|
|
7900
|
+
if (linksToAnchor(s2.blocks, targets, page.id, fragment)) continue;
|
|
7901
|
+
const name = [
|
|
7902
|
+
s2.label ?? "",
|
|
7903
|
+
s2.anchor ?? "",
|
|
7904
|
+
(s2.preset ?? "").replace(/@\d+$/, ""),
|
|
7905
|
+
...s2.blocks.map((b) => b.type.replace(/^component\//, ""))
|
|
7906
|
+
].join(" ");
|
|
7907
|
+
const text7 = readableStrings(s2.blocks.map((b) => b.fields)).reduce((n, t) => n + match(t), 0);
|
|
7908
|
+
const score = match(name) * 4 + Math.min(text7, 4);
|
|
7909
|
+
if (score > bestScore) {
|
|
7910
|
+
best = s2;
|
|
7911
|
+
bestScore = score;
|
|
7912
|
+
}
|
|
7913
|
+
}
|
|
7914
|
+
return bestScore >= 2 ? best : void 0;
|
|
7915
|
+
}
|
|
7916
|
+
function retargetAnchor(value, doc, pageId, from, to, currentPage) {
|
|
7917
|
+
const targets = new LinkTargets(doc);
|
|
7918
|
+
let changed = 0;
|
|
7919
|
+
const swap = (href) => {
|
|
7920
|
+
const base = href.slice(0, href.length - from.length - 1);
|
|
7921
|
+
if (to) return `${base}#${to}`;
|
|
7922
|
+
return base || "#top";
|
|
7923
|
+
};
|
|
7924
|
+
const fix = (v) => {
|
|
7925
|
+
if (Array.isArray(v)) return v.map(fix);
|
|
7926
|
+
if (!isObject(v)) return v;
|
|
7927
|
+
const ref = anchorRef(v, targets, currentPage);
|
|
7928
|
+
if (ref && ref.page.id === pageId && ref.fragment === from) {
|
|
7929
|
+
changed++;
|
|
7930
|
+
if (v.type === "page") {
|
|
7931
|
+
const { anchor: _a3, ...rest } = v;
|
|
7932
|
+
return to ? { ...rest, anchor: to } : rest;
|
|
7933
|
+
}
|
|
7934
|
+
if (v.type === "url") return { ...v, url: swap(v.url) };
|
|
7935
|
+
const attrs = v.attrs;
|
|
7936
|
+
return { ...v, attrs: { ...attrs, href: swap(attrs.href) } };
|
|
7937
|
+
}
|
|
7938
|
+
const out = {};
|
|
7939
|
+
for (const [k, x] of Object.entries(v)) out[k] = fix(x);
|
|
7940
|
+
return out;
|
|
7941
|
+
};
|
|
7942
|
+
return { value: fix(value), changed };
|
|
7943
|
+
}
|
|
7791
7944
|
|
|
7792
7945
|
// ../schema/src/secrets.ts
|
|
7793
7946
|
function shannonEntropy(value) {
|
|
@@ -12385,6 +12538,11 @@ function linkPlace(doc, link2) {
|
|
|
12385
12538
|
}
|
|
12386
12539
|
function describeBrokenLink(doc, link2) {
|
|
12387
12540
|
const where2 = linkPlace(doc, link2);
|
|
12541
|
+
if (link2.kind === "anchor") {
|
|
12542
|
+
const page = link2.anchor ? doc.pages[link2.anchor.page] : void 0;
|
|
12543
|
+
if (!link2.anchor || !page) return `${where2} links to a section that no longer exists.`;
|
|
12544
|
+
return `${where2} links to \u201C${link2.anchor.fragment}\u201D on ${titleOf(page)}, but no section there is called that.`;
|
|
12545
|
+
}
|
|
12388
12546
|
switch (link2.kind) {
|
|
12389
12547
|
case "page":
|
|
12390
12548
|
return `${where2} links to a page that no longer exists.`;
|
|
@@ -12426,10 +12584,73 @@ function linkFix(doc, link2, meta) {
|
|
|
12426
12584
|
if (link2.scope.kind === "site" && link2.path[0] === "nav") {
|
|
12427
12585
|
const { value, changes } = resolveInternalLinks(doc.site.nav, doc);
|
|
12428
12586
|
if (!changes.length) return void 0;
|
|
12429
|
-
return { label, ops: [{ op: "
|
|
12587
|
+
return { label, ops: [{ op: "nav.update", ...meta(), tree: value }] };
|
|
12430
12588
|
}
|
|
12431
12589
|
return void 0;
|
|
12432
12590
|
}
|
|
12591
|
+
function sectionName(section) {
|
|
12592
|
+
if (section.label) return section.label;
|
|
12593
|
+
for (const b of section.blocks) {
|
|
12594
|
+
const t = b.fields.text;
|
|
12595
|
+
if (typeof t === "string" && t.trim()) return t.trim().slice(0, 60);
|
|
12596
|
+
}
|
|
12597
|
+
return "that";
|
|
12598
|
+
}
|
|
12599
|
+
function retargetOps(doc, link2, from, to, meta) {
|
|
12600
|
+
const anchorPage = link2.anchor?.page;
|
|
12601
|
+
if (!anchorPage) return void 0;
|
|
12602
|
+
if (link2.block && link2.field) {
|
|
12603
|
+
const container2 = link2.container === "footer" ? doc.site.footer : doc.pages[link2.container]?.sections.find((x) => x.id === link2.section);
|
|
12604
|
+
const block4 = container2?.blocks.find((b) => b.id === link2.block);
|
|
12605
|
+
if (!block4) return void 0;
|
|
12606
|
+
const current2 = link2.container === "footer" ? void 0 : link2.container;
|
|
12607
|
+
const r = retargetAnchor(block4.fields[link2.field], doc, anchorPage, from, to, current2);
|
|
12608
|
+
if (!r.changed) return void 0;
|
|
12609
|
+
return [
|
|
12610
|
+
{
|
|
12611
|
+
op: "block.update",
|
|
12612
|
+
...meta(),
|
|
12613
|
+
page: link2.container,
|
|
12614
|
+
block: block4.id,
|
|
12615
|
+
set: { [link2.field]: r.value }
|
|
12616
|
+
}
|
|
12617
|
+
];
|
|
12618
|
+
}
|
|
12619
|
+
if (link2.scope.kind === "site" && link2.path[0] === "nav") {
|
|
12620
|
+
const r = retargetAnchor(doc.site.nav, doc, anchorPage, from, to);
|
|
12621
|
+
if (!r.changed) return void 0;
|
|
12622
|
+
return [{ op: "nav.update", ...meta(), tree: r.value }];
|
|
12623
|
+
}
|
|
12624
|
+
return void 0;
|
|
12625
|
+
}
|
|
12626
|
+
function anchorFix(doc, link2, meta) {
|
|
12627
|
+
if (!link2.anchor) return void 0;
|
|
12628
|
+
const { page: pageId, fragment } = link2.anchor;
|
|
12629
|
+
const page = doc.pages[pageId];
|
|
12630
|
+
if (!page) return void 0;
|
|
12631
|
+
const section = suggestAnchorSection(doc, pageId, fragment);
|
|
12632
|
+
if (!section) {
|
|
12633
|
+
const ops2 = retargetOps(doc, link2, fragment, void 0, meta);
|
|
12634
|
+
return ops2 ? { label: `Link to the top of ${titleOf(page)} instead`, ops: ops2 } : void 0;
|
|
12635
|
+
}
|
|
12636
|
+
const label = `Connect it to the \u201C${sectionName(section)}\u201D section`;
|
|
12637
|
+
const taken = new Set(page.sections.map((s2) => s2.anchor).filter(Boolean));
|
|
12638
|
+
if (!section.anchor && SlugSchema.safeParse(fragment).success && !taken.has(fragment))
|
|
12639
|
+
return {
|
|
12640
|
+
label,
|
|
12641
|
+
ops: [
|
|
12642
|
+
{
|
|
12643
|
+
op: "section.update",
|
|
12644
|
+
...meta(),
|
|
12645
|
+
page: pageId,
|
|
12646
|
+
section: section.id,
|
|
12647
|
+
set: { anchor: fragment }
|
|
12648
|
+
}
|
|
12649
|
+
]
|
|
12650
|
+
};
|
|
12651
|
+
const ops = retargetOps(doc, link2, fragment, section.anchor ?? section.id, meta);
|
|
12652
|
+
return ops ? { label, ops } : void 0;
|
|
12653
|
+
}
|
|
12433
12654
|
function linkIssues(doc, pageId, options = {}, meta = opMeta(options)) {
|
|
12434
12655
|
const out = [];
|
|
12435
12656
|
for (const link2 of findBrokenLinks(doc)) {
|
|
@@ -12437,7 +12658,7 @@ function linkIssues(doc, pageId, options = {}, meta = opMeta(options)) {
|
|
|
12437
12658
|
if (pageId && s2.kind === "page" && s2.page !== pageId) continue;
|
|
12438
12659
|
if (pageId && s2.kind === "item") continue;
|
|
12439
12660
|
const page = link2.container ?? (s2.kind === "page" ? s2.page : void 0);
|
|
12440
|
-
const autofix = linkFix(doc, link2, meta);
|
|
12661
|
+
const autofix = link2.kind === "anchor" ? anchorFix(doc, link2, meta) : linkFix(doc, link2, meta);
|
|
12441
12662
|
out.push(
|
|
12442
12663
|
issue({
|
|
12443
12664
|
rule: "broken-link",
|
|
@@ -12447,7 +12668,7 @@ function linkIssues(doc, pageId, options = {}, meta = opMeta(options)) {
|
|
|
12447
12668
|
...link2.block ? { block: link2.block } : {},
|
|
12448
12669
|
key: `${s2.kind === "item" ? `${s2.collection}/${s2.slug}:` : ""}${link2.path.join(".")}`,
|
|
12449
12670
|
message: describeBrokenLink(doc, link2),
|
|
12450
|
-
fix: link2.severity === "error" ? "Point the link at a page that exists, or add the missing page. The site can't be published until then." : "Point the link at a page that exists, or add the missing page.",
|
|
12671
|
+
fix: link2.kind === "anchor" ? "Give the section that name (Section settings \u2192 Link name), or point the link at another section." : link2.severity === "error" ? "Point the link at a page that exists, or add the missing page. The site can't be published until then." : "Point the link at a page that exists, or add the missing page.",
|
|
12451
12672
|
...autofix ? { autofix } : {}
|
|
12452
12673
|
})
|
|
12453
12674
|
);
|
|
@@ -13264,8 +13485,8 @@ function phoneIssuesFromProbe(doc, pageId, results, options = {}) {
|
|
|
13264
13485
|
const at = locate(doc, pageId, f);
|
|
13265
13486
|
if (f.kind === "cta-below-fold" && at.section?.preset && !at.section.preset.startsWith("hero/"))
|
|
13266
13487
|
continue;
|
|
13267
|
-
const
|
|
13268
|
-
const where2 = f.component ? ` in the \u201C${f.component}\u201D component` :
|
|
13488
|
+
const sectionName2 = at.section && at.page !== FOOTER ? " in this section" : at.page === FOOTER ? " in the footer" : "";
|
|
13489
|
+
const where2 = f.component ? ` in the \u201C${f.component}\u201D component` : sectionName2;
|
|
13269
13490
|
const [message, fix] = MESSAGE[f.kind](f, where2);
|
|
13270
13491
|
const key2 = `${f.kind}:${at.block?.id ?? f.block ?? ""}:${f.kind === "tap-target" || f.kind === "photo-contrast" ? f.text ?? "" : ""}`;
|
|
13271
13492
|
const i = issue({
|
|
@@ -45432,6 +45653,7 @@ Design check flags (custom components too: invalid code, budgets, motion, fixed
|
|
|
45432
45653
|
- Styles come from Site Styles tokens (palette, pairing, density, shape, buttons, motion, image treatment). Never write inline CSS; restyle with set_styles or apply_template_look.
|
|
45433
45654
|
- Section color themes: light, light-accent, dark, dark-accent, brand. The contrast solver keeps text readable in all five.
|
|
45434
45655
|
- Rich text is Markdown: headings (#\u2013####), **bold**, *italic*, [links](/contact), lists, > quotes; a trailing {.lead} sets a paragraph preset.
|
|
45656
|
+
- Menu entries and buttons that jump to a section (one-page sites: "How it works", "Prices", "Join"): give the section a link name (anchor: insert_section / insert_component, or update_section set {"anchor": "how-it-works"}), then link to it \u2014 menu entry {type:"page", page, label, anchor}, button or field link {type:"page", page, anchor}. The same entries serve the desktop menu and the phone menu. design_check warns about links to a section name nothing has.
|
|
45435
45657
|
- Edits land in the draft. The owner publishes \u2014 call publish only when they ask, with confirm: true.
|
|
45436
45658
|
`;
|
|
45437
45659
|
var ITERATION_GUIDANCE = `# Design iteration loop
|
|
@@ -46025,9 +46247,807 @@ var positionArg = external_exports.union([
|
|
|
46025
46247
|
external_exports.strictObject({ before: external_exports.string() }),
|
|
46026
46248
|
external_exports.strictObject({ after: external_exports.string() })
|
|
46027
46249
|
]).describe('Where: an index, "start", "end", {"before": id} or {"after": id}.');
|
|
46250
|
+
var anchorArg = external_exports.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "Use lowercase words joined by hyphens, like how-it-works.").max(60).describe(
|
|
46251
|
+
`The section's link name, so menu entries and buttons can jump to it: lowercase words with hyphens, e.g. "how-it-works". Link to it with {type:"page", page, anchor}.`
|
|
46252
|
+
);
|
|
46028
46253
|
var confirmArg = external_exports.boolean().optional().describe("Must be true. Ask the owner first: this cannot be undone from here.");
|
|
46029
46254
|
var deviceArg = external_exports.enum(["desktop", "tablet", "mobile"]);
|
|
46030
46255
|
|
|
46256
|
+
// ../mcp-tools/src/tools/read.ts
|
|
46257
|
+
var listSites = defineTool({
|
|
46258
|
+
name: "list_sites",
|
|
46259
|
+
title: "List sites",
|
|
46260
|
+
description: "Sites this connection can edit, with their titles and live addresses. Start here when you don't know the site id.",
|
|
46261
|
+
input: external_exports.object({}),
|
|
46262
|
+
annotations: { readOnlyHint: true },
|
|
46263
|
+
async handler(ctx) {
|
|
46264
|
+
const allowed = new Set(await ctx.allowedSites());
|
|
46265
|
+
const sites = (await ctx.store.listSites()).filter((s2) => allowed.has(s2.id));
|
|
46266
|
+
return { data: { sites, canPublish: ctx.grant.publish && ctx.store.capabilities.canPublish } };
|
|
46267
|
+
}
|
|
46268
|
+
});
|
|
46269
|
+
function stylesSummary(doc) {
|
|
46270
|
+
const compiled2 = compileStyles(doc.styles);
|
|
46271
|
+
const pairing = getPairing(doc.styles.choices.pairing);
|
|
46272
|
+
return {
|
|
46273
|
+
choices: doc.styles.choices,
|
|
46274
|
+
pairing: pairing ? { id: pairing.id, heading: pairing.heading.family, body: pairing.body.family } : { id: doc.styles.choices.pairing, unknown: true },
|
|
46275
|
+
overrides: doc.styles.overrides ?? null,
|
|
46276
|
+
brand: compiled2.tokens.palette.brand,
|
|
46277
|
+
themes: Object.fromEntries(
|
|
46278
|
+
Object.entries(compiled2.tokens.themes).map(([theme, c]) => [
|
|
46279
|
+
theme,
|
|
46280
|
+
{
|
|
46281
|
+
background: c.background,
|
|
46282
|
+
text: c.text,
|
|
46283
|
+
heading: c.heading,
|
|
46284
|
+
accent: c.accent,
|
|
46285
|
+
"accent-text": c["accent-text"]
|
|
46286
|
+
}
|
|
46287
|
+
])
|
|
46288
|
+
),
|
|
46289
|
+
contrast: {
|
|
46290
|
+
mode: compiled2.contrast.mode,
|
|
46291
|
+
adjustedBySolver: compiled2.contrast.adjustments.length,
|
|
46292
|
+
failures: compiled2.contrast.failures.length
|
|
46293
|
+
}
|
|
46294
|
+
};
|
|
46295
|
+
}
|
|
46296
|
+
function pageSummary(page) {
|
|
46297
|
+
return {
|
|
46298
|
+
id: page.id,
|
|
46299
|
+
title: page.title,
|
|
46300
|
+
slug: page.slug,
|
|
46301
|
+
path: pagePathOf(page),
|
|
46302
|
+
sections: page.sections.length,
|
|
46303
|
+
hidden: page.hidden ?? false
|
|
46304
|
+
};
|
|
46305
|
+
}
|
|
46306
|
+
var getSite = defineTool({
|
|
46307
|
+
name: "get_site",
|
|
46308
|
+
title: "Get site",
|
|
46309
|
+
description: "Site settings, navigation, header/footer, Site Styles summary, page list and collections. Call before editing to learn page ids and the current look.",
|
|
46310
|
+
input: external_exports.object({ site: siteArg }),
|
|
46311
|
+
annotations: { readOnlyHint: true },
|
|
46312
|
+
async handler(ctx, input) {
|
|
46313
|
+
const siteId = await ctx.siteId(input.site);
|
|
46314
|
+
const doc = await ctx.doc(siteId);
|
|
46315
|
+
const { site: site2 } = doc;
|
|
46316
|
+
return {
|
|
46317
|
+
data: {
|
|
46318
|
+
id: siteId,
|
|
46319
|
+
title: site2.title,
|
|
46320
|
+
tagline: site2.tagline ?? null,
|
|
46321
|
+
template: doc.config.template ?? null,
|
|
46322
|
+
url: doc.config.deploy.customDomain ? `https://${doc.config.deploy.customDomain}` : doc.config.deploy.url,
|
|
46323
|
+
nav: site2.nav,
|
|
46324
|
+
header: site2.header,
|
|
46325
|
+
footer: {
|
|
46326
|
+
sectionId: site2.footer.id,
|
|
46327
|
+
layout: site2.footer.layout.mode,
|
|
46328
|
+
blocks: site2.footer.blocks.map((b) => ({ id: b.id, type: b.type })),
|
|
46329
|
+
note: 'Edit footer blocks with the block tools using page "footer".'
|
|
46330
|
+
},
|
|
46331
|
+
announcement: site2.announcement ?? null,
|
|
46332
|
+
social: site2.social ?? [],
|
|
46333
|
+
seo: site2.seo ?? null,
|
|
46334
|
+
styles: stylesSummary(doc),
|
|
46335
|
+
pages: Object.values(doc.pages).map(pageSummary),
|
|
46336
|
+
collections: Object.entries(doc.collections).map(([name, items]) => ({
|
|
46337
|
+
name,
|
|
46338
|
+
kind: Object.values(items)[0]?.kind ?? doc.collectionConfigs?.[name]?.kind ?? null,
|
|
46339
|
+
items: Object.keys(items).length
|
|
46340
|
+
})),
|
|
46341
|
+
media: Object.keys(doc.media).length,
|
|
46342
|
+
capabilities: {
|
|
46343
|
+
...ctx.store.capabilities,
|
|
46344
|
+
// Same answer as list_sites: publishing needs both the store and this connection.
|
|
46345
|
+
canPublish: ctx.grant.publish && ctx.store.capabilities.canPublish,
|
|
46346
|
+
grantPublish: ctx.grant.publish
|
|
46347
|
+
}
|
|
46348
|
+
}
|
|
46349
|
+
};
|
|
46350
|
+
}
|
|
46351
|
+
});
|
|
46352
|
+
function sectionView(ctx, section, detail, doc) {
|
|
46353
|
+
const registry3 = doc ? ctx.registryFor(doc) : ctx.registry;
|
|
46354
|
+
const blocks2 = section.blocks.map((b) => {
|
|
46355
|
+
if (detail === "outline") {
|
|
46356
|
+
const out = {
|
|
46357
|
+
id: b.id,
|
|
46358
|
+
type: b.type,
|
|
46359
|
+
text: blockPreview(registry3, b)
|
|
46360
|
+
};
|
|
46361
|
+
if (b.variant) out.variant = b.variant;
|
|
46362
|
+
if (b.column !== void 0) out.column = b.column;
|
|
46363
|
+
if (b.hidden) out.hidden = true;
|
|
46364
|
+
return out;
|
|
46365
|
+
}
|
|
46366
|
+
return blockToAgent(registry3, b);
|
|
46367
|
+
});
|
|
46368
|
+
const base = {
|
|
46369
|
+
id: section.id,
|
|
46370
|
+
...section.preset ? { preset: section.preset } : {},
|
|
46371
|
+
...section.label ? { label: section.label } : {},
|
|
46372
|
+
...section.hidden ? { hidden: true } : {},
|
|
46373
|
+
...section.locked ? { locked: true } : {},
|
|
46374
|
+
theme: section.style?.colorTheme ?? "light"
|
|
46375
|
+
};
|
|
46376
|
+
if (detail === "outline") {
|
|
46377
|
+
base.layout = section.layout.mode === "grid" ? "grid" : `stack ${JSON.stringify(section.layout.columns.d)}`;
|
|
46378
|
+
base.blocks = blocks2;
|
|
46379
|
+
return base;
|
|
46380
|
+
}
|
|
46381
|
+
return {
|
|
46382
|
+
...base,
|
|
46383
|
+
layout: section.layout,
|
|
46384
|
+
style: section.style ?? {},
|
|
46385
|
+
...section.anchor ? { anchor: section.anchor } : {},
|
|
46386
|
+
blocks: blocks2
|
|
46387
|
+
};
|
|
46388
|
+
}
|
|
46389
|
+
var getPage2 = defineTool({
|
|
46390
|
+
name: "get_page",
|
|
46391
|
+
title: "Get page",
|
|
46392
|
+
description: `A page's section/block tree with stable ids, fields (rich text as Markdown), layout and style. Use detail "outline" for a compact overview (ids, types, text previews) and "full" before editing fields or grid placements. page "footer" returns the site footer section.`,
|
|
46393
|
+
input: external_exports.object({
|
|
46394
|
+
site: siteArg,
|
|
46395
|
+
page: containerArg,
|
|
46396
|
+
detail: external_exports.enum(["full", "outline"]).optional().describe('Default "full".')
|
|
46397
|
+
}),
|
|
46398
|
+
annotations: { readOnlyHint: true },
|
|
46399
|
+
async handler(ctx, input) {
|
|
46400
|
+
const siteId = await ctx.siteId(input.site);
|
|
46401
|
+
const doc = await ctx.doc(siteId);
|
|
46402
|
+
const detail = input.detail ?? "full";
|
|
46403
|
+
const { id, page } = resolvePage(doc, input.page, { allowFooter: true });
|
|
46404
|
+
if (id === FOOTER || !page)
|
|
46405
|
+
return { data: { page: FOOTER, sections: [sectionView(ctx, doc.site.footer, detail, doc)] } };
|
|
46406
|
+
return {
|
|
46407
|
+
data: {
|
|
46408
|
+
id: page.id,
|
|
46409
|
+
title: page.title,
|
|
46410
|
+
slug: page.slug,
|
|
46411
|
+
path: pagePathOf(page),
|
|
46412
|
+
seo: page.seo ?? {},
|
|
46413
|
+
...page.header ? { header: page.header } : {},
|
|
46414
|
+
...page.hidden ? { hidden: true } : {},
|
|
46415
|
+
sections: page.sections.map((s2) => sectionView(ctx, s2, detail, doc))
|
|
46416
|
+
}
|
|
46417
|
+
};
|
|
46418
|
+
}
|
|
46419
|
+
});
|
|
46420
|
+
function blockMetaView(m, full) {
|
|
46421
|
+
const fields = Object.fromEntries(
|
|
46422
|
+
Object.entries(m.fields).map(([k, f]) => {
|
|
46423
|
+
const v = {
|
|
46424
|
+
kind: f.kind === "richText" ? "richText (Markdown)" : f.kind
|
|
46425
|
+
};
|
|
46426
|
+
if (f.optional) v.optional = true;
|
|
46427
|
+
if (f.label) v.label = f.label;
|
|
46428
|
+
if (full && f.help) v.help = f.help;
|
|
46429
|
+
if (f.ai) v.ai = f.ai;
|
|
46430
|
+
if (f.kind === "select") v.options = f.options.map((o) => o.value);
|
|
46431
|
+
if (f.kind === "text" && f.maxLength) v.maxLength = f.maxLength;
|
|
46432
|
+
if (f.kind === "list")
|
|
46433
|
+
v.itemFields = Object.fromEntries(
|
|
46434
|
+
Object.entries(f.fields).map(([ik, iv]) => [
|
|
46435
|
+
ik,
|
|
46436
|
+
iv.kind === "richText" ? "richText (Markdown)" : iv.kind
|
|
46437
|
+
])
|
|
46438
|
+
);
|
|
46439
|
+
if (f.kind === "image") v.shape = "{ src, alt, focal? }";
|
|
46440
|
+
if (f.kind === "link") v.allow = f.allow;
|
|
46441
|
+
return [k, v];
|
|
46442
|
+
})
|
|
46443
|
+
);
|
|
46444
|
+
return {
|
|
46445
|
+
type: m.type,
|
|
46446
|
+
label: m.label,
|
|
46447
|
+
category: m.category,
|
|
46448
|
+
description: m.description,
|
|
46449
|
+
variants: m.variants,
|
|
46450
|
+
styleSlots: m.styleSlots,
|
|
46451
|
+
fields,
|
|
46452
|
+
ai: m.ai
|
|
46453
|
+
};
|
|
46454
|
+
}
|
|
46455
|
+
var listBlocks = defineTool({
|
|
46456
|
+
name: "list_blocks",
|
|
46457
|
+
title: "List blocks",
|
|
46458
|
+
description: "The block catalog: each block's fields (rich text as Markdown), variants, style slots and AI guidance (purpose, whenToUse, avoid). Filter by category or type. Prefer section presets for whole sections; use blocks to add or adjust pieces.",
|
|
46459
|
+
input: external_exports.object({
|
|
46460
|
+
category: external_exports.string().optional(),
|
|
46461
|
+
type: external_exports.string().optional().describe("One block type for full detail.")
|
|
46462
|
+
}),
|
|
46463
|
+
annotations: { readOnlyHint: true },
|
|
46464
|
+
async handler(ctx, input) {
|
|
46465
|
+
let metas = ctx.registry.meta();
|
|
46466
|
+
if (input.type) {
|
|
46467
|
+
metas = metas.filter((m) => m.type === input.type);
|
|
46468
|
+
if (!metas.length)
|
|
46469
|
+
throw new ToolError(
|
|
46470
|
+
`Unknown block type "${input.type}". Types: ${ctx.registry.types().join(", ")}.`,
|
|
46471
|
+
"not_found"
|
|
46472
|
+
);
|
|
46473
|
+
}
|
|
46474
|
+
if (input.category) metas = metas.filter((m) => m.category === input.category);
|
|
46475
|
+
return {
|
|
46476
|
+
data: {
|
|
46477
|
+
blocks: metas.map((m) => blockMetaView(m, Boolean(input.type))),
|
|
46478
|
+
categories: [...new Set(ctx.registry.meta().map((m) => m.category))]
|
|
46479
|
+
}
|
|
46480
|
+
};
|
|
46481
|
+
}
|
|
46482
|
+
});
|
|
46483
|
+
var getBlockSchema = defineTool({
|
|
46484
|
+
name: "get_block_schema",
|
|
46485
|
+
title: "Get block schema",
|
|
46486
|
+
description: "JSON Schema for one block type's fields (with labels and AI hints). Rich-text fields are shown as ProseMirror JSON but the edit tools also accept a Markdown string for them.",
|
|
46487
|
+
input: external_exports.object({ type: external_exports.string() }),
|
|
46488
|
+
annotations: { readOnlyHint: true },
|
|
46489
|
+
async handler(ctx, input) {
|
|
46490
|
+
const def = ctx.registry.get(input.type);
|
|
46491
|
+
if (!def)
|
|
46492
|
+
throw new ToolError(
|
|
46493
|
+
`Unknown block type "${input.type}". Types: ${ctx.registry.types().join(", ")}.`,
|
|
46494
|
+
"not_found"
|
|
46495
|
+
);
|
|
46496
|
+
return {
|
|
46497
|
+
data: {
|
|
46498
|
+
type: def.type,
|
|
46499
|
+
version: def.version,
|
|
46500
|
+
variants: def.meta.variants,
|
|
46501
|
+
styleSlots: def.meta.styleSlots,
|
|
46502
|
+
fields: blockFieldsJsonSchema(def)
|
|
46503
|
+
}
|
|
46504
|
+
};
|
|
46505
|
+
}
|
|
46506
|
+
});
|
|
46507
|
+
var listSectionPresets = defineTool({
|
|
46508
|
+
name: "list_section_presets",
|
|
46509
|
+
title: "List section presets",
|
|
46510
|
+
description: "Designed section presets by category (hero, about, services, gallery, testimonials, pricing, faq, team, stats, cta, contact, newsletter, blog, events, products, footer, header) with descriptions, layout, designed-for color themes, copy-length ranges and AI guidance. Compose pages from these with insert_section/create_page.",
|
|
46511
|
+
input: external_exports.object({
|
|
46512
|
+
category: external_exports.enum(PRESET_CATEGORIES).optional(),
|
|
46513
|
+
query: external_exports.string().optional().describe("Words to match in label, description or tags."),
|
|
46514
|
+
detail: external_exports.enum(["brief", "full"]).optional().describe('Default "brief".')
|
|
46515
|
+
}),
|
|
46516
|
+
annotations: { readOnlyHint: true },
|
|
46517
|
+
async handler(_ctx, input) {
|
|
46518
|
+
let list5 = presetCatalog.slice();
|
|
46519
|
+
if (input.category) list5 = list5.filter((p) => p.category === input.category);
|
|
46520
|
+
if (input.query) {
|
|
46521
|
+
const words4 = input.query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
46522
|
+
list5 = list5.filter((p) => {
|
|
46523
|
+
const hay = `${p.id} ${p.label} ${p.description} ${p.tags.join(" ")} ${p.ai.whenToUse}`.toLowerCase();
|
|
46524
|
+
return words4.every((w) => hay.includes(w));
|
|
46525
|
+
});
|
|
46526
|
+
}
|
|
46527
|
+
const full = input.detail === "full";
|
|
46528
|
+
return {
|
|
46529
|
+
data: {
|
|
46530
|
+
presets: list5.map(
|
|
46531
|
+
(p) => full ? p : {
|
|
46532
|
+
id: p.id,
|
|
46533
|
+
label: p.label,
|
|
46534
|
+
description: p.description,
|
|
46535
|
+
target: p.target,
|
|
46536
|
+
layout: p.layout,
|
|
46537
|
+
overlap: p.overlap,
|
|
46538
|
+
themes: p.themes,
|
|
46539
|
+
whenToUse: p.ai.whenToUse
|
|
46540
|
+
}
|
|
46541
|
+
),
|
|
46542
|
+
categories: PRESET_CATEGORIES
|
|
46543
|
+
}
|
|
46544
|
+
};
|
|
46545
|
+
}
|
|
46546
|
+
});
|
|
46547
|
+
function findPreset(id) {
|
|
46548
|
+
const exact = getPreset(id);
|
|
46549
|
+
if (exact) return exact;
|
|
46550
|
+
const base = id.replace(/@\d+$/, "");
|
|
46551
|
+
const matches = presetCatalog.filter((p) => p.id.startsWith(`${base}@`)).sort((a, b) => b.version - a.version);
|
|
46552
|
+
const best = matches[0] && getPreset(matches[0].id);
|
|
46553
|
+
if (best) return best;
|
|
46554
|
+
const category = base.split("/")[0] ?? "";
|
|
46555
|
+
const near = presetCatalog.filter((p) => p.category === category).map((p) => p.id);
|
|
46556
|
+
throw new ToolError(
|
|
46557
|
+
`Unknown preset "${id}".${near.length ? ` In ${category}: ${near.join(", ")}.` : " Call list_section_presets."}`,
|
|
46558
|
+
"not_found"
|
|
46559
|
+
);
|
|
46560
|
+
}
|
|
46561
|
+
|
|
46562
|
+
// ../mcp-tools/src/tools/edit.ts
|
|
46563
|
+
var isRecord2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
46564
|
+
var themeArg = external_exports.enum(COLOR_THEMES).describe("Section color theme.");
|
|
46565
|
+
var blockSummary = (registry3, b) => ({
|
|
46566
|
+
id: b.id,
|
|
46567
|
+
type: b.type,
|
|
46568
|
+
...b.variant ? { variant: b.variant } : {},
|
|
46569
|
+
...b.column !== void 0 ? { column: b.column } : {},
|
|
46570
|
+
text: blockPreview(registry3, b)
|
|
46571
|
+
});
|
|
46572
|
+
function sectionSummary(registry3, s2) {
|
|
46573
|
+
return {
|
|
46574
|
+
id: s2.id,
|
|
46575
|
+
...s2.preset ? { preset: s2.preset } : {},
|
|
46576
|
+
theme: s2.style?.colorTheme ?? "light",
|
|
46577
|
+
layout: s2.layout.mode,
|
|
46578
|
+
blocks: s2.blocks.map((b) => blockSummary(registry3, b))
|
|
46579
|
+
};
|
|
46580
|
+
}
|
|
46581
|
+
function container(doc, ref) {
|
|
46582
|
+
return resolvePage(doc, ref, { allowFooter: true }).id;
|
|
46583
|
+
}
|
|
46584
|
+
function pageOnly(doc, ref) {
|
|
46585
|
+
if (ref === FOOTER)
|
|
46586
|
+
throw new ToolError(
|
|
46587
|
+
"Sections can't be inserted, moved or deleted in the footer; replace it with a footer preset (insert_section with a footer/\u2026 preset) or edit its blocks.",
|
|
46588
|
+
"invalid_arguments"
|
|
46589
|
+
);
|
|
46590
|
+
return resolvePage(doc, ref).id;
|
|
46591
|
+
}
|
|
46592
|
+
function requireSection2(doc, page, section) {
|
|
46593
|
+
const loc = findSection(doc, page, section);
|
|
46594
|
+
if (!loc) {
|
|
46595
|
+
const ids = page === FOOTER ? [doc.site.footer.id] : doc.pages[page]?.sections.map(
|
|
46596
|
+
(s2) => s2.id
|
|
46597
|
+
) ?? [];
|
|
46598
|
+
throw new ToolError(
|
|
46599
|
+
`No section ${section} on ${page}. Sections there: ${ids.join(", ") || "none"}.`,
|
|
46600
|
+
"not_found"
|
|
46601
|
+
);
|
|
46602
|
+
}
|
|
46603
|
+
return loc.section;
|
|
46604
|
+
}
|
|
46605
|
+
function requireBlock(doc, page, block4) {
|
|
46606
|
+
const loc = findBlock(doc, page, block4);
|
|
46607
|
+
if (loc) return { block: loc.block, section: loc.section };
|
|
46608
|
+
const elsewhere = locateBlock(doc, block4);
|
|
46609
|
+
throw new ToolError(
|
|
46610
|
+
elsewhere ? `Block ${block4} is on ${elsewhere.container}, not ${page}.` : `No block ${block4}. Call get_page to see block ids.`,
|
|
46611
|
+
"not_found"
|
|
46612
|
+
);
|
|
46613
|
+
}
|
|
46614
|
+
function makeBlock2(registry3, input) {
|
|
46615
|
+
const def = registry3.get(input.type);
|
|
46616
|
+
if (!def)
|
|
46617
|
+
throw new ToolError(
|
|
46618
|
+
`Unknown block type "${input.type}". Types: ${registry3.types().join(", ")}.`,
|
|
46619
|
+
"invalid_arguments"
|
|
46620
|
+
);
|
|
46621
|
+
const fields = {};
|
|
46622
|
+
for (const [k, m] of Object.entries(def.meta.fields))
|
|
46623
|
+
if (!m.optional && m.default !== void 0) fields[k] = structuredClone(m.default);
|
|
46624
|
+
Object.assign(fields, fieldsFromAgent(registry3, input.type, input.fields ?? {}));
|
|
46625
|
+
const { type, fields: _f, id, v: _v, ...rest } = input;
|
|
46626
|
+
return {
|
|
46627
|
+
...rest,
|
|
46628
|
+
id: id ?? newId("blk"),
|
|
46629
|
+
type,
|
|
46630
|
+
v: def.version,
|
|
46631
|
+
fields
|
|
46632
|
+
};
|
|
46633
|
+
}
|
|
46634
|
+
function makeSection(registry3, raw2) {
|
|
46635
|
+
const blocks2 = Array.isArray(raw2.blocks) ? raw2.blocks : [];
|
|
46636
|
+
return {
|
|
46637
|
+
...raw2,
|
|
46638
|
+
id: typeof raw2.id === "string" ? raw2.id : newId("sec"),
|
|
46639
|
+
layout: raw2.layout ?? { mode: "stack", columns: { d: [12] } },
|
|
46640
|
+
style: isRecord2(raw2.style) ? raw2.style : { colorTheme: "light" },
|
|
46641
|
+
blocks: blocks2.map((b, i) => {
|
|
46642
|
+
if (!isRecord2(b) || typeof b.type !== "string")
|
|
46643
|
+
throw new ToolError(`section.blocks[${i}] needs a "type".`, "invalid_arguments");
|
|
46644
|
+
return makeBlock2(registry3, b);
|
|
46645
|
+
})
|
|
46646
|
+
};
|
|
46647
|
+
}
|
|
46648
|
+
function takenAnchor(doc, page, anchor) {
|
|
46649
|
+
const other = doc.pages[page]?.sections.find(
|
|
46650
|
+
(s2) => s2.anchor === anchor
|
|
46651
|
+
);
|
|
46652
|
+
if (other)
|
|
46653
|
+
throw new ToolError(
|
|
46654
|
+
`Another section on this page is already called \u201C${anchor}\u201D (${other.id}).`,
|
|
46655
|
+
"invalid_arguments",
|
|
46656
|
+
[],
|
|
46657
|
+
"Pick another anchor, or link to that section."
|
|
46658
|
+
);
|
|
46659
|
+
return anchor;
|
|
46660
|
+
}
|
|
46661
|
+
var insertSection = defineTool({
|
|
46662
|
+
name: "insert_section",
|
|
46663
|
+
title: "Insert section",
|
|
46664
|
+
description: "Add a section to a page from a preset id (preferred \u2014 see list_section_presets) or an explicit section. Preset sections arrive with designed demo copy: replace it with the owner's real content using update_block. Header presets restyle the site header and footer presets replace the site footer (page is ignored for those). Returns the new section and block ids.",
|
|
46665
|
+
input: external_exports.object({
|
|
46666
|
+
site: siteArg,
|
|
46667
|
+
page: external_exports.string().optional().describe("Page id/slug/title (not needed for header/footer presets)."),
|
|
46668
|
+
preset: external_exports.string().optional().describe('Preset id, e.g. "hero/split-image@1" (version optional).'),
|
|
46669
|
+
section: external_exports.looseObject({}).optional().describe(
|
|
46670
|
+
"Explicit section ({layout, style, blocks:[{type, fields, variant?, column?}]}); ids are generated when omitted; rich text as Markdown."
|
|
46671
|
+
),
|
|
46672
|
+
at: positionArg.optional(),
|
|
46673
|
+
colorTheme: themeArg.optional(),
|
|
46674
|
+
primaryLink: external_exports.unknown().optional().describe(
|
|
46675
|
+
"Link for the main call to action ({type:'page', page:'pg_\u2026'} or {type:'url', url})."
|
|
46676
|
+
),
|
|
46677
|
+
secondaryLink: external_exports.unknown().optional(),
|
|
46678
|
+
collection: external_exports.string().optional().describe("Collection for summary/events/product presets."),
|
|
46679
|
+
label: external_exports.string().max(80).optional().describe("Layers label for the section."),
|
|
46680
|
+
anchor: anchorArg.optional()
|
|
46681
|
+
}),
|
|
46682
|
+
annotations: {},
|
|
46683
|
+
async handler(ctx, input) {
|
|
46684
|
+
const siteId = await ctx.siteId(input.site);
|
|
46685
|
+
const doc = await ctx.doc(siteId);
|
|
46686
|
+
if (!!input.preset === !!input.section)
|
|
46687
|
+
throw new ToolError("Give exactly one of preset or section.", "invalid_arguments");
|
|
46688
|
+
const links = {};
|
|
46689
|
+
for (const k of ["primaryLink", "secondaryLink"]) {
|
|
46690
|
+
if (input[k] === void 0) continue;
|
|
46691
|
+
const r = LinkSchema.safeParse(input[k]);
|
|
46692
|
+
if (!r.success)
|
|
46693
|
+
throw new ToolError(
|
|
46694
|
+
`${k} is not a valid link: ${r.error.issues.map((i) => i.message).join("; ")}`,
|
|
46695
|
+
"invalid_arguments"
|
|
46696
|
+
);
|
|
46697
|
+
links[k] = r.data;
|
|
46698
|
+
}
|
|
46699
|
+
if (input.preset) {
|
|
46700
|
+
const preset = findPreset(input.preset);
|
|
46701
|
+
const opts = { colorTheme: input.colorTheme, collection: input.collection, ...links };
|
|
46702
|
+
const linkNote = (changes2) => changes2.length ? {
|
|
46703
|
+
linksChanged: changes2.map((c) => ({ from: c.from, to: c.to })),
|
|
46704
|
+
linkNote: "Example links that lead nowhere on this site were pointed at existing pages; change them with update_block."
|
|
46705
|
+
} : {};
|
|
46706
|
+
if (preset.target === "header") {
|
|
46707
|
+
const { value: header, changes: changes2 } = resolveInternalLinks(preset.build(opts), doc);
|
|
46708
|
+
await ctx.apply(siteId, [{ op: "site.update", set: { header } }]);
|
|
46709
|
+
return {
|
|
46710
|
+
data: {
|
|
46711
|
+
applied: preset.id,
|
|
46712
|
+
header,
|
|
46713
|
+
note: "The site header now uses this preset.",
|
|
46714
|
+
...linkNote(changes2)
|
|
46715
|
+
}
|
|
46716
|
+
};
|
|
46717
|
+
}
|
|
46718
|
+
const { value: section2, changes } = resolveInternalLinks(preset.build(opts), doc);
|
|
46719
|
+
if (input.label) section2.label = input.label;
|
|
46720
|
+
if (input.anchor && preset.target !== "footer" && input.page)
|
|
46721
|
+
section2.anchor = takenAnchor(doc, pageOnly(doc, input.page), input.anchor);
|
|
46722
|
+
if (preset.target === "footer") {
|
|
46723
|
+
await ctx.apply(siteId, [{ op: "site.update", set: { footer: section2 } }]);
|
|
46724
|
+
return {
|
|
46725
|
+
data: {
|
|
46726
|
+
replaced: "footer",
|
|
46727
|
+
section: sectionSummary(ctx.registry, section2),
|
|
46728
|
+
note: 'Edit footer blocks with page "footer".',
|
|
46729
|
+
...linkNote(changes)
|
|
46730
|
+
}
|
|
46731
|
+
};
|
|
46732
|
+
}
|
|
46733
|
+
if (!input.page)
|
|
46734
|
+
throw new ToolError("page is required for page sections.", "invalid_arguments");
|
|
46735
|
+
const page2 = pageOnly(doc, input.page);
|
|
46736
|
+
await ctx.apply(siteId, [
|
|
46737
|
+
{ op: "section.insert", page: page2, at: input.at ?? "end", section: section2 }
|
|
46738
|
+
]);
|
|
46739
|
+
return {
|
|
46740
|
+
data: {
|
|
46741
|
+
page: page2,
|
|
46742
|
+
section: sectionSummary(ctx.registry, section2),
|
|
46743
|
+
copyGuidance: preset.copy.map((c) => `${c.label}: ${c.words[0]}\u2013${c.words[1]} words`),
|
|
46744
|
+
next: "Replace the demo copy with the owner's content (update_block), then run design_check.",
|
|
46745
|
+
...linkNote(changes)
|
|
46746
|
+
}
|
|
46747
|
+
};
|
|
46748
|
+
}
|
|
46749
|
+
if (!input.page) throw new ToolError("page is required.", "invalid_arguments");
|
|
46750
|
+
const page = pageOnly(doc, input.page);
|
|
46751
|
+
const section = makeSection(ctx.registry, input.section);
|
|
46752
|
+
if (input.colorTheme)
|
|
46753
|
+
section.style = { ...section.style ?? {}, colorTheme: input.colorTheme };
|
|
46754
|
+
if (input.label) section.label = input.label;
|
|
46755
|
+
if (input.anchor) section.anchor = takenAnchor(doc, page, input.anchor);
|
|
46756
|
+
await ctx.apply(siteId, [
|
|
46757
|
+
{ op: "section.insert", page, at: input.at ?? "end", section }
|
|
46758
|
+
]);
|
|
46759
|
+
return { data: { page, section: sectionSummary(ctx.registry, section) } };
|
|
46760
|
+
}
|
|
46761
|
+
});
|
|
46762
|
+
var updateSection = defineTool({
|
|
46763
|
+
name: "update_section",
|
|
46764
|
+
title: "Update section",
|
|
46765
|
+
description: `Change a section's settings with dot-path patches, e.g. set {"style.colorTheme": "dark", "style.padding": {"d": "xl", "m": "lg"}, "label": "Intro", "anchor": "classes", "hidden": true, "layout": {...}}. Shortcuts: colorTheme. Blocks are edited with the block tools.`,
|
|
46766
|
+
input: external_exports.object({
|
|
46767
|
+
site: siteArg,
|
|
46768
|
+
page: containerArg,
|
|
46769
|
+
section: external_exports.string(),
|
|
46770
|
+
set: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
46771
|
+
unset: external_exports.array(external_exports.string()).optional(),
|
|
46772
|
+
colorTheme: themeArg.optional()
|
|
46773
|
+
}),
|
|
46774
|
+
annotations: { idempotentHint: true },
|
|
46775
|
+
async handler(ctx, input) {
|
|
46776
|
+
const siteId = await ctx.siteId(input.site);
|
|
46777
|
+
const doc = await ctx.doc(siteId);
|
|
46778
|
+
const page = container(doc, input.page);
|
|
46779
|
+
requireSection2(doc, page, input.section);
|
|
46780
|
+
const set2 = { ...input.set ?? {} };
|
|
46781
|
+
if (input.colorTheme) set2["style.colorTheme"] = input.colorTheme;
|
|
46782
|
+
const body = { op: "section.update", page, section: input.section };
|
|
46783
|
+
if (Object.keys(set2).length) body.set = set2;
|
|
46784
|
+
if (input.unset?.length) body.unset = input.unset;
|
|
46785
|
+
const { doc: next } = await ctx.apply(siteId, [body]);
|
|
46786
|
+
return {
|
|
46787
|
+
data: { section: sectionSummary(ctx.registry, requireSection2(next, page, input.section)) }
|
|
46788
|
+
};
|
|
46789
|
+
}
|
|
46790
|
+
});
|
|
46791
|
+
var moveSection = defineTool({
|
|
46792
|
+
name: "move_section",
|
|
46793
|
+
title: "Move section",
|
|
46794
|
+
description: "Reorder a section within its page.",
|
|
46795
|
+
input: external_exports.object({ site: siteArg, page: external_exports.string(), section: external_exports.string(), at: positionArg }),
|
|
46796
|
+
annotations: { idempotentHint: true },
|
|
46797
|
+
async handler(ctx, input) {
|
|
46798
|
+
const siteId = await ctx.siteId(input.site);
|
|
46799
|
+
const doc = await ctx.doc(siteId);
|
|
46800
|
+
const page = pageOnly(doc, input.page);
|
|
46801
|
+
requireSection2(doc, page, input.section);
|
|
46802
|
+
const { doc: next } = await ctx.apply(siteId, [
|
|
46803
|
+
{ op: "section.move", page, section: input.section, at: input.at }
|
|
46804
|
+
]);
|
|
46805
|
+
return { data: { order: next.pages[page]?.sections.map((s2) => s2.id) } };
|
|
46806
|
+
}
|
|
46807
|
+
});
|
|
46808
|
+
var deleteSection = defineTool({
|
|
46809
|
+
name: "delete_section",
|
|
46810
|
+
title: "Delete section",
|
|
46811
|
+
description: "Remove a section and its blocks from a page (recoverable from the session snapshot or discard_draft).",
|
|
46812
|
+
input: external_exports.object({ site: siteArg, page: external_exports.string(), section: external_exports.string() }),
|
|
46813
|
+
annotations: { destructiveHint: true },
|
|
46814
|
+
async handler(ctx, input) {
|
|
46815
|
+
const siteId = await ctx.siteId(input.site);
|
|
46816
|
+
const doc = await ctx.doc(siteId);
|
|
46817
|
+
const page = pageOnly(doc, input.page);
|
|
46818
|
+
requireSection2(doc, page, input.section);
|
|
46819
|
+
await ctx.apply(siteId, [{ op: "section.delete", page, section: input.section }]);
|
|
46820
|
+
return { data: { deleted: input.section } };
|
|
46821
|
+
}
|
|
46822
|
+
});
|
|
46823
|
+
var gridItemArg = external_exports.looseObject({}).describe(
|
|
46824
|
+
"Grid placement {d:{x,y,w,h,z?,bleed?}, t?, m?}: 24 columns on desktop/tablet, 8 on mobile, 1-based."
|
|
46825
|
+
);
|
|
46826
|
+
var insertBlock = defineTool({
|
|
46827
|
+
name: "insert_block",
|
|
46828
|
+
title: "Insert block",
|
|
46829
|
+
description: "Add a block to a section. Fields are validated against the block type (see list_blocks); rich text as Markdown. In stack sections pick a column (0-based); in grid sections give place (auto-placed at the bottom otherwise) and then check the mobile layout (auto_mobile_layout).",
|
|
46830
|
+
input: external_exports.object({
|
|
46831
|
+
site: siteArg,
|
|
46832
|
+
page: containerArg,
|
|
46833
|
+
section: external_exports.string(),
|
|
46834
|
+
block: external_exports.looseObject({
|
|
46835
|
+
type: external_exports.string(),
|
|
46836
|
+
fields: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
46837
|
+
variant: external_exports.string().optional(),
|
|
46838
|
+
style: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
46839
|
+
column: external_exports.int().min(0).max(3).optional(),
|
|
46840
|
+
label: external_exports.string().optional()
|
|
46841
|
+
}).describe("{type, fields, variant?, style?, column?, label?}"),
|
|
46842
|
+
at: positionArg.optional(),
|
|
46843
|
+
place: gridItemArg.optional()
|
|
46844
|
+
}),
|
|
46845
|
+
annotations: {},
|
|
46846
|
+
async handler(ctx, input) {
|
|
46847
|
+
const siteId = await ctx.siteId(input.site);
|
|
46848
|
+
const doc = await ctx.doc(siteId);
|
|
46849
|
+
const page = container(doc, input.page);
|
|
46850
|
+
requireSection2(doc, page, input.section);
|
|
46851
|
+
const made = makeBlock2(ctx.registryFor(doc), input.block);
|
|
46852
|
+
const block4 = resolveInternalLinks(made, doc).value;
|
|
46853
|
+
for (const k of Object.keys(input.block.fields ?? {}))
|
|
46854
|
+
if (k in made.fields) block4.fields[k] = made.fields[k];
|
|
46855
|
+
const body = {
|
|
46856
|
+
op: "block.insert",
|
|
46857
|
+
page,
|
|
46858
|
+
section: input.section,
|
|
46859
|
+
at: input.at ?? "end",
|
|
46860
|
+
block: block4
|
|
46861
|
+
};
|
|
46862
|
+
if (input.place) body.place = input.place;
|
|
46863
|
+
const { doc: next, warnings } = await ctx.apply(siteId, [body]);
|
|
46864
|
+
const placed = findBlock(next, page, block4.id);
|
|
46865
|
+
return {
|
|
46866
|
+
data: {
|
|
46867
|
+
block: blockSummary(ctx.registryFor(next), placed?.block ?? block4),
|
|
46868
|
+
...warnings.length ? { warnings } : {}
|
|
46869
|
+
}
|
|
46870
|
+
};
|
|
46871
|
+
}
|
|
46872
|
+
});
|
|
46873
|
+
var updateBlock = defineTool({
|
|
46874
|
+
name: "update_block",
|
|
46875
|
+
title: "Update block",
|
|
46876
|
+
description: "Edit a block: set replaces whole top-level fields (rich text as Markdown; lists are replaced whole), unset removes optional fields; variant/style/column/label/hidden adjust presentation (null removes). Ids are preserved.",
|
|
46877
|
+
input: external_exports.object({
|
|
46878
|
+
site: siteArg,
|
|
46879
|
+
page: containerArg,
|
|
46880
|
+
block: external_exports.string(),
|
|
46881
|
+
set: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
46882
|
+
unset: external_exports.array(external_exports.string()).optional(),
|
|
46883
|
+
variant: external_exports.string().nullable().optional(),
|
|
46884
|
+
style: external_exports.record(external_exports.string(), external_exports.unknown()).nullable().optional(),
|
|
46885
|
+
column: external_exports.int().min(0).max(3).nullable().optional(),
|
|
46886
|
+
label: external_exports.string().max(80).nullable().optional(),
|
|
46887
|
+
hidden: external_exports.boolean().nullable().optional()
|
|
46888
|
+
}),
|
|
46889
|
+
annotations: { idempotentHint: true },
|
|
46890
|
+
async handler(ctx, input) {
|
|
46891
|
+
const siteId = await ctx.siteId(input.site);
|
|
46892
|
+
const doc = await ctx.doc(siteId);
|
|
46893
|
+
const page = container(doc, input.page);
|
|
46894
|
+
const { block: block4 } = requireBlock(doc, page, input.block);
|
|
46895
|
+
const body = { op: "block.update", page, block: input.block };
|
|
46896
|
+
if (input.set) body.set = fieldsFromAgent(ctx.registryFor(doc), block4.type, input.set);
|
|
46897
|
+
if (input.unset?.length) body.unset = input.unset;
|
|
46898
|
+
for (const k of ["variant", "style", "column", "label", "hidden"])
|
|
46899
|
+
if (input[k] !== void 0) body[k] = input[k];
|
|
46900
|
+
if (Object.keys(body).length === 3)
|
|
46901
|
+
throw new ToolError(
|
|
46902
|
+
"Nothing to change: give set, unset, variant, style, column, label or hidden.",
|
|
46903
|
+
"invalid_arguments"
|
|
46904
|
+
);
|
|
46905
|
+
const { doc: next } = await ctx.apply(siteId, [body]);
|
|
46906
|
+
const updated = findBlock(next, page, input.block)?.block ?? block4;
|
|
46907
|
+
return { data: { block: blockSummary(ctx.registryFor(next), updated) } };
|
|
46908
|
+
}
|
|
46909
|
+
});
|
|
46910
|
+
var moveBlock = defineTool({
|
|
46911
|
+
name: "move_block",
|
|
46912
|
+
title: "Move block",
|
|
46913
|
+
description: "Move a block within its page (to another section and/or position). Give column for stack sections and place for grid sections.",
|
|
46914
|
+
input: external_exports.object({
|
|
46915
|
+
site: siteArg,
|
|
46916
|
+
page: containerArg,
|
|
46917
|
+
block: external_exports.string(),
|
|
46918
|
+
to: external_exports.object({
|
|
46919
|
+
section: external_exports.string(),
|
|
46920
|
+
at: positionArg.optional(),
|
|
46921
|
+
column: external_exports.int().min(0).max(3).nullable().optional(),
|
|
46922
|
+
place: gridItemArg.optional()
|
|
46923
|
+
})
|
|
46924
|
+
}),
|
|
46925
|
+
annotations: { idempotentHint: true },
|
|
46926
|
+
async handler(ctx, input) {
|
|
46927
|
+
const siteId = await ctx.siteId(input.site);
|
|
46928
|
+
const doc = await ctx.doc(siteId);
|
|
46929
|
+
const page = container(doc, input.page);
|
|
46930
|
+
requireBlock(doc, page, input.block);
|
|
46931
|
+
requireSection2(doc, page, input.to.section);
|
|
46932
|
+
const to = { section: input.to.section, at: input.to.at ?? "end" };
|
|
46933
|
+
if (input.to.column !== void 0) to.column = input.to.column;
|
|
46934
|
+
if (input.to.place) to.place = input.to.place;
|
|
46935
|
+
await ctx.apply(siteId, [{ op: "block.move", page, block: input.block, to }]);
|
|
46936
|
+
return { data: { moved: input.block, to } };
|
|
46937
|
+
}
|
|
46938
|
+
});
|
|
46939
|
+
var deleteBlock = defineTool({
|
|
46940
|
+
name: "delete_block",
|
|
46941
|
+
title: "Delete block",
|
|
46942
|
+
description: "Remove a block from its section (recoverable via the session snapshot or discard_draft).",
|
|
46943
|
+
input: external_exports.object({ site: siteArg, page: containerArg, block: external_exports.string() }),
|
|
46944
|
+
annotations: { destructiveHint: true },
|
|
46945
|
+
async handler(ctx, input) {
|
|
46946
|
+
const siteId = await ctx.siteId(input.site);
|
|
46947
|
+
const doc = await ctx.doc(siteId);
|
|
46948
|
+
const page = container(doc, input.page);
|
|
46949
|
+
requireBlock(doc, page, input.block);
|
|
46950
|
+
await ctx.apply(siteId, [{ op: "block.delete", page, block: input.block }]);
|
|
46951
|
+
return { data: { deleted: input.block } };
|
|
46952
|
+
}
|
|
46953
|
+
});
|
|
46954
|
+
var rectArg = external_exports.looseObject({}).describe(
|
|
46955
|
+
"{x, y, w, h, z?, bleed?: 'left'|'right'|'both'} (1-based; 24 columns desktop/tablet, 8 mobile)"
|
|
46956
|
+
);
|
|
46957
|
+
var placeInGrid2 = defineTool({
|
|
46958
|
+
name: "place_in_grid",
|
|
46959
|
+
title: "Place in grid",
|
|
46960
|
+
description: "Set a block's grid placement per breakpoint in a grid section. d = desktop (required on first placement), t = tablet override (null removes), m = mobile (null removes; regenerate with auto_mobile_layout). Rows grow automatically. Overlap is allowed (use z) but text must not overlap text.",
|
|
46961
|
+
input: external_exports.object({
|
|
46962
|
+
site: siteArg,
|
|
46963
|
+
page: containerArg,
|
|
46964
|
+
section: external_exports.string(),
|
|
46965
|
+
block: external_exports.string(),
|
|
46966
|
+
d: rectArg.optional(),
|
|
46967
|
+
t: rectArg.nullable().optional(),
|
|
46968
|
+
m: rectArg.nullable().optional()
|
|
46969
|
+
}),
|
|
46970
|
+
annotations: { idempotentHint: true },
|
|
46971
|
+
async handler(ctx, input) {
|
|
46972
|
+
const siteId = await ctx.siteId(input.site);
|
|
46973
|
+
const doc = await ctx.doc(siteId);
|
|
46974
|
+
const page = container(doc, input.page);
|
|
46975
|
+
const section = requireSection2(doc, page, input.section);
|
|
46976
|
+
if (section.layout.mode !== "grid")
|
|
46977
|
+
throw new ToolError(
|
|
46978
|
+
`Section ${section.id} is a stack section; set the block's column with update_block instead.`,
|
|
46979
|
+
"invalid_arguments"
|
|
46980
|
+
);
|
|
46981
|
+
const bodies = [];
|
|
46982
|
+
for (const bp of ["d", "t", "m"]) {
|
|
46983
|
+
const rect = input[bp];
|
|
46984
|
+
if (rect === void 0) continue;
|
|
46985
|
+
bodies.push({
|
|
46986
|
+
op: "grid.place",
|
|
46987
|
+
page,
|
|
46988
|
+
section: section.id,
|
|
46989
|
+
block: input.block,
|
|
46990
|
+
bp,
|
|
46991
|
+
rect
|
|
46992
|
+
});
|
|
46993
|
+
}
|
|
46994
|
+
if (!bodies.length) throw new ToolError("Give at least one of d, t or m.", "invalid_arguments");
|
|
46995
|
+
const { doc: next } = await ctx.apply(siteId, bodies);
|
|
46996
|
+
const after = requireSection2(next, page, section.id);
|
|
46997
|
+
return {
|
|
46998
|
+
data: {
|
|
46999
|
+
placement: after.layout.mode === "grid" ? after.layout.items[input.block] : null,
|
|
47000
|
+
rows: after.layout.mode === "grid" ? after.layout.rows : null,
|
|
47001
|
+
next: input.d && input.m === void 0 ? "Desktop changed: review mobile (auto_mobile_layout or screenshot device mobile)." : void 0
|
|
47002
|
+
}
|
|
47003
|
+
};
|
|
47004
|
+
}
|
|
47005
|
+
});
|
|
47006
|
+
var autoMobileLayout2 = defineTool({
|
|
47007
|
+
name: "auto_mobile_layout",
|
|
47008
|
+
title: "Auto mobile layout",
|
|
47009
|
+
description: "Generate mobile grid placements for a grid section by visual reading order (top-to-bottom, left-to-right on the desktop grid, layered items kept on their host). Keeps existing mobile placements unless reset is true.",
|
|
47010
|
+
input: external_exports.object({
|
|
47011
|
+
site: siteArg,
|
|
47012
|
+
page: containerArg,
|
|
47013
|
+
section: external_exports.string(),
|
|
47014
|
+
reset: external_exports.boolean().optional()
|
|
47015
|
+
}),
|
|
47016
|
+
annotations: { idempotentHint: true },
|
|
47017
|
+
async handler(ctx, input) {
|
|
47018
|
+
const siteId = await ctx.siteId(input.site);
|
|
47019
|
+
const doc = await ctx.doc(siteId);
|
|
47020
|
+
const page = container(doc, input.page);
|
|
47021
|
+
const section = requireSection2(doc, page, input.section);
|
|
47022
|
+
if (section.layout.mode !== "grid")
|
|
47023
|
+
return { data: { changed: 0, note: "Stack sections stack automatically on mobile." } };
|
|
47024
|
+
const ops = autoMobileLayoutOps(doc, page, section.id, { reset: input.reset });
|
|
47025
|
+
if (!ops.length)
|
|
47026
|
+
return { data: { changed: 0, note: "Mobile placements are already complete." } };
|
|
47027
|
+
const bodies = ops.map(({ id: _i, actor: _a3, ts: _t, ...b }) => b);
|
|
47028
|
+
const { doc: next } = await ctx.apply(siteId, bodies);
|
|
47029
|
+
const after = requireSection2(next, page, section.id);
|
|
47030
|
+
return {
|
|
47031
|
+
data: {
|
|
47032
|
+
changed: ops.length,
|
|
47033
|
+
mobile: after.layout.mode === "grid" ? Object.fromEntries(Object.entries(after.layout.items).map(([id, it]) => [id, it.m])) : null
|
|
47034
|
+
}
|
|
47035
|
+
};
|
|
47036
|
+
}
|
|
47037
|
+
});
|
|
47038
|
+
var editTools = [
|
|
47039
|
+
insertSection,
|
|
47040
|
+
updateSection,
|
|
47041
|
+
moveSection,
|
|
47042
|
+
deleteSection,
|
|
47043
|
+
insertBlock,
|
|
47044
|
+
updateBlock,
|
|
47045
|
+
moveBlock,
|
|
47046
|
+
deleteBlock,
|
|
47047
|
+
placeInGrid2,
|
|
47048
|
+
autoMobileLayout2
|
|
47049
|
+
];
|
|
47050
|
+
|
|
46031
47051
|
// ../mcp-tools/src/tools/components.ts
|
|
46032
47052
|
var nameArg = external_exports.string().describe('Component name, lowercase-hyphenated (e.g. "editorial-hero").');
|
|
46033
47053
|
var GUIDE = "Prefer section presets. Use a component for a bespoke moment presets can't express. Always expose every word, image and link as a field (owners edit fields, never code), always style with theme tokens (var(--os-color-\u2026), var(--os-font-\u2026), var(--os-space-\u2026)), use the 767/768/1199/1200 breakpoints and honour reduced motion.";
|
|
@@ -46330,13 +47350,14 @@ ${notes.join("\n")}` : ""}`,
|
|
|
46330
47350
|
var insertComponent = defineTool({
|
|
46331
47351
|
name: "insert_component",
|
|
46332
47352
|
title: "Insert component",
|
|
46333
|
-
description: "Add a section using a custom component to a page (a site component, or a Designer components library name \u2014 it is added to the site first). fields override the sample content (rich text as Markdown); replace sample copy and photos with the owner's real content. Returns the section and block ids (edit later with update_block).",
|
|
47353
|
+
description: "Add a section using a custom component to a page (a site component, or a Designer components library name \u2014 it is added to the site first). fields override the sample content (rich text as Markdown); replace sample copy and photos with the owner's real content. Give it an anchor when the menu or a button should jump to it. Returns the section and block ids (edit later with update_block).",
|
|
46334
47354
|
input: external_exports.object({
|
|
46335
47355
|
site: siteArg,
|
|
46336
47356
|
page: external_exports.string().describe("Page id/slug/title."),
|
|
46337
47357
|
name: nameArg,
|
|
46338
47358
|
fields: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
46339
47359
|
theme: external_exports.enum(COLOR_THEMES).optional(),
|
|
47360
|
+
anchor: anchorArg.optional(),
|
|
46340
47361
|
at: positionArg.optional()
|
|
46341
47362
|
}),
|
|
46342
47363
|
annotations: {},
|
|
@@ -46361,6 +47382,7 @@ var insertComponent = defineTool({
|
|
|
46361
47382
|
).value;
|
|
46362
47383
|
const block4 = section.blocks[0];
|
|
46363
47384
|
block4.fields = { ...block4.fields, ...given };
|
|
47385
|
+
if (input.anchor) section.anchor = takenAnchor(doc, page, input.anchor);
|
|
46364
47386
|
bodies.push({ op: "section.insert", page, at: input.at ?? "end", section });
|
|
46365
47387
|
const { doc: next } = await ctx.apply(siteId, bodies);
|
|
46366
47388
|
const target = next.pages[page];
|
|
@@ -46691,17 +47713,17 @@ function builtInTemplates() {
|
|
|
46691
47713
|
};
|
|
46692
47714
|
});
|
|
46693
47715
|
}
|
|
46694
|
-
var
|
|
47716
|
+
var isRecord3 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
46695
47717
|
function normaliseTemplate(raw2) {
|
|
46696
|
-
if (!
|
|
46697
|
-
const meta =
|
|
47718
|
+
if (!isRecord3(raw2)) return null;
|
|
47719
|
+
const meta = isRecord3(raw2.template) ? { ...raw2, ...raw2.template } : raw2;
|
|
46698
47720
|
const id = typeof meta.id === "string" ? meta.id : null;
|
|
46699
47721
|
if (!id) return null;
|
|
46700
47722
|
const rawLooks = Array.isArray(meta.looks) ? meta.looks : [];
|
|
46701
47723
|
const looks = [];
|
|
46702
47724
|
rawLooks.forEach((l, i) => {
|
|
46703
|
-
if (!
|
|
46704
|
-
const candidate =
|
|
47725
|
+
if (!isRecord3(l)) return;
|
|
47726
|
+
const candidate = isRecord3(l.choices) ? l.choices : isRecord3(l.styles) && isRecord3(l.styles.choices) ? l.styles.choices : l;
|
|
46705
47727
|
const parsed = StyleChoicesSchema.safeParse(candidate);
|
|
46706
47728
|
if (!parsed.success) return;
|
|
46707
47729
|
const name = typeof l.name === "string" ? l.name : `Look ${i + 1}`;
|
|
@@ -46727,12 +47749,12 @@ async function templatesFromModule(mod) {
|
|
|
46727
47749
|
let raws = null;
|
|
46728
47750
|
const cat = mod.templateCatalog;
|
|
46729
47751
|
if (Array.isArray(cat)) raws = cat;
|
|
46730
|
-
else if (
|
|
47752
|
+
else if (isRecord3(cat) && typeof cat.list === "function")
|
|
46731
47753
|
raws = await cat.list();
|
|
46732
47754
|
else if (typeof mod.listTemplates === "function")
|
|
46733
47755
|
raws = await mod.listTemplates();
|
|
46734
47756
|
else if (Array.isArray(mod.templates)) raws = mod.templates;
|
|
46735
|
-
else if (
|
|
47757
|
+
else if (isRecord3(mod.templates)) raws = Object.values(mod.templates);
|
|
46736
47758
|
else if (typeof mod.loadTemplate === "function") {
|
|
46737
47759
|
const ids = mod.TEMPLATE_IDS ?? mod.templateIds;
|
|
46738
47760
|
if (Array.isArray(ids))
|
|
@@ -46757,312 +47779,6 @@ async function loadTemplates(store, importer = (spec) => import(
|
|
|
46757
47779
|
return { source: "built-in", templates: builtInTemplates() };
|
|
46758
47780
|
}
|
|
46759
47781
|
|
|
46760
|
-
// ../mcp-tools/src/tools/read.ts
|
|
46761
|
-
var listSites = defineTool({
|
|
46762
|
-
name: "list_sites",
|
|
46763
|
-
title: "List sites",
|
|
46764
|
-
description: "Sites this connection can edit, with their titles and live addresses. Start here when you don't know the site id.",
|
|
46765
|
-
input: external_exports.object({}),
|
|
46766
|
-
annotations: { readOnlyHint: true },
|
|
46767
|
-
async handler(ctx) {
|
|
46768
|
-
const allowed = new Set(await ctx.allowedSites());
|
|
46769
|
-
const sites = (await ctx.store.listSites()).filter((s2) => allowed.has(s2.id));
|
|
46770
|
-
return { data: { sites, canPublish: ctx.grant.publish && ctx.store.capabilities.canPublish } };
|
|
46771
|
-
}
|
|
46772
|
-
});
|
|
46773
|
-
function stylesSummary(doc) {
|
|
46774
|
-
const compiled2 = compileStyles(doc.styles);
|
|
46775
|
-
const pairing = getPairing(doc.styles.choices.pairing);
|
|
46776
|
-
return {
|
|
46777
|
-
choices: doc.styles.choices,
|
|
46778
|
-
pairing: pairing ? { id: pairing.id, heading: pairing.heading.family, body: pairing.body.family } : { id: doc.styles.choices.pairing, unknown: true },
|
|
46779
|
-
overrides: doc.styles.overrides ?? null,
|
|
46780
|
-
brand: compiled2.tokens.palette.brand,
|
|
46781
|
-
themes: Object.fromEntries(
|
|
46782
|
-
Object.entries(compiled2.tokens.themes).map(([theme, c]) => [
|
|
46783
|
-
theme,
|
|
46784
|
-
{
|
|
46785
|
-
background: c.background,
|
|
46786
|
-
text: c.text,
|
|
46787
|
-
heading: c.heading,
|
|
46788
|
-
accent: c.accent,
|
|
46789
|
-
"accent-text": c["accent-text"]
|
|
46790
|
-
}
|
|
46791
|
-
])
|
|
46792
|
-
),
|
|
46793
|
-
contrast: {
|
|
46794
|
-
mode: compiled2.contrast.mode,
|
|
46795
|
-
adjustedBySolver: compiled2.contrast.adjustments.length,
|
|
46796
|
-
failures: compiled2.contrast.failures.length
|
|
46797
|
-
}
|
|
46798
|
-
};
|
|
46799
|
-
}
|
|
46800
|
-
function pageSummary(page) {
|
|
46801
|
-
return {
|
|
46802
|
-
id: page.id,
|
|
46803
|
-
title: page.title,
|
|
46804
|
-
slug: page.slug,
|
|
46805
|
-
path: pagePathOf(page),
|
|
46806
|
-
sections: page.sections.length,
|
|
46807
|
-
hidden: page.hidden ?? false
|
|
46808
|
-
};
|
|
46809
|
-
}
|
|
46810
|
-
var getSite = defineTool({
|
|
46811
|
-
name: "get_site",
|
|
46812
|
-
title: "Get site",
|
|
46813
|
-
description: "Site settings, navigation, header/footer, Site Styles summary, page list and collections. Call before editing to learn page ids and the current look.",
|
|
46814
|
-
input: external_exports.object({ site: siteArg }),
|
|
46815
|
-
annotations: { readOnlyHint: true },
|
|
46816
|
-
async handler(ctx, input) {
|
|
46817
|
-
const siteId = await ctx.siteId(input.site);
|
|
46818
|
-
const doc = await ctx.doc(siteId);
|
|
46819
|
-
const { site: site2 } = doc;
|
|
46820
|
-
return {
|
|
46821
|
-
data: {
|
|
46822
|
-
id: siteId,
|
|
46823
|
-
title: site2.title,
|
|
46824
|
-
tagline: site2.tagline ?? null,
|
|
46825
|
-
template: doc.config.template ?? null,
|
|
46826
|
-
url: doc.config.deploy.customDomain ? `https://${doc.config.deploy.customDomain}` : doc.config.deploy.url,
|
|
46827
|
-
nav: site2.nav,
|
|
46828
|
-
header: site2.header,
|
|
46829
|
-
footer: {
|
|
46830
|
-
sectionId: site2.footer.id,
|
|
46831
|
-
layout: site2.footer.layout.mode,
|
|
46832
|
-
blocks: site2.footer.blocks.map((b) => ({ id: b.id, type: b.type })),
|
|
46833
|
-
note: 'Edit footer blocks with the block tools using page "footer".'
|
|
46834
|
-
},
|
|
46835
|
-
announcement: site2.announcement ?? null,
|
|
46836
|
-
social: site2.social ?? [],
|
|
46837
|
-
seo: site2.seo ?? null,
|
|
46838
|
-
styles: stylesSummary(doc),
|
|
46839
|
-
pages: Object.values(doc.pages).map(pageSummary),
|
|
46840
|
-
collections: Object.entries(doc.collections).map(([name, items]) => ({
|
|
46841
|
-
name,
|
|
46842
|
-
kind: Object.values(items)[0]?.kind ?? doc.collectionConfigs?.[name]?.kind ?? null,
|
|
46843
|
-
items: Object.keys(items).length
|
|
46844
|
-
})),
|
|
46845
|
-
media: Object.keys(doc.media).length,
|
|
46846
|
-
capabilities: {
|
|
46847
|
-
...ctx.store.capabilities,
|
|
46848
|
-
// Same answer as list_sites: publishing needs both the store and this connection.
|
|
46849
|
-
canPublish: ctx.grant.publish && ctx.store.capabilities.canPublish,
|
|
46850
|
-
grantPublish: ctx.grant.publish
|
|
46851
|
-
}
|
|
46852
|
-
}
|
|
46853
|
-
};
|
|
46854
|
-
}
|
|
46855
|
-
});
|
|
46856
|
-
function sectionView(ctx, section, detail, doc) {
|
|
46857
|
-
const registry3 = doc ? ctx.registryFor(doc) : ctx.registry;
|
|
46858
|
-
const blocks2 = section.blocks.map((b) => {
|
|
46859
|
-
if (detail === "outline") {
|
|
46860
|
-
const out = {
|
|
46861
|
-
id: b.id,
|
|
46862
|
-
type: b.type,
|
|
46863
|
-
text: blockPreview(registry3, b)
|
|
46864
|
-
};
|
|
46865
|
-
if (b.variant) out.variant = b.variant;
|
|
46866
|
-
if (b.column !== void 0) out.column = b.column;
|
|
46867
|
-
if (b.hidden) out.hidden = true;
|
|
46868
|
-
return out;
|
|
46869
|
-
}
|
|
46870
|
-
return blockToAgent(registry3, b);
|
|
46871
|
-
});
|
|
46872
|
-
const base = {
|
|
46873
|
-
id: section.id,
|
|
46874
|
-
...section.preset ? { preset: section.preset } : {},
|
|
46875
|
-
...section.label ? { label: section.label } : {},
|
|
46876
|
-
...section.hidden ? { hidden: true } : {},
|
|
46877
|
-
...section.locked ? { locked: true } : {},
|
|
46878
|
-
theme: section.style?.colorTheme ?? "light"
|
|
46879
|
-
};
|
|
46880
|
-
if (detail === "outline") {
|
|
46881
|
-
base.layout = section.layout.mode === "grid" ? "grid" : `stack ${JSON.stringify(section.layout.columns.d)}`;
|
|
46882
|
-
base.blocks = blocks2;
|
|
46883
|
-
return base;
|
|
46884
|
-
}
|
|
46885
|
-
return {
|
|
46886
|
-
...base,
|
|
46887
|
-
layout: section.layout,
|
|
46888
|
-
style: section.style ?? {},
|
|
46889
|
-
...section.anchor ? { anchor: section.anchor } : {},
|
|
46890
|
-
blocks: blocks2
|
|
46891
|
-
};
|
|
46892
|
-
}
|
|
46893
|
-
var getPage2 = defineTool({
|
|
46894
|
-
name: "get_page",
|
|
46895
|
-
title: "Get page",
|
|
46896
|
-
description: `A page's section/block tree with stable ids, fields (rich text as Markdown), layout and style. Use detail "outline" for a compact overview (ids, types, text previews) and "full" before editing fields or grid placements. page "footer" returns the site footer section.`,
|
|
46897
|
-
input: external_exports.object({
|
|
46898
|
-
site: siteArg,
|
|
46899
|
-
page: containerArg,
|
|
46900
|
-
detail: external_exports.enum(["full", "outline"]).optional().describe('Default "full".')
|
|
46901
|
-
}),
|
|
46902
|
-
annotations: { readOnlyHint: true },
|
|
46903
|
-
async handler(ctx, input) {
|
|
46904
|
-
const siteId = await ctx.siteId(input.site);
|
|
46905
|
-
const doc = await ctx.doc(siteId);
|
|
46906
|
-
const detail = input.detail ?? "full";
|
|
46907
|
-
const { id, page } = resolvePage(doc, input.page, { allowFooter: true });
|
|
46908
|
-
if (id === FOOTER || !page)
|
|
46909
|
-
return { data: { page: FOOTER, sections: [sectionView(ctx, doc.site.footer, detail, doc)] } };
|
|
46910
|
-
return {
|
|
46911
|
-
data: {
|
|
46912
|
-
id: page.id,
|
|
46913
|
-
title: page.title,
|
|
46914
|
-
slug: page.slug,
|
|
46915
|
-
path: pagePathOf(page),
|
|
46916
|
-
seo: page.seo ?? {},
|
|
46917
|
-
...page.header ? { header: page.header } : {},
|
|
46918
|
-
...page.hidden ? { hidden: true } : {},
|
|
46919
|
-
sections: page.sections.map((s2) => sectionView(ctx, s2, detail, doc))
|
|
46920
|
-
}
|
|
46921
|
-
};
|
|
46922
|
-
}
|
|
46923
|
-
});
|
|
46924
|
-
function blockMetaView(m, full) {
|
|
46925
|
-
const fields = Object.fromEntries(
|
|
46926
|
-
Object.entries(m.fields).map(([k, f]) => {
|
|
46927
|
-
const v = {
|
|
46928
|
-
kind: f.kind === "richText" ? "richText (Markdown)" : f.kind
|
|
46929
|
-
};
|
|
46930
|
-
if (f.optional) v.optional = true;
|
|
46931
|
-
if (f.label) v.label = f.label;
|
|
46932
|
-
if (full && f.help) v.help = f.help;
|
|
46933
|
-
if (f.ai) v.ai = f.ai;
|
|
46934
|
-
if (f.kind === "select") v.options = f.options.map((o) => o.value);
|
|
46935
|
-
if (f.kind === "text" && f.maxLength) v.maxLength = f.maxLength;
|
|
46936
|
-
if (f.kind === "list")
|
|
46937
|
-
v.itemFields = Object.fromEntries(
|
|
46938
|
-
Object.entries(f.fields).map(([ik, iv]) => [
|
|
46939
|
-
ik,
|
|
46940
|
-
iv.kind === "richText" ? "richText (Markdown)" : iv.kind
|
|
46941
|
-
])
|
|
46942
|
-
);
|
|
46943
|
-
if (f.kind === "image") v.shape = "{ src, alt, focal? }";
|
|
46944
|
-
if (f.kind === "link") v.allow = f.allow;
|
|
46945
|
-
return [k, v];
|
|
46946
|
-
})
|
|
46947
|
-
);
|
|
46948
|
-
return {
|
|
46949
|
-
type: m.type,
|
|
46950
|
-
label: m.label,
|
|
46951
|
-
category: m.category,
|
|
46952
|
-
description: m.description,
|
|
46953
|
-
variants: m.variants,
|
|
46954
|
-
styleSlots: m.styleSlots,
|
|
46955
|
-
fields,
|
|
46956
|
-
ai: m.ai
|
|
46957
|
-
};
|
|
46958
|
-
}
|
|
46959
|
-
var listBlocks = defineTool({
|
|
46960
|
-
name: "list_blocks",
|
|
46961
|
-
title: "List blocks",
|
|
46962
|
-
description: "The block catalog: each block's fields (rich text as Markdown), variants, style slots and AI guidance (purpose, whenToUse, avoid). Filter by category or type. Prefer section presets for whole sections; use blocks to add or adjust pieces.",
|
|
46963
|
-
input: external_exports.object({
|
|
46964
|
-
category: external_exports.string().optional(),
|
|
46965
|
-
type: external_exports.string().optional().describe("One block type for full detail.")
|
|
46966
|
-
}),
|
|
46967
|
-
annotations: { readOnlyHint: true },
|
|
46968
|
-
async handler(ctx, input) {
|
|
46969
|
-
let metas = ctx.registry.meta();
|
|
46970
|
-
if (input.type) {
|
|
46971
|
-
metas = metas.filter((m) => m.type === input.type);
|
|
46972
|
-
if (!metas.length)
|
|
46973
|
-
throw new ToolError(
|
|
46974
|
-
`Unknown block type "${input.type}". Types: ${ctx.registry.types().join(", ")}.`,
|
|
46975
|
-
"not_found"
|
|
46976
|
-
);
|
|
46977
|
-
}
|
|
46978
|
-
if (input.category) metas = metas.filter((m) => m.category === input.category);
|
|
46979
|
-
return {
|
|
46980
|
-
data: {
|
|
46981
|
-
blocks: metas.map((m) => blockMetaView(m, Boolean(input.type))),
|
|
46982
|
-
categories: [...new Set(ctx.registry.meta().map((m) => m.category))]
|
|
46983
|
-
}
|
|
46984
|
-
};
|
|
46985
|
-
}
|
|
46986
|
-
});
|
|
46987
|
-
var getBlockSchema = defineTool({
|
|
46988
|
-
name: "get_block_schema",
|
|
46989
|
-
title: "Get block schema",
|
|
46990
|
-
description: "JSON Schema for one block type's fields (with labels and AI hints). Rich-text fields are shown as ProseMirror JSON but the edit tools also accept a Markdown string for them.",
|
|
46991
|
-
input: external_exports.object({ type: external_exports.string() }),
|
|
46992
|
-
annotations: { readOnlyHint: true },
|
|
46993
|
-
async handler(ctx, input) {
|
|
46994
|
-
const def = ctx.registry.get(input.type);
|
|
46995
|
-
if (!def)
|
|
46996
|
-
throw new ToolError(
|
|
46997
|
-
`Unknown block type "${input.type}". Types: ${ctx.registry.types().join(", ")}.`,
|
|
46998
|
-
"not_found"
|
|
46999
|
-
);
|
|
47000
|
-
return {
|
|
47001
|
-
data: {
|
|
47002
|
-
type: def.type,
|
|
47003
|
-
version: def.version,
|
|
47004
|
-
variants: def.meta.variants,
|
|
47005
|
-
styleSlots: def.meta.styleSlots,
|
|
47006
|
-
fields: blockFieldsJsonSchema(def)
|
|
47007
|
-
}
|
|
47008
|
-
};
|
|
47009
|
-
}
|
|
47010
|
-
});
|
|
47011
|
-
var listSectionPresets = defineTool({
|
|
47012
|
-
name: "list_section_presets",
|
|
47013
|
-
title: "List section presets",
|
|
47014
|
-
description: "Designed section presets by category (hero, about, services, gallery, testimonials, pricing, faq, team, stats, cta, contact, newsletter, blog, events, products, footer, header) with descriptions, layout, designed-for color themes, copy-length ranges and AI guidance. Compose pages from these with insert_section/create_page.",
|
|
47015
|
-
input: external_exports.object({
|
|
47016
|
-
category: external_exports.enum(PRESET_CATEGORIES).optional(),
|
|
47017
|
-
query: external_exports.string().optional().describe("Words to match in label, description or tags."),
|
|
47018
|
-
detail: external_exports.enum(["brief", "full"]).optional().describe('Default "brief".')
|
|
47019
|
-
}),
|
|
47020
|
-
annotations: { readOnlyHint: true },
|
|
47021
|
-
async handler(_ctx, input) {
|
|
47022
|
-
let list5 = presetCatalog.slice();
|
|
47023
|
-
if (input.category) list5 = list5.filter((p) => p.category === input.category);
|
|
47024
|
-
if (input.query) {
|
|
47025
|
-
const words4 = input.query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
47026
|
-
list5 = list5.filter((p) => {
|
|
47027
|
-
const hay = `${p.id} ${p.label} ${p.description} ${p.tags.join(" ")} ${p.ai.whenToUse}`.toLowerCase();
|
|
47028
|
-
return words4.every((w) => hay.includes(w));
|
|
47029
|
-
});
|
|
47030
|
-
}
|
|
47031
|
-
const full = input.detail === "full";
|
|
47032
|
-
return {
|
|
47033
|
-
data: {
|
|
47034
|
-
presets: list5.map(
|
|
47035
|
-
(p) => full ? p : {
|
|
47036
|
-
id: p.id,
|
|
47037
|
-
label: p.label,
|
|
47038
|
-
description: p.description,
|
|
47039
|
-
target: p.target,
|
|
47040
|
-
layout: p.layout,
|
|
47041
|
-
overlap: p.overlap,
|
|
47042
|
-
themes: p.themes,
|
|
47043
|
-
whenToUse: p.ai.whenToUse
|
|
47044
|
-
}
|
|
47045
|
-
),
|
|
47046
|
-
categories: PRESET_CATEGORIES
|
|
47047
|
-
}
|
|
47048
|
-
};
|
|
47049
|
-
}
|
|
47050
|
-
});
|
|
47051
|
-
function findPreset(id) {
|
|
47052
|
-
const exact = getPreset(id);
|
|
47053
|
-
if (exact) return exact;
|
|
47054
|
-
const base = id.replace(/@\d+$/, "");
|
|
47055
|
-
const matches = presetCatalog.filter((p) => p.id.startsWith(`${base}@`)).sort((a, b) => b.version - a.version);
|
|
47056
|
-
const best = matches[0] && getPreset(matches[0].id);
|
|
47057
|
-
if (best) return best;
|
|
47058
|
-
const category = base.split("/")[0] ?? "";
|
|
47059
|
-
const near = presetCatalog.filter((p) => p.category === category).map((p) => p.id);
|
|
47060
|
-
throw new ToolError(
|
|
47061
|
-
`Unknown preset "${id}".${near.length ? ` In ${category}: ${near.join(", ")}.` : " Call list_section_presets."}`,
|
|
47062
|
-
"not_found"
|
|
47063
|
-
);
|
|
47064
|
-
}
|
|
47065
|
-
|
|
47066
47782
|
// ../mcp-tools/src/tools/design.ts
|
|
47067
47783
|
var stripMeta = (op2) => {
|
|
47068
47784
|
const { id: _i, actor: _a3, ts: _t, ...body } = op2;
|
|
@@ -47207,7 +47923,7 @@ ${(adjusted.ok ? left.map((i) => `- ${i.message}`) : issueLines(adjusted.errors)
|
|
|
47207
47923
|
solverNotes
|
|
47208
47924
|
};
|
|
47209
47925
|
}
|
|
47210
|
-
var
|
|
47926
|
+
var isRecord4 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
47211
47927
|
var setStyles = defineTool({
|
|
47212
47928
|
name: "set_styles",
|
|
47213
47929
|
title: "Set Site Styles",
|
|
@@ -47258,8 +47974,8 @@ var setStyles = defineTool({
|
|
|
47258
47974
|
for (const [k, v] of Object.entries(c.layout ?? {}))
|
|
47259
47975
|
if (v !== void 0) set2[`choices.layout.${k}`] = v;
|
|
47260
47976
|
for (const [path2, v] of Object.entries(input.overrides?.set ?? {})) {
|
|
47261
|
-
const token =
|
|
47262
|
-
if (path2.startsWith("color.") &&
|
|
47977
|
+
const token = isRecord4(v) && "$value" in v ? v : { $value: v };
|
|
47978
|
+
if (path2.startsWith("color.") && isRecord4(token) && token.$type === void 0)
|
|
47263
47979
|
token.$type = "color";
|
|
47264
47980
|
set2[`overrides.${path2}`] = token;
|
|
47265
47981
|
}
|
|
@@ -47700,478 +48416,6 @@ var updateDesignSystem = defineTool({
|
|
|
47700
48416
|
});
|
|
47701
48417
|
var designSystemTools = [getDesignSystem, updateDesignSystem];
|
|
47702
48418
|
|
|
47703
|
-
// ../mcp-tools/src/tools/edit.ts
|
|
47704
|
-
var isRecord4 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
47705
|
-
var themeArg = external_exports.enum(COLOR_THEMES).describe("Section color theme.");
|
|
47706
|
-
var blockSummary = (registry3, b) => ({
|
|
47707
|
-
id: b.id,
|
|
47708
|
-
type: b.type,
|
|
47709
|
-
...b.variant ? { variant: b.variant } : {},
|
|
47710
|
-
...b.column !== void 0 ? { column: b.column } : {},
|
|
47711
|
-
text: blockPreview(registry3, b)
|
|
47712
|
-
});
|
|
47713
|
-
function sectionSummary(registry3, s2) {
|
|
47714
|
-
return {
|
|
47715
|
-
id: s2.id,
|
|
47716
|
-
...s2.preset ? { preset: s2.preset } : {},
|
|
47717
|
-
theme: s2.style?.colorTheme ?? "light",
|
|
47718
|
-
layout: s2.layout.mode,
|
|
47719
|
-
blocks: s2.blocks.map((b) => blockSummary(registry3, b))
|
|
47720
|
-
};
|
|
47721
|
-
}
|
|
47722
|
-
function container(doc, ref) {
|
|
47723
|
-
return resolvePage(doc, ref, { allowFooter: true }).id;
|
|
47724
|
-
}
|
|
47725
|
-
function pageOnly(doc, ref) {
|
|
47726
|
-
if (ref === FOOTER)
|
|
47727
|
-
throw new ToolError(
|
|
47728
|
-
"Sections can't be inserted, moved or deleted in the footer; replace it with a footer preset (insert_section with a footer/\u2026 preset) or edit its blocks.",
|
|
47729
|
-
"invalid_arguments"
|
|
47730
|
-
);
|
|
47731
|
-
return resolvePage(doc, ref).id;
|
|
47732
|
-
}
|
|
47733
|
-
function requireSection2(doc, page, section) {
|
|
47734
|
-
const loc = findSection(doc, page, section);
|
|
47735
|
-
if (!loc) {
|
|
47736
|
-
const ids = page === FOOTER ? [doc.site.footer.id] : doc.pages[page]?.sections.map(
|
|
47737
|
-
(s2) => s2.id
|
|
47738
|
-
) ?? [];
|
|
47739
|
-
throw new ToolError(
|
|
47740
|
-
`No section ${section} on ${page}. Sections there: ${ids.join(", ") || "none"}.`,
|
|
47741
|
-
"not_found"
|
|
47742
|
-
);
|
|
47743
|
-
}
|
|
47744
|
-
return loc.section;
|
|
47745
|
-
}
|
|
47746
|
-
function requireBlock(doc, page, block4) {
|
|
47747
|
-
const loc = findBlock(doc, page, block4);
|
|
47748
|
-
if (loc) return { block: loc.block, section: loc.section };
|
|
47749
|
-
const elsewhere = locateBlock(doc, block4);
|
|
47750
|
-
throw new ToolError(
|
|
47751
|
-
elsewhere ? `Block ${block4} is on ${elsewhere.container}, not ${page}.` : `No block ${block4}. Call get_page to see block ids.`,
|
|
47752
|
-
"not_found"
|
|
47753
|
-
);
|
|
47754
|
-
}
|
|
47755
|
-
function makeBlock2(registry3, input) {
|
|
47756
|
-
const def = registry3.get(input.type);
|
|
47757
|
-
if (!def)
|
|
47758
|
-
throw new ToolError(
|
|
47759
|
-
`Unknown block type "${input.type}". Types: ${registry3.types().join(", ")}.`,
|
|
47760
|
-
"invalid_arguments"
|
|
47761
|
-
);
|
|
47762
|
-
const fields = {};
|
|
47763
|
-
for (const [k, m] of Object.entries(def.meta.fields))
|
|
47764
|
-
if (!m.optional && m.default !== void 0) fields[k] = structuredClone(m.default);
|
|
47765
|
-
Object.assign(fields, fieldsFromAgent(registry3, input.type, input.fields ?? {}));
|
|
47766
|
-
const { type, fields: _f, id, v: _v, ...rest } = input;
|
|
47767
|
-
return {
|
|
47768
|
-
...rest,
|
|
47769
|
-
id: id ?? newId("blk"),
|
|
47770
|
-
type,
|
|
47771
|
-
v: def.version,
|
|
47772
|
-
fields
|
|
47773
|
-
};
|
|
47774
|
-
}
|
|
47775
|
-
function makeSection(registry3, raw2) {
|
|
47776
|
-
const blocks2 = Array.isArray(raw2.blocks) ? raw2.blocks : [];
|
|
47777
|
-
return {
|
|
47778
|
-
...raw2,
|
|
47779
|
-
id: typeof raw2.id === "string" ? raw2.id : newId("sec"),
|
|
47780
|
-
layout: raw2.layout ?? { mode: "stack", columns: { d: [12] } },
|
|
47781
|
-
style: isRecord4(raw2.style) ? raw2.style : { colorTheme: "light" },
|
|
47782
|
-
blocks: blocks2.map((b, i) => {
|
|
47783
|
-
if (!isRecord4(b) || typeof b.type !== "string")
|
|
47784
|
-
throw new ToolError(`section.blocks[${i}] needs a "type".`, "invalid_arguments");
|
|
47785
|
-
return makeBlock2(registry3, b);
|
|
47786
|
-
})
|
|
47787
|
-
};
|
|
47788
|
-
}
|
|
47789
|
-
var insertSection = defineTool({
|
|
47790
|
-
name: "insert_section",
|
|
47791
|
-
title: "Insert section",
|
|
47792
|
-
description: "Add a section to a page from a preset id (preferred \u2014 see list_section_presets) or an explicit section. Preset sections arrive with designed demo copy: replace it with the owner's real content using update_block. Header presets restyle the site header and footer presets replace the site footer (page is ignored for those). Returns the new section and block ids.",
|
|
47793
|
-
input: external_exports.object({
|
|
47794
|
-
site: siteArg,
|
|
47795
|
-
page: external_exports.string().optional().describe("Page id/slug/title (not needed for header/footer presets)."),
|
|
47796
|
-
preset: external_exports.string().optional().describe('Preset id, e.g. "hero/split-image@1" (version optional).'),
|
|
47797
|
-
section: external_exports.looseObject({}).optional().describe(
|
|
47798
|
-
"Explicit section ({layout, style, blocks:[{type, fields, variant?, column?}]}); ids are generated when omitted; rich text as Markdown."
|
|
47799
|
-
),
|
|
47800
|
-
at: positionArg.optional(),
|
|
47801
|
-
colorTheme: themeArg.optional(),
|
|
47802
|
-
primaryLink: external_exports.unknown().optional().describe(
|
|
47803
|
-
"Link for the main call to action ({type:'page', page:'pg_\u2026'} or {type:'url', url})."
|
|
47804
|
-
),
|
|
47805
|
-
secondaryLink: external_exports.unknown().optional(),
|
|
47806
|
-
collection: external_exports.string().optional().describe("Collection for summary/events/product presets."),
|
|
47807
|
-
label: external_exports.string().max(80).optional().describe("Layers label for the section.")
|
|
47808
|
-
}),
|
|
47809
|
-
annotations: {},
|
|
47810
|
-
async handler(ctx, input) {
|
|
47811
|
-
const siteId = await ctx.siteId(input.site);
|
|
47812
|
-
const doc = await ctx.doc(siteId);
|
|
47813
|
-
if (!!input.preset === !!input.section)
|
|
47814
|
-
throw new ToolError("Give exactly one of preset or section.", "invalid_arguments");
|
|
47815
|
-
const links = {};
|
|
47816
|
-
for (const k of ["primaryLink", "secondaryLink"]) {
|
|
47817
|
-
if (input[k] === void 0) continue;
|
|
47818
|
-
const r = LinkSchema.safeParse(input[k]);
|
|
47819
|
-
if (!r.success)
|
|
47820
|
-
throw new ToolError(
|
|
47821
|
-
`${k} is not a valid link: ${r.error.issues.map((i) => i.message).join("; ")}`,
|
|
47822
|
-
"invalid_arguments"
|
|
47823
|
-
);
|
|
47824
|
-
links[k] = r.data;
|
|
47825
|
-
}
|
|
47826
|
-
if (input.preset) {
|
|
47827
|
-
const preset = findPreset(input.preset);
|
|
47828
|
-
const opts = { colorTheme: input.colorTheme, collection: input.collection, ...links };
|
|
47829
|
-
const linkNote = (changes2) => changes2.length ? {
|
|
47830
|
-
linksChanged: changes2.map((c) => ({ from: c.from, to: c.to })),
|
|
47831
|
-
linkNote: "Example links that lead nowhere on this site were pointed at existing pages; change them with update_block."
|
|
47832
|
-
} : {};
|
|
47833
|
-
if (preset.target === "header") {
|
|
47834
|
-
const { value: header, changes: changes2 } = resolveInternalLinks(preset.build(opts), doc);
|
|
47835
|
-
await ctx.apply(siteId, [{ op: "site.update", set: { header } }]);
|
|
47836
|
-
return {
|
|
47837
|
-
data: {
|
|
47838
|
-
applied: preset.id,
|
|
47839
|
-
header,
|
|
47840
|
-
note: "The site header now uses this preset.",
|
|
47841
|
-
...linkNote(changes2)
|
|
47842
|
-
}
|
|
47843
|
-
};
|
|
47844
|
-
}
|
|
47845
|
-
const { value: section2, changes } = resolveInternalLinks(preset.build(opts), doc);
|
|
47846
|
-
if (input.label) section2.label = input.label;
|
|
47847
|
-
if (preset.target === "footer") {
|
|
47848
|
-
await ctx.apply(siteId, [{ op: "site.update", set: { footer: section2 } }]);
|
|
47849
|
-
return {
|
|
47850
|
-
data: {
|
|
47851
|
-
replaced: "footer",
|
|
47852
|
-
section: sectionSummary(ctx.registry, section2),
|
|
47853
|
-
note: 'Edit footer blocks with page "footer".',
|
|
47854
|
-
...linkNote(changes)
|
|
47855
|
-
}
|
|
47856
|
-
};
|
|
47857
|
-
}
|
|
47858
|
-
if (!input.page)
|
|
47859
|
-
throw new ToolError("page is required for page sections.", "invalid_arguments");
|
|
47860
|
-
const page2 = pageOnly(doc, input.page);
|
|
47861
|
-
await ctx.apply(siteId, [
|
|
47862
|
-
{ op: "section.insert", page: page2, at: input.at ?? "end", section: section2 }
|
|
47863
|
-
]);
|
|
47864
|
-
return {
|
|
47865
|
-
data: {
|
|
47866
|
-
page: page2,
|
|
47867
|
-
section: sectionSummary(ctx.registry, section2),
|
|
47868
|
-
copyGuidance: preset.copy.map((c) => `${c.label}: ${c.words[0]}\u2013${c.words[1]} words`),
|
|
47869
|
-
next: "Replace the demo copy with the owner's content (update_block), then run design_check.",
|
|
47870
|
-
...linkNote(changes)
|
|
47871
|
-
}
|
|
47872
|
-
};
|
|
47873
|
-
}
|
|
47874
|
-
if (!input.page) throw new ToolError("page is required.", "invalid_arguments");
|
|
47875
|
-
const page = pageOnly(doc, input.page);
|
|
47876
|
-
const section = makeSection(ctx.registry, input.section);
|
|
47877
|
-
if (input.colorTheme)
|
|
47878
|
-
section.style = { ...section.style ?? {}, colorTheme: input.colorTheme };
|
|
47879
|
-
if (input.label) section.label = input.label;
|
|
47880
|
-
await ctx.apply(siteId, [
|
|
47881
|
-
{ op: "section.insert", page, at: input.at ?? "end", section }
|
|
47882
|
-
]);
|
|
47883
|
-
return { data: { page, section: sectionSummary(ctx.registry, section) } };
|
|
47884
|
-
}
|
|
47885
|
-
});
|
|
47886
|
-
var updateSection = defineTool({
|
|
47887
|
-
name: "update_section",
|
|
47888
|
-
title: "Update section",
|
|
47889
|
-
description: `Change a section's settings with dot-path patches, e.g. set {"style.colorTheme": "dark", "style.padding": {"d": "xl", "m": "lg"}, "label": "Intro", "anchor": "classes", "hidden": true, "layout": {...}}. Shortcuts: colorTheme. Blocks are edited with the block tools.`,
|
|
47890
|
-
input: external_exports.object({
|
|
47891
|
-
site: siteArg,
|
|
47892
|
-
page: containerArg,
|
|
47893
|
-
section: external_exports.string(),
|
|
47894
|
-
set: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
47895
|
-
unset: external_exports.array(external_exports.string()).optional(),
|
|
47896
|
-
colorTheme: themeArg.optional()
|
|
47897
|
-
}),
|
|
47898
|
-
annotations: { idempotentHint: true },
|
|
47899
|
-
async handler(ctx, input) {
|
|
47900
|
-
const siteId = await ctx.siteId(input.site);
|
|
47901
|
-
const doc = await ctx.doc(siteId);
|
|
47902
|
-
const page = container(doc, input.page);
|
|
47903
|
-
requireSection2(doc, page, input.section);
|
|
47904
|
-
const set2 = { ...input.set ?? {} };
|
|
47905
|
-
if (input.colorTheme) set2["style.colorTheme"] = input.colorTheme;
|
|
47906
|
-
const body = { op: "section.update", page, section: input.section };
|
|
47907
|
-
if (Object.keys(set2).length) body.set = set2;
|
|
47908
|
-
if (input.unset?.length) body.unset = input.unset;
|
|
47909
|
-
const { doc: next } = await ctx.apply(siteId, [body]);
|
|
47910
|
-
return {
|
|
47911
|
-
data: { section: sectionSummary(ctx.registry, requireSection2(next, page, input.section)) }
|
|
47912
|
-
};
|
|
47913
|
-
}
|
|
47914
|
-
});
|
|
47915
|
-
var moveSection = defineTool({
|
|
47916
|
-
name: "move_section",
|
|
47917
|
-
title: "Move section",
|
|
47918
|
-
description: "Reorder a section within its page.",
|
|
47919
|
-
input: external_exports.object({ site: siteArg, page: external_exports.string(), section: external_exports.string(), at: positionArg }),
|
|
47920
|
-
annotations: { idempotentHint: true },
|
|
47921
|
-
async handler(ctx, input) {
|
|
47922
|
-
const siteId = await ctx.siteId(input.site);
|
|
47923
|
-
const doc = await ctx.doc(siteId);
|
|
47924
|
-
const page = pageOnly(doc, input.page);
|
|
47925
|
-
requireSection2(doc, page, input.section);
|
|
47926
|
-
const { doc: next } = await ctx.apply(siteId, [
|
|
47927
|
-
{ op: "section.move", page, section: input.section, at: input.at }
|
|
47928
|
-
]);
|
|
47929
|
-
return { data: { order: next.pages[page]?.sections.map((s2) => s2.id) } };
|
|
47930
|
-
}
|
|
47931
|
-
});
|
|
47932
|
-
var deleteSection = defineTool({
|
|
47933
|
-
name: "delete_section",
|
|
47934
|
-
title: "Delete section",
|
|
47935
|
-
description: "Remove a section and its blocks from a page (recoverable from the session snapshot or discard_draft).",
|
|
47936
|
-
input: external_exports.object({ site: siteArg, page: external_exports.string(), section: external_exports.string() }),
|
|
47937
|
-
annotations: { destructiveHint: true },
|
|
47938
|
-
async handler(ctx, input) {
|
|
47939
|
-
const siteId = await ctx.siteId(input.site);
|
|
47940
|
-
const doc = await ctx.doc(siteId);
|
|
47941
|
-
const page = pageOnly(doc, input.page);
|
|
47942
|
-
requireSection2(doc, page, input.section);
|
|
47943
|
-
await ctx.apply(siteId, [{ op: "section.delete", page, section: input.section }]);
|
|
47944
|
-
return { data: { deleted: input.section } };
|
|
47945
|
-
}
|
|
47946
|
-
});
|
|
47947
|
-
var gridItemArg = external_exports.looseObject({}).describe(
|
|
47948
|
-
"Grid placement {d:{x,y,w,h,z?,bleed?}, t?, m?}: 24 columns on desktop/tablet, 8 on mobile, 1-based."
|
|
47949
|
-
);
|
|
47950
|
-
var insertBlock = defineTool({
|
|
47951
|
-
name: "insert_block",
|
|
47952
|
-
title: "Insert block",
|
|
47953
|
-
description: "Add a block to a section. Fields are validated against the block type (see list_blocks); rich text as Markdown. In stack sections pick a column (0-based); in grid sections give place (auto-placed at the bottom otherwise) and then check the mobile layout (auto_mobile_layout).",
|
|
47954
|
-
input: external_exports.object({
|
|
47955
|
-
site: siteArg,
|
|
47956
|
-
page: containerArg,
|
|
47957
|
-
section: external_exports.string(),
|
|
47958
|
-
block: external_exports.looseObject({
|
|
47959
|
-
type: external_exports.string(),
|
|
47960
|
-
fields: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
47961
|
-
variant: external_exports.string().optional(),
|
|
47962
|
-
style: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
47963
|
-
column: external_exports.int().min(0).max(3).optional(),
|
|
47964
|
-
label: external_exports.string().optional()
|
|
47965
|
-
}).describe("{type, fields, variant?, style?, column?, label?}"),
|
|
47966
|
-
at: positionArg.optional(),
|
|
47967
|
-
place: gridItemArg.optional()
|
|
47968
|
-
}),
|
|
47969
|
-
annotations: {},
|
|
47970
|
-
async handler(ctx, input) {
|
|
47971
|
-
const siteId = await ctx.siteId(input.site);
|
|
47972
|
-
const doc = await ctx.doc(siteId);
|
|
47973
|
-
const page = container(doc, input.page);
|
|
47974
|
-
requireSection2(doc, page, input.section);
|
|
47975
|
-
const made = makeBlock2(ctx.registryFor(doc), input.block);
|
|
47976
|
-
const block4 = resolveInternalLinks(made, doc).value;
|
|
47977
|
-
for (const k of Object.keys(input.block.fields ?? {}))
|
|
47978
|
-
if (k in made.fields) block4.fields[k] = made.fields[k];
|
|
47979
|
-
const body = {
|
|
47980
|
-
op: "block.insert",
|
|
47981
|
-
page,
|
|
47982
|
-
section: input.section,
|
|
47983
|
-
at: input.at ?? "end",
|
|
47984
|
-
block: block4
|
|
47985
|
-
};
|
|
47986
|
-
if (input.place) body.place = input.place;
|
|
47987
|
-
const { doc: next, warnings } = await ctx.apply(siteId, [body]);
|
|
47988
|
-
const placed = findBlock(next, page, block4.id);
|
|
47989
|
-
return {
|
|
47990
|
-
data: {
|
|
47991
|
-
block: blockSummary(ctx.registryFor(next), placed?.block ?? block4),
|
|
47992
|
-
...warnings.length ? { warnings } : {}
|
|
47993
|
-
}
|
|
47994
|
-
};
|
|
47995
|
-
}
|
|
47996
|
-
});
|
|
47997
|
-
var updateBlock = defineTool({
|
|
47998
|
-
name: "update_block",
|
|
47999
|
-
title: "Update block",
|
|
48000
|
-
description: "Edit a block: set replaces whole top-level fields (rich text as Markdown; lists are replaced whole), unset removes optional fields; variant/style/column/label/hidden adjust presentation (null removes). Ids are preserved.",
|
|
48001
|
-
input: external_exports.object({
|
|
48002
|
-
site: siteArg,
|
|
48003
|
-
page: containerArg,
|
|
48004
|
-
block: external_exports.string(),
|
|
48005
|
-
set: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
48006
|
-
unset: external_exports.array(external_exports.string()).optional(),
|
|
48007
|
-
variant: external_exports.string().nullable().optional(),
|
|
48008
|
-
style: external_exports.record(external_exports.string(), external_exports.unknown()).nullable().optional(),
|
|
48009
|
-
column: external_exports.int().min(0).max(3).nullable().optional(),
|
|
48010
|
-
label: external_exports.string().max(80).nullable().optional(),
|
|
48011
|
-
hidden: external_exports.boolean().nullable().optional()
|
|
48012
|
-
}),
|
|
48013
|
-
annotations: { idempotentHint: true },
|
|
48014
|
-
async handler(ctx, input) {
|
|
48015
|
-
const siteId = await ctx.siteId(input.site);
|
|
48016
|
-
const doc = await ctx.doc(siteId);
|
|
48017
|
-
const page = container(doc, input.page);
|
|
48018
|
-
const { block: block4 } = requireBlock(doc, page, input.block);
|
|
48019
|
-
const body = { op: "block.update", page, block: input.block };
|
|
48020
|
-
if (input.set) body.set = fieldsFromAgent(ctx.registryFor(doc), block4.type, input.set);
|
|
48021
|
-
if (input.unset?.length) body.unset = input.unset;
|
|
48022
|
-
for (const k of ["variant", "style", "column", "label", "hidden"])
|
|
48023
|
-
if (input[k] !== void 0) body[k] = input[k];
|
|
48024
|
-
if (Object.keys(body).length === 3)
|
|
48025
|
-
throw new ToolError(
|
|
48026
|
-
"Nothing to change: give set, unset, variant, style, column, label or hidden.",
|
|
48027
|
-
"invalid_arguments"
|
|
48028
|
-
);
|
|
48029
|
-
const { doc: next } = await ctx.apply(siteId, [body]);
|
|
48030
|
-
const updated = findBlock(next, page, input.block)?.block ?? block4;
|
|
48031
|
-
return { data: { block: blockSummary(ctx.registryFor(next), updated) } };
|
|
48032
|
-
}
|
|
48033
|
-
});
|
|
48034
|
-
var moveBlock = defineTool({
|
|
48035
|
-
name: "move_block",
|
|
48036
|
-
title: "Move block",
|
|
48037
|
-
description: "Move a block within its page (to another section and/or position). Give column for stack sections and place for grid sections.",
|
|
48038
|
-
input: external_exports.object({
|
|
48039
|
-
site: siteArg,
|
|
48040
|
-
page: containerArg,
|
|
48041
|
-
block: external_exports.string(),
|
|
48042
|
-
to: external_exports.object({
|
|
48043
|
-
section: external_exports.string(),
|
|
48044
|
-
at: positionArg.optional(),
|
|
48045
|
-
column: external_exports.int().min(0).max(3).nullable().optional(),
|
|
48046
|
-
place: gridItemArg.optional()
|
|
48047
|
-
})
|
|
48048
|
-
}),
|
|
48049
|
-
annotations: { idempotentHint: true },
|
|
48050
|
-
async handler(ctx, input) {
|
|
48051
|
-
const siteId = await ctx.siteId(input.site);
|
|
48052
|
-
const doc = await ctx.doc(siteId);
|
|
48053
|
-
const page = container(doc, input.page);
|
|
48054
|
-
requireBlock(doc, page, input.block);
|
|
48055
|
-
requireSection2(doc, page, input.to.section);
|
|
48056
|
-
const to = { section: input.to.section, at: input.to.at ?? "end" };
|
|
48057
|
-
if (input.to.column !== void 0) to.column = input.to.column;
|
|
48058
|
-
if (input.to.place) to.place = input.to.place;
|
|
48059
|
-
await ctx.apply(siteId, [{ op: "block.move", page, block: input.block, to }]);
|
|
48060
|
-
return { data: { moved: input.block, to } };
|
|
48061
|
-
}
|
|
48062
|
-
});
|
|
48063
|
-
var deleteBlock = defineTool({
|
|
48064
|
-
name: "delete_block",
|
|
48065
|
-
title: "Delete block",
|
|
48066
|
-
description: "Remove a block from its section (recoverable via the session snapshot or discard_draft).",
|
|
48067
|
-
input: external_exports.object({ site: siteArg, page: containerArg, block: external_exports.string() }),
|
|
48068
|
-
annotations: { destructiveHint: true },
|
|
48069
|
-
async handler(ctx, input) {
|
|
48070
|
-
const siteId = await ctx.siteId(input.site);
|
|
48071
|
-
const doc = await ctx.doc(siteId);
|
|
48072
|
-
const page = container(doc, input.page);
|
|
48073
|
-
requireBlock(doc, page, input.block);
|
|
48074
|
-
await ctx.apply(siteId, [{ op: "block.delete", page, block: input.block }]);
|
|
48075
|
-
return { data: { deleted: input.block } };
|
|
48076
|
-
}
|
|
48077
|
-
});
|
|
48078
|
-
var rectArg = external_exports.looseObject({}).describe(
|
|
48079
|
-
"{x, y, w, h, z?, bleed?: 'left'|'right'|'both'} (1-based; 24 columns desktop/tablet, 8 mobile)"
|
|
48080
|
-
);
|
|
48081
|
-
var placeInGrid2 = defineTool({
|
|
48082
|
-
name: "place_in_grid",
|
|
48083
|
-
title: "Place in grid",
|
|
48084
|
-
description: "Set a block's grid placement per breakpoint in a grid section. d = desktop (required on first placement), t = tablet override (null removes), m = mobile (null removes; regenerate with auto_mobile_layout). Rows grow automatically. Overlap is allowed (use z) but text must not overlap text.",
|
|
48085
|
-
input: external_exports.object({
|
|
48086
|
-
site: siteArg,
|
|
48087
|
-
page: containerArg,
|
|
48088
|
-
section: external_exports.string(),
|
|
48089
|
-
block: external_exports.string(),
|
|
48090
|
-
d: rectArg.optional(),
|
|
48091
|
-
t: rectArg.nullable().optional(),
|
|
48092
|
-
m: rectArg.nullable().optional()
|
|
48093
|
-
}),
|
|
48094
|
-
annotations: { idempotentHint: true },
|
|
48095
|
-
async handler(ctx, input) {
|
|
48096
|
-
const siteId = await ctx.siteId(input.site);
|
|
48097
|
-
const doc = await ctx.doc(siteId);
|
|
48098
|
-
const page = container(doc, input.page);
|
|
48099
|
-
const section = requireSection2(doc, page, input.section);
|
|
48100
|
-
if (section.layout.mode !== "grid")
|
|
48101
|
-
throw new ToolError(
|
|
48102
|
-
`Section ${section.id} is a stack section; set the block's column with update_block instead.`,
|
|
48103
|
-
"invalid_arguments"
|
|
48104
|
-
);
|
|
48105
|
-
const bodies = [];
|
|
48106
|
-
for (const bp of ["d", "t", "m"]) {
|
|
48107
|
-
const rect = input[bp];
|
|
48108
|
-
if (rect === void 0) continue;
|
|
48109
|
-
bodies.push({
|
|
48110
|
-
op: "grid.place",
|
|
48111
|
-
page,
|
|
48112
|
-
section: section.id,
|
|
48113
|
-
block: input.block,
|
|
48114
|
-
bp,
|
|
48115
|
-
rect
|
|
48116
|
-
});
|
|
48117
|
-
}
|
|
48118
|
-
if (!bodies.length) throw new ToolError("Give at least one of d, t or m.", "invalid_arguments");
|
|
48119
|
-
const { doc: next } = await ctx.apply(siteId, bodies);
|
|
48120
|
-
const after = requireSection2(next, page, section.id);
|
|
48121
|
-
return {
|
|
48122
|
-
data: {
|
|
48123
|
-
placement: after.layout.mode === "grid" ? after.layout.items[input.block] : null,
|
|
48124
|
-
rows: after.layout.mode === "grid" ? after.layout.rows : null,
|
|
48125
|
-
next: input.d && input.m === void 0 ? "Desktop changed: review mobile (auto_mobile_layout or screenshot device mobile)." : void 0
|
|
48126
|
-
}
|
|
48127
|
-
};
|
|
48128
|
-
}
|
|
48129
|
-
});
|
|
48130
|
-
var autoMobileLayout2 = defineTool({
|
|
48131
|
-
name: "auto_mobile_layout",
|
|
48132
|
-
title: "Auto mobile layout",
|
|
48133
|
-
description: "Generate mobile grid placements for a grid section by visual reading order (top-to-bottom, left-to-right on the desktop grid, layered items kept on their host). Keeps existing mobile placements unless reset is true.",
|
|
48134
|
-
input: external_exports.object({
|
|
48135
|
-
site: siteArg,
|
|
48136
|
-
page: containerArg,
|
|
48137
|
-
section: external_exports.string(),
|
|
48138
|
-
reset: external_exports.boolean().optional()
|
|
48139
|
-
}),
|
|
48140
|
-
annotations: { idempotentHint: true },
|
|
48141
|
-
async handler(ctx, input) {
|
|
48142
|
-
const siteId = await ctx.siteId(input.site);
|
|
48143
|
-
const doc = await ctx.doc(siteId);
|
|
48144
|
-
const page = container(doc, input.page);
|
|
48145
|
-
const section = requireSection2(doc, page, input.section);
|
|
48146
|
-
if (section.layout.mode !== "grid")
|
|
48147
|
-
return { data: { changed: 0, note: "Stack sections stack automatically on mobile." } };
|
|
48148
|
-
const ops = autoMobileLayoutOps(doc, page, section.id, { reset: input.reset });
|
|
48149
|
-
if (!ops.length)
|
|
48150
|
-
return { data: { changed: 0, note: "Mobile placements are already complete." } };
|
|
48151
|
-
const bodies = ops.map(({ id: _i, actor: _a3, ts: _t, ...b }) => b);
|
|
48152
|
-
const { doc: next } = await ctx.apply(siteId, bodies);
|
|
48153
|
-
const after = requireSection2(next, page, section.id);
|
|
48154
|
-
return {
|
|
48155
|
-
data: {
|
|
48156
|
-
changed: ops.length,
|
|
48157
|
-
mobile: after.layout.mode === "grid" ? Object.fromEntries(Object.entries(after.layout.items).map(([id, it]) => [id, it.m])) : null
|
|
48158
|
-
}
|
|
48159
|
-
};
|
|
48160
|
-
}
|
|
48161
|
-
});
|
|
48162
|
-
var editTools = [
|
|
48163
|
-
insertSection,
|
|
48164
|
-
updateSection,
|
|
48165
|
-
moveSection,
|
|
48166
|
-
deleteSection,
|
|
48167
|
-
insertBlock,
|
|
48168
|
-
updateBlock,
|
|
48169
|
-
moveBlock,
|
|
48170
|
-
deleteBlock,
|
|
48171
|
-
placeInGrid2,
|
|
48172
|
-
autoMobileLayout2
|
|
48173
|
-
];
|
|
48174
|
-
|
|
48175
48419
|
// ../mcp-tools/src/tools/pages.ts
|
|
48176
48420
|
function requireConfirm(confirm, what) {
|
|
48177
48421
|
if (confirm !== true)
|
|
@@ -48308,16 +48552,59 @@ var deletePage = defineTool({
|
|
|
48308
48552
|
};
|
|
48309
48553
|
}
|
|
48310
48554
|
});
|
|
48555
|
+
function connectSections(doc, tree) {
|
|
48556
|
+
const view = { ...doc, site: { ...doc.site, nav: tree } };
|
|
48557
|
+
const bodies = [];
|
|
48558
|
+
const connected = [];
|
|
48559
|
+
const warnings = [];
|
|
48560
|
+
const claimed = /* @__PURE__ */ new Map();
|
|
48561
|
+
for (const l of findBrokenLinks(view)) {
|
|
48562
|
+
if (l.kind !== "anchor" || l.scope.kind !== "site" || l.path[0] !== "nav") continue;
|
|
48563
|
+
if (!l.anchor) {
|
|
48564
|
+
warnings.push(`${l.message}.`);
|
|
48565
|
+
continue;
|
|
48566
|
+
}
|
|
48567
|
+
const { page, fragment } = l.anchor;
|
|
48568
|
+
const key2 = `${page}#${fragment}`;
|
|
48569
|
+
if (claimed.has(key2)) continue;
|
|
48570
|
+
const section = suggestAnchorSection(view, page, fragment);
|
|
48571
|
+
const free = section && !section.anchor && ![...claimed.values()].includes(section.id) && SlugSchema.safeParse(fragment).success;
|
|
48572
|
+
if (section && free) {
|
|
48573
|
+
claimed.set(key2, section.id);
|
|
48574
|
+
bodies.push({ op: "section.update", page, section: section.id, set: { anchor: fragment } });
|
|
48575
|
+
connected.push({
|
|
48576
|
+
anchor: fragment,
|
|
48577
|
+
section: section.id,
|
|
48578
|
+
sectionLabel: section.label ?? section.preset ?? section.blocks[0]?.type ?? "section"
|
|
48579
|
+
});
|
|
48580
|
+
} else
|
|
48581
|
+
warnings.push(
|
|
48582
|
+
`The menu links to \u201C${fragment}\u201D, but no section on that page is called that. Name the section it should jump to with update_section (page, section, set {"anchor": "${fragment}"}), or change the entry.`
|
|
48583
|
+
);
|
|
48584
|
+
}
|
|
48585
|
+
return { bodies, connected, warnings };
|
|
48586
|
+
}
|
|
48311
48587
|
var updateNavigation = defineTool({
|
|
48312
48588
|
name: "update_navigation",
|
|
48313
48589
|
title: "Update navigation",
|
|
48314
|
-
description:
|
|
48590
|
+
description: `Replace the navigation tree. Items: {type:"page", page:"pg_\u2026", label?, anchor?}, {type:"collection", collection, label}, {type:"link", label, href, newTab?}, {type:"button", label, link, style:"primary"|"secondary"|"tertiary"}, {type:"folder", label, page?, items:[\u2026one level\u2026]}. Keep one button at most (the header call to action). A menu entry that scrolls to a section of a page (a one-page site's "How it works", "Prices") is {type:"page", page, label, anchor:"how-it-works"} and a button to one has link {type:"page", page, anchor}; the anchor is that section's link name \u2014 set it when you add the section (insert_section / insert_component anchor) or with update_section set {"anchor": \u2026}. Entries whose anchor no section has yet are connected to the section they most likely mean (listed in connected \u2014 check them); the rest come back in warnings.`,
|
|
48315
48591
|
input: external_exports.object({ site: siteArg, tree: external_exports.array(external_exports.looseObject({})) }),
|
|
48316
48592
|
annotations: { idempotentHint: true },
|
|
48317
48593
|
async handler(ctx, input) {
|
|
48318
48594
|
const siteId = await ctx.siteId(input.site);
|
|
48319
|
-
const
|
|
48320
|
-
|
|
48595
|
+
const before = await ctx.doc(siteId);
|
|
48596
|
+
const { bodies, connected, warnings } = connectSections(before, input.tree);
|
|
48597
|
+
const { doc } = await ctx.apply(siteId, [
|
|
48598
|
+
{ op: "nav.update", tree: input.tree },
|
|
48599
|
+
...bodies
|
|
48600
|
+
]);
|
|
48601
|
+
return {
|
|
48602
|
+
data: {
|
|
48603
|
+
nav: doc.site.nav,
|
|
48604
|
+
...connected.length ? { connected } : {},
|
|
48605
|
+
...warnings.length ? { warnings } : {}
|
|
48606
|
+
}
|
|
48607
|
+
};
|
|
48321
48608
|
}
|
|
48322
48609
|
});
|
|
48323
48610
|
var SITE_FORBIDDEN = ["nav", "customCss", "headInjection", "footerInjection"];
|
|
@@ -51559,10 +51846,10 @@ function setRole(doc, theme, role, value) {
|
|
|
51559
51846
|
doc.styles.overrides = overrides;
|
|
51560
51847
|
return true;
|
|
51561
51848
|
}
|
|
51562
|
-
function sectionLabel(heading2, blueprint,
|
|
51849
|
+
function sectionLabel(heading2, blueprint, pageTitle2, n) {
|
|
51563
51850
|
const first2 = heading2?.trim() || blueprint?.find((i) => i.kind === "heading" && i.text?.trim())?.text || blueprint?.find((i) => i.kind === "text" && i.text?.trim())?.text;
|
|
51564
51851
|
const words4 = (first2 ?? "").replace(/\s+/g, " ").trim();
|
|
51565
|
-
if (!words4) return `${
|
|
51852
|
+
if (!words4) return `${pageTitle2} section ${n}`.slice(0, 60);
|
|
51566
51853
|
const short = words4.length > 40 ? `${words4.slice(0, 40).replace(/\s+\S*$/, "")}\u2026` : words4;
|
|
51567
51854
|
const cased = /[a-z]/.test(short) ? short : short.charAt(0) + short.slice(1).toLowerCase();
|
|
51568
51855
|
return cased.slice(0, 60);
|
|
@@ -59666,6 +59953,24 @@ function sectionElements(main, skip) {
|
|
|
59666
59953
|
}
|
|
59667
59954
|
return [];
|
|
59668
59955
|
}
|
|
59956
|
+
function linkedFragments(pages) {
|
|
59957
|
+
const out = /* @__PURE__ */ new Set();
|
|
59958
|
+
for (const p of pages)
|
|
59959
|
+
for (const a of findAll(p.root, (e) => e.tagName === "a")) {
|
|
59960
|
+
const id = slugId(/#([^#?]+)$/.exec(attr(a, "href") ?? "")?.[1]);
|
|
59961
|
+
if (id) out.add(id);
|
|
59962
|
+
}
|
|
59963
|
+
return out;
|
|
59964
|
+
}
|
|
59965
|
+
function sectionAnchor(el, linked) {
|
|
59966
|
+
const own3 = slugId(attr(el, "id"));
|
|
59967
|
+
if (own3 && linked.has(own3)) return own3;
|
|
59968
|
+
const inner = find(el, (e) => {
|
|
59969
|
+
const id = slugId(attr(e, "id"));
|
|
59970
|
+
return !!id && linked.has(id);
|
|
59971
|
+
});
|
|
59972
|
+
return (inner ? slugId(attr(inner, "id")) : void 0) ?? own3;
|
|
59973
|
+
}
|
|
59669
59974
|
var ROOT_SEL = /^(?::root|html|body|:root\s*,\s*html|html\s*,\s*body|:host)$/i;
|
|
59670
59975
|
function serializeNodes(nodes) {
|
|
59671
59976
|
return nodes.map(
|
|
@@ -60075,6 +60380,7 @@ ${await r.text()}`;
|
|
|
60075
60380
|
const siteTitle = ((wordmark && wordmark.length <= 60 ? titleCase(wordmark) : void 0) ?? opts.title ?? homePage.siteName ?? (docTitle.split(/\s+[|·—–-]\s+/)[0] || "My site")).slice(0, 120);
|
|
60076
60381
|
const logoImg = brand ? find(brand, (e) => e.tagName === "img") : void 0;
|
|
60077
60382
|
const logoRef = logoImg ? resolveRef(bundle, attr(logoImg, "src") ?? "", home.path) : void 0;
|
|
60383
|
+
const linked = linkedFragments(pages);
|
|
60078
60384
|
const segsByPage = /* @__PURE__ */ new Map();
|
|
60079
60385
|
const sqPages = [];
|
|
60080
60386
|
const takenNames = /* @__PURE__ */ new Set();
|
|
@@ -60106,6 +60412,7 @@ ${await r.text()}`;
|
|
|
60106
60412
|
const segs = [];
|
|
60107
60413
|
const sqSections = [];
|
|
60108
60414
|
for (const el of p.sections) {
|
|
60415
|
+
const anchor = sectionAnchor(el, linked);
|
|
60109
60416
|
const name = uniqueName(componentNameFor(el, p.sitePath), takenNames);
|
|
60110
60417
|
const label2 = componentLabelFor(el);
|
|
60111
60418
|
const dry = await new SectionConverter(el, dryCtx(p)).convert(name, label2);
|
|
@@ -60116,13 +60423,13 @@ ${await r.text()}`;
|
|
|
60116
60423
|
if (i === 0) s2.id = m2;
|
|
60117
60424
|
sqSections.push(s2);
|
|
60118
60425
|
});
|
|
60119
|
-
segs.push({ kind: "preset", marker: m2, reason: dry.error });
|
|
60426
|
+
segs.push({ kind: "preset", marker: m2, reason: dry.error, ...anchor ? { anchor } : {} });
|
|
60120
60427
|
presets.push({ page: p.sitePath, reason: dry.error });
|
|
60121
60428
|
continue;
|
|
60122
60429
|
}
|
|
60123
60430
|
takenNames.add(name);
|
|
60124
60431
|
const m = marker();
|
|
60125
|
-
segs.push({ kind: "component", el, marker: m, name, label: label2 });
|
|
60432
|
+
segs.push({ kind: "component", el, marker: m, name, label: label2, ...anchor ? { anchor } : {} });
|
|
60126
60433
|
const blocks2 = [];
|
|
60127
60434
|
for (const f of dry.forms) {
|
|
60128
60435
|
const form = formFromHtml(f);
|
|
@@ -60318,6 +60625,11 @@ ${await r.text()}`;
|
|
|
60318
60625
|
return rest;
|
|
60319
60626
|
});
|
|
60320
60627
|
if (seg.kind === "preset") {
|
|
60628
|
+
const first2 = group[0];
|
|
60629
|
+
if (first2 && seg.anchor && !anchors.has(seg.anchor)) {
|
|
60630
|
+
first2.anchor = seg.anchor;
|
|
60631
|
+
anchors.add(seg.anchor);
|
|
60632
|
+
}
|
|
60321
60633
|
out.push(...group);
|
|
60322
60634
|
continue;
|
|
60323
60635
|
}
|
|
@@ -60333,9 +60645,10 @@ ${await r.text()}`;
|
|
|
60333
60645
|
components[r.def.name] = r.def;
|
|
60334
60646
|
const section = componentSection(r.def, { fields: r.fields, colorTheme: "light" });
|
|
60335
60647
|
section.label = seg.label;
|
|
60336
|
-
|
|
60337
|
-
|
|
60338
|
-
|
|
60648
|
+
const anchor = seg.anchor ?? r.anchor;
|
|
60649
|
+
if (anchor && !anchors.has(anchor)) {
|
|
60650
|
+
section.anchor = anchor;
|
|
60651
|
+
anchors.add(anchor);
|
|
60339
60652
|
}
|
|
60340
60653
|
section.style = { ...section.style, colorTheme: "light", padding: { d: "none" } };
|
|
60341
60654
|
out.push(section);
|
|
@@ -62249,4 +62562,4 @@ export {
|
|
|
62249
62562
|
createProgram,
|
|
62250
62563
|
run
|
|
62251
62564
|
};
|
|
62252
|
-
//# sourceMappingURL=chunk-
|
|
62565
|
+
//# sourceMappingURL=chunk-HUS7TQPS.js.map
|