@omniharmonic/openspace-cli 0.1.15 → 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-PBN36VUE.js → chunk-HUS7TQPS.js} +1144 -813
- package/dist/chunk-HUS7TQPS.js.map +1 -0
- package/dist/index.js +1 -1
- package/package.json +9 -9
- package/dist/chunk-PBN36VUE.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({
|
|
@@ -45274,13 +45495,24 @@ var ToolContext = class {
|
|
|
45274
45495
|
const allowed = await this.allowedSites();
|
|
45275
45496
|
if (input) {
|
|
45276
45497
|
if (allowed.includes(input)) return input;
|
|
45498
|
+
const wanted = input.trim().toLowerCase();
|
|
45499
|
+
const named = (await this.store.listSites()).filter(
|
|
45500
|
+
(s2) => allowed.includes(s2.id) && s2.title.trim().toLowerCase() === wanted
|
|
45501
|
+
);
|
|
45502
|
+
if (named.length === 1 && named[0]) return named[0].id;
|
|
45503
|
+
const explained = await this.store.explainUnreachable?.(input);
|
|
45504
|
+
if (explained) throw explained;
|
|
45277
45505
|
throw new ToolError(
|
|
45278
45506
|
`This connection can't reach site "${input}". Sites available: ${allowed.join(", ") || "none"}.`,
|
|
45279
45507
|
"forbidden"
|
|
45280
45508
|
);
|
|
45281
45509
|
}
|
|
45282
45510
|
if (allowed.length === 1) return allowed[0];
|
|
45283
|
-
if (!allowed.length)
|
|
45511
|
+
if (!allowed.length) {
|
|
45512
|
+
const explained = await this.store.explainUnreachable?.(void 0);
|
|
45513
|
+
if (explained) throw explained;
|
|
45514
|
+
throw new ToolError("This connection has no sites.", "forbidden");
|
|
45515
|
+
}
|
|
45284
45516
|
throw new ToolError(
|
|
45285
45517
|
`Several sites are available; pass "site" (one of: ${allowed.join(", ")}).`,
|
|
45286
45518
|
"invalid_arguments"
|
|
@@ -45421,6 +45653,7 @@ Design check flags (custom components too: invalid code, budgets, motion, fixed
|
|
|
45421
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.
|
|
45422
45654
|
- Section color themes: light, light-accent, dark, dark-accent, brand. The contrast solver keeps text readable in all five.
|
|
45423
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.
|
|
45424
45657
|
- Edits land in the draft. The owner publishes \u2014 call publish only when they ask, with confirm: true.
|
|
45425
45658
|
`;
|
|
45426
45659
|
var ITERATION_GUIDANCE = `# Design iteration loop
|
|
@@ -45815,11 +46048,11 @@ ${ITERATION_GUIDANCE}
|
|
|
45815
46048
|
Do at most five passes, and after each pass say in one line what you changed and what you saw in the screenshot. Stop when design_check shows no errors and the page reads clearly on desktop and mobile.`
|
|
45816
46049
|
}
|
|
45817
46050
|
];
|
|
45818
|
-
function getPrompt(name, args = {}) {
|
|
45819
|
-
const p =
|
|
46051
|
+
function getPrompt(name, args = {}, prompts = PROMPTS) {
|
|
46052
|
+
const p = prompts.find((x) => x.name === name);
|
|
45820
46053
|
if (!p)
|
|
45821
46054
|
throw new ToolError(
|
|
45822
|
-
`Unknown prompt "${name}". Prompts: ${
|
|
46055
|
+
`Unknown prompt "${name}". Prompts: ${prompts.map((x) => x.name).join(", ")}.`,
|
|
45823
46056
|
"not_found"
|
|
45824
46057
|
);
|
|
45825
46058
|
const missing = p.arguments.filter((x) => x.required && !args[x.name]?.trim()).map((x) => x.name);
|
|
@@ -46014,9 +46247,807 @@ var positionArg = external_exports.union([
|
|
|
46014
46247
|
external_exports.strictObject({ before: external_exports.string() }),
|
|
46015
46248
|
external_exports.strictObject({ after: external_exports.string() })
|
|
46016
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
|
+
);
|
|
46017
46253
|
var confirmArg = external_exports.boolean().optional().describe("Must be true. Ask the owner first: this cannot be undone from here.");
|
|
46018
46254
|
var deviceArg = external_exports.enum(["desktop", "tablet", "mobile"]);
|
|
46019
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
|
+
|
|
46020
47051
|
// ../mcp-tools/src/tools/components.ts
|
|
46021
47052
|
var nameArg = external_exports.string().describe('Component name, lowercase-hyphenated (e.g. "editorial-hero").');
|
|
46022
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.";
|
|
@@ -46319,13 +47350,14 @@ ${notes.join("\n")}` : ""}`,
|
|
|
46319
47350
|
var insertComponent = defineTool({
|
|
46320
47351
|
name: "insert_component",
|
|
46321
47352
|
title: "Insert component",
|
|
46322
|
-
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).",
|
|
46323
47354
|
input: external_exports.object({
|
|
46324
47355
|
site: siteArg,
|
|
46325
47356
|
page: external_exports.string().describe("Page id/slug/title."),
|
|
46326
47357
|
name: nameArg,
|
|
46327
47358
|
fields: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
46328
47359
|
theme: external_exports.enum(COLOR_THEMES).optional(),
|
|
47360
|
+
anchor: anchorArg.optional(),
|
|
46329
47361
|
at: positionArg.optional()
|
|
46330
47362
|
}),
|
|
46331
47363
|
annotations: {},
|
|
@@ -46350,6 +47382,7 @@ var insertComponent = defineTool({
|
|
|
46350
47382
|
).value;
|
|
46351
47383
|
const block4 = section.blocks[0];
|
|
46352
47384
|
block4.fields = { ...block4.fields, ...given };
|
|
47385
|
+
if (input.anchor) section.anchor = takenAnchor(doc, page, input.anchor);
|
|
46353
47386
|
bodies.push({ op: "section.insert", page, at: input.at ?? "end", section });
|
|
46354
47387
|
const { doc: next } = await ctx.apply(siteId, bodies);
|
|
46355
47388
|
const target = next.pages[page];
|
|
@@ -46680,17 +47713,17 @@ function builtInTemplates() {
|
|
|
46680
47713
|
};
|
|
46681
47714
|
});
|
|
46682
47715
|
}
|
|
46683
|
-
var
|
|
47716
|
+
var isRecord3 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
46684
47717
|
function normaliseTemplate(raw2) {
|
|
46685
|
-
if (!
|
|
46686
|
-
const meta =
|
|
47718
|
+
if (!isRecord3(raw2)) return null;
|
|
47719
|
+
const meta = isRecord3(raw2.template) ? { ...raw2, ...raw2.template } : raw2;
|
|
46687
47720
|
const id = typeof meta.id === "string" ? meta.id : null;
|
|
46688
47721
|
if (!id) return null;
|
|
46689
47722
|
const rawLooks = Array.isArray(meta.looks) ? meta.looks : [];
|
|
46690
47723
|
const looks = [];
|
|
46691
47724
|
rawLooks.forEach((l, i) => {
|
|
46692
|
-
if (!
|
|
46693
|
-
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;
|
|
46694
47727
|
const parsed = StyleChoicesSchema.safeParse(candidate);
|
|
46695
47728
|
if (!parsed.success) return;
|
|
46696
47729
|
const name = typeof l.name === "string" ? l.name : `Look ${i + 1}`;
|
|
@@ -46716,12 +47749,12 @@ async function templatesFromModule(mod) {
|
|
|
46716
47749
|
let raws = null;
|
|
46717
47750
|
const cat = mod.templateCatalog;
|
|
46718
47751
|
if (Array.isArray(cat)) raws = cat;
|
|
46719
|
-
else if (
|
|
47752
|
+
else if (isRecord3(cat) && typeof cat.list === "function")
|
|
46720
47753
|
raws = await cat.list();
|
|
46721
47754
|
else if (typeof mod.listTemplates === "function")
|
|
46722
47755
|
raws = await mod.listTemplates();
|
|
46723
47756
|
else if (Array.isArray(mod.templates)) raws = mod.templates;
|
|
46724
|
-
else if (
|
|
47757
|
+
else if (isRecord3(mod.templates)) raws = Object.values(mod.templates);
|
|
46725
47758
|
else if (typeof mod.loadTemplate === "function") {
|
|
46726
47759
|
const ids = mod.TEMPLATE_IDS ?? mod.templateIds;
|
|
46727
47760
|
if (Array.isArray(ids))
|
|
@@ -46746,312 +47779,6 @@ async function loadTemplates(store, importer = (spec) => import(
|
|
|
46746
47779
|
return { source: "built-in", templates: builtInTemplates() };
|
|
46747
47780
|
}
|
|
46748
47781
|
|
|
46749
|
-
// ../mcp-tools/src/tools/read.ts
|
|
46750
|
-
var listSites = defineTool({
|
|
46751
|
-
name: "list_sites",
|
|
46752
|
-
title: "List sites",
|
|
46753
|
-
description: "Sites this connection can edit, with their titles and live addresses. Start here when you don't know the site id.",
|
|
46754
|
-
input: external_exports.object({}),
|
|
46755
|
-
annotations: { readOnlyHint: true },
|
|
46756
|
-
async handler(ctx) {
|
|
46757
|
-
const allowed = new Set(await ctx.allowedSites());
|
|
46758
|
-
const sites = (await ctx.store.listSites()).filter((s2) => allowed.has(s2.id));
|
|
46759
|
-
return { data: { sites, canPublish: ctx.grant.publish && ctx.store.capabilities.canPublish } };
|
|
46760
|
-
}
|
|
46761
|
-
});
|
|
46762
|
-
function stylesSummary(doc) {
|
|
46763
|
-
const compiled2 = compileStyles(doc.styles);
|
|
46764
|
-
const pairing = getPairing(doc.styles.choices.pairing);
|
|
46765
|
-
return {
|
|
46766
|
-
choices: doc.styles.choices,
|
|
46767
|
-
pairing: pairing ? { id: pairing.id, heading: pairing.heading.family, body: pairing.body.family } : { id: doc.styles.choices.pairing, unknown: true },
|
|
46768
|
-
overrides: doc.styles.overrides ?? null,
|
|
46769
|
-
brand: compiled2.tokens.palette.brand,
|
|
46770
|
-
themes: Object.fromEntries(
|
|
46771
|
-
Object.entries(compiled2.tokens.themes).map(([theme, c]) => [
|
|
46772
|
-
theme,
|
|
46773
|
-
{
|
|
46774
|
-
background: c.background,
|
|
46775
|
-
text: c.text,
|
|
46776
|
-
heading: c.heading,
|
|
46777
|
-
accent: c.accent,
|
|
46778
|
-
"accent-text": c["accent-text"]
|
|
46779
|
-
}
|
|
46780
|
-
])
|
|
46781
|
-
),
|
|
46782
|
-
contrast: {
|
|
46783
|
-
mode: compiled2.contrast.mode,
|
|
46784
|
-
adjustedBySolver: compiled2.contrast.adjustments.length,
|
|
46785
|
-
failures: compiled2.contrast.failures.length
|
|
46786
|
-
}
|
|
46787
|
-
};
|
|
46788
|
-
}
|
|
46789
|
-
function pageSummary(page) {
|
|
46790
|
-
return {
|
|
46791
|
-
id: page.id,
|
|
46792
|
-
title: page.title,
|
|
46793
|
-
slug: page.slug,
|
|
46794
|
-
path: pagePathOf(page),
|
|
46795
|
-
sections: page.sections.length,
|
|
46796
|
-
hidden: page.hidden ?? false
|
|
46797
|
-
};
|
|
46798
|
-
}
|
|
46799
|
-
var getSite = defineTool({
|
|
46800
|
-
name: "get_site",
|
|
46801
|
-
title: "Get site",
|
|
46802
|
-
description: "Site settings, navigation, header/footer, Site Styles summary, page list and collections. Call before editing to learn page ids and the current look.",
|
|
46803
|
-
input: external_exports.object({ site: siteArg }),
|
|
46804
|
-
annotations: { readOnlyHint: true },
|
|
46805
|
-
async handler(ctx, input) {
|
|
46806
|
-
const siteId = await ctx.siteId(input.site);
|
|
46807
|
-
const doc = await ctx.doc(siteId);
|
|
46808
|
-
const { site: site2 } = doc;
|
|
46809
|
-
return {
|
|
46810
|
-
data: {
|
|
46811
|
-
id: siteId,
|
|
46812
|
-
title: site2.title,
|
|
46813
|
-
tagline: site2.tagline ?? null,
|
|
46814
|
-
template: doc.config.template ?? null,
|
|
46815
|
-
url: doc.config.deploy.customDomain ? `https://${doc.config.deploy.customDomain}` : doc.config.deploy.url,
|
|
46816
|
-
nav: site2.nav,
|
|
46817
|
-
header: site2.header,
|
|
46818
|
-
footer: {
|
|
46819
|
-
sectionId: site2.footer.id,
|
|
46820
|
-
layout: site2.footer.layout.mode,
|
|
46821
|
-
blocks: site2.footer.blocks.map((b) => ({ id: b.id, type: b.type })),
|
|
46822
|
-
note: 'Edit footer blocks with the block tools using page "footer".'
|
|
46823
|
-
},
|
|
46824
|
-
announcement: site2.announcement ?? null,
|
|
46825
|
-
social: site2.social ?? [],
|
|
46826
|
-
seo: site2.seo ?? null,
|
|
46827
|
-
styles: stylesSummary(doc),
|
|
46828
|
-
pages: Object.values(doc.pages).map(pageSummary),
|
|
46829
|
-
collections: Object.entries(doc.collections).map(([name, items]) => ({
|
|
46830
|
-
name,
|
|
46831
|
-
kind: Object.values(items)[0]?.kind ?? doc.collectionConfigs?.[name]?.kind ?? null,
|
|
46832
|
-
items: Object.keys(items).length
|
|
46833
|
-
})),
|
|
46834
|
-
media: Object.keys(doc.media).length,
|
|
46835
|
-
capabilities: {
|
|
46836
|
-
...ctx.store.capabilities,
|
|
46837
|
-
// Same answer as list_sites: publishing needs both the store and this connection.
|
|
46838
|
-
canPublish: ctx.grant.publish && ctx.store.capabilities.canPublish,
|
|
46839
|
-
grantPublish: ctx.grant.publish
|
|
46840
|
-
}
|
|
46841
|
-
}
|
|
46842
|
-
};
|
|
46843
|
-
}
|
|
46844
|
-
});
|
|
46845
|
-
function sectionView(ctx, section, detail, doc) {
|
|
46846
|
-
const registry3 = doc ? ctx.registryFor(doc) : ctx.registry;
|
|
46847
|
-
const blocks2 = section.blocks.map((b) => {
|
|
46848
|
-
if (detail === "outline") {
|
|
46849
|
-
const out = {
|
|
46850
|
-
id: b.id,
|
|
46851
|
-
type: b.type,
|
|
46852
|
-
text: blockPreview(registry3, b)
|
|
46853
|
-
};
|
|
46854
|
-
if (b.variant) out.variant = b.variant;
|
|
46855
|
-
if (b.column !== void 0) out.column = b.column;
|
|
46856
|
-
if (b.hidden) out.hidden = true;
|
|
46857
|
-
return out;
|
|
46858
|
-
}
|
|
46859
|
-
return blockToAgent(registry3, b);
|
|
46860
|
-
});
|
|
46861
|
-
const base = {
|
|
46862
|
-
id: section.id,
|
|
46863
|
-
...section.preset ? { preset: section.preset } : {},
|
|
46864
|
-
...section.label ? { label: section.label } : {},
|
|
46865
|
-
...section.hidden ? { hidden: true } : {},
|
|
46866
|
-
...section.locked ? { locked: true } : {},
|
|
46867
|
-
theme: section.style?.colorTheme ?? "light"
|
|
46868
|
-
};
|
|
46869
|
-
if (detail === "outline") {
|
|
46870
|
-
base.layout = section.layout.mode === "grid" ? "grid" : `stack ${JSON.stringify(section.layout.columns.d)}`;
|
|
46871
|
-
base.blocks = blocks2;
|
|
46872
|
-
return base;
|
|
46873
|
-
}
|
|
46874
|
-
return {
|
|
46875
|
-
...base,
|
|
46876
|
-
layout: section.layout,
|
|
46877
|
-
style: section.style ?? {},
|
|
46878
|
-
...section.anchor ? { anchor: section.anchor } : {},
|
|
46879
|
-
blocks: blocks2
|
|
46880
|
-
};
|
|
46881
|
-
}
|
|
46882
|
-
var getPage2 = defineTool({
|
|
46883
|
-
name: "get_page",
|
|
46884
|
-
title: "Get page",
|
|
46885
|
-
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.`,
|
|
46886
|
-
input: external_exports.object({
|
|
46887
|
-
site: siteArg,
|
|
46888
|
-
page: containerArg,
|
|
46889
|
-
detail: external_exports.enum(["full", "outline"]).optional().describe('Default "full".')
|
|
46890
|
-
}),
|
|
46891
|
-
annotations: { readOnlyHint: true },
|
|
46892
|
-
async handler(ctx, input) {
|
|
46893
|
-
const siteId = await ctx.siteId(input.site);
|
|
46894
|
-
const doc = await ctx.doc(siteId);
|
|
46895
|
-
const detail = input.detail ?? "full";
|
|
46896
|
-
const { id, page } = resolvePage(doc, input.page, { allowFooter: true });
|
|
46897
|
-
if (id === FOOTER || !page)
|
|
46898
|
-
return { data: { page: FOOTER, sections: [sectionView(ctx, doc.site.footer, detail, doc)] } };
|
|
46899
|
-
return {
|
|
46900
|
-
data: {
|
|
46901
|
-
id: page.id,
|
|
46902
|
-
title: page.title,
|
|
46903
|
-
slug: page.slug,
|
|
46904
|
-
path: pagePathOf(page),
|
|
46905
|
-
seo: page.seo ?? {},
|
|
46906
|
-
...page.header ? { header: page.header } : {},
|
|
46907
|
-
...page.hidden ? { hidden: true } : {},
|
|
46908
|
-
sections: page.sections.map((s2) => sectionView(ctx, s2, detail, doc))
|
|
46909
|
-
}
|
|
46910
|
-
};
|
|
46911
|
-
}
|
|
46912
|
-
});
|
|
46913
|
-
function blockMetaView(m, full) {
|
|
46914
|
-
const fields = Object.fromEntries(
|
|
46915
|
-
Object.entries(m.fields).map(([k, f]) => {
|
|
46916
|
-
const v = {
|
|
46917
|
-
kind: f.kind === "richText" ? "richText (Markdown)" : f.kind
|
|
46918
|
-
};
|
|
46919
|
-
if (f.optional) v.optional = true;
|
|
46920
|
-
if (f.label) v.label = f.label;
|
|
46921
|
-
if (full && f.help) v.help = f.help;
|
|
46922
|
-
if (f.ai) v.ai = f.ai;
|
|
46923
|
-
if (f.kind === "select") v.options = f.options.map((o) => o.value);
|
|
46924
|
-
if (f.kind === "text" && f.maxLength) v.maxLength = f.maxLength;
|
|
46925
|
-
if (f.kind === "list")
|
|
46926
|
-
v.itemFields = Object.fromEntries(
|
|
46927
|
-
Object.entries(f.fields).map(([ik, iv]) => [
|
|
46928
|
-
ik,
|
|
46929
|
-
iv.kind === "richText" ? "richText (Markdown)" : iv.kind
|
|
46930
|
-
])
|
|
46931
|
-
);
|
|
46932
|
-
if (f.kind === "image") v.shape = "{ src, alt, focal? }";
|
|
46933
|
-
if (f.kind === "link") v.allow = f.allow;
|
|
46934
|
-
return [k, v];
|
|
46935
|
-
})
|
|
46936
|
-
);
|
|
46937
|
-
return {
|
|
46938
|
-
type: m.type,
|
|
46939
|
-
label: m.label,
|
|
46940
|
-
category: m.category,
|
|
46941
|
-
description: m.description,
|
|
46942
|
-
variants: m.variants,
|
|
46943
|
-
styleSlots: m.styleSlots,
|
|
46944
|
-
fields,
|
|
46945
|
-
ai: m.ai
|
|
46946
|
-
};
|
|
46947
|
-
}
|
|
46948
|
-
var listBlocks = defineTool({
|
|
46949
|
-
name: "list_blocks",
|
|
46950
|
-
title: "List blocks",
|
|
46951
|
-
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.",
|
|
46952
|
-
input: external_exports.object({
|
|
46953
|
-
category: external_exports.string().optional(),
|
|
46954
|
-
type: external_exports.string().optional().describe("One block type for full detail.")
|
|
46955
|
-
}),
|
|
46956
|
-
annotations: { readOnlyHint: true },
|
|
46957
|
-
async handler(ctx, input) {
|
|
46958
|
-
let metas = ctx.registry.meta();
|
|
46959
|
-
if (input.type) {
|
|
46960
|
-
metas = metas.filter((m) => m.type === input.type);
|
|
46961
|
-
if (!metas.length)
|
|
46962
|
-
throw new ToolError(
|
|
46963
|
-
`Unknown block type "${input.type}". Types: ${ctx.registry.types().join(", ")}.`,
|
|
46964
|
-
"not_found"
|
|
46965
|
-
);
|
|
46966
|
-
}
|
|
46967
|
-
if (input.category) metas = metas.filter((m) => m.category === input.category);
|
|
46968
|
-
return {
|
|
46969
|
-
data: {
|
|
46970
|
-
blocks: metas.map((m) => blockMetaView(m, Boolean(input.type))),
|
|
46971
|
-
categories: [...new Set(ctx.registry.meta().map((m) => m.category))]
|
|
46972
|
-
}
|
|
46973
|
-
};
|
|
46974
|
-
}
|
|
46975
|
-
});
|
|
46976
|
-
var getBlockSchema = defineTool({
|
|
46977
|
-
name: "get_block_schema",
|
|
46978
|
-
title: "Get block schema",
|
|
46979
|
-
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.",
|
|
46980
|
-
input: external_exports.object({ type: external_exports.string() }),
|
|
46981
|
-
annotations: { readOnlyHint: true },
|
|
46982
|
-
async handler(ctx, input) {
|
|
46983
|
-
const def = ctx.registry.get(input.type);
|
|
46984
|
-
if (!def)
|
|
46985
|
-
throw new ToolError(
|
|
46986
|
-
`Unknown block type "${input.type}". Types: ${ctx.registry.types().join(", ")}.`,
|
|
46987
|
-
"not_found"
|
|
46988
|
-
);
|
|
46989
|
-
return {
|
|
46990
|
-
data: {
|
|
46991
|
-
type: def.type,
|
|
46992
|
-
version: def.version,
|
|
46993
|
-
variants: def.meta.variants,
|
|
46994
|
-
styleSlots: def.meta.styleSlots,
|
|
46995
|
-
fields: blockFieldsJsonSchema(def)
|
|
46996
|
-
}
|
|
46997
|
-
};
|
|
46998
|
-
}
|
|
46999
|
-
});
|
|
47000
|
-
var listSectionPresets = defineTool({
|
|
47001
|
-
name: "list_section_presets",
|
|
47002
|
-
title: "List section presets",
|
|
47003
|
-
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.",
|
|
47004
|
-
input: external_exports.object({
|
|
47005
|
-
category: external_exports.enum(PRESET_CATEGORIES).optional(),
|
|
47006
|
-
query: external_exports.string().optional().describe("Words to match in label, description or tags."),
|
|
47007
|
-
detail: external_exports.enum(["brief", "full"]).optional().describe('Default "brief".')
|
|
47008
|
-
}),
|
|
47009
|
-
annotations: { readOnlyHint: true },
|
|
47010
|
-
async handler(_ctx, input) {
|
|
47011
|
-
let list5 = presetCatalog.slice();
|
|
47012
|
-
if (input.category) list5 = list5.filter((p) => p.category === input.category);
|
|
47013
|
-
if (input.query) {
|
|
47014
|
-
const words4 = input.query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
47015
|
-
list5 = list5.filter((p) => {
|
|
47016
|
-
const hay = `${p.id} ${p.label} ${p.description} ${p.tags.join(" ")} ${p.ai.whenToUse}`.toLowerCase();
|
|
47017
|
-
return words4.every((w) => hay.includes(w));
|
|
47018
|
-
});
|
|
47019
|
-
}
|
|
47020
|
-
const full = input.detail === "full";
|
|
47021
|
-
return {
|
|
47022
|
-
data: {
|
|
47023
|
-
presets: list5.map(
|
|
47024
|
-
(p) => full ? p : {
|
|
47025
|
-
id: p.id,
|
|
47026
|
-
label: p.label,
|
|
47027
|
-
description: p.description,
|
|
47028
|
-
target: p.target,
|
|
47029
|
-
layout: p.layout,
|
|
47030
|
-
overlap: p.overlap,
|
|
47031
|
-
themes: p.themes,
|
|
47032
|
-
whenToUse: p.ai.whenToUse
|
|
47033
|
-
}
|
|
47034
|
-
),
|
|
47035
|
-
categories: PRESET_CATEGORIES
|
|
47036
|
-
}
|
|
47037
|
-
};
|
|
47038
|
-
}
|
|
47039
|
-
});
|
|
47040
|
-
function findPreset(id) {
|
|
47041
|
-
const exact = getPreset(id);
|
|
47042
|
-
if (exact) return exact;
|
|
47043
|
-
const base = id.replace(/@\d+$/, "");
|
|
47044
|
-
const matches = presetCatalog.filter((p) => p.id.startsWith(`${base}@`)).sort((a, b) => b.version - a.version);
|
|
47045
|
-
const best = matches[0] && getPreset(matches[0].id);
|
|
47046
|
-
if (best) return best;
|
|
47047
|
-
const category = base.split("/")[0] ?? "";
|
|
47048
|
-
const near = presetCatalog.filter((p) => p.category === category).map((p) => p.id);
|
|
47049
|
-
throw new ToolError(
|
|
47050
|
-
`Unknown preset "${id}".${near.length ? ` In ${category}: ${near.join(", ")}.` : " Call list_section_presets."}`,
|
|
47051
|
-
"not_found"
|
|
47052
|
-
);
|
|
47053
|
-
}
|
|
47054
|
-
|
|
47055
47782
|
// ../mcp-tools/src/tools/design.ts
|
|
47056
47783
|
var stripMeta = (op2) => {
|
|
47057
47784
|
const { id: _i, actor: _a3, ts: _t, ...body } = op2;
|
|
@@ -47196,7 +47923,7 @@ ${(adjusted.ok ? left.map((i) => `- ${i.message}`) : issueLines(adjusted.errors)
|
|
|
47196
47923
|
solverNotes
|
|
47197
47924
|
};
|
|
47198
47925
|
}
|
|
47199
|
-
var
|
|
47926
|
+
var isRecord4 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
47200
47927
|
var setStyles = defineTool({
|
|
47201
47928
|
name: "set_styles",
|
|
47202
47929
|
title: "Set Site Styles",
|
|
@@ -47247,8 +47974,8 @@ var setStyles = defineTool({
|
|
|
47247
47974
|
for (const [k, v] of Object.entries(c.layout ?? {}))
|
|
47248
47975
|
if (v !== void 0) set2[`choices.layout.${k}`] = v;
|
|
47249
47976
|
for (const [path2, v] of Object.entries(input.overrides?.set ?? {})) {
|
|
47250
|
-
const token =
|
|
47251
|
-
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)
|
|
47252
47979
|
token.$type = "color";
|
|
47253
47980
|
set2[`overrides.${path2}`] = token;
|
|
47254
47981
|
}
|
|
@@ -47689,478 +48416,6 @@ var updateDesignSystem = defineTool({
|
|
|
47689
48416
|
});
|
|
47690
48417
|
var designSystemTools = [getDesignSystem, updateDesignSystem];
|
|
47691
48418
|
|
|
47692
|
-
// ../mcp-tools/src/tools/edit.ts
|
|
47693
|
-
var isRecord4 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
47694
|
-
var themeArg = external_exports.enum(COLOR_THEMES).describe("Section color theme.");
|
|
47695
|
-
var blockSummary = (registry3, b) => ({
|
|
47696
|
-
id: b.id,
|
|
47697
|
-
type: b.type,
|
|
47698
|
-
...b.variant ? { variant: b.variant } : {},
|
|
47699
|
-
...b.column !== void 0 ? { column: b.column } : {},
|
|
47700
|
-
text: blockPreview(registry3, b)
|
|
47701
|
-
});
|
|
47702
|
-
function sectionSummary(registry3, s2) {
|
|
47703
|
-
return {
|
|
47704
|
-
id: s2.id,
|
|
47705
|
-
...s2.preset ? { preset: s2.preset } : {},
|
|
47706
|
-
theme: s2.style?.colorTheme ?? "light",
|
|
47707
|
-
layout: s2.layout.mode,
|
|
47708
|
-
blocks: s2.blocks.map((b) => blockSummary(registry3, b))
|
|
47709
|
-
};
|
|
47710
|
-
}
|
|
47711
|
-
function container(doc, ref) {
|
|
47712
|
-
return resolvePage(doc, ref, { allowFooter: true }).id;
|
|
47713
|
-
}
|
|
47714
|
-
function pageOnly(doc, ref) {
|
|
47715
|
-
if (ref === FOOTER)
|
|
47716
|
-
throw new ToolError(
|
|
47717
|
-
"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.",
|
|
47718
|
-
"invalid_arguments"
|
|
47719
|
-
);
|
|
47720
|
-
return resolvePage(doc, ref).id;
|
|
47721
|
-
}
|
|
47722
|
-
function requireSection2(doc, page, section) {
|
|
47723
|
-
const loc = findSection(doc, page, section);
|
|
47724
|
-
if (!loc) {
|
|
47725
|
-
const ids = page === FOOTER ? [doc.site.footer.id] : doc.pages[page]?.sections.map(
|
|
47726
|
-
(s2) => s2.id
|
|
47727
|
-
) ?? [];
|
|
47728
|
-
throw new ToolError(
|
|
47729
|
-
`No section ${section} on ${page}. Sections there: ${ids.join(", ") || "none"}.`,
|
|
47730
|
-
"not_found"
|
|
47731
|
-
);
|
|
47732
|
-
}
|
|
47733
|
-
return loc.section;
|
|
47734
|
-
}
|
|
47735
|
-
function requireBlock(doc, page, block4) {
|
|
47736
|
-
const loc = findBlock(doc, page, block4);
|
|
47737
|
-
if (loc) return { block: loc.block, section: loc.section };
|
|
47738
|
-
const elsewhere = locateBlock(doc, block4);
|
|
47739
|
-
throw new ToolError(
|
|
47740
|
-
elsewhere ? `Block ${block4} is on ${elsewhere.container}, not ${page}.` : `No block ${block4}. Call get_page to see block ids.`,
|
|
47741
|
-
"not_found"
|
|
47742
|
-
);
|
|
47743
|
-
}
|
|
47744
|
-
function makeBlock2(registry3, input) {
|
|
47745
|
-
const def = registry3.get(input.type);
|
|
47746
|
-
if (!def)
|
|
47747
|
-
throw new ToolError(
|
|
47748
|
-
`Unknown block type "${input.type}". Types: ${registry3.types().join(", ")}.`,
|
|
47749
|
-
"invalid_arguments"
|
|
47750
|
-
);
|
|
47751
|
-
const fields = {};
|
|
47752
|
-
for (const [k, m] of Object.entries(def.meta.fields))
|
|
47753
|
-
if (!m.optional && m.default !== void 0) fields[k] = structuredClone(m.default);
|
|
47754
|
-
Object.assign(fields, fieldsFromAgent(registry3, input.type, input.fields ?? {}));
|
|
47755
|
-
const { type, fields: _f, id, v: _v, ...rest } = input;
|
|
47756
|
-
return {
|
|
47757
|
-
...rest,
|
|
47758
|
-
id: id ?? newId("blk"),
|
|
47759
|
-
type,
|
|
47760
|
-
v: def.version,
|
|
47761
|
-
fields
|
|
47762
|
-
};
|
|
47763
|
-
}
|
|
47764
|
-
function makeSection(registry3, raw2) {
|
|
47765
|
-
const blocks2 = Array.isArray(raw2.blocks) ? raw2.blocks : [];
|
|
47766
|
-
return {
|
|
47767
|
-
...raw2,
|
|
47768
|
-
id: typeof raw2.id === "string" ? raw2.id : newId("sec"),
|
|
47769
|
-
layout: raw2.layout ?? { mode: "stack", columns: { d: [12] } },
|
|
47770
|
-
style: isRecord4(raw2.style) ? raw2.style : { colorTheme: "light" },
|
|
47771
|
-
blocks: blocks2.map((b, i) => {
|
|
47772
|
-
if (!isRecord4(b) || typeof b.type !== "string")
|
|
47773
|
-
throw new ToolError(`section.blocks[${i}] needs a "type".`, "invalid_arguments");
|
|
47774
|
-
return makeBlock2(registry3, b);
|
|
47775
|
-
})
|
|
47776
|
-
};
|
|
47777
|
-
}
|
|
47778
|
-
var insertSection = defineTool({
|
|
47779
|
-
name: "insert_section",
|
|
47780
|
-
title: "Insert section",
|
|
47781
|
-
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.",
|
|
47782
|
-
input: external_exports.object({
|
|
47783
|
-
site: siteArg,
|
|
47784
|
-
page: external_exports.string().optional().describe("Page id/slug/title (not needed for header/footer presets)."),
|
|
47785
|
-
preset: external_exports.string().optional().describe('Preset id, e.g. "hero/split-image@1" (version optional).'),
|
|
47786
|
-
section: external_exports.looseObject({}).optional().describe(
|
|
47787
|
-
"Explicit section ({layout, style, blocks:[{type, fields, variant?, column?}]}); ids are generated when omitted; rich text as Markdown."
|
|
47788
|
-
),
|
|
47789
|
-
at: positionArg.optional(),
|
|
47790
|
-
colorTheme: themeArg.optional(),
|
|
47791
|
-
primaryLink: external_exports.unknown().optional().describe(
|
|
47792
|
-
"Link for the main call to action ({type:'page', page:'pg_\u2026'} or {type:'url', url})."
|
|
47793
|
-
),
|
|
47794
|
-
secondaryLink: external_exports.unknown().optional(),
|
|
47795
|
-
collection: external_exports.string().optional().describe("Collection for summary/events/product presets."),
|
|
47796
|
-
label: external_exports.string().max(80).optional().describe("Layers label for the section.")
|
|
47797
|
-
}),
|
|
47798
|
-
annotations: {},
|
|
47799
|
-
async handler(ctx, input) {
|
|
47800
|
-
const siteId = await ctx.siteId(input.site);
|
|
47801
|
-
const doc = await ctx.doc(siteId);
|
|
47802
|
-
if (!!input.preset === !!input.section)
|
|
47803
|
-
throw new ToolError("Give exactly one of preset or section.", "invalid_arguments");
|
|
47804
|
-
const links = {};
|
|
47805
|
-
for (const k of ["primaryLink", "secondaryLink"]) {
|
|
47806
|
-
if (input[k] === void 0) continue;
|
|
47807
|
-
const r = LinkSchema.safeParse(input[k]);
|
|
47808
|
-
if (!r.success)
|
|
47809
|
-
throw new ToolError(
|
|
47810
|
-
`${k} is not a valid link: ${r.error.issues.map((i) => i.message).join("; ")}`,
|
|
47811
|
-
"invalid_arguments"
|
|
47812
|
-
);
|
|
47813
|
-
links[k] = r.data;
|
|
47814
|
-
}
|
|
47815
|
-
if (input.preset) {
|
|
47816
|
-
const preset = findPreset(input.preset);
|
|
47817
|
-
const opts = { colorTheme: input.colorTheme, collection: input.collection, ...links };
|
|
47818
|
-
const linkNote = (changes2) => changes2.length ? {
|
|
47819
|
-
linksChanged: changes2.map((c) => ({ from: c.from, to: c.to })),
|
|
47820
|
-
linkNote: "Example links that lead nowhere on this site were pointed at existing pages; change them with update_block."
|
|
47821
|
-
} : {};
|
|
47822
|
-
if (preset.target === "header") {
|
|
47823
|
-
const { value: header, changes: changes2 } = resolveInternalLinks(preset.build(opts), doc);
|
|
47824
|
-
await ctx.apply(siteId, [{ op: "site.update", set: { header } }]);
|
|
47825
|
-
return {
|
|
47826
|
-
data: {
|
|
47827
|
-
applied: preset.id,
|
|
47828
|
-
header,
|
|
47829
|
-
note: "The site header now uses this preset.",
|
|
47830
|
-
...linkNote(changes2)
|
|
47831
|
-
}
|
|
47832
|
-
};
|
|
47833
|
-
}
|
|
47834
|
-
const { value: section2, changes } = resolveInternalLinks(preset.build(opts), doc);
|
|
47835
|
-
if (input.label) section2.label = input.label;
|
|
47836
|
-
if (preset.target === "footer") {
|
|
47837
|
-
await ctx.apply(siteId, [{ op: "site.update", set: { footer: section2 } }]);
|
|
47838
|
-
return {
|
|
47839
|
-
data: {
|
|
47840
|
-
replaced: "footer",
|
|
47841
|
-
section: sectionSummary(ctx.registry, section2),
|
|
47842
|
-
note: 'Edit footer blocks with page "footer".',
|
|
47843
|
-
...linkNote(changes)
|
|
47844
|
-
}
|
|
47845
|
-
};
|
|
47846
|
-
}
|
|
47847
|
-
if (!input.page)
|
|
47848
|
-
throw new ToolError("page is required for page sections.", "invalid_arguments");
|
|
47849
|
-
const page2 = pageOnly(doc, input.page);
|
|
47850
|
-
await ctx.apply(siteId, [
|
|
47851
|
-
{ op: "section.insert", page: page2, at: input.at ?? "end", section: section2 }
|
|
47852
|
-
]);
|
|
47853
|
-
return {
|
|
47854
|
-
data: {
|
|
47855
|
-
page: page2,
|
|
47856
|
-
section: sectionSummary(ctx.registry, section2),
|
|
47857
|
-
copyGuidance: preset.copy.map((c) => `${c.label}: ${c.words[0]}\u2013${c.words[1]} words`),
|
|
47858
|
-
next: "Replace the demo copy with the owner's content (update_block), then run design_check.",
|
|
47859
|
-
...linkNote(changes)
|
|
47860
|
-
}
|
|
47861
|
-
};
|
|
47862
|
-
}
|
|
47863
|
-
if (!input.page) throw new ToolError("page is required.", "invalid_arguments");
|
|
47864
|
-
const page = pageOnly(doc, input.page);
|
|
47865
|
-
const section = makeSection(ctx.registry, input.section);
|
|
47866
|
-
if (input.colorTheme)
|
|
47867
|
-
section.style = { ...section.style ?? {}, colorTheme: input.colorTheme };
|
|
47868
|
-
if (input.label) section.label = input.label;
|
|
47869
|
-
await ctx.apply(siteId, [
|
|
47870
|
-
{ op: "section.insert", page, at: input.at ?? "end", section }
|
|
47871
|
-
]);
|
|
47872
|
-
return { data: { page, section: sectionSummary(ctx.registry, section) } };
|
|
47873
|
-
}
|
|
47874
|
-
});
|
|
47875
|
-
var updateSection = defineTool({
|
|
47876
|
-
name: "update_section",
|
|
47877
|
-
title: "Update section",
|
|
47878
|
-
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.`,
|
|
47879
|
-
input: external_exports.object({
|
|
47880
|
-
site: siteArg,
|
|
47881
|
-
page: containerArg,
|
|
47882
|
-
section: external_exports.string(),
|
|
47883
|
-
set: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
47884
|
-
unset: external_exports.array(external_exports.string()).optional(),
|
|
47885
|
-
colorTheme: themeArg.optional()
|
|
47886
|
-
}),
|
|
47887
|
-
annotations: { idempotentHint: true },
|
|
47888
|
-
async handler(ctx, input) {
|
|
47889
|
-
const siteId = await ctx.siteId(input.site);
|
|
47890
|
-
const doc = await ctx.doc(siteId);
|
|
47891
|
-
const page = container(doc, input.page);
|
|
47892
|
-
requireSection2(doc, page, input.section);
|
|
47893
|
-
const set2 = { ...input.set ?? {} };
|
|
47894
|
-
if (input.colorTheme) set2["style.colorTheme"] = input.colorTheme;
|
|
47895
|
-
const body = { op: "section.update", page, section: input.section };
|
|
47896
|
-
if (Object.keys(set2).length) body.set = set2;
|
|
47897
|
-
if (input.unset?.length) body.unset = input.unset;
|
|
47898
|
-
const { doc: next } = await ctx.apply(siteId, [body]);
|
|
47899
|
-
return {
|
|
47900
|
-
data: { section: sectionSummary(ctx.registry, requireSection2(next, page, input.section)) }
|
|
47901
|
-
};
|
|
47902
|
-
}
|
|
47903
|
-
});
|
|
47904
|
-
var moveSection = defineTool({
|
|
47905
|
-
name: "move_section",
|
|
47906
|
-
title: "Move section",
|
|
47907
|
-
description: "Reorder a section within its page.",
|
|
47908
|
-
input: external_exports.object({ site: siteArg, page: external_exports.string(), section: external_exports.string(), at: positionArg }),
|
|
47909
|
-
annotations: { idempotentHint: true },
|
|
47910
|
-
async handler(ctx, input) {
|
|
47911
|
-
const siteId = await ctx.siteId(input.site);
|
|
47912
|
-
const doc = await ctx.doc(siteId);
|
|
47913
|
-
const page = pageOnly(doc, input.page);
|
|
47914
|
-
requireSection2(doc, page, input.section);
|
|
47915
|
-
const { doc: next } = await ctx.apply(siteId, [
|
|
47916
|
-
{ op: "section.move", page, section: input.section, at: input.at }
|
|
47917
|
-
]);
|
|
47918
|
-
return { data: { order: next.pages[page]?.sections.map((s2) => s2.id) } };
|
|
47919
|
-
}
|
|
47920
|
-
});
|
|
47921
|
-
var deleteSection = defineTool({
|
|
47922
|
-
name: "delete_section",
|
|
47923
|
-
title: "Delete section",
|
|
47924
|
-
description: "Remove a section and its blocks from a page (recoverable from the session snapshot or discard_draft).",
|
|
47925
|
-
input: external_exports.object({ site: siteArg, page: external_exports.string(), section: external_exports.string() }),
|
|
47926
|
-
annotations: { destructiveHint: true },
|
|
47927
|
-
async handler(ctx, input) {
|
|
47928
|
-
const siteId = await ctx.siteId(input.site);
|
|
47929
|
-
const doc = await ctx.doc(siteId);
|
|
47930
|
-
const page = pageOnly(doc, input.page);
|
|
47931
|
-
requireSection2(doc, page, input.section);
|
|
47932
|
-
await ctx.apply(siteId, [{ op: "section.delete", page, section: input.section }]);
|
|
47933
|
-
return { data: { deleted: input.section } };
|
|
47934
|
-
}
|
|
47935
|
-
});
|
|
47936
|
-
var gridItemArg = external_exports.looseObject({}).describe(
|
|
47937
|
-
"Grid placement {d:{x,y,w,h,z?,bleed?}, t?, m?}: 24 columns on desktop/tablet, 8 on mobile, 1-based."
|
|
47938
|
-
);
|
|
47939
|
-
var insertBlock = defineTool({
|
|
47940
|
-
name: "insert_block",
|
|
47941
|
-
title: "Insert block",
|
|
47942
|
-
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).",
|
|
47943
|
-
input: external_exports.object({
|
|
47944
|
-
site: siteArg,
|
|
47945
|
-
page: containerArg,
|
|
47946
|
-
section: external_exports.string(),
|
|
47947
|
-
block: external_exports.looseObject({
|
|
47948
|
-
type: external_exports.string(),
|
|
47949
|
-
fields: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
47950
|
-
variant: external_exports.string().optional(),
|
|
47951
|
-
style: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
47952
|
-
column: external_exports.int().min(0).max(3).optional(),
|
|
47953
|
-
label: external_exports.string().optional()
|
|
47954
|
-
}).describe("{type, fields, variant?, style?, column?, label?}"),
|
|
47955
|
-
at: positionArg.optional(),
|
|
47956
|
-
place: gridItemArg.optional()
|
|
47957
|
-
}),
|
|
47958
|
-
annotations: {},
|
|
47959
|
-
async handler(ctx, input) {
|
|
47960
|
-
const siteId = await ctx.siteId(input.site);
|
|
47961
|
-
const doc = await ctx.doc(siteId);
|
|
47962
|
-
const page = container(doc, input.page);
|
|
47963
|
-
requireSection2(doc, page, input.section);
|
|
47964
|
-
const made = makeBlock2(ctx.registryFor(doc), input.block);
|
|
47965
|
-
const block4 = resolveInternalLinks(made, doc).value;
|
|
47966
|
-
for (const k of Object.keys(input.block.fields ?? {}))
|
|
47967
|
-
if (k in made.fields) block4.fields[k] = made.fields[k];
|
|
47968
|
-
const body = {
|
|
47969
|
-
op: "block.insert",
|
|
47970
|
-
page,
|
|
47971
|
-
section: input.section,
|
|
47972
|
-
at: input.at ?? "end",
|
|
47973
|
-
block: block4
|
|
47974
|
-
};
|
|
47975
|
-
if (input.place) body.place = input.place;
|
|
47976
|
-
const { doc: next, warnings } = await ctx.apply(siteId, [body]);
|
|
47977
|
-
const placed = findBlock(next, page, block4.id);
|
|
47978
|
-
return {
|
|
47979
|
-
data: {
|
|
47980
|
-
block: blockSummary(ctx.registryFor(next), placed?.block ?? block4),
|
|
47981
|
-
...warnings.length ? { warnings } : {}
|
|
47982
|
-
}
|
|
47983
|
-
};
|
|
47984
|
-
}
|
|
47985
|
-
});
|
|
47986
|
-
var updateBlock = defineTool({
|
|
47987
|
-
name: "update_block",
|
|
47988
|
-
title: "Update block",
|
|
47989
|
-
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.",
|
|
47990
|
-
input: external_exports.object({
|
|
47991
|
-
site: siteArg,
|
|
47992
|
-
page: containerArg,
|
|
47993
|
-
block: external_exports.string(),
|
|
47994
|
-
set: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
47995
|
-
unset: external_exports.array(external_exports.string()).optional(),
|
|
47996
|
-
variant: external_exports.string().nullable().optional(),
|
|
47997
|
-
style: external_exports.record(external_exports.string(), external_exports.unknown()).nullable().optional(),
|
|
47998
|
-
column: external_exports.int().min(0).max(3).nullable().optional(),
|
|
47999
|
-
label: external_exports.string().max(80).nullable().optional(),
|
|
48000
|
-
hidden: external_exports.boolean().nullable().optional()
|
|
48001
|
-
}),
|
|
48002
|
-
annotations: { idempotentHint: true },
|
|
48003
|
-
async handler(ctx, input) {
|
|
48004
|
-
const siteId = await ctx.siteId(input.site);
|
|
48005
|
-
const doc = await ctx.doc(siteId);
|
|
48006
|
-
const page = container(doc, input.page);
|
|
48007
|
-
const { block: block4 } = requireBlock(doc, page, input.block);
|
|
48008
|
-
const body = { op: "block.update", page, block: input.block };
|
|
48009
|
-
if (input.set) body.set = fieldsFromAgent(ctx.registryFor(doc), block4.type, input.set);
|
|
48010
|
-
if (input.unset?.length) body.unset = input.unset;
|
|
48011
|
-
for (const k of ["variant", "style", "column", "label", "hidden"])
|
|
48012
|
-
if (input[k] !== void 0) body[k] = input[k];
|
|
48013
|
-
if (Object.keys(body).length === 3)
|
|
48014
|
-
throw new ToolError(
|
|
48015
|
-
"Nothing to change: give set, unset, variant, style, column, label or hidden.",
|
|
48016
|
-
"invalid_arguments"
|
|
48017
|
-
);
|
|
48018
|
-
const { doc: next } = await ctx.apply(siteId, [body]);
|
|
48019
|
-
const updated = findBlock(next, page, input.block)?.block ?? block4;
|
|
48020
|
-
return { data: { block: blockSummary(ctx.registryFor(next), updated) } };
|
|
48021
|
-
}
|
|
48022
|
-
});
|
|
48023
|
-
var moveBlock = defineTool({
|
|
48024
|
-
name: "move_block",
|
|
48025
|
-
title: "Move block",
|
|
48026
|
-
description: "Move a block within its page (to another section and/or position). Give column for stack sections and place for grid sections.",
|
|
48027
|
-
input: external_exports.object({
|
|
48028
|
-
site: siteArg,
|
|
48029
|
-
page: containerArg,
|
|
48030
|
-
block: external_exports.string(),
|
|
48031
|
-
to: external_exports.object({
|
|
48032
|
-
section: external_exports.string(),
|
|
48033
|
-
at: positionArg.optional(),
|
|
48034
|
-
column: external_exports.int().min(0).max(3).nullable().optional(),
|
|
48035
|
-
place: gridItemArg.optional()
|
|
48036
|
-
})
|
|
48037
|
-
}),
|
|
48038
|
-
annotations: { idempotentHint: true },
|
|
48039
|
-
async handler(ctx, input) {
|
|
48040
|
-
const siteId = await ctx.siteId(input.site);
|
|
48041
|
-
const doc = await ctx.doc(siteId);
|
|
48042
|
-
const page = container(doc, input.page);
|
|
48043
|
-
requireBlock(doc, page, input.block);
|
|
48044
|
-
requireSection2(doc, page, input.to.section);
|
|
48045
|
-
const to = { section: input.to.section, at: input.to.at ?? "end" };
|
|
48046
|
-
if (input.to.column !== void 0) to.column = input.to.column;
|
|
48047
|
-
if (input.to.place) to.place = input.to.place;
|
|
48048
|
-
await ctx.apply(siteId, [{ op: "block.move", page, block: input.block, to }]);
|
|
48049
|
-
return { data: { moved: input.block, to } };
|
|
48050
|
-
}
|
|
48051
|
-
});
|
|
48052
|
-
var deleteBlock = defineTool({
|
|
48053
|
-
name: "delete_block",
|
|
48054
|
-
title: "Delete block",
|
|
48055
|
-
description: "Remove a block from its section (recoverable via the session snapshot or discard_draft).",
|
|
48056
|
-
input: external_exports.object({ site: siteArg, page: containerArg, block: external_exports.string() }),
|
|
48057
|
-
annotations: { destructiveHint: true },
|
|
48058
|
-
async handler(ctx, input) {
|
|
48059
|
-
const siteId = await ctx.siteId(input.site);
|
|
48060
|
-
const doc = await ctx.doc(siteId);
|
|
48061
|
-
const page = container(doc, input.page);
|
|
48062
|
-
requireBlock(doc, page, input.block);
|
|
48063
|
-
await ctx.apply(siteId, [{ op: "block.delete", page, block: input.block }]);
|
|
48064
|
-
return { data: { deleted: input.block } };
|
|
48065
|
-
}
|
|
48066
|
-
});
|
|
48067
|
-
var rectArg = external_exports.looseObject({}).describe(
|
|
48068
|
-
"{x, y, w, h, z?, bleed?: 'left'|'right'|'both'} (1-based; 24 columns desktop/tablet, 8 mobile)"
|
|
48069
|
-
);
|
|
48070
|
-
var placeInGrid2 = defineTool({
|
|
48071
|
-
name: "place_in_grid",
|
|
48072
|
-
title: "Place in grid",
|
|
48073
|
-
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.",
|
|
48074
|
-
input: external_exports.object({
|
|
48075
|
-
site: siteArg,
|
|
48076
|
-
page: containerArg,
|
|
48077
|
-
section: external_exports.string(),
|
|
48078
|
-
block: external_exports.string(),
|
|
48079
|
-
d: rectArg.optional(),
|
|
48080
|
-
t: rectArg.nullable().optional(),
|
|
48081
|
-
m: rectArg.nullable().optional()
|
|
48082
|
-
}),
|
|
48083
|
-
annotations: { idempotentHint: true },
|
|
48084
|
-
async handler(ctx, input) {
|
|
48085
|
-
const siteId = await ctx.siteId(input.site);
|
|
48086
|
-
const doc = await ctx.doc(siteId);
|
|
48087
|
-
const page = container(doc, input.page);
|
|
48088
|
-
const section = requireSection2(doc, page, input.section);
|
|
48089
|
-
if (section.layout.mode !== "grid")
|
|
48090
|
-
throw new ToolError(
|
|
48091
|
-
`Section ${section.id} is a stack section; set the block's column with update_block instead.`,
|
|
48092
|
-
"invalid_arguments"
|
|
48093
|
-
);
|
|
48094
|
-
const bodies = [];
|
|
48095
|
-
for (const bp of ["d", "t", "m"]) {
|
|
48096
|
-
const rect = input[bp];
|
|
48097
|
-
if (rect === void 0) continue;
|
|
48098
|
-
bodies.push({
|
|
48099
|
-
op: "grid.place",
|
|
48100
|
-
page,
|
|
48101
|
-
section: section.id,
|
|
48102
|
-
block: input.block,
|
|
48103
|
-
bp,
|
|
48104
|
-
rect
|
|
48105
|
-
});
|
|
48106
|
-
}
|
|
48107
|
-
if (!bodies.length) throw new ToolError("Give at least one of d, t or m.", "invalid_arguments");
|
|
48108
|
-
const { doc: next } = await ctx.apply(siteId, bodies);
|
|
48109
|
-
const after = requireSection2(next, page, section.id);
|
|
48110
|
-
return {
|
|
48111
|
-
data: {
|
|
48112
|
-
placement: after.layout.mode === "grid" ? after.layout.items[input.block] : null,
|
|
48113
|
-
rows: after.layout.mode === "grid" ? after.layout.rows : null,
|
|
48114
|
-
next: input.d && input.m === void 0 ? "Desktop changed: review mobile (auto_mobile_layout or screenshot device mobile)." : void 0
|
|
48115
|
-
}
|
|
48116
|
-
};
|
|
48117
|
-
}
|
|
48118
|
-
});
|
|
48119
|
-
var autoMobileLayout2 = defineTool({
|
|
48120
|
-
name: "auto_mobile_layout",
|
|
48121
|
-
title: "Auto mobile layout",
|
|
48122
|
-
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.",
|
|
48123
|
-
input: external_exports.object({
|
|
48124
|
-
site: siteArg,
|
|
48125
|
-
page: containerArg,
|
|
48126
|
-
section: external_exports.string(),
|
|
48127
|
-
reset: external_exports.boolean().optional()
|
|
48128
|
-
}),
|
|
48129
|
-
annotations: { idempotentHint: true },
|
|
48130
|
-
async handler(ctx, input) {
|
|
48131
|
-
const siteId = await ctx.siteId(input.site);
|
|
48132
|
-
const doc = await ctx.doc(siteId);
|
|
48133
|
-
const page = container(doc, input.page);
|
|
48134
|
-
const section = requireSection2(doc, page, input.section);
|
|
48135
|
-
if (section.layout.mode !== "grid")
|
|
48136
|
-
return { data: { changed: 0, note: "Stack sections stack automatically on mobile." } };
|
|
48137
|
-
const ops = autoMobileLayoutOps(doc, page, section.id, { reset: input.reset });
|
|
48138
|
-
if (!ops.length)
|
|
48139
|
-
return { data: { changed: 0, note: "Mobile placements are already complete." } };
|
|
48140
|
-
const bodies = ops.map(({ id: _i, actor: _a3, ts: _t, ...b }) => b);
|
|
48141
|
-
const { doc: next } = await ctx.apply(siteId, bodies);
|
|
48142
|
-
const after = requireSection2(next, page, section.id);
|
|
48143
|
-
return {
|
|
48144
|
-
data: {
|
|
48145
|
-
changed: ops.length,
|
|
48146
|
-
mobile: after.layout.mode === "grid" ? Object.fromEntries(Object.entries(after.layout.items).map(([id, it]) => [id, it.m])) : null
|
|
48147
|
-
}
|
|
48148
|
-
};
|
|
48149
|
-
}
|
|
48150
|
-
});
|
|
48151
|
-
var editTools = [
|
|
48152
|
-
insertSection,
|
|
48153
|
-
updateSection,
|
|
48154
|
-
moveSection,
|
|
48155
|
-
deleteSection,
|
|
48156
|
-
insertBlock,
|
|
48157
|
-
updateBlock,
|
|
48158
|
-
moveBlock,
|
|
48159
|
-
deleteBlock,
|
|
48160
|
-
placeInGrid2,
|
|
48161
|
-
autoMobileLayout2
|
|
48162
|
-
];
|
|
48163
|
-
|
|
48164
48419
|
// ../mcp-tools/src/tools/pages.ts
|
|
48165
48420
|
function requireConfirm(confirm, what) {
|
|
48166
48421
|
if (confirm !== true)
|
|
@@ -48297,16 +48552,59 @@ var deletePage = defineTool({
|
|
|
48297
48552
|
};
|
|
48298
48553
|
}
|
|
48299
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
|
+
}
|
|
48300
48587
|
var updateNavigation = defineTool({
|
|
48301
48588
|
name: "update_navigation",
|
|
48302
48589
|
title: "Update navigation",
|
|
48303
|
-
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.`,
|
|
48304
48591
|
input: external_exports.object({ site: siteArg, tree: external_exports.array(external_exports.looseObject({})) }),
|
|
48305
48592
|
annotations: { idempotentHint: true },
|
|
48306
48593
|
async handler(ctx, input) {
|
|
48307
48594
|
const siteId = await ctx.siteId(input.site);
|
|
48308
|
-
const
|
|
48309
|
-
|
|
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
|
+
};
|
|
48310
48608
|
}
|
|
48311
48609
|
});
|
|
48312
48610
|
var SITE_FORBIDDEN = ["nav", "customCss", "headInjection", "footerInjection"];
|
|
@@ -48895,14 +49193,21 @@ var OpenSpaceMcpServer = class {
|
|
|
48895
49193
|
this.initialized = Boolean(options.assumeInitialized);
|
|
48896
49194
|
for (const t of ALL_TOOLS) this.tools.set(t.name, t);
|
|
48897
49195
|
for (const t of options.tools ?? []) this.tools.set(t.name, t);
|
|
49196
|
+
const extra = options.prompts ?? [];
|
|
49197
|
+
this.prompts = [...PROMPTS.filter((p) => !extra.some((e) => e.name === p.name)), ...extra];
|
|
48898
49198
|
}
|
|
48899
49199
|
options;
|
|
48900
49200
|
ctx;
|
|
48901
49201
|
tools = /* @__PURE__ */ new Map();
|
|
49202
|
+
prompts;
|
|
48902
49203
|
clientName;
|
|
48903
49204
|
initialized;
|
|
48904
49205
|
protocolVersion;
|
|
48905
49206
|
closed = false;
|
|
49207
|
+
/** Prompts served by this instance. */
|
|
49208
|
+
promptList() {
|
|
49209
|
+
return [...this.prompts];
|
|
49210
|
+
}
|
|
48906
49211
|
/** Tool definitions served by this instance. */
|
|
48907
49212
|
toolList() {
|
|
48908
49213
|
return [...this.tools.values()];
|
|
@@ -49024,7 +49329,7 @@ var OpenSpaceMcpServer = class {
|
|
|
49024
49329
|
}
|
|
49025
49330
|
case "prompts/list":
|
|
49026
49331
|
return {
|
|
49027
|
-
prompts:
|
|
49332
|
+
prompts: this.prompts.map((p) => ({
|
|
49028
49333
|
name: p.name,
|
|
49029
49334
|
title: p.title,
|
|
49030
49335
|
description: p.description,
|
|
@@ -49035,7 +49340,7 @@ var OpenSpaceMcpServer = class {
|
|
|
49035
49340
|
if (typeof params.name !== "string")
|
|
49036
49341
|
throw new RpcError(ErrorCode.InvalidParams, "name is required");
|
|
49037
49342
|
const args = isRecord5(params.arguments) ? Object.fromEntries(Object.entries(params.arguments).map(([k, v]) => [k, String(v)])) : {};
|
|
49038
|
-
return getPrompt(params.name, args);
|
|
49343
|
+
return getPrompt(params.name, args, this.prompts);
|
|
49039
49344
|
}
|
|
49040
49345
|
case "completion/complete":
|
|
49041
49346
|
return { completion: { values: [], hasMore: false } };
|
|
@@ -49063,7 +49368,7 @@ var OpenSpaceMcpServer = class {
|
|
|
49063
49368
|
logging: {}
|
|
49064
49369
|
},
|
|
49065
49370
|
serverInfo: { name: SERVER_NAME, title: "OpenSpace", version: SERVER_VERSION },
|
|
49066
|
-
instructions: SERVER_INSTRUCTIONS
|
|
49371
|
+
instructions: this.options.instructions ?? SERVER_INSTRUCTIONS
|
|
49067
49372
|
};
|
|
49068
49373
|
}
|
|
49069
49374
|
/** Name the client reported in `initialize`. */
|
|
@@ -51541,10 +51846,10 @@ function setRole(doc, theme, role, value) {
|
|
|
51541
51846
|
doc.styles.overrides = overrides;
|
|
51542
51847
|
return true;
|
|
51543
51848
|
}
|
|
51544
|
-
function sectionLabel(heading2, blueprint,
|
|
51849
|
+
function sectionLabel(heading2, blueprint, pageTitle2, n) {
|
|
51545
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;
|
|
51546
51851
|
const words4 = (first2 ?? "").replace(/\s+/g, " ").trim();
|
|
51547
|
-
if (!words4) return `${
|
|
51852
|
+
if (!words4) return `${pageTitle2} section ${n}`.slice(0, 60);
|
|
51548
51853
|
const short = words4.length > 40 ? `${words4.slice(0, 40).replace(/\s+\S*$/, "")}\u2026` : words4;
|
|
51549
51854
|
const cased = /[a-z]/.test(short) ? short : short.charAt(0) + short.slice(1).toLowerCase();
|
|
51550
51855
|
return cased.slice(0, 60);
|
|
@@ -59648,6 +59953,24 @@ function sectionElements(main, skip) {
|
|
|
59648
59953
|
}
|
|
59649
59954
|
return [];
|
|
59650
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
|
+
}
|
|
59651
59974
|
var ROOT_SEL = /^(?::root|html|body|:root\s*,\s*html|html\s*,\s*body|:host)$/i;
|
|
59652
59975
|
function serializeNodes(nodes) {
|
|
59653
59976
|
return nodes.map(
|
|
@@ -60057,6 +60380,7 @@ ${await r.text()}`;
|
|
|
60057
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);
|
|
60058
60381
|
const logoImg = brand ? find(brand, (e) => e.tagName === "img") : void 0;
|
|
60059
60382
|
const logoRef = logoImg ? resolveRef(bundle, attr(logoImg, "src") ?? "", home.path) : void 0;
|
|
60383
|
+
const linked = linkedFragments(pages);
|
|
60060
60384
|
const segsByPage = /* @__PURE__ */ new Map();
|
|
60061
60385
|
const sqPages = [];
|
|
60062
60386
|
const takenNames = /* @__PURE__ */ new Set();
|
|
@@ -60088,6 +60412,7 @@ ${await r.text()}`;
|
|
|
60088
60412
|
const segs = [];
|
|
60089
60413
|
const sqSections = [];
|
|
60090
60414
|
for (const el of p.sections) {
|
|
60415
|
+
const anchor = sectionAnchor(el, linked);
|
|
60091
60416
|
const name = uniqueName(componentNameFor(el, p.sitePath), takenNames);
|
|
60092
60417
|
const label2 = componentLabelFor(el);
|
|
60093
60418
|
const dry = await new SectionConverter(el, dryCtx(p)).convert(name, label2);
|
|
@@ -60098,13 +60423,13 @@ ${await r.text()}`;
|
|
|
60098
60423
|
if (i === 0) s2.id = m2;
|
|
60099
60424
|
sqSections.push(s2);
|
|
60100
60425
|
});
|
|
60101
|
-
segs.push({ kind: "preset", marker: m2, reason: dry.error });
|
|
60426
|
+
segs.push({ kind: "preset", marker: m2, reason: dry.error, ...anchor ? { anchor } : {} });
|
|
60102
60427
|
presets.push({ page: p.sitePath, reason: dry.error });
|
|
60103
60428
|
continue;
|
|
60104
60429
|
}
|
|
60105
60430
|
takenNames.add(name);
|
|
60106
60431
|
const m = marker();
|
|
60107
|
-
segs.push({ kind: "component", el, marker: m, name, label: label2 });
|
|
60432
|
+
segs.push({ kind: "component", el, marker: m, name, label: label2, ...anchor ? { anchor } : {} });
|
|
60108
60433
|
const blocks2 = [];
|
|
60109
60434
|
for (const f of dry.forms) {
|
|
60110
60435
|
const form = formFromHtml(f);
|
|
@@ -60300,6 +60625,11 @@ ${await r.text()}`;
|
|
|
60300
60625
|
return rest;
|
|
60301
60626
|
});
|
|
60302
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
|
+
}
|
|
60303
60633
|
out.push(...group);
|
|
60304
60634
|
continue;
|
|
60305
60635
|
}
|
|
@@ -60315,9 +60645,10 @@ ${await r.text()}`;
|
|
|
60315
60645
|
components[r.def.name] = r.def;
|
|
60316
60646
|
const section = componentSection(r.def, { fields: r.fields, colorTheme: "light" });
|
|
60317
60647
|
section.label = seg.label;
|
|
60318
|
-
|
|
60319
|
-
|
|
60320
|
-
|
|
60648
|
+
const anchor = seg.anchor ?? r.anchor;
|
|
60649
|
+
if (anchor && !anchors.has(anchor)) {
|
|
60650
|
+
section.anchor = anchor;
|
|
60651
|
+
anchors.add(anchor);
|
|
60321
60652
|
}
|
|
60322
60653
|
section.style = { ...section.style, colorTheme: "light", padding: { d: "none" } };
|
|
60323
60654
|
out.push(section);
|
|
@@ -62231,4 +62562,4 @@ export {
|
|
|
62231
62562
|
createProgram,
|
|
62232
62563
|
run
|
|
62233
62564
|
};
|
|
62234
|
-
//# sourceMappingURL=chunk-
|
|
62565
|
+
//# sourceMappingURL=chunk-HUS7TQPS.js.map
|