@nextblock-cms/cortex 0.18.6 → 0.18.8

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.
@@ -1,9 +1,11 @@
1
- import { tool as L } from "ai";
1
+ import { tool as O } from "ai";
2
+ import { parseSiteSocialImageSetting as ke, SITE_SOCIAL_IMAGE_SETTING_KEY as H } from "@nextblock-cms/utils/seo";
3
+ import { findOriginalUploadVariant as ve, pickOriginalUploadObjectKey as $e } from "@nextblock-cms/utils/media-variants";
2
4
  import { requireActorRole as se } from "./ai-global-agent-theming-tools.es.js";
3
- import { cortexSiteBriefInputSchema as le, safeParseCortexSiteBrief as de, CORTEX_AI_SITE_BRIEF_SETTING_KEY as V, cortexBuildSessionSchema as ue, CORTEX_AI_BUILD_SESSION_SETTING_KEY as G, cortexSiteBriefSchema as we, isCortexBuildSessionUsable as ke } from "./site-brief.es.js";
5
+ import { cortexSiteBriefInputSchema as de, safeParseCortexSiteBrief as ue, CORTEX_AI_SITE_BRIEF_SETTING_KEY as Q, cortexBuildSessionSchema as ce, CORTEX_AI_BUILD_SESSION_SETTING_KEY as F, cortexSiteBriefSchema as Te, isCortexBuildSessionUsable as Ee } from "./site-brief.es.js";
4
6
  import "./zod-config.es.js";
5
- import { z as l } from "zod";
6
- const te = /* @__PURE__ */ new Set([
7
+ import { z as u } from "zod";
8
+ const ie = /* @__PURE__ */ new Set([
7
9
  // pages: home, articles, contact, privacy policy, terms of service
8
10
  "0098e4f0-e5f3-4e28-acfc-bb9fdb3a4a6b",
9
11
  "50adeea5-5040-4ae7-bacd-992647920982",
@@ -18,7 +20,7 @@ const te = /* @__PURE__ */ new Set([
18
20
  "c0d3f1a2-8b47-4e19-9a52-7f6b1d4e8c30",
19
21
  "a7c4e2d1-3b58-4f96-9e0a-6d2c8b1f5a73",
20
22
  "dbe2a06f-1a9d-48ba-bc35-0f6ef4869156"
21
- ]), J = /* @__PURE__ */ new Set([
23
+ ]), V = /* @__PURE__ */ new Set([
22
24
  "images/NBcover.webp",
23
25
  "images/commerce-plan.webp",
24
26
  "images/commerce-wide.webp",
@@ -30,60 +32,61 @@ const te = /* @__PURE__ */ new Set([
30
32
  "images/nextblock-logo-small.webp",
31
33
  "images/programmer-upscaled.webp",
32
34
  "images/update_nextblock.webp"
33
- ]), ve = "NextBlock™ CMS", $e = "nextblock cms", N = "home", Te = [
35
+ ]), Ce = "NextBlock™ CMS", Ie = "nextblock cms", A = "home", xe = [
34
36
  "active_logo_id",
35
37
  "footer_copyright",
36
38
  "footer_show_attribution",
37
39
  "site_description",
38
40
  "site_keywords",
39
- "site_title"
41
+ "site_title",
42
+ H
40
43
  ];
41
- function O(t) {
42
- if (!t?.supabase)
44
+ function L(e) {
45
+ if (!e?.supabase)
43
46
  throw new Error("No database connection is available for this tool.");
44
- return t.supabase;
47
+ return e.supabase;
45
48
  }
46
- function h(t) {
47
- return t ? t instanceof Error ? t.message : typeof t == "object" && "message" in t ? String(t.message) : String(t) : "unknown error";
49
+ function y(e) {
50
+ return e ? e instanceof Error ? e.message : typeof e == "object" && "message" in e ? String(e.message) : String(e) : "unknown error";
48
51
  }
49
- function ie(t) {
50
- return t === null || typeof t != "object" ? JSON.stringify(t) : Array.isArray(t) ? `[${t.map((i) => ie(i)).join(",")}]` : `{${Object.keys(t).sort().map((i) => `${JSON.stringify(i)}:${ie(t[i])}`).join(",")}}`;
52
+ function ae(e) {
53
+ return e === null || typeof e != "object" ? JSON.stringify(e) : Array.isArray(e) ? `[${e.map((t) => ae(t)).join(",")}]` : `{${Object.keys(e).sort().map((t) => `${JSON.stringify(t)}:${ae(e[t])}`).join(",")}}`;
51
54
  }
52
- function Ce(t) {
53
- let i = 2166136261;
54
- const a = ie(t);
55
- for (let s = 0; s < a.length; s++)
56
- i ^= a.charCodeAt(s), i = Math.imul(i, 16777619);
57
- return (i >>> 0).toString(16).padStart(8, "0");
55
+ function Ne(e) {
56
+ let t = 2166136261;
57
+ const a = ae(e);
58
+ for (let i = 0; i < a.length; i++)
59
+ t ^= a.charCodeAt(i), t = Math.imul(t, 16777619);
60
+ return (t >>> 0).toString(16).padStart(8, "0");
58
61
  }
59
- function ee(t) {
60
- return t.replace(/\s+/g, " ").trim().toUpperCase();
62
+ function te(e) {
63
+ return e.replace(/\s+/g, " ").trim().toUpperCase();
61
64
  }
62
- function ae(t) {
63
- if (t.context?.skipConfirmation)
65
+ function oe(e) {
66
+ if (e.context?.skipConfirmation)
64
67
  return null;
65
- const i = `${ee(
66
- `CONFIRM ${t.action} ${t.subject}`
67
- )} #${Ce(t.payload)}`;
68
- return ee(t.context?.latestUserMessage || "").includes(ee(i)) ? null : {
69
- confirmationPhrase: i,
68
+ const t = `${te(
69
+ `CONFIRM ${e.action} ${e.subject}`
70
+ )} #${Ne(e.payload)}`;
71
+ return te(e.context?.latestUserMessage || "").includes(te(t)) ? null : {
72
+ confirmationPhrase: t,
70
73
  mutationExecuted: !1,
71
- preview: t.preview,
74
+ preview: e.preview,
72
75
  requiresConfirmation: !0,
73
76
  success: !0
74
77
  };
75
78
  }
76
- function xe() {
79
+ function Ae() {
77
80
  return globalThis.crypto?.randomUUID?.() || `id-${Date.now()}-${Math.random().toString(36).slice(2)}`;
78
81
  }
79
- function ce(t, i = 200) {
82
+ function ge(e, t = 200) {
80
83
  const a = [];
81
- for (let s = 0; s < t.length; s += i)
82
- a.push(t.slice(s, s + i));
84
+ for (let i = 0; i < e.length; i += t)
85
+ a.push(e.slice(i, i + t));
83
86
  return a;
84
87
  }
85
- function oe(t, i = []) {
86
- let a = t?.revalidatePath;
88
+ function ne(e, t = []) {
89
+ let a = e?.revalidatePath;
87
90
  if (!a)
88
91
  try {
89
92
  a = require("next/cache").revalidatePath;
@@ -92,267 +95,268 @@ function oe(t, i = []) {
92
95
  }
93
96
  try {
94
97
  a("/", "layout");
95
- for (const s of i)
96
- a(s);
98
+ for (const i of t)
99
+ a(i);
97
100
  } catch {
98
101
  }
99
102
  }
100
- async function Ee(t, i) {
101
- const { data: a, error: s } = await t.from("site_settings").select("key, value").in("key", [...i]);
102
- if (s)
103
- throw new Error(`Could not read the site settings: ${h(s)}`);
104
- return new Map((Array.isArray(a) ? a : []).map((n) => [n.key, n.value]));
103
+ async function De(e, t) {
104
+ const { data: a, error: i } = await e.from("site_settings").select("key, value").in("key", [...t]);
105
+ if (i)
106
+ throw new Error(`Could not read the site settings: ${y(i)}`);
107
+ return new Map((Array.isArray(a) ? a : []).map((r) => [r.key, r.value]));
105
108
  }
106
- async function k(t, i, a) {
107
- const { error: s } = await t.from("site_settings").upsert({ key: i, value: a });
108
- if (s)
109
- throw new Error(`Could not save the "${i}" setting: ${h(s)}`);
109
+ async function k(e, t, a) {
110
+ const { error: i } = await e.from("site_settings").upsert({ key: t, value: a });
111
+ if (i)
112
+ throw new Error(`Could not save the "${t}" setting: ${y(i)}`);
110
113
  }
111
- async function I(t, i, a, s) {
112
- let n = 0;
113
- for (const o of ce(s)) {
114
- const { error: r } = await t.from(i).delete().in(a, o);
115
- if (r)
116
- throw new Error(`Could not delete from ${i}: ${h(r)}`);
117
- n += o.length;
114
+ async function N(e, t, a, i) {
115
+ let r = 0;
116
+ for (const o of ge(i)) {
117
+ const { error: l } = await e.from(t).delete().in(a, o);
118
+ if (l)
119
+ throw new Error(`Could not delete from ${t}: ${y(l)}`);
120
+ r += o.length;
118
121
  }
119
- return n;
122
+ return r;
120
123
  }
121
- function Ie(t) {
122
- return !t || typeof t != "object" ? !1 : Object.values(t).some(
123
- (i) => typeof i == "string" && i.toLowerCase().includes($e)
124
+ function Be(e) {
125
+ return !e || typeof e != "object" ? !1 : Object.values(e).some(
126
+ (t) => typeof t == "string" && t.toLowerCase().includes(Ie)
124
127
  );
125
128
  }
126
- async function ne(t) {
127
- const { data: i, error: a } = await t.from("site_settings").select("value").eq("key", V).maybeSingle();
128
- return a ? null : de(i?.value);
129
+ async function re(e) {
130
+ const { data: t, error: a } = await e.from("site_settings").select("value").eq("key", Q).maybeSingle();
131
+ return a ? null : ue(t?.value);
129
132
  }
130
- const ge = l.strictObject({
131
- brief: le.describe(
133
+ const me = u.strictObject({
134
+ brief: de.describe(
132
135
  'The fields you learned. With mode "merge" (default) they are layered over the saved brief, so you can save after every answer.'
133
136
  ),
134
- mode: l.enum(["merge", "replace"]).default("merge")
137
+ mode: u.enum(["merge", "replace"]).default("merge")
135
138
  });
136
- async function Q(t, i) {
137
- const a = ge.parse(t), s = O(i), n = a.mode === "merge" ? await ne(s) : null, o = new Set(Object.keys(t.brief ?? {})), r = Object.fromEntries(
138
- Object.entries(a.brief).filter(([f]) => o.has(f))
139
- ), d = we.parse({
140
- ...n ?? {},
141
- ...r,
139
+ async function Z(e, t) {
140
+ const a = me.parse(e), i = L(t), r = a.mode === "merge" ? await re(i) : null, o = new Set(Object.keys(e.brief ?? {})), l = Object.fromEntries(
141
+ Object.entries(a.brief).filter(([m]) => o.has(m))
142
+ ), d = Te.parse({
143
+ ...r ?? {},
144
+ ...l,
142
145
  updated_at: (/* @__PURE__ */ new Date()).toISOString()
143
146
  });
144
- return await k(s, V, d), {
147
+ return await k(i, Q, d), {
145
148
  brief: d,
146
- created: !n,
149
+ created: !r,
147
150
  mutationExecuted: !0,
148
151
  success: !0
149
152
  };
150
153
  }
151
- async function pe(t) {
152
- const { data: i, error: a } = await t.from("site_settings").select("value").eq("key", G).maybeSingle();
154
+ async function pe(e) {
155
+ const { data: t, error: a } = await e.from("site_settings").select("value").eq("key", F).maybeSingle();
153
156
  if (a)
154
157
  return null;
155
- const s = ue.safeParse(i?.value);
156
- return s.success ? s.data : null;
158
+ const i = ce.safeParse(t?.value);
159
+ return i.success ? i.data : null;
157
160
  }
158
- async function He(t, i) {
159
- const a = await pe(t);
160
- return ke(a, { actorUserId: i }) ? a : null;
161
+ async function Ze(e, t) {
162
+ const a = await pe(e);
163
+ return Ee(a, { actorUserId: t }) ? a : null;
161
164
  }
162
- async function Ne(t) {
163
- const { error: i } = await t.from("site_settings").delete().eq("key", G);
164
- if (i)
165
- throw new Error(`Could not close the build session: ${h(i)}`);
165
+ async function Re(e) {
166
+ const { error: t } = await e.from("site_settings").delete().eq("key", F);
167
+ if (t)
168
+ throw new Error(`Could not close the build session: ${y(t)}`);
166
169
  }
167
- const me = l.strictObject({
168
- includeMedia: l.boolean().default(!1).describe("Also list the most recent media library entries (id, file name, alt text)."),
169
- includeNavigation: l.boolean().default(!0),
170
- limit: l.number().int().min(10).max(300).default(120).describe("Maximum pages, posts, and products to list.")
170
+ const fe = u.strictObject({
171
+ includeMedia: u.boolean().default(!1).describe("Also list the most recent media library entries (id, file name, alt text)."),
172
+ includeNavigation: u.boolean().default(!0),
173
+ limit: u.number().int().min(10).max(300).default(120).describe("Maximum pages, posts, and products to list.")
171
174
  });
172
- function Ae(t, i) {
173
- const a = { footer: {}, header: {} }, s = /* @__PURE__ */ new Map();
174
- for (const o of t) {
175
- const r = String(o.menu_key).toUpperCase() === "FOOTER" ? "footer" : "header";
176
- s.set(Number(o.id), {
175
+ function Oe(e, t) {
176
+ const a = { footer: {}, header: {} }, i = /* @__PURE__ */ new Map();
177
+ for (const o of e) {
178
+ const l = String(o.menu_key).toUpperCase() === "FOOTER" ? "footer" : "header";
179
+ i.set(Number(o.id), {
177
180
  children: [],
178
181
  id: Number(o.id),
179
182
  label: String(o.label ?? ""),
180
- languageCode: i.get(Number(o.language_id)) ?? String(o.language_id),
181
- menu: r,
183
+ languageCode: t.get(Number(o.language_id)) ?? String(o.language_id),
184
+ menu: l,
182
185
  order: Number(o.order ?? 0),
183
186
  parentId: o.parent_id === null || o.parent_id === void 0 ? null : Number(o.parent_id),
184
187
  url: String(o.url ?? "")
185
188
  });
186
189
  }
187
- const n = [...s.values()].sort((o, r) => o.order - r.order);
188
- for (const o of n) {
189
- const r = o.parentId === null ? null : s.get(o.parentId);
190
- if (r) {
191
- r.children.push({ children: o.children, id: o.id, label: o.label, url: o.url });
190
+ const r = [...i.values()].sort((o, l) => o.order - l.order);
191
+ for (const o of r) {
192
+ const l = o.parentId === null ? null : i.get(o.parentId);
193
+ if (l) {
194
+ l.children.push({ children: o.children, id: o.id, label: o.label, url: o.url });
192
195
  continue;
193
196
  }
194
197
  (a[o.menu][o.languageCode] ??= []).push({ children: o.children, id: o.id, label: o.label, url: o.url });
195
198
  }
196
199
  return a;
197
200
  }
198
- async function De(t, i) {
199
- const a = me.parse(t), s = O(i), [n, o, r, d, f, c, g] = await Promise.all([
200
- s.from("languages").select("id, code, name, is_default, is_active").order("id"),
201
- Ee(s, [...Te, V, G]),
201
+ async function Le(e, t) {
202
+ const a = fe.parse(e), i = L(t), [r, o, l, d, m, c, p] = await Promise.all([
203
+ i.from("languages").select("id, code, name, is_default, is_active").order("id"),
204
+ De(i, [...xe, Q, F]),
202
205
  // `count: 'exact'` so the totals are the site's, not the capped list's.
203
- s.from("pages").select("id, slug, title, status, language_id, translation_group_id, author_id, updated_at", { count: "exact" }).order("id").limit(a.limit),
204
- s.from("posts").select("id, slug, title, status, language_id, translation_group_id, author_id, published_at", { count: "exact" }).order("id").limit(a.limit),
205
- s.from("site_themes").select("slug, name, is_default, is_active, color_scheme").order("sort_order"),
206
- s.from("custom_block_definitions").select("slug, name, fields").order("name"),
207
- s.from("content_drafts").select("parent_type, parent_id, updated_at")
206
+ i.from("pages").select("id, slug, title, status, language_id, translation_group_id, author_id, updated_at", { count: "exact" }).order("id").limit(a.limit),
207
+ i.from("posts").select("id, slug, title, status, language_id, translation_group_id, author_id, published_at", { count: "exact" }).order("id").limit(a.limit),
208
+ i.from("site_themes").select("slug, name, is_default, is_active, color_scheme").order("sort_order"),
209
+ i.from("custom_block_definitions").select("slug, name, fields").order("name"),
210
+ i.from("content_drafts").select("parent_type, parent_id, updated_at")
208
211
  ]);
209
- for (const [e, u] of [
210
- ["languages", n],
211
- ["pages", r],
212
+ for (const [s, h] of [
213
+ ["languages", r],
214
+ ["pages", l],
212
215
  ["posts", d],
213
- ["themes", f],
216
+ ["themes", m],
214
217
  ["custom blocks", c],
215
- ["drafts", g]
218
+ ["drafts", p]
216
219
  ])
217
- if (u?.error)
218
- throw new Error(`Could not read the ${e}: ${h(u.error)}`);
219
- const p = n.data ?? [], _ = new Map(p.map((e) => [Number(e.id), String(e.code)])), M = p.find((e) => e.is_default) ?? p[0] ?? null;
220
- let v = [];
220
+ if (h?.error)
221
+ throw new Error(`Could not read the ${s}: ${y(h.error)}`);
222
+ const f = r.data ?? [], S = new Map(f.map((s) => [Number(s.id), String(s.code)])), P = f.find((s) => s.is_default) ?? f[0] ?? null;
223
+ let T = [];
221
224
  try {
222
- const { data: e } = await s.from("products").select("id, slug, title, status, language_id").order("created_at", { ascending: !1 }).limit(a.limit);
223
- v = Array.isArray(e) ? e : [];
225
+ const { data: s } = await i.from("products").select("id, slug, title, status, language_id").order("created_at", { ascending: !1 }).limit(a.limit);
226
+ T = Array.isArray(s) ? s : [];
224
227
  } catch {
225
- v = [];
228
+ T = [];
226
229
  }
227
- const $ = /* @__PURE__ */ new Map();
230
+ const E = /* @__PURE__ */ new Map();
228
231
  {
229
- const { data: e } = await s.from("blocks").select("page_id, post_id").limit(5e3);
230
- for (const u of Array.isArray(e) ? e : []) {
231
- const b = u.page_id ? `page:${u.page_id}` : u.post_id ? `post:${u.post_id}` : null;
232
- b && $.set(b, ($.get(b) ?? 0) + 1);
232
+ const { data: s } = await i.from("blocks").select("page_id, post_id").limit(5e3);
233
+ for (const h of Array.isArray(s) ? s : []) {
234
+ const _ = h.page_id ? `page:${h.page_id}` : h.post_id ? `post:${h.post_id}` : null;
235
+ _ && E.set(_, (E.get(_) ?? 0) + 1);
233
236
  }
234
237
  }
235
- let F = null;
238
+ let Y = null;
236
239
  if (a.includeNavigation) {
237
- const { data: e, error: u } = await s.from("navigation_items").select("id, label, url, menu_key, language_id, parent_id, order, page_id").order("order");
238
- if (u)
239
- throw new Error(`Could not read the navigation: ${h(u)}`);
240
- F = Ae(Array.isArray(e) ? e : [], _);
240
+ const { data: s, error: h } = await i.from("navigation_items").select("id, label, url, menu_key, language_id, parent_id, order, page_id").order("order");
241
+ if (h)
242
+ throw new Error(`Could not read the navigation: ${y(h)}`);
243
+ Y = Oe(Array.isArray(s) ? s : [], S);
241
244
  }
242
- const { data: H } = await s.from("media").select("id, object_key, file_name, description, created_at").order("created_at", { ascending: !1 }).limit(500), y = Array.isArray(H) ? H : [], T = y.filter((e) => J.has(String(e.object_key))).length, { data: C } = await s.from("logos").select("id, name, media_id, created_at").order("created_at", { ascending: !1 }), j = Array.isArray(C) ? C : [], K = typeof o.get("active_logo_id") == "string" ? o.get("active_logo_id") : null, w = (K ? j.find((e) => e.id === K) : null) ?? j[0] ?? null, A = w ? y.find((e) => e.id === w.media_id) ?? null : null, D = A ? String(A.object_key) : null, B = (r.data ?? []).map((e) => ({
243
- blockCount: $.get(`page:${e.id}`) ?? 0,
244
- id: Number(e.id),
245
- isHome: e.slug === N && (!M || Number(e.language_id) === Number(M.id)),
246
- isSeeded: te.has(String(e.translation_group_id)),
247
- languageCode: _.get(Number(e.language_id)) ?? String(e.language_id),
248
- slug: String(e.slug),
249
- status: String(e.status),
250
- title: String(e.title ?? ""),
251
- translationGroupId: e.translation_group_id ? String(e.translation_group_id) : null
252
- })), P = (d.data ?? []).map((e) => ({
253
- blockCount: $.get(`post:${e.id}`) ?? 0,
254
- id: Number(e.id),
255
- isSeeded: te.has(String(e.translation_group_id)),
256
- languageCode: _.get(Number(e.language_id)) ?? String(e.language_id),
257
- publishedAt: e.published_at ?? null,
258
- slug: String(e.slug),
259
- status: String(e.status),
260
- title: String(e.title ?? ""),
261
- translationGroupId: e.translation_group_id ? String(e.translation_group_id) : null
262
- })), U = typeof o.get("site_title") == "string" ? o.get("site_title") : "", Y = o.get("footer_copyright"), m = {
263
- copyright: Ie(Y),
264
- logo: !!(D && J.has(D)),
265
- media: T,
266
- pages: B.filter((e) => e.isSeeded).length,
267
- posts: P.filter((e) => e.isSeeded).length,
268
- siteTitle: U.trim() === ve
269
- }, W = m.copyright || m.logo || m.media > 0 || m.pages > 0 || m.posts > 0 || m.siteTitle, q = de(o.get(V)), X = ue.safeParse(o.get(G)), x = X.success ? X.data : null, R = B.find((e) => e.isHome) ?? null, S = [];
270
- return R ? R.blockCount === 0 && S.push("The home page exists but has no blocks. Fill it with rewrite_page_draft then publish_content_draft.") : S.push(
271
- `There is no "${N}" page in the default language, so "/" returns 404. Create one with create_cms_page (slug "${N}", status "published").`
272
- ), W && S.push(
273
- `NextBlock demo content is still present (${m.pages} seeded pages, ${m.posts} seeded posts, ${m.media} bundled images${m.logo ? ", the NextBlock logo" : ""}${m.siteTitle ? ", the default site title" : ""}${m.copyright ? ", the default copyright" : ""}). Remove it with reset_site_content, or pass reset to start_site_build.`
274
- ), q || S.push("No site brief is saved yet. Interview the client and record the answers with save_site_brief."), {
245
+ const { data: W } = await i.from("media").select("id, object_key, file_name, description, created_at").order("created_at", { ascending: !1 }).limit(500), w = Array.isArray(W) ? W : [], C = w.filter((s) => V.has(String(s.object_key))).length, { data: I } = await i.from("logos").select("id, name, media_id, created_at").order("created_at", { ascending: !1 }), j = Array.isArray(I) ? I : [], M = typeof o.get("active_logo_id") == "string" ? o.get("active_logo_id") : null, v = (M ? j.find((s) => s.id === M) : null) ?? j[0] ?? null, D = v ? w.find((s) => s.id === v.media_id) ?? null : null, B = D ? String(D.object_key) : null, R = ke(o.get(H)), X = R ? { mediaId: R.media_id, objectKey: R.object_key, url: R.url } : null, x = (l.data ?? []).map((s) => ({
246
+ blockCount: E.get(`page:${s.id}`) ?? 0,
247
+ id: Number(s.id),
248
+ isHome: s.slug === A && (!P || Number(s.language_id) === Number(P.id)),
249
+ isSeeded: ie.has(String(s.translation_group_id)),
250
+ languageCode: S.get(Number(s.language_id)) ?? String(s.language_id),
251
+ slug: String(s.slug),
252
+ status: String(s.status),
253
+ title: String(s.title ?? ""),
254
+ translationGroupId: s.translation_group_id ? String(s.translation_group_id) : null
255
+ })), K = (d.data ?? []).map((s) => ({
256
+ blockCount: E.get(`post:${s.id}`) ?? 0,
257
+ id: Number(s.id),
258
+ isSeeded: ie.has(String(s.translation_group_id)),
259
+ languageCode: S.get(Number(s.language_id)) ?? String(s.language_id),
260
+ publishedAt: s.published_at ?? null,
261
+ slug: String(s.slug),
262
+ status: String(s.status),
263
+ title: String(s.title ?? ""),
264
+ translationGroupId: s.translation_group_id ? String(s.translation_group_id) : null
265
+ })), U = typeof o.get("site_title") == "string" ? o.get("site_title") : "", z = o.get("footer_copyright"), b = {
266
+ copyright: Be(z),
267
+ logo: !!(B && V.has(B)),
268
+ media: C,
269
+ pages: x.filter((s) => s.isSeeded).length,
270
+ posts: K.filter((s) => s.isSeeded).length,
271
+ siteTitle: U.trim() === Ce
272
+ }, J = b.copyright || b.logo || b.media > 0 || b.pages > 0 || b.posts > 0 || b.siteTitle, q = ue(o.get(Q)), G = ce.safeParse(o.get(F)), $ = G.success ? G.data : null, n = x.find((s) => s.isHome) ?? null, g = [];
273
+ return n ? n.blockCount === 0 && g.push("The home page exists but has no blocks. Fill it with rewrite_page_draft then publish_content_draft.") : g.push(
274
+ `There is no "${A}" page in the default language, so "/" returns 404. Create one with create_cms_page (slug "${A}", status "published").`
275
+ ), J && g.push(
276
+ `NextBlock demo content is still present (${b.pages} seeded pages, ${b.posts} seeded posts, ${b.media} bundled images${b.logo ? ", the NextBlock logo" : ""}${b.siteTitle ? ", the default site title" : ""}${b.copyright ? ", the default copyright" : ""}). Remove it with reset_site_content, or pass reset to start_site_build.`
277
+ ), q || g.push("No site brief is saved yet. Interview the client and record the answers with save_site_brief."), {
275
278
  brief: q,
276
- buildSession: x ? { expiresAt: x.expiresAt, id: x.id, summary: x.summary } : null,
279
+ buildSession: $ ? { expiresAt: $.expiresAt, id: $.id, summary: $.summary } : null,
277
280
  counts: {
278
281
  customBlocks: (c.data ?? []).length,
279
- drafts: (g.data ?? []).length,
280
- media: y.length,
281
- pages: typeof r.count == "number" ? r.count : B.length,
282
- posts: typeof d.count == "number" ? d.count : P.length,
283
- products: v.length
282
+ drafts: (p.data ?? []).length,
283
+ media: w.length,
284
+ pages: typeof l.count == "number" ? l.count : x.length,
285
+ posts: typeof d.count == "number" ? d.count : K.length,
286
+ products: T.length
284
287
  },
285
- customBlocks: (c.data ?? []).map((e) => ({
286
- fieldCount: Array.isArray(e.fields) ? e.fields.length : 0,
287
- fields: Array.isArray(e.fields) ? e.fields.map((u) => ({ key: u?.key, required: u?.required === !0, type: u?.type })) : [],
288
- name: String(e.name ?? ""),
289
- slug: String(e.slug)
288
+ customBlocks: (c.data ?? []).map((s) => ({
289
+ fieldCount: Array.isArray(s.fields) ? s.fields.length : 0,
290
+ fields: Array.isArray(s.fields) ? s.fields.map((h) => ({ key: h?.key, required: h?.required === !0, type: h?.type })) : [],
291
+ name: String(s.name ?? ""),
292
+ slug: String(s.slug)
290
293
  })),
291
- drafts: (g.data ?? []).map((e) => ({
292
- parentId: Number(e.parent_id),
293
- parentType: String(e.parent_type)
294
+ drafts: (p.data ?? []).map((s) => ({
295
+ parentId: Number(s.parent_id),
296
+ parentType: String(s.parent_type)
294
297
  })),
295
- homePage: R,
298
+ homePage: n,
296
299
  identity: {
297
- activeLogo: w ? {
298
- id: String(w.id),
299
- isSeeded: m.logo,
300
- name: String(w.name ?? ""),
301
- objectKey: D
300
+ activeLogo: v ? {
301
+ id: String(v.id),
302
+ isSeeded: b.logo,
303
+ name: String(v.name ?? ""),
304
+ objectKey: B
302
305
  } : null,
303
- footerCopyright: Y ?? null,
306
+ footerCopyright: z ?? null,
304
307
  footerShowAttribution: o.get("footer_show_attribution") !== !1,
305
308
  siteDescription: typeof o.get("site_description") == "string" ? o.get("site_description") : "",
306
309
  siteKeywords: typeof o.get("site_keywords") == "string" ? o.get("site_keywords") : "",
307
- siteTitle: U
310
+ siteTitle: U,
311
+ socialImage: X
308
312
  },
309
- languages: p.map((e) => ({
310
- code: String(e.code),
311
- id: Number(e.id),
312
- isActive: e.is_active !== !1,
313
- isDefault: e.is_default === !0,
314
- name: String(e.name ?? "")
313
+ languages: f.map((s) => ({
314
+ code: String(s.code),
315
+ id: Number(s.id),
316
+ isActive: s.is_active !== !1,
317
+ isDefault: s.is_default === !0,
318
+ name: String(s.name ?? "")
315
319
  })),
316
320
  media: {
317
- count: y.length,
318
- sample: a.includeMedia ? y.slice(0, 40).map((e) => ({
319
- altText: e.description ?? null,
320
- fileName: e.file_name ?? null,
321
- id: String(e.id),
322
- isSeeded: J.has(String(e.object_key))
321
+ count: w.length,
322
+ sample: a.includeMedia ? w.slice(0, 40).map((s) => ({
323
+ altText: s.description ?? null,
324
+ fileName: s.file_name ?? null,
325
+ id: String(s.id),
326
+ isSeeded: V.has(String(s.object_key))
323
327
  })) : void 0,
324
- seededCount: T
328
+ seededCount: C
325
329
  },
326
- navigation: F,
327
- nextSteps: S,
328
- pages: B,
329
- posts: P,
330
- products: v.map((e) => ({
331
- id: String(e.id),
332
- languageCode: _.get(Number(e.language_id)) ?? String(e.language_id),
333
- slug: String(e.slug),
334
- status: String(e.status),
335
- title: String(e.title ?? "")
330
+ navigation: Y,
331
+ nextSteps: g,
332
+ pages: x,
333
+ posts: K,
334
+ products: T.map((s) => ({
335
+ id: String(s.id),
336
+ languageCode: S.get(Number(s.language_id)) ?? String(s.language_id),
337
+ slug: String(s.slug),
338
+ status: String(s.status),
339
+ title: String(s.title ?? "")
336
340
  })),
337
- seeded: { ...m, anyPresent: W },
341
+ seeded: { ...b, anyPresent: J },
338
342
  success: !0,
339
- themes: (f.data ?? []).map((e) => ({
340
- colorScheme: String(e.color_scheme ?? "light"),
341
- isActive: e.is_active !== !1,
342
- isDefault: e.is_default === !0,
343
- name: String(e.name ?? ""),
344
- slug: String(e.slug)
343
+ themes: (m.data ?? []).map((s) => ({
344
+ colorScheme: String(s.color_scheme ?? "light"),
345
+ isActive: s.is_active !== !1,
346
+ isDefault: s.is_default === !0,
347
+ name: String(s.name ?? ""),
348
+ slug: String(s.slug)
345
349
  }))
346
350
  };
347
351
  }
348
- const Be = 30;
349
- function Ye(t) {
350
- const { counts: i, seeded: a } = t, s = [];
351
- s.push(
352
- `Counts: ${i.pages} pages, ${i.posts} posts, ${i.products} products, ${i.media} media, ${i.customBlocks} custom blocks, ${i.drafts} drafts.`
352
+ const Pe = 30;
353
+ function et(e) {
354
+ const { counts: t, seeded: a } = e, i = [];
355
+ i.push(
356
+ `Counts: ${t.pages} pages, ${t.posts} posts, ${t.products} products, ${t.media} media, ${t.customBlocks} custom blocks, ${t.drafts} drafts.`
353
357
  );
354
- const n = t.languages.filter((c) => c.isActive), o = (n.length > 0 ? n : t.languages).map((c) => `${c.code}${c.isDefault ? " (default)" : ""}`).join(", ");
355
- if (s.push(`Languages: ${o || "none"}.`), a.anyPresent) {
358
+ const r = e.languages.filter((c) => c.isActive), o = (r.length > 0 ? r : e.languages).map((c) => `${c.code}${c.isDefault ? " (default)" : ""}`).join(", ");
359
+ if (i.push(`Languages: ${o || "none"}.`), a.anyPresent) {
356
360
  const c = [
357
361
  a.pages > 0 ? `${a.pages} demo pages` : null,
358
362
  a.posts > 0 ? `${a.posts} demo posts` : null,
@@ -360,279 +364,315 @@ function Ye(t) {
360
364
  a.logo ? "the NextBlock logo" : null,
361
365
  a.siteTitle ? "the default site title" : null,
362
366
  a.copyright ? "the default copyright" : null
363
- ].filter((g) => !!g);
364
- s.push(`NextBlock demo content: present (${c.join(", ")}).`);
367
+ ].filter((p) => !!p);
368
+ i.push(`NextBlock demo content: present (${c.join(", ")}).`);
365
369
  } else
366
- s.push("NextBlock demo content: none detected.");
367
- const r = t.homePage;
368
- s.push(
369
- r ? `Home page: "${r.title}" (/${r.slug}) ${r.status}, ${r.blockCount} blocks${r.isSeeded ? ", seeded demo content" : ""}.` : 'Home page: missing in the default language ("/" returns 404).'
370
+ i.push("NextBlock demo content: none detected.");
371
+ const l = e.homePage;
372
+ i.push(
373
+ l ? `Home page: "${l.title}" (/${l.slug}) ${l.status}, ${l.blockCount} blocks${l.isSeeded ? ", seeded demo content" : ""}.` : 'Home page: missing in the default language ("/" returns 404).'
370
374
  );
371
- const d = t.identity, f = d.activeLogo ? `logo "${d.activeLogo.name || d.activeLogo.id}"${d.activeLogo.isSeeded ? " (NextBlock demo logo)" : ""}` : "no logo";
372
- if (s.push(`Site title: ${d.siteTitle ? `"${d.siteTitle}"` : "not set"}; ${f}.`), t.themes.length > 0 && s.push(
373
- `Themes: ${t.themes.map(
375
+ const d = e.identity, m = d.activeLogo ? `logo "${d.activeLogo.name || d.activeLogo.id}"${d.activeLogo.isSeeded ? " (NextBlock demo logo)" : ""}` : "no logo";
376
+ if (i.push(`Site title: ${d.siteTitle ? `"${d.siteTitle}"` : "not set"}; ${m}.`), i.push(
377
+ d.socialImage ? "Social preview image: set." : "Social preview image: not set (link previews show the NextBlock banner; set one with update_site_identity social_image)."
378
+ ), e.themes.length > 0 && i.push(
379
+ `Themes: ${e.themes.map(
374
380
  (c) => `${c.name || c.slug} (${c.colorScheme}${c.isDefault ? ", default" : ""}${c.isActive ? "" : ", inactive"})`
375
381
  ).join("; ")}.`
376
- ), s.push(t.brief ? `Site brief: saved (status ${t.brief.status}).` : "Site brief: none saved."), s.push(
377
- t.buildSession ? `Build session: active until ${t.buildSession.expiresAt}.` : "Build session: none."
378
- ), t.pages.length > 0) {
379
- const c = t.pages.slice(0, Be), g = Math.max(i.pages, t.pages.length) - c.length;
380
- s.push(
381
- `Pages: ${c.map((p) => `${p.title || p.slug} (/${p.slug}) [${p.languageCode}]`).join(", ")}${g > 0 ? `, +${g} more` : ""}.`
382
+ ), i.push(e.brief ? `Site brief: saved (status ${e.brief.status}).` : "Site brief: none saved."), i.push(
383
+ e.buildSession ? `Build session: active until ${e.buildSession.expiresAt}.` : "Build session: none."
384
+ ), e.pages.length > 0) {
385
+ const c = e.pages.slice(0, Pe), p = Math.max(t.pages, e.pages.length) - c.length;
386
+ i.push(
387
+ `Pages: ${c.map((f) => `${f.title || f.slug} (/${f.slug}) [${f.languageCode}]`).join(", ")}${p > 0 ? `, +${p} more` : ""}.`
382
388
  );
383
389
  } else
384
- s.push("Pages: none.");
385
- return s.join(`
390
+ i.push("Pages: none.");
391
+ return i.join(`
386
392
  `);
387
393
  }
388
- const fe = l.strictObject({
389
- active_logo_id: l.string().uuid().nullable().optional().describe("Pin a logos.id as the active logo, or null to fall back to the newest logo."),
390
- footer_copyright: l.record(l.string().trim().min(2).max(12), l.string().trim().min(1).max(500)).optional().describe('Copyright line per locale code; "{year}" is replaced with the current year. Example: { "en": "© {year} Acme Bakery", "fr": "© {year} Boulangerie Acme" }.'),
391
- footer_show_attribution: l.boolean().optional().describe('Whether the "Published with NextBlock" credit shows in the footer. Set false when the client wants no NextBlock mention.'),
392
- site_description: l.string().trim().max(500).optional().describe("Default meta description and header tagline."),
393
- site_keywords: l.string().trim().max(500).optional().describe("Comma-separated SEO keywords."),
394
- site_title: l.string().trim().min(1).max(160).optional().describe("The brand name shown in the header and <title>.")
395
- }).refine((t) => Object.values(t).some((i) => i !== void 0), {
394
+ const he = u.strictObject({
395
+ active_logo_id: u.string().uuid().nullable().optional().describe("Pin a logos.id as the active logo, or null to fall back to the newest logo."),
396
+ footer_copyright: u.record(u.string().trim().min(2).max(12), u.string().trim().min(1).max(500)).optional().describe('Copyright line per locale code; "{year}" is replaced with the current year. Example: { "en": "© {year} Acme Bakery", "fr": "© {year} Boulangerie Acme" }.'),
397
+ footer_show_attribution: u.boolean().optional().describe('Whether the "Published with NextBlock" credit shows in the footer. Set false when the client wants no NextBlock mention.'),
398
+ site_description: u.string().trim().max(500).optional().describe("Default meta description and header tagline."),
399
+ site_keywords: u.string().trim().max(500).optional().describe("Comma-separated SEO keywords."),
400
+ site_title: u.string().trim().min(1).max(160).optional().describe("The brand name shown in the header and <title>."),
401
+ social_image: u.string().trim().min(1).max(2048).nullable().optional().describe(
402
+ "The site-wide social preview image (Open Graph / Twitter card) for every page, post or product without a feature image of its own — the home page above all, which must NOT get a feature image (on a page that renders a full-width title banner above the hero). A media library id (from list_media or upload_media) or an https image URL, hotlinked as-is (a search_stock_photos `url` works). Use a wide landscape image, ideally 1200×630. Null clears it and the NextBlock banner is used."
403
+ )
404
+ }).refine((e) => Object.values(e).some((t) => t !== void 0), {
396
405
  message: "Provide at least one identity field to update."
397
406
  });
398
- async function Pe(t, i) {
399
- const a = fe.parse(t);
400
- await se(i, ["ADMIN", "WRITER"], "Updating the site identity");
401
- const s = O(i), n = Object.entries(a).filter(([, r]) => r !== void 0), o = ae({
407
+ async function je(e, t) {
408
+ const a = he.parse(e);
409
+ await se(t, ["ADMIN", "WRITER"], "Updating the site identity");
410
+ const i = L(t), r = Object.entries(a).filter(([, d]) => d !== void 0), o = oe({
402
411
  action: "UPDATE SITE IDENTITY",
403
- context: i,
412
+ context: t,
404
413
  payload: { input: a, tool: "update_site_identity" },
405
414
  preview: {
406
- changes: Object.fromEntries(n),
407
- summary: `Update ${n.map(([r]) => r).join(", ")} for the whole site.`
415
+ changes: Object.fromEntries(r),
416
+ summary: `Update ${r.map(([d]) => d).join(", ")} for the whole site.`
408
417
  },
409
- subject: n.map(([r]) => r).join(" ")
418
+ subject: r.map(([d]) => d).join(" ")
410
419
  });
411
420
  if (o)
412
421
  return o;
413
- for (const [r, d] of n)
414
- await k(s, r, d);
415
- return oe(i, ["/cms/settings/logos", "/cms/settings/copyright"]), {
422
+ const l = [];
423
+ for (const [d, m] of r)
424
+ l.push(
425
+ d === "social_image" ? [H, await Ke(i, m)] : [d, m]
426
+ );
427
+ for (const [d, m] of l)
428
+ await k(i, d, m);
429
+ return ne(t, ["/cms/settings/logos", "/cms/settings/copyright"]), {
416
430
  mutationExecuted: !0,
417
431
  success: !0,
418
- updatedKeys: n.map(([r]) => r)
432
+ updatedKeys: l.map(([d]) => d)
433
+ };
434
+ }
435
+ const Me = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
436
+ async function Ke(e, t) {
437
+ if (t === null)
438
+ return null;
439
+ const a = t.trim();
440
+ if (/^https?:\/\//i.test(a))
441
+ return { alt: null, height: null, media_id: null, object_key: null, url: a, width: null };
442
+ if (!Me.test(a))
443
+ throw new Error(`"${a}" is not a usable social image — provide an https:// image URL or a media library id.`);
444
+ const { data: i, error: r } = await e.from("media").select("id, object_key, file_path, width, height, description, variants").eq("id", a).maybeSingle();
445
+ if (r)
446
+ throw new Error(`Could not read media ${a}: ${y(r)}`);
447
+ if (!i)
448
+ throw new Error(`No media library item has the id ${a}. Call list_media to find one, or upload_media to import an image first.`);
449
+ const o = ve(i), l = $e(i);
450
+ if (!l)
451
+ throw new Error(`Media ${a} has no usable file to serve as the social image.`);
452
+ return {
453
+ alt: typeof i.description == "string" && i.description.trim() ? i.description.trim() : null,
454
+ height: o?.height ?? (typeof i.height == "number" ? i.height : null),
455
+ media_id: String(i.id),
456
+ object_key: l,
457
+ url: null,
458
+ width: o?.width ?? (typeof i.width == "number" ? i.width : null)
419
459
  };
420
460
  }
421
- const Re = l.strictObject({
422
- customBlocks: l.boolean().default(!1).describe("Delete every custom block definition."),
423
- identity: l.boolean().default(!0).describe("Clear the NextBlock site title, description, keywords, copyright line, and the seeded logo."),
424
- navigation: l.boolean().default(!0).describe("Delete every header and footer navigation item."),
425
- pages: l.boolean().default(!0).describe("Delete pages (all languages) except keepPageSlugs."),
426
- posts: l.boolean().default(!0).describe("Delete every blog post."),
427
- products: l.boolean().default(!1).describe("Delete every product, with its variants, images, and drafts."),
428
- seededMedia: l.boolean().default(!0).describe("Delete the bundled NextBlock demo images from the media library.")
429
- }).partial(), Z = l.strictObject({
430
- clearKeptPageBlocks: l.boolean().default(!0).describe('Empty the blocks of the kept pages (usually "home") so they can be rebuilt. Set false to leave their content untouched.'),
431
- dryRun: l.boolean().default(!1).describe("Report what would be removed without changing anything. No confirmation needed."),
432
- keepLanguages: l.array(l.string().trim().min(2).max(10)).max(10).optional().describe("Locale codes the site should keep. Other languages are deactivated (never deleted) and their pages and posts removed. Omit to keep every language as it is."),
433
- keepPageSlugs: l.array(l.string().trim().min(1).max(300)).max(50).default([N]).describe('Pages to keep, by slug in any language; every translation of a kept page is kept. Defaults to ["home"] because "/" must always resolve.'),
434
- onlySeeded: l.boolean().default(!1).describe("Remove only rows that match the NextBlock demo seed (fixed translation groups, bundled images). Use when the site already has real content next to the demo content."),
435
- scope: Re.default({})
461
+ const Ue = u.strictObject({
462
+ customBlocks: u.boolean().default(!1).describe("Delete every custom block definition."),
463
+ identity: u.boolean().default(!0).describe("Clear the NextBlock site title, description, keywords, copyright line, social preview image, and the seeded logo."),
464
+ navigation: u.boolean().default(!0).describe("Delete every header and footer navigation item."),
465
+ pages: u.boolean().default(!0).describe("Delete pages (all languages) except keepPageSlugs."),
466
+ posts: u.boolean().default(!0).describe("Delete every blog post."),
467
+ products: u.boolean().default(!1).describe("Delete every product, with its variants, images, and drafts."),
468
+ seededMedia: u.boolean().default(!0).describe("Delete the bundled NextBlock demo images from the media library.")
469
+ }).partial(), ee = u.strictObject({
470
+ clearKeptPageBlocks: u.boolean().default(!0).describe('Empty the blocks of the kept pages (usually "home") so they can be rebuilt. Set false to leave their content untouched.'),
471
+ dryRun: u.boolean().default(!1).describe("Report what would be removed without changing anything. No confirmation needed."),
472
+ keepLanguages: u.array(u.string().trim().min(2).max(10)).max(10).optional().describe("Locale codes the site should keep. Other languages are deactivated (never deleted) and their pages and posts removed. Omit to keep every language as it is."),
473
+ keepPageSlugs: u.array(u.string().trim().min(1).max(300)).max(50).default([A]).describe('Pages to keep, by slug in any language; every translation of a kept page is kept. Defaults to ["home"] because "/" must always resolve.'),
474
+ onlySeeded: u.boolean().default(!1).describe("Remove only rows that match the NextBlock demo seed (fixed translation groups, bundled images). Use when the site already has real content next to the demo content."),
475
+ scope: Ue.default({})
436
476
  });
437
- async function he(t, i) {
477
+ async function be(e, t) {
438
478
  const a = {
439
- customBlocks: t.scope.customBlocks ?? !1,
440
- identity: t.scope.identity ?? !0,
441
- navigation: t.scope.navigation ?? !0,
442
- pages: t.scope.pages ?? !0,
443
- posts: t.scope.posts ?? !0,
444
- products: t.scope.products ?? !1,
445
- seededMedia: t.scope.seededMedia ?? !0
446
- }, { data: s, error: n } = await i.from("languages").select("id, code, name, is_default, is_active");
447
- if (n)
448
- throw new Error(`Could not read the languages: ${h(n)}`);
449
- const o = Array.isArray(s) ? s : [], r = new Map(o.map((e) => [Number(e.id), String(e.code).toLowerCase()])), d = t.keepLanguages ? new Set(t.keepLanguages.map((e) => e.toLowerCase())) : null;
479
+ customBlocks: e.scope.customBlocks ?? !1,
480
+ identity: e.scope.identity ?? !0,
481
+ navigation: e.scope.navigation ?? !0,
482
+ pages: e.scope.pages ?? !0,
483
+ posts: e.scope.posts ?? !0,
484
+ products: e.scope.products ?? !1,
485
+ seededMedia: e.scope.seededMedia ?? !0
486
+ }, { data: i, error: r } = await t.from("languages").select("id, code, name, is_default, is_active");
487
+ if (r)
488
+ throw new Error(`Could not read the languages: ${y(r)}`);
489
+ const o = Array.isArray(i) ? i : [], l = new Map(o.map((n) => [Number(n.id), String(n.code).toLowerCase()])), d = e.keepLanguages ? new Set(e.keepLanguages.map((n) => n.toLowerCase())) : null;
450
490
  if (d) {
451
- const e = new Set(r.values()), u = [...d].filter((b) => !e.has(b));
452
- if (u.length > 0)
453
- throw new Error(`Unknown language code(s) in keepLanguages: ${u.join(", ")}. Existing: ${[...e].join(", ")}.`);
491
+ const n = new Set(l.values()), g = [...d].filter((s) => !n.has(s));
492
+ if (g.length > 0)
493
+ throw new Error(`Unknown language code(s) in keepLanguages: ${g.join(", ")}. Existing: ${[...n].join(", ")}.`);
454
494
  }
455
- const f = new Set(
456
- o.filter((e) => !d || d.has(String(e.code).toLowerCase())).map((e) => Number(e.id))
457
- ), c = o.filter((e) => d && !d.has(String(e.code).toLowerCase()) && e.is_active !== !1).map((e) => ({ code: String(e.code), id: Number(e.id) })), g = o.find((e) => e.is_default === !0), p = d && g && !d.has(String(g.code).toLowerCase()) ? (() => {
458
- const e = o.find((u) => d.has(String(u.code).toLowerCase()));
459
- return e ? { code: String(e.code), id: Number(e.id) } : null;
460
- })() : null, _ = (e) => te.has(String(e.translation_group_id)), { data: M, error: v } = await i.from("pages").select("id, slug, language_id, translation_group_id, author_id");
461
- if (v)
462
- throw new Error(`Could not read the pages: ${h(v)}`);
463
- const $ = Array.isArray(M) ? M : [], F = new Set(t.keepPageSlugs.map((e) => e.toLowerCase())), H = new Set(
464
- $.filter((e) => F.has(String(e.slug).toLowerCase())).map((e) => String(e.translation_group_id))
465
- ), y = (e) => ({
466
- id: Number(e.id),
467
- languageCode: r.get(Number(e.language_id)) ?? String(e.language_id),
468
- slug: String(e.slug)
469
- }), T = [], C = [];
470
- for (const e of $) {
471
- const u = H.has(String(e.translation_group_id)), b = f.has(Number(e.language_id)), z = !t.onlySeeded || _(e);
472
- u && b ? C.push(y(e)) : a.pages && z || !b ? T.push(y(e)) : C.push(y(e));
495
+ const m = new Set(
496
+ o.filter((n) => !d || d.has(String(n.code).toLowerCase())).map((n) => Number(n.id))
497
+ ), c = o.filter((n) => d && !d.has(String(n.code).toLowerCase()) && n.is_active !== !1).map((n) => ({ code: String(n.code), id: Number(n.id) })), p = o.find((n) => n.is_default === !0), f = d && p && !d.has(String(p.code).toLowerCase()) ? (() => {
498
+ const n = o.find((g) => d.has(String(g.code).toLowerCase()));
499
+ return n ? { code: String(n.code), id: Number(n.id) } : null;
500
+ })() : null, S = (n) => ie.has(String(n.translation_group_id)), { data: P, error: T } = await t.from("pages").select("id, slug, language_id, translation_group_id, author_id");
501
+ if (T)
502
+ throw new Error(`Could not read the pages: ${y(T)}`);
503
+ const E = Array.isArray(P) ? P : [], Y = new Set(e.keepPageSlugs.map((n) => n.toLowerCase())), W = new Set(
504
+ E.filter((n) => Y.has(String(n.slug).toLowerCase())).map((n) => String(n.translation_group_id))
505
+ ), w = (n) => ({
506
+ id: Number(n.id),
507
+ languageCode: l.get(Number(n.language_id)) ?? String(n.language_id),
508
+ slug: String(n.slug)
509
+ }), C = [], I = [];
510
+ for (const n of E) {
511
+ const g = W.has(String(n.translation_group_id)), s = m.has(Number(n.language_id)), h = !e.onlySeeded || S(n);
512
+ g && s ? I.push(w(n)) : a.pages && h || !s ? C.push(w(n)) : I.push(w(n));
473
513
  }
474
- const { data: j, error: K } = await i.from("posts").select("id, slug, language_id, translation_group_id, author_id");
475
- if (K)
476
- throw new Error(`Could not read the posts: ${h(K)}`);
477
- const w = (Array.isArray(j) ? j : []).filter((e) => {
478
- const u = f.has(Number(e.language_id)), b = !t.onlySeeded || _(e);
479
- return a.posts && b || !u;
480
- }).map(y);
481
- let A = [];
514
+ const { data: j, error: M } = await t.from("posts").select("id, slug, language_id, translation_group_id, author_id");
515
+ if (M)
516
+ throw new Error(`Could not read the posts: ${y(M)}`);
517
+ const v = (Array.isArray(j) ? j : []).filter((n) => {
518
+ const g = m.has(Number(n.language_id)), s = !e.onlySeeded || S(n);
519
+ return a.posts && s || !g;
520
+ }).map(w);
521
+ let D = [];
482
522
  if (a.products)
483
523
  try {
484
- const { data: e } = await i.from("products").select("id");
485
- A = (Array.isArray(e) ? e : []).map((u) => String(u.id));
524
+ const { data: n } = await t.from("products").select("id");
525
+ D = (Array.isArray(n) ? n : []).map((g) => String(g.id));
486
526
  } catch {
487
- A = [];
527
+ D = [];
488
528
  }
489
- const D = new Set(T.map((e) => e.id)), B = new Set(T.map((e) => `/${e.slug}`)), { data: P, error: U } = await i.from("navigation_items").select("id, url, page_id");
490
- if (U)
491
- throw new Error(`Could not read the navigation: ${h(U)}`);
492
- const Y = (Array.isArray(P) ? P : []).filter(
493
- (e) => a.navigation || e.page_id !== null && e.page_id !== void 0 && D.has(Number(e.page_id)) || B.has(String(e.url))
494
- ).map((e) => Number(e.id)), m = C.map((e) => e.id), W = new Set(w.map((e) => e.id)), { data: q } = await i.from("content_drafts").select("id, parent_type, parent_id"), X = (Array.isArray(q) ? q : []).filter((e) => {
495
- const u = Number(e.parent_id);
496
- return e.parent_type === "page" ? D.has(u) || t.clearKeptPageBlocks && m.includes(u) : e.parent_type === "post" && W.has(u);
497
- }).map((e) => ({ parentId: Number(e.parent_id), parentType: String(e.parent_type) }));
498
- let x = [];
529
+ const B = new Set(C.map((n) => n.id)), R = new Set(C.map((n) => `/${n.slug}`)), { data: X, error: x } = await t.from("navigation_items").select("id, url, page_id");
530
+ if (x)
531
+ throw new Error(`Could not read the navigation: ${y(x)}`);
532
+ const K = (Array.isArray(X) ? X : []).filter(
533
+ (n) => a.navigation || n.page_id !== null && n.page_id !== void 0 && B.has(Number(n.page_id)) || R.has(String(n.url))
534
+ ).map((n) => Number(n.id)), U = I.map((n) => n.id), z = new Set(v.map((n) => n.id)), { data: b } = await t.from("content_drafts").select("id, parent_type, parent_id"), J = (Array.isArray(b) ? b : []).filter((n) => {
535
+ const g = Number(n.parent_id);
536
+ return n.parent_type === "page" ? B.has(g) || e.clearKeptPageBlocks && U.includes(g) : n.parent_type === "post" && z.has(g);
537
+ }).map((n) => ({ parentId: Number(n.parent_id), parentType: String(n.parent_type) }));
538
+ let q = [];
499
539
  if (a.customBlocks) {
500
- const { data: e } = await i.from("custom_block_definitions").select("slug");
501
- x = (Array.isArray(e) ? e : []).map((u) => String(u.slug));
540
+ const { data: n } = await t.from("custom_block_definitions").select("slug");
541
+ q = (Array.isArray(n) ? n : []).map((g) => String(g.slug));
502
542
  }
503
- let R = [], S = [];
543
+ let G = [], $ = [];
504
544
  if (a.seededMedia || a.identity) {
505
- const { data: e } = await i.from("media").select("id, object_key"), u = (Array.isArray(e) ? e : []).filter(
506
- (E) => J.has(String(E.object_key))
507
- ), b = new Set(u.map((E) => String(E.id))), { data: z } = await i.from("logos").select("id, media_id");
508
- S = (Array.isArray(z) ? z : []).filter((E) => b.has(String(E.media_id))).map((E) => String(E.id)), R = a.seededMedia ? [...b] : [], a.identity || (S = []);
545
+ const { data: n } = await t.from("media").select("id, object_key"), g = (Array.isArray(n) ? n : []).filter(
546
+ (_) => V.has(String(_.object_key))
547
+ ), s = new Set(g.map((_) => String(_.id))), { data: h } = await t.from("logos").select("id, media_id");
548
+ $ = (Array.isArray(h) ? h : []).filter((_) => s.has(String(_.media_id))).map((_) => String(_.id)), G = a.seededMedia ? [...s] : [], a.identity || ($ = []);
509
549
  }
510
550
  return {
511
- blocksToClearPageIds: t.clearKeptPageBlocks ? m : [],
512
- customBlockSlugs: x,
513
- draftsToDelete: X,
514
- identityKeys: a.identity ? ["site_title", "site_description", "site_keywords", "footer_copyright"] : [],
551
+ blocksToClearPageIds: e.clearKeptPageBlocks ? U : [],
552
+ customBlockSlugs: q,
553
+ draftsToDelete: J,
554
+ identityKeys: a.identity ? ["site_title", "site_description", "site_keywords", "footer_copyright", H] : [],
515
555
  languagesToDeactivate: c,
516
- logosToDelete: S,
517
- mediaToDelete: R,
518
- navigationToDelete: Y,
519
- newDefaultLanguage: p,
520
- pagesToDelete: T,
521
- pagesToKeep: C,
522
- postsToDelete: w,
523
- productsToDelete: A
556
+ logosToDelete: $,
557
+ mediaToDelete: G,
558
+ navigationToDelete: K,
559
+ newDefaultLanguage: f,
560
+ pagesToDelete: C,
561
+ pagesToKeep: I,
562
+ postsToDelete: v,
563
+ productsToDelete: D
524
564
  };
525
565
  }
526
- function be(t) {
566
+ function ye(e) {
527
567
  return {
528
- blocksClearedOnPages: t.blocksToClearPageIds.length,
529
- customBlocks: t.customBlockSlugs.length,
530
- drafts: t.draftsToDelete.length,
531
- identityCleared: t.identityKeys.length > 0,
532
- languagesDeactivated: t.languagesToDeactivate.map((i) => i.code),
533
- logos: t.logosToDelete.length,
534
- media: t.mediaToDelete.length,
535
- navigationItems: t.navigationToDelete.length,
536
- pages: t.pagesToDelete.length,
537
- pagesKept: t.pagesToKeep.map((i) => `${i.slug} (${i.languageCode})`),
538
- posts: t.postsToDelete.length,
539
- products: t.productsToDelete.length
568
+ blocksClearedOnPages: e.blocksToClearPageIds.length,
569
+ customBlocks: e.customBlockSlugs.length,
570
+ drafts: e.draftsToDelete.length,
571
+ identityCleared: e.identityKeys.length > 0,
572
+ languagesDeactivated: e.languagesToDeactivate.map((t) => t.code),
573
+ logos: e.logosToDelete.length,
574
+ media: e.mediaToDelete.length,
575
+ navigationItems: e.navigationToDelete.length,
576
+ pages: e.pagesToDelete.length,
577
+ pagesKept: e.pagesToKeep.map((t) => `${t.slug} (${t.languageCode})`),
578
+ posts: e.postsToDelete.length,
579
+ products: e.productsToDelete.length
540
580
  };
541
581
  }
542
- async function Le(t, i, a) {
543
- for (const s of t.draftsToDelete) {
544
- const { error: n } = await i.from("content_drafts").delete().eq("parent_type", s.parentType).eq("parent_id", s.parentId);
545
- if (n)
546
- throw new Error(`Could not delete a draft: ${h(n)}`);
582
+ async function qe(e, t, a) {
583
+ for (const i of e.draftsToDelete) {
584
+ const { error: r } = await t.from("content_drafts").delete().eq("parent_type", i.parentType).eq("parent_id", i.parentId);
585
+ if (r)
586
+ throw new Error(`Could not delete a draft: ${y(r)}`);
547
587
  }
548
- if (await I(i, "navigation_items", "id", t.navigationToDelete), t.blocksToClearPageIds.length > 0)
549
- for (const s of ce(t.blocksToClearPageIds)) {
550
- const { error: n } = await i.from("blocks").delete().in("page_id", s);
551
- if (n)
552
- throw new Error(`Could not clear the kept pages: ${h(n)}`);
588
+ if (await N(t, "navigation_items", "id", e.navigationToDelete), e.blocksToClearPageIds.length > 0)
589
+ for (const i of ge(e.blocksToClearPageIds)) {
590
+ const { error: r } = await t.from("blocks").delete().in("page_id", i);
591
+ if (r)
592
+ throw new Error(`Could not clear the kept pages: ${y(r)}`);
553
593
  }
554
- if (await I(i, "pages", "id", t.pagesToDelete.map((s) => s.id)), await I(i, "posts", "id", t.postsToDelete.map((s) => s.id)), await I(i, "products", "id", t.productsToDelete), await I(i, "custom_block_definitions", "slug", t.customBlockSlugs), await I(i, "logos", "id", t.logosToDelete), await I(i, "media", "id", t.mediaToDelete), t.identityKeys.length > 0 && (await k(i, "site_title", ""), await k(i, "site_description", ""), await k(i, "site_keywords", ""), await k(
555
- i,
594
+ if (await N(t, "pages", "id", e.pagesToDelete.map((i) => i.id)), await N(t, "posts", "id", e.postsToDelete.map((i) => i.id)), await N(t, "products", "id", e.productsToDelete), await N(t, "custom_block_definitions", "slug", e.customBlockSlugs), await N(t, "logos", "id", e.logosToDelete), await N(t, "media", "id", e.mediaToDelete), e.identityKeys.length > 0 && (await k(t, "site_title", ""), await k(t, "site_description", ""), await k(t, "site_keywords", ""), await k(t, H, null), await k(
595
+ t,
556
596
  "footer_copyright",
557
- Object.fromEntries(a.map((s) => [s, "© {year}"]))
558
- )), t.logosToDelete.length > 0) {
559
- const { data: s } = await i.from("site_settings").select("value").eq("key", "active_logo_id").maybeSingle();
560
- typeof s?.value == "string" && t.logosToDelete.includes(s.value) && await k(i, "active_logo_id", null);
597
+ Object.fromEntries(a.map((i) => [i, "© {year}"]))
598
+ )), e.logosToDelete.length > 0) {
599
+ const { data: i } = await t.from("site_settings").select("value").eq("key", "active_logo_id").maybeSingle();
600
+ typeof i?.value == "string" && e.logosToDelete.includes(i.value) && await k(t, "active_logo_id", null);
561
601
  }
562
- for (const s of t.languagesToDeactivate) {
563
- const { error: n } = await i.from("languages").update({ is_active: !1, is_default: !1 }).eq("id", s.id);
564
- if (n)
565
- throw new Error(`Could not deactivate the "${s.code}" language: ${h(n)}`);
602
+ for (const i of e.languagesToDeactivate) {
603
+ const { error: r } = await t.from("languages").update({ is_active: !1, is_default: !1 }).eq("id", i.id);
604
+ if (r)
605
+ throw new Error(`Could not deactivate the "${i.code}" language: ${y(r)}`);
566
606
  }
567
- if (t.newDefaultLanguage) {
568
- const { error: s } = await i.from("languages").update({ is_default: !1 }).neq("id", t.newDefaultLanguage.id);
569
- if (s)
570
- throw new Error(`Could not change the default language: ${h(s)}`);
571
- const { error: n } = await i.from("languages").update({ is_active: !0, is_default: !0 }).eq("id", t.newDefaultLanguage.id);
572
- if (n)
573
- throw new Error(`Could not change the default language: ${h(n)}`);
607
+ if (e.newDefaultLanguage) {
608
+ const { error: i } = await t.from("languages").update({ is_default: !1 }).neq("id", e.newDefaultLanguage.id);
609
+ if (i)
610
+ throw new Error(`Could not change the default language: ${y(i)}`);
611
+ const { error: r } = await t.from("languages").update({ is_active: !0, is_default: !0 }).eq("id", e.newDefaultLanguage.id);
612
+ if (r)
613
+ throw new Error(`Could not change the default language: ${y(r)}`);
574
614
  }
575
615
  }
576
- function re(t) {
577
- const i = [], a = t.pagesToKeep.some((s) => s.slug === N);
578
- return a && t.blocksToClearPageIds.length > 0 ? i.push('The home page was kept but is now empty: rebuild it with rewrite_page_draft (contentType "page", slug "home") and then publish_content_draft.') : a || i.push(`No "${N}" page remains, so "/" is a 404 until you create_cms_page with slug "${N}" and status "published".`), t.navigationToDelete.length > 0 && i.push('The header and footer menus are empty: rebuild them with update_navigation_bar (mode "replace") and update_footer for every active language.'), t.identityKeys.length > 0 && i.push("Site title, description, keywords, and copyright are blank: set them with update_site_identity."), i.push('The "Published with NextBlock" footer credit is unchanged; set footer_show_attribution to false with update_site_identity if the client wants no NextBlock mention.'), i;
616
+ function le(e) {
617
+ const t = [], a = e.pagesToKeep.some((i) => i.slug === A);
618
+ return a && e.blocksToClearPageIds.length > 0 ? t.push('The home page was kept but is now empty: rebuild it with rewrite_page_draft (contentType "page", slug "home") and then publish_content_draft.') : a || t.push(`No "${A}" page remains, so "/" is a 404 until you create_cms_page with slug "${A}" and status "published".`), e.navigationToDelete.length > 0 && t.push('The header and footer menus are empty: rebuild them with update_navigation_bar (mode "replace") and update_footer for every active language.'), e.identityKeys.length > 0 && t.push("Site title, description, keywords, and copyright are blank: set them with update_site_identity."), t.push('The "Published with NextBlock" footer credit is unchanged; set footer_show_attribution to false with update_site_identity if the client wants no NextBlock mention.'), t;
579
619
  }
580
- async function ye(t, i) {
581
- const a = Z.parse(t);
582
- await se(i, ["ADMIN"], "Resetting the site content");
583
- const s = O(i), n = await he(a, s), o = be(n), r = o.pages + o.posts + o.products + o.navigationItems + o.customBlocks + o.media + o.logos;
620
+ async function _e(e, t) {
621
+ const a = ee.parse(e);
622
+ await se(t, ["ADMIN"], "Resetting the site content");
623
+ const i = L(t), r = await be(a, i), o = ye(r), l = o.pages + o.posts + o.products + o.navigationItems + o.customBlocks + o.media + o.logos;
584
624
  if (a.dryRun)
585
625
  return {
586
626
  dryRun: !0,
587
627
  mutationExecuted: !1,
588
- nextSteps: re(n),
628
+ nextSteps: le(r),
589
629
  success: !0,
590
630
  summary: o,
591
631
  wouldDelete: {
592
- pages: n.pagesToDelete,
593
- posts: n.postsToDelete
632
+ pages: r.pagesToDelete,
633
+ posts: r.postsToDelete
594
634
  }
595
635
  };
596
- const d = ae({
636
+ const d = oe({
597
637
  action: "RESET SITE CONTENT",
598
- context: i,
638
+ context: t,
599
639
  payload: { input: a, summary: o, tool: "reset_site_content" },
600
640
  preview: {
601
641
  irreversible: !0,
602
642
  summary: `Permanently delete ${o.pages} page(s), ${o.posts} post(s), ${o.products} product(s), ${o.navigationItems} navigation item(s), ${o.customBlocks} custom block definition(s), ${o.media} demo image(s) and ${o.logos} logo(s)${o.identityCleared ? ", clear the site identity" : ""}${o.languagesDeactivated.length ? `, deactivate ${o.languagesDeactivated.join(", ")}` : ""}. Kept: ${o.pagesKept.join(", ") || "nothing"}. This cannot be undone — take a backup first at /cms/settings/backup-restore if the content matters.`,
603
643
  ...o
604
644
  },
605
- subject: `${r} items`
645
+ subject: `${l} items`
606
646
  });
607
647
  if (d)
608
648
  return d;
609
- const f = a.keepLanguages?.map((g) => g.toLowerCase()) ?? [], c = f.length > 0 ? f : await (async () => {
610
- const { data: g } = await s.from("languages").select("code, is_active");
611
- return (Array.isArray(g) ? g : []).filter((p) => p.is_active !== !1).map((p) => String(p.code).toLowerCase());
649
+ const m = a.keepLanguages?.map((p) => p.toLowerCase()) ?? [], c = m.length > 0 ? m : await (async () => {
650
+ const { data: p } = await i.from("languages").select("code, is_active");
651
+ return (Array.isArray(p) ? p : []).filter((f) => f.is_active !== !1).map((f) => String(f.code).toLowerCase());
612
652
  })();
613
- return await Le(n, s, c), oe(i, ["/cms/pages", "/cms/posts", "/cms/navigation", "/cms/media", "/cms/custom-blocks", "/cms/products"]), {
653
+ return await qe(r, i, c), ne(t, ["/cms/pages", "/cms/posts", "/cms/navigation", "/cms/media", "/cms/custom-blocks", "/cms/products"]), {
614
654
  deleted: o,
615
655
  mutationExecuted: !0,
616
- nextSteps: re(n),
656
+ nextSteps: le(r),
617
657
  success: !0
618
658
  };
619
659
  }
620
- const Oe = 60, _e = l.strictObject({
621
- brief: le.optional().describe("The brief as agreed with the client. Merged over the saved brief and marked confirmed."),
622
- durationMinutes: l.number().int().min(15).max(180).default(Oe).describe("How long the unattended build session stays open."),
623
- reset: Z.optional().describe("Remove the seeded NextBlock demo content as the first step of the build. Omit when the client keeps existing content."),
624
- summary: l.string().trim().min(20).max(4e3).describe("The plan the operator is approving, in plain language: what is removed, every page and its sections, navigation, footer, theme, languages.")
660
+ const Ge = 60, we = u.strictObject({
661
+ brief: de.optional().describe("The brief as agreed with the client. Merged over the saved brief and marked confirmed."),
662
+ durationMinutes: u.number().int().min(15).max(180).default(Ge).describe("How long the unattended build session stays open."),
663
+ reset: ee.optional().describe("Remove the seeded NextBlock demo content as the first step of the build. Omit when the client keeps existing content."),
664
+ summary: u.string().trim().min(20).max(4e3).describe("The plan the operator is approving, in plain language: what is removed, every page and its sections, navigation, footer, theme, languages.")
625
665
  });
626
- async function Me(t, i) {
627
- const a = _e.parse(t);
628
- await se(i, ["ADMIN"], "Starting a site build");
629
- const s = O(i), n = i?.actorUserId;
630
- let o = null, r = null;
631
- a.reset && (r = Z.parse({ ...a.reset, dryRun: !1 }), o = be(await he(r, s)));
632
- const d = ae({
666
+ async function Fe(e, t) {
667
+ const a = we.parse(e);
668
+ await se(t, ["ADMIN"], "Starting a site build");
669
+ const i = L(t), r = t?.actorUserId;
670
+ let o = null, l = null;
671
+ a.reset && (l = ee.parse({ ...a.reset, dryRun: !1 }), o = ye(await be(l, i)));
672
+ const d = oe({
633
673
  action: "START SITE BUILD",
634
- context: i,
635
- payload: { brief: a.brief ?? null, durationMinutes: a.durationMinutes, reset: r, summary: a.summary, tool: "start_site_build" },
674
+ context: t,
675
+ payload: { brief: a.brief ?? null, durationMinutes: a.durationMinutes, reset: l, summary: a.summary, tool: "start_site_build" },
636
676
  preview: {
637
677
  durationMinutes: a.durationMinutes,
638
678
  plan: a.summary,
@@ -643,112 +683,112 @@ async function Me(t, i) {
643
683
  });
644
684
  if (d)
645
685
  return d;
646
- let f = null;
647
- a.brief ? f = (await Q(
686
+ let m = null;
687
+ a.brief ? m = (await Z(
648
688
  { brief: { ...a.brief, status: "confirmed" }, mode: "merge" },
649
- { ...i }
650
- )).brief : await ne(s) && (f = (await Q({ brief: { status: "confirmed" }, mode: "merge" }, { ...i })).brief);
689
+ { ...t }
690
+ )).brief : await re(i) && (m = (await Z({ brief: { status: "confirmed" }, mode: "merge" }, { ...t })).brief);
651
691
  let c = null;
652
- r && (c = await ye(r, { ...i, skipConfirmation: !0 }));
653
- const g = /* @__PURE__ */ new Date(), p = {
654
- actorUserId: n,
655
- createdAt: g.toISOString(),
656
- expiresAt: new Date(g.getTime() + a.durationMinutes * 6e4).toISOString(),
657
- id: xe(),
692
+ l && (c = await _e(l, { ...t, skipConfirmation: !0 }));
693
+ const p = /* @__PURE__ */ new Date(), f = {
694
+ actorUserId: r,
695
+ createdAt: p.toISOString(),
696
+ expiresAt: new Date(p.getTime() + a.durationMinutes * 6e4).toISOString(),
697
+ id: Ae(),
658
698
  summary: a.summary
659
699
  };
660
- return await k(s, G, p), {
661
- brief: f,
662
- buildSession: { expiresAt: p.expiresAt, id: p.id },
700
+ return await k(i, F, f), {
701
+ brief: m,
702
+ buildSession: { expiresAt: f.expiresAt, id: f.id },
663
703
  continuePrompt: "The build session is active and the plan is approved. Continue the site build now, step by step, without asking for confirmation: identity, languages, theme, pages (rewrite home, create the others), publish every draft, navigation, footer, translations, then call finish_site_build and summarize what was built with links.",
664
704
  mutationExecuted: !0,
665
705
  resetResult: c,
666
706
  success: !0
667
707
  };
668
708
  }
669
- const Se = l.strictObject({
670
- outcome: l.enum(["built", "stopped"]).default("built").describe('"built" when the plan was completed; "stopped" when the operator interrupted it.'),
671
- summary: l.string().trim().max(4e3).optional().describe("What was built, for the record.")
709
+ const Se = u.strictObject({
710
+ outcome: u.enum(["built", "stopped"]).default("built").describe('"built" when the plan was completed; "stopped" when the operator interrupted it.'),
711
+ summary: u.string().trim().max(4e3).optional().describe("What was built, for the record.")
672
712
  });
673
- async function je(t, i) {
674
- const a = Se.parse(t), s = O(i), n = await pe(s);
675
- await Ne(s);
713
+ async function He(e, t) {
714
+ const a = Se.parse(e), i = L(t), r = await pe(i);
715
+ await Re(i);
676
716
  let o = null;
677
717
  if (a.outcome === "built") {
678
- const r = await ne(s);
679
- r && (o = (await Q(
680
- { brief: { status: "built", ...a.summary ? { notes: `${r.notes ? `${r.notes}
718
+ const l = await re(i);
719
+ l && (o = (await Z(
720
+ { brief: { status: "built", ...a.summary ? { notes: `${l.notes ? `${l.notes}
681
721
  ` : ""}Built: ${a.summary}` } : {} }, mode: "merge" },
682
- { ...i }
722
+ { ...t }
683
723
  )).brief);
684
724
  }
685
- return oe(i, ["/cms/dashboard"]), {
725
+ return ne(t, ["/cms/dashboard"]), {
686
726
  brief: o,
687
727
  buildSessionEnded: !0,
688
- hadSession: !!n,
728
+ hadSession: !!r,
689
729
  mutationExecuted: !0,
690
730
  outcome: a.outcome,
691
731
  success: !0
692
732
  };
693
733
  }
694
- function We(t) {
734
+ function tt(e) {
695
735
  return {
696
- get_site_overview: L({
736
+ get_site_overview: O({
697
737
  description: 'Read the WHOLE site in one call: languages, site identity (title, description, copyright, active logo), every page, post, and product with status and language, the header and footer menus per language, themes, custom block definitions, media counts, pending Live Drafts, the saved site brief, and whether NextBlock demo content is still present. Read-only. Call this FIRST for any request about the site as a whole ("build my site", "update the whole website", "what do I have?") instead of several narrower reads.',
698
- execute: (i) => De(i, t),
699
- inputSchema: me,
738
+ execute: (t) => Le(t, e),
739
+ inputSchema: fe,
700
740
  strict: !0
701
741
  }),
702
- update_site_identity: L({
703
- description: 'Set the site-wide identity: site_title (brand name in the header and <title>), site_description, site_keywords, footer_copyright per locale ("{year}" is substituted), footer_show_attribution (the "Published with NextBlock" credit), and the pinned active_logo_id. Use it right after a reset and whenever the client renames or re-describes their business. Mutating: first returns a confirmation phrase; only executes after exact confirmation.',
704
- execute: (i) => Pe(i, t),
705
- inputSchema: fe,
742
+ update_site_identity: O({
743
+ description: 'Set the site-wide identity: site_title (brand name in the header and <title>), site_description, site_keywords, footer_copyright per locale ("{year}" is substituted), footer_show_attribution (the "Published with NextBlock" credit), the pinned active_logo_id, and social_image — the site-wide Open Graph / share preview image (a media id or an https URL) used by every page without a feature image, the home page above all, which must never get a feature image of its own. Use it right after a reset and whenever the client renames or re-describes their business. Mutating: first returns a confirmation phrase; only executes after exact confirmation.',
744
+ execute: (t) => je(t, e),
745
+ inputSchema: he,
706
746
  strict: !0
707
747
  }),
708
- save_site_brief: L({
748
+ save_site_brief: O({
709
749
  description: 'Record what the client wants their website to be — business, audience, goals, site type (a one-page landing site is fine), pages and their sections, languages, brand colours and tone, contact details, whether existing content is kept. Saved site-wide and shown to you at the start of every later conversation, so save it as soon as you learn something (mode "merge" adds to what is saved). Runs immediately, no confirmation.',
710
- execute: (i) => Q(i, t),
711
- inputSchema: ge,
750
+ execute: (t) => Z(t, e),
751
+ inputSchema: me,
712
752
  strict: !0
713
753
  }),
714
- reset_site_content: L({
754
+ reset_site_content: O({
715
755
  description: 'Remove the NextBlock demo content that ships with a fresh install — or, by scope, everything: pages (all languages, except keepPageSlugs, default ["home"] which is kept but emptied), posts, navigation, the bundled demo images and logo, the default site title and copyright; optionally products and custom block definitions. keepLanguages deactivates every other language and removes its content, so a client who only wants English gets an English-only site. Pass dryRun true to see the counts first. ADMIN only, irreversible; take a backup at /cms/settings/backup-restore if in doubt. Mutating: first returns a confirmation phrase; only executes after exact confirmation. Prefer start_site_build with `reset` when you are about to rebuild the site, so one confirmation covers both.',
716
- execute: (i) => ye(i, t),
717
- inputSchema: Z,
756
+ execute: (t) => _e(t, e),
757
+ inputSchema: ee,
718
758
  strict: !0
719
759
  }),
720
- start_site_build: L({
760
+ start_site_build: O({
721
761
  description: "Approve a whole-site plan with ONE confirmation. Pass the plain-language plan in `summary`, the agreed `brief`, and (usually) `reset` so the demo content is removed first. On confirmation it saves the brief, runs the reset, and opens a time-boxed build session: until it expires, every later tool call in this chat executes without asking for confirmation, so you can create pages, publish drafts, set navigation, footer, theme, and translations in one unattended run. Call it only after the client has seen the plan and agreed. After it succeeds, keep working through the plan immediately and finish with finish_site_build. ADMIN only.",
722
- execute: (i) => Me(i, t),
723
- inputSchema: _e,
762
+ execute: (t) => Fe(t, e),
763
+ inputSchema: we,
724
764
  strict: !0
725
765
  }),
726
- finish_site_build: L({
766
+ finish_site_build: O({
727
767
  description: 'Close the current build session (opened by start_site_build) and mark the brief as built. Call it as the LAST step of a site build, or with outcome "stopped" if the operator interrupted. Runs immediately, no confirmation.',
728
- execute: (i) => je(i, t),
768
+ execute: (t) => He(t, e),
729
769
  inputSchema: Se,
730
770
  strict: !0
731
771
  })
732
772
  };
733
773
  }
734
774
  export {
735
- J as NEXTBLOCK_SEED_MEDIA_OBJECT_KEYS,
736
- te as NEXTBLOCK_SEED_TRANSLATION_GROUP_IDS,
737
- We as createCortexSiteTools,
738
- je as executeFinishSiteBuild,
739
- De as executeGetSiteOverview,
740
- ye as executeResetSiteContent,
741
- Q as executeSaveSiteBrief,
742
- Me as executeStartSiteBuild,
743
- Pe as executeUpdateSiteIdentity,
775
+ V as NEXTBLOCK_SEED_MEDIA_OBJECT_KEYS,
776
+ ie as NEXTBLOCK_SEED_TRANSLATION_GROUP_IDS,
777
+ tt as createCortexSiteTools,
778
+ He as executeFinishSiteBuild,
779
+ Le as executeGetSiteOverview,
780
+ _e as executeResetSiteContent,
781
+ Z as executeSaveSiteBrief,
782
+ Fe as executeStartSiteBuild,
783
+ je as executeUpdateSiteIdentity,
744
784
  Se as finishSiteBuildInputSchema,
745
- Ye as formatCortexSiteOverviewForPrompt,
746
- me as getSiteOverviewInputSchema,
785
+ et as formatCortexSiteOverviewForPrompt,
786
+ fe as getSiteOverviewInputSchema,
747
787
  pe as readCortexBuildSession,
748
- ne as readCortexSiteBrief,
749
- Z as resetSiteContentInputSchema,
750
- He as resolveCortexBuildSession,
751
- ge as saveSiteBriefInputSchema,
752
- _e as startSiteBuildInputSchema,
753
- fe as updateSiteIdentityInputSchema
788
+ re as readCortexSiteBrief,
789
+ ee as resetSiteContentInputSchema,
790
+ Ze as resolveCortexBuildSession,
791
+ me as saveSiteBriefInputSchema,
792
+ we as startSiteBuildInputSchema,
793
+ he as updateSiteIdentityInputSchema
754
794
  };