@nextblock-cms/cortex 0.17.6 → 0.17.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,7 +1,8 @@
1
- import { availableCortexAiBlockTypes as b, createCortexGlobalAgentTools as y } from "./ai-global-agent-tools.es.js";
1
+ import { createCortexGlobalAgentTools as b } from "./ai-global-agent-tools.es.js";
2
+ import { availableCortexAiBlockTypes as y } from "./block-content-schemas.es.js";
2
3
  import "./zod-config.es.js";
3
- import { z as k } from "zod";
4
- const i = {
4
+ import { z as w } from "zod";
5
+ const o = {
5
6
  create_cms_page: "write",
6
7
  create_cms_post: "write",
7
8
  create_cms_product: "write",
@@ -14,6 +15,8 @@ const i = {
14
15
  execute_database_mutation: "write",
15
16
  fetch_ecommerce_stats: "read",
16
17
  fetch_url_content: "read",
18
+ finish_site_build: "write",
19
+ get_site_overview: "read",
17
20
  insert_content_block: "write",
18
21
  list_custom_blocks: "read",
19
22
  list_media: "read",
@@ -30,11 +33,14 @@ const i = {
30
33
  publish_content_draft: "write",
31
34
  read_current_cms_item: "read",
32
35
  read_database_records: "read",
36
+ reset_site_content: "write",
33
37
  revert_site_script: "write",
34
38
  rewrite_page_draft: "write",
39
+ save_site_brief: "write",
35
40
  search_documentation: "read",
36
41
  search_stock_photos: "read",
37
42
  set_content_images: "write",
43
+ start_site_build: "write",
38
44
  translate_content_bulk: "write",
39
45
  translate_page: "write",
40
46
  update_cms_item_field: "write",
@@ -45,7 +51,8 @@ const i = {
45
51
  update_footer: "write",
46
52
  update_global_css: "write",
47
53
  update_navigation_bar: "write",
48
- update_section_column_block: "write"
54
+ update_section_column_block: "write",
55
+ update_site_identity: "write"
49
56
  }, u = {
50
57
  generate_jsonb_layout: {
51
58
  canonical: "rewrite_page_draft",
@@ -73,74 +80,74 @@ const i = {
73
80
  title: "Update site navigation"
74
81
  }
75
82
  };
76
- function h(e) {
77
- return e in u ? u[e].canonical : e in i ? e : null;
83
+ function _(e) {
84
+ return e in u ? u[e].canonical : e in o ? e : null;
78
85
  }
79
- function w(e) {
80
- const t = h(e);
81
- return t ? i[t] : null;
86
+ function k(e) {
87
+ const t = _(e);
88
+ return t ? o[t] : null;
82
89
  }
83
90
  function d(e, t) {
84
- const r = w(t);
85
- return r ? r === "read" ? e.length > 0 : e.includes("write") : !1;
91
+ const a = k(t);
92
+ return a ? a === "read" ? e.length > 0 : e.includes("write") : !1;
86
93
  }
87
94
  function p(e) {
88
- return e.split("_").map((r, o) => o === 0 ? r.charAt(0).toUpperCase() + r.slice(1) : r).join(" ");
95
+ return e.split("_").map((a, r) => r === 0 ? a.charAt(0).toUpperCase() + a.slice(1) : a).join(" ");
89
96
  }
90
- function g(e) {
97
+ function h(e) {
91
98
  const t = { additionalProperties: !1, type: "object" };
92
99
  if (!e || typeof e != "object")
93
100
  return t;
94
101
  try {
95
- const r = k.toJSONSchema(e, {
102
+ const a = w.toJSONSchema(e, {
96
103
  cycles: "ref",
97
104
  io: "input",
98
105
  reused: "inline",
99
106
  target: "draft-2020-12",
100
107
  unrepresentable: "any"
101
108
  });
102
- return delete r.$schema, r.type === "object" ? r : t;
109
+ return delete a.$schema, a.type === "object" ? a : t;
103
110
  } catch {
104
111
  return t;
105
112
  }
106
113
  }
107
114
  function s(e) {
108
- return y(e);
115
+ return b(e);
109
116
  }
110
- function j(e) {
111
- const t = Object.keys(s(e)), r = Object.keys(i);
117
+ function M(e) {
118
+ const t = Object.keys(s(e)), a = Object.keys(o);
112
119
  return {
113
- missingFromRegistry: r.filter((o) => !t.includes(o)),
114
- unclassified: t.filter((o) => !r.includes(o))
120
+ missingFromRegistry: a.filter((r) => !t.includes(r)),
121
+ unclassified: t.filter((r) => !a.includes(r))
115
122
  };
116
123
  }
117
- function O(e) {
118
- const t = s(e.context), r = [];
119
- for (const [o, n] of Object.entries(t))
120
- !(o in i) || !d(e.scopes, o) || r.push({
121
- description: n.description ?? `Cortex AI ${p(o)} tool.`,
122
- inputSchema: g(n.inputSchema),
123
- name: o,
124
- title: p(o)
124
+ function N(e) {
125
+ const t = s(e.context), a = [];
126
+ for (const [r, n] of Object.entries(t))
127
+ !(r in o) || !d(e.scopes, r) || a.push({
128
+ description: n.description ?? `Cortex AI ${p(r)} tool.`,
129
+ inputSchema: h(n.inputSchema),
130
+ name: r,
131
+ title: p(r)
125
132
  });
126
- for (const [o, n] of Object.entries(u)) {
127
- const a = t[n.canonical];
128
- !a || !d(e.scopes, o) || r.push({
133
+ for (const [r, n] of Object.entries(u)) {
134
+ const i = t[n.canonical];
135
+ !i || !d(e.scopes, r) || a.push({
129
136
  description: n.description,
130
- inputSchema: g(a.inputSchema),
131
- name: o,
137
+ inputSchema: h(i.inputSchema),
138
+ name: r,
132
139
  title: n.title
133
140
  });
134
141
  }
135
- return r.sort((o, n) => o.name.localeCompare(n.name));
142
+ return a.sort((r, n) => r.name.localeCompare(n.name));
136
143
  }
137
- function m(e, t) {
144
+ function g(e, t) {
138
145
  return { content: [{ text: e, type: "text" }], isError: t };
139
146
  }
140
147
  function x(e) {
141
148
  return e.issues.map((t) => `${t.path.length > 0 ? t.path.join(".") : "(root)"}: ${t.message}`).join("; ");
142
149
  }
143
- class _ extends Error {
150
+ class m extends Error {
144
151
  constructor(t) {
145
152
  super(`Unknown tool: ${t}`), this.name = "CortexMcpUnknownToolError";
146
153
  }
@@ -152,34 +159,34 @@ class v extends Error {
152
159
  ), this.name = "CortexMcpForbiddenToolError";
153
160
  }
154
161
  }
155
- async function N(e) {
156
- const t = h(e.name);
162
+ async function O(e) {
163
+ const t = _(e.name);
157
164
  if (!t)
158
- throw new _(e.name);
165
+ throw new m(e.name);
159
166
  if (!d(e.scopes, e.name))
160
167
  throw new v(e.name);
161
- const o = s(e.context)[t];
162
- if (!o || typeof o.execute != "function")
163
- throw new _(e.name);
168
+ const r = s(e.context)[t];
169
+ if (!r || typeof r.execute != "function")
170
+ throw new m(e.name);
164
171
  const n = e.args && typeof e.args == "object" ? e.args : {};
165
- if (o.inputSchema && typeof o.inputSchema.safeParse == "function") {
166
- const a = o.inputSchema.safeParse(n);
167
- if (!a.success)
168
- return m(
169
- `Invalid arguments for "${e.name}": ${x(a.error)}`,
172
+ if (r.inputSchema && typeof r.inputSchema.safeParse == "function") {
173
+ const i = r.inputSchema.safeParse(n);
174
+ if (!i.success)
175
+ return g(
176
+ `Invalid arguments for "${e.name}": ${x(i.error)}`,
170
177
  !0
171
178
  );
172
179
  }
173
180
  try {
174
- const a = await o.execute(n), c = JSON.stringify(a ?? { success: !0 }, null, 2), l = a && typeof a == "object" && !Array.isArray(a) ? a : null, f = l?.success === !1;
181
+ const i = await r.execute(n), c = JSON.stringify(i ?? { success: !0 }, null, 2), l = i && typeof i == "object" && !Array.isArray(i) ? i : null, f = l?.success === !1;
175
182
  return {
176
183
  content: [{ text: c, type: "text" }],
177
184
  isError: f,
178
185
  ...l ? { structuredContent: l } : {}
179
186
  };
180
- } catch (a) {
181
- const c = a instanceof Error ? a.message : String(a);
182
- return m(`Tool "${e.name}" failed: ${c}`, !0);
187
+ } catch (i) {
188
+ const c = i instanceof Error ? i.message : String(i);
189
+ return g(`Tool "${e.name}" failed: ${c}`, !0);
183
190
  }
184
191
  }
185
192
  const A = [
@@ -205,33 +212,45 @@ const A = [
205
212
  uri: "cortex://schema/custom-blocks"
206
213
  }
207
214
  ];
208
- async function B(e) {
215
+ async function P(e) {
209
216
  const t = s(e.context);
210
217
  if (e.uri === "cortex://schema/database") {
211
- const r = await t.describe_database_schema?.execute?.({ includeReadOnly: !0 });
218
+ const a = await t.describe_database_schema?.execute?.({ includeReadOnly: !0 });
212
219
  return {
213
220
  mimeType: "application/json",
214
- text: JSON.stringify(r ?? {}, null, 2),
221
+ text: JSON.stringify(a ?? {}, null, 2),
215
222
  uri: e.uri
216
223
  };
217
224
  }
218
225
  if (e.uri === "cortex://schema/blocks")
219
226
  return {
220
227
  mimeType: "application/json",
221
- text: JSON.stringify({ blockTypes: b }, null, 2),
228
+ text: JSON.stringify({ blockTypes: y }, null, 2),
222
229
  uri: e.uri
223
230
  };
224
231
  if (e.uri === "cortex://schema/custom-blocks") {
225
- const r = await t.list_custom_blocks?.execute?.({});
232
+ const a = await t.list_custom_blocks?.execute?.({});
226
233
  return {
227
234
  mimeType: "application/json",
228
- text: JSON.stringify(r ?? {}, null, 2),
235
+ text: JSON.stringify(a ?? {}, null, 2),
229
236
  uri: e.uri
230
237
  };
231
238
  }
232
239
  return null;
233
240
  }
234
241
  const C = [
242
+ {
243
+ arguments: [
244
+ {
245
+ description: 'Everything known about the client and the site they want: business, audience, goals, pages (or "one landing page"), languages, brand, contact details. Leave blank to be asked.',
246
+ name: "brief",
247
+ required: !1
248
+ }
249
+ ],
250
+ description: "Build (or rebuild) the whole NextBlock site from a brief: remove the sample content, set the identity and theme, create and publish every page, the menus, the footer, and the translations.",
251
+ name: "build-site",
252
+ title: "Build the whole site"
253
+ },
235
254
  {
236
255
  arguments: [
237
256
  { description: "What the page is for, and any brand or tone notes.", name: "brief", required: !0 },
@@ -260,6 +279,20 @@ const C = [
260
279
  title: "Translate a page or post"
261
280
  }
262
281
  ], S = {
282
+ "build-site": (e) => [
283
+ "Build the whole NextBlock site for this client.",
284
+ "",
285
+ e.brief?.trim() ? `Brief:
286
+ ${e.brief}` : "No brief was given: first ask, in one short numbered list, about the business and what it does, the audience and the one action visitors should take, the site shape (one landing page or which pages), the languages, brand colours and tone, contact details, and whether existing content is kept.",
287
+ "",
288
+ "Process:",
289
+ "1. Call get_site_overview to see what exists, the active languages, and whether the NextBlock demo content is still present. Record what you know with save_site_brief.",
290
+ "2. Present the plan (what is removed, every page with its sections, menus, footer, theme, languages) and call start_site_build with `summary`, `brief`, and `reset` (keepLanguages = the languages wanted) unless existing content is kept. Over MCP the host approves the call; there is no chat confirmation.",
291
+ '3. Build: update_site_identity; manage_language as needed; manage_site_theme with the brand colours; search_stock_media for imagery when available; rewrite the empty "home" page with generate_jsonb_layout then publish_content_draft; create_cms_page (status "published") for every other page; update_site_navigation (mode "replace") and update_footer for every language; translate_content_bulk for each extra language.',
292
+ "4. Call finish_site_build with a summary, then report every page with its URL.",
293
+ "Write real copy from the brief, never placeholders. Every page must exist in every active language."
294
+ ].join(`
295
+ `),
263
296
  "build-page": (e) => [
264
297
  `Build the NextBlock page with slug "${e.slug ?? "<slug>"}" from this brief:`,
265
298
  "",
@@ -288,12 +321,12 @@ const C = [
288
321
  ].join(`
289
322
  `)
290
323
  };
291
- function P(e) {
292
- const t = C.find((o) => o.name === e.name), r = S[e.name];
293
- return !t || !r ? null : {
324
+ function E(e) {
325
+ const t = C.find((r) => r.name === e.name), a = S[e.name];
326
+ return !t || !a ? null : {
294
327
  description: t.description,
295
328
  messages: [
296
- { content: { text: r(e.args ?? {}), type: "text" }, role: "user" }
329
+ { content: { text: a(e.args ?? {}), type: "text" }, role: "user" }
297
330
  ]
298
331
  };
299
332
  }
@@ -301,15 +334,15 @@ export {
301
334
  C as CORTEX_MCP_PROMPTS,
302
335
  A as CORTEX_MCP_RESOURCES,
303
336
  u as CORTEX_MCP_TOOL_ALIASES,
304
- i as CORTEX_MCP_TOOL_KINDS,
337
+ o as CORTEX_MCP_TOOL_KINDS,
305
338
  v as CortexMcpForbiddenToolError,
306
- _ as CortexMcpUnknownToolError,
307
- j as assertCortexMcpToolCoverage,
308
- O as buildCortexMcpToolDefinitions,
309
- N as callCortexMcpTool,
339
+ m as CortexMcpUnknownToolError,
340
+ M as assertCortexMcpToolCoverage,
341
+ N as buildCortexMcpToolDefinitions,
342
+ O as callCortexMcpTool,
310
343
  d as cortexMcpScopesAllow,
311
- P as getCortexMcpPrompt,
312
- w as getCortexMcpToolKind,
313
- B as readCortexMcpResource,
314
- h as resolveCortexMcpToolName
344
+ E as getCortexMcpPrompt,
345
+ k as getCortexMcpToolKind,
346
+ P as readCortexMcpResource,
347
+ _ as resolveCortexMcpToolName
315
348
  };
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});require("./zod-config.cjs.js");const e=require("zod"),c="cortex_ai_site_brief",m="cortex_ai_build_session",l=e.z.string().trim().min(2).max(10),p=e.z.strictObject({purpose:e.z.string().trim().max(600).optional().describe("What this page is for and what a visitor should do there."),sections:e.z.array(e.z.string().trim().min(1).max(200)).max(20).optional().describe('Planned sections, top to bottom, e.g. ["hero", "services", "testimonials", "contact form"].'),slug:e.z.string().trim().min(1).max(120).describe('URL slug in the primary language, e.g. "home", "services", "contact".'),title:e.z.string().trim().min(1).max(200)}),u=e.z.strictObject({accent_color:e.z.string().trim().max(40).optional(),color_scheme:e.z.enum(["light","dark"]).optional().describe("Overall look the client prefers."),font_style:e.z.string().trim().max(120).optional().describe('e.g. "clean sans-serif", "elegant serif".'),logo_notes:e.z.string().trim().max(600).optional().describe("Whether they have a logo, where it is, or what to do meanwhile."),primary_color:e.z.string().trim().max(40).optional().describe('Hex or plain colour name, e.g. "#1e6f5c" or "forest green".'),secondary_color:e.z.string().trim().max(40).optional(),style_keywords:e.z.array(e.z.string().trim().min(1).max(60)).max(12).optional().describe('e.g. ["minimal", "warm", "premium"].'),tone:e.z.string().trim().max(200).optional().describe('Voice for the copy, e.g. "friendly and plain-spoken".')}).partial(),d=e.z.strictObject({address:e.z.string().trim().max(400).optional(),email:e.z.string().trim().max(200).optional(),hours:e.z.string().trim().max(400).optional(),phone:e.z.string().trim().max(60).optional(),social:e.z.array(e.z.strictObject({platform:e.z.string().trim().min(1).max(60),url:e.z.string().trim().min(1).max(2048)})).max(12).optional()}).partial(),r=e.z.strictObject({audience:e.z.string().trim().max(1e3).optional().describe("Who the site is for."),brand:u.optional(),business_name:e.z.string().trim().min(1).max(160),contact:d.optional(),description:e.z.string().trim().max(3e3).optional().describe("What the business does, in the client's own words where possible."),ecommerce:e.z.strictObject({enabled:e.z.boolean().default(!1),products_summary:e.z.string().trim().max(1e3).optional()}).optional(),goals:e.z.array(e.z.string().trim().min(1).max(300)).max(10).optional().describe('What the site must achieve, e.g. ["get quote requests", "show the menu"].'),keep_existing_content:e.z.boolean().default(!1).describe("true when the client wants existing pages/posts kept rather than replaced."),languages:e.z.array(l).min(1).max(10).default(["en"]).describe("Locale codes the public site must offer, primary first."),notes:e.z.string().trim().max(4e3).optional().describe("Anything else the client said that matters."),pages:e.z.array(p).max(30).default([]).describe('The pages to build. A one-page landing site is a single "home" entry.'),primary_language:l.default("en"),site_type:e.z.enum(["landing-page","multi-page","blog","store","portfolio","other"]).default("multi-page"),status:e.z.enum(["draft","confirmed","built"]).default("draft").describe("draft while interviewing, confirmed once the client approved the plan, built when the build finished."),tagline:e.z.string().trim().max(200).optional(),updated_at:e.z.string().max(80).optional()}),g=r.partial(),h=e.z.strictObject({actorUserId:e.z.string().min(1),createdAt:e.z.string().min(1),expiresAt:e.z.string().min(1),id:e.z.string().min(1),summary:e.z.string().max(4e3)});function _(t){const o=r.safeParse(t);return o.success?o.data:null}function x(t,o){if(!t||!o.actorUserId||t.actorUserId!==o.actorUserId)return!1;const i=Date.parse(t.expiresAt),s=(o.now??new Date).getTime();return Number.isFinite(i)&&i>s}function f(t){const o=[`Business: ${t.business_name}${t.tagline?` — ${t.tagline}`:""}`];t.description&&o.push(`What they do: ${t.description}`),t.audience&&o.push(`Audience: ${t.audience}`),t.goals?.length&&o.push(`Goals: ${t.goals.join("; ")}`),o.push(`Site type: ${t.site_type}. Languages: ${t.languages.join(", ")} (primary ${t.primary_language}).`),t.pages.length>0&&o.push(`Pages: ${t.pages.map(n=>`${n.title} (/${n.slug})${n.purpose?` — ${n.purpose}`:""}`).join("; ")}`);const i=t.brand;if(i&&Object.keys(i).length>0){const n=[i.primary_color?`primary ${i.primary_color}`:null,i.secondary_color?`secondary ${i.secondary_color}`:null,i.accent_color?`accent ${i.accent_color}`:null,i.color_scheme?`${i.color_scheme} scheme`:null,i.font_style?`fonts: ${i.font_style}`:null,i.tone?`tone: ${i.tone}`:null,i.style_keywords?.length?`style: ${i.style_keywords.join(", ")}`:null,i.logo_notes?`logo: ${i.logo_notes}`:null].filter(Boolean);n.length>0&&o.push(`Brand: ${n.join("; ")}`)}const s=t.contact;if(s&&Object.keys(s).length>0){const n=[s.email?`email ${s.email}`:null,s.phone?`phone ${s.phone}`:null,s.address?`address ${s.address}`:null,s.hours?`hours ${s.hours}`:null,s.social?.length?`social: ${s.social.map(a=>`${a.platform} ${a.url}`).join(", ")}`:null].filter(Boolean);n.length>0&&o.push(`Contact: ${n.join("; ")}`)}return t.ecommerce?.enabled&&o.push(`Ecommerce: yes${t.ecommerce.products_summary?` — ${t.ecommerce.products_summary}`:""}`),t.keep_existing_content&&o.push("Keep existing content: yes (do not wipe pages or posts)."),t.notes&&o.push(`Notes: ${t.notes}`),o.push(`Brief status: ${t.status}.`),o.join(`
2
+ `)}exports.CORTEX_AI_BUILD_SESSION_SETTING_KEY=m;exports.CORTEX_AI_SITE_BRIEF_SETTING_KEY=c;exports.cortexBuildSessionSchema=h;exports.cortexSiteBriefInputSchema=g;exports.cortexSiteBriefSchema=r;exports.formatCortexSiteBriefForPrompt=f;exports.isCortexBuildSessionUsable=x;exports.safeParseCortexSiteBrief=_;
@@ -0,0 +1,159 @@
1
+ import { z } from './zod-config';
2
+ /**
3
+ * The site brief: what the client wants their website to be.
4
+ *
5
+ * Cortex fills this in during the site-builder interview and persists it in
6
+ * `site_settings` so that every later conversation — dashboard chat, MCP client,
7
+ * a follow-up "make the whole site feel warmer" prompt weeks later — starts from
8
+ * the same understanding of the business instead of re-asking. It is plain,
9
+ * non-secret JSON: readable by any admin session and by every tool context.
10
+ */
11
+ export declare const CORTEX_AI_SITE_BRIEF_SETTING_KEY = "cortex_ai_site_brief";
12
+ /**
13
+ * A time-boxed authorisation for Cortex to apply an approved build plan without a
14
+ * confirmation click per tool call. Written only by `start_site_build` after the
15
+ * operator confirms the plan; the chat route honours it for the actor who opened it
16
+ * and only until `expiresAt`.
17
+ */
18
+ export declare const CORTEX_AI_BUILD_SESSION_SETTING_KEY = "cortex_ai_build_session";
19
+ /** The complete brief as stored. Every field a build step may need has a default. */
20
+ export declare const cortexSiteBriefSchema: z.ZodObject<{
21
+ audience: z.ZodOptional<z.ZodString>;
22
+ brand: z.ZodOptional<z.ZodObject<{
23
+ accent_color: z.ZodOptional<z.ZodOptional<z.ZodString>>;
24
+ color_scheme: z.ZodOptional<z.ZodOptional<z.ZodEnum<{
25
+ light: "light";
26
+ dark: "dark";
27
+ }>>>;
28
+ font_style: z.ZodOptional<z.ZodOptional<z.ZodString>>;
29
+ logo_notes: z.ZodOptional<z.ZodOptional<z.ZodString>>;
30
+ primary_color: z.ZodOptional<z.ZodOptional<z.ZodString>>;
31
+ secondary_color: z.ZodOptional<z.ZodOptional<z.ZodString>>;
32
+ style_keywords: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>;
33
+ tone: z.ZodOptional<z.ZodOptional<z.ZodString>>;
34
+ }, z.core.$strict>>;
35
+ business_name: z.ZodString;
36
+ contact: z.ZodOptional<z.ZodObject<{
37
+ address: z.ZodOptional<z.ZodOptional<z.ZodString>>;
38
+ email: z.ZodOptional<z.ZodOptional<z.ZodString>>;
39
+ hours: z.ZodOptional<z.ZodOptional<z.ZodString>>;
40
+ phone: z.ZodOptional<z.ZodOptional<z.ZodString>>;
41
+ social: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodObject<{
42
+ platform: z.ZodString;
43
+ url: z.ZodString;
44
+ }, z.core.$strict>>>>;
45
+ }, z.core.$strict>>;
46
+ description: z.ZodOptional<z.ZodString>;
47
+ ecommerce: z.ZodOptional<z.ZodObject<{
48
+ enabled: z.ZodDefault<z.ZodBoolean>;
49
+ products_summary: z.ZodOptional<z.ZodString>;
50
+ }, z.core.$strict>>;
51
+ goals: z.ZodOptional<z.ZodArray<z.ZodString>>;
52
+ keep_existing_content: z.ZodDefault<z.ZodBoolean>;
53
+ languages: z.ZodDefault<z.ZodArray<z.ZodString>>;
54
+ notes: z.ZodOptional<z.ZodString>;
55
+ pages: z.ZodDefault<z.ZodArray<z.ZodObject<{
56
+ purpose: z.ZodOptional<z.ZodString>;
57
+ sections: z.ZodOptional<z.ZodArray<z.ZodString>>;
58
+ slug: z.ZodString;
59
+ title: z.ZodString;
60
+ }, z.core.$strict>>>;
61
+ primary_language: z.ZodDefault<z.ZodString>;
62
+ site_type: z.ZodDefault<z.ZodEnum<{
63
+ other: "other";
64
+ "landing-page": "landing-page";
65
+ "multi-page": "multi-page";
66
+ blog: "blog";
67
+ store: "store";
68
+ portfolio: "portfolio";
69
+ }>>;
70
+ status: z.ZodDefault<z.ZodEnum<{
71
+ draft: "draft";
72
+ confirmed: "confirmed";
73
+ built: "built";
74
+ }>>;
75
+ tagline: z.ZodOptional<z.ZodString>;
76
+ updated_at: z.ZodOptional<z.ZodString>;
77
+ }, z.core.$strict>;
78
+ export type CortexSiteBrief = z.infer<typeof cortexSiteBriefSchema>;
79
+ /** What a tool may pass to save or merge: every field optional. */
80
+ export declare const cortexSiteBriefInputSchema: z.ZodObject<{
81
+ audience: z.ZodOptional<z.ZodOptional<z.ZodString>>;
82
+ brand: z.ZodOptional<z.ZodOptional<z.ZodObject<{
83
+ accent_color: z.ZodOptional<z.ZodOptional<z.ZodString>>;
84
+ color_scheme: z.ZodOptional<z.ZodOptional<z.ZodEnum<{
85
+ light: "light";
86
+ dark: "dark";
87
+ }>>>;
88
+ font_style: z.ZodOptional<z.ZodOptional<z.ZodString>>;
89
+ logo_notes: z.ZodOptional<z.ZodOptional<z.ZodString>>;
90
+ primary_color: z.ZodOptional<z.ZodOptional<z.ZodString>>;
91
+ secondary_color: z.ZodOptional<z.ZodOptional<z.ZodString>>;
92
+ style_keywords: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>;
93
+ tone: z.ZodOptional<z.ZodOptional<z.ZodString>>;
94
+ }, z.core.$strict>>>;
95
+ business_name: z.ZodOptional<z.ZodString>;
96
+ contact: z.ZodOptional<z.ZodOptional<z.ZodObject<{
97
+ address: z.ZodOptional<z.ZodOptional<z.ZodString>>;
98
+ email: z.ZodOptional<z.ZodOptional<z.ZodString>>;
99
+ hours: z.ZodOptional<z.ZodOptional<z.ZodString>>;
100
+ phone: z.ZodOptional<z.ZodOptional<z.ZodString>>;
101
+ social: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodObject<{
102
+ platform: z.ZodString;
103
+ url: z.ZodString;
104
+ }, z.core.$strict>>>>;
105
+ }, z.core.$strict>>>;
106
+ description: z.ZodOptional<z.ZodOptional<z.ZodString>>;
107
+ ecommerce: z.ZodOptional<z.ZodOptional<z.ZodObject<{
108
+ enabled: z.ZodDefault<z.ZodBoolean>;
109
+ products_summary: z.ZodOptional<z.ZodString>;
110
+ }, z.core.$strict>>>;
111
+ goals: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>;
112
+ keep_existing_content: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
113
+ languages: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
114
+ notes: z.ZodOptional<z.ZodOptional<z.ZodString>>;
115
+ pages: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodObject<{
116
+ purpose: z.ZodOptional<z.ZodString>;
117
+ sections: z.ZodOptional<z.ZodArray<z.ZodString>>;
118
+ slug: z.ZodString;
119
+ title: z.ZodString;
120
+ }, z.core.$strict>>>>;
121
+ primary_language: z.ZodOptional<z.ZodDefault<z.ZodString>>;
122
+ site_type: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
123
+ other: "other";
124
+ "landing-page": "landing-page";
125
+ "multi-page": "multi-page";
126
+ blog: "blog";
127
+ store: "store";
128
+ portfolio: "portfolio";
129
+ }>>>;
130
+ status: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
131
+ draft: "draft";
132
+ confirmed: "confirmed";
133
+ built: "built";
134
+ }>>>;
135
+ tagline: z.ZodOptional<z.ZodOptional<z.ZodString>>;
136
+ updated_at: z.ZodOptional<z.ZodOptional<z.ZodString>>;
137
+ }, z.core.$strict>;
138
+ export type CortexSiteBriefInput = z.infer<typeof cortexSiteBriefInputSchema>;
139
+ export declare const cortexBuildSessionSchema: z.ZodObject<{
140
+ actorUserId: z.ZodString;
141
+ createdAt: z.ZodString;
142
+ expiresAt: z.ZodString;
143
+ id: z.ZodString;
144
+ summary: z.ZodString;
145
+ }, z.core.$strict>;
146
+ export type CortexBuildSession = z.infer<typeof cortexBuildSessionSchema>;
147
+ /** Parse a stored brief leniently: an unreadable row is treated as "no brief". */
148
+ export declare function safeParseCortexSiteBrief(value: unknown): CortexSiteBrief | null;
149
+ /** A build session is only usable by the admin who opened it, and only until it expires. */
150
+ export declare function isCortexBuildSessionUsable(session: CortexBuildSession | null | undefined, params: {
151
+ actorUserId: string | null | undefined;
152
+ now?: Date;
153
+ }): session is CortexBuildSession;
154
+ /**
155
+ * Compact, prompt-ready rendering of the brief. The model reads this at the top of
156
+ * every conversation once a brief exists, so it is deliberately short: facts, not
157
+ * prose, and never the raw JSON (which wastes tokens on braces and quotes).
158
+ */
159
+ export declare function formatCortexSiteBriefForPrompt(brief: CortexSiteBrief): string;
@@ -0,0 +1,109 @@
1
+ import "./zod-config.es.js";
2
+ import { z as e } from "zod";
3
+ const d = "cortex_ai_site_brief", h = "cortex_ai_build_session", r = e.string().trim().min(2).max(10), c = e.strictObject({
4
+ purpose: e.string().trim().max(600).optional().describe("What this page is for and what a visitor should do there."),
5
+ sections: e.array(e.string().trim().min(1).max(200)).max(20).optional().describe('Planned sections, top to bottom, e.g. ["hero", "services", "testimonials", "contact form"].'),
6
+ slug: e.string().trim().min(1).max(120).describe('URL slug in the primary language, e.g. "home", "services", "contact".'),
7
+ title: e.string().trim().min(1).max(200)
8
+ }), m = e.strictObject({
9
+ accent_color: e.string().trim().max(40).optional(),
10
+ color_scheme: e.enum(["light", "dark"]).optional().describe("Overall look the client prefers."),
11
+ font_style: e.string().trim().max(120).optional().describe('e.g. "clean sans-serif", "elegant serif".'),
12
+ logo_notes: e.string().trim().max(600).optional().describe("Whether they have a logo, where it is, or what to do meanwhile."),
13
+ primary_color: e.string().trim().max(40).optional().describe('Hex or plain colour name, e.g. "#1e6f5c" or "forest green".'),
14
+ secondary_color: e.string().trim().max(40).optional(),
15
+ style_keywords: e.array(e.string().trim().min(1).max(60)).max(12).optional().describe('e.g. ["minimal", "warm", "premium"].'),
16
+ tone: e.string().trim().max(200).optional().describe('Voice for the copy, e.g. "friendly and plain-spoken".')
17
+ }).partial(), p = e.strictObject({
18
+ address: e.string().trim().max(400).optional(),
19
+ email: e.string().trim().max(200).optional(),
20
+ hours: e.string().trim().max(400).optional(),
21
+ phone: e.string().trim().max(60).optional(),
22
+ social: e.array(
23
+ e.strictObject({
24
+ platform: e.string().trim().min(1).max(60),
25
+ url: e.string().trim().min(1).max(2048)
26
+ })
27
+ ).max(12).optional()
28
+ }).partial(), l = e.strictObject({
29
+ audience: e.string().trim().max(1e3).optional().describe("Who the site is for."),
30
+ brand: m.optional(),
31
+ business_name: e.string().trim().min(1).max(160),
32
+ contact: p.optional(),
33
+ description: e.string().trim().max(3e3).optional().describe("What the business does, in the client's own words where possible."),
34
+ ecommerce: e.strictObject({
35
+ enabled: e.boolean().default(!1),
36
+ products_summary: e.string().trim().max(1e3).optional()
37
+ }).optional(),
38
+ goals: e.array(e.string().trim().min(1).max(300)).max(10).optional().describe('What the site must achieve, e.g. ["get quote requests", "show the menu"].'),
39
+ keep_existing_content: e.boolean().default(!1).describe("true when the client wants existing pages/posts kept rather than replaced."),
40
+ languages: e.array(r).min(1).max(10).default(["en"]).describe("Locale codes the public site must offer, primary first."),
41
+ notes: e.string().trim().max(4e3).optional().describe("Anything else the client said that matters."),
42
+ pages: e.array(c).max(30).default([]).describe('The pages to build. A one-page landing site is a single "home" entry.'),
43
+ primary_language: r.default("en"),
44
+ site_type: e.enum(["landing-page", "multi-page", "blog", "store", "portfolio", "other"]).default("multi-page"),
45
+ status: e.enum(["draft", "confirmed", "built"]).default("draft").describe("draft while interviewing, confirmed once the client approved the plan, built when the build finished."),
46
+ tagline: e.string().trim().max(200).optional(),
47
+ updated_at: e.string().max(80).optional()
48
+ }), _ = l.partial(), x = e.strictObject({
49
+ actorUserId: e.string().min(1),
50
+ createdAt: e.string().min(1),
51
+ expiresAt: e.string().min(1),
52
+ id: e.string().min(1),
53
+ summary: e.string().max(4e3)
54
+ });
55
+ function y(t) {
56
+ const o = l.safeParse(t);
57
+ return o.success ? o.data : null;
58
+ }
59
+ function f(t, o) {
60
+ if (!t || !o.actorUserId || t.actorUserId !== o.actorUserId)
61
+ return !1;
62
+ const i = Date.parse(t.expiresAt), n = (o.now ?? /* @__PURE__ */ new Date()).getTime();
63
+ return Number.isFinite(i) && i > n;
64
+ }
65
+ function $(t) {
66
+ const o = [
67
+ `Business: ${t.business_name}${t.tagline ? ` — ${t.tagline}` : ""}`
68
+ ];
69
+ t.description && o.push(`What they do: ${t.description}`), t.audience && o.push(`Audience: ${t.audience}`), t.goals?.length && o.push(`Goals: ${t.goals.join("; ")}`), o.push(`Site type: ${t.site_type}. Languages: ${t.languages.join(", ")} (primary ${t.primary_language}).`), t.pages.length > 0 && o.push(
70
+ `Pages: ${t.pages.map((s) => `${s.title} (/${s.slug})${s.purpose ? ` — ${s.purpose}` : ""}`).join("; ")}`
71
+ );
72
+ const i = t.brand;
73
+ if (i && Object.keys(i).length > 0) {
74
+ const s = [
75
+ i.primary_color ? `primary ${i.primary_color}` : null,
76
+ i.secondary_color ? `secondary ${i.secondary_color}` : null,
77
+ i.accent_color ? `accent ${i.accent_color}` : null,
78
+ i.color_scheme ? `${i.color_scheme} scheme` : null,
79
+ i.font_style ? `fonts: ${i.font_style}` : null,
80
+ i.tone ? `tone: ${i.tone}` : null,
81
+ i.style_keywords?.length ? `style: ${i.style_keywords.join(", ")}` : null,
82
+ i.logo_notes ? `logo: ${i.logo_notes}` : null
83
+ ].filter(Boolean);
84
+ s.length > 0 && o.push(`Brand: ${s.join("; ")}`);
85
+ }
86
+ const n = t.contact;
87
+ if (n && Object.keys(n).length > 0) {
88
+ const s = [
89
+ n.email ? `email ${n.email}` : null,
90
+ n.phone ? `phone ${n.phone}` : null,
91
+ n.address ? `address ${n.address}` : null,
92
+ n.hours ? `hours ${n.hours}` : null,
93
+ n.social?.length ? `social: ${n.social.map((a) => `${a.platform} ${a.url}`).join(", ")}` : null
94
+ ].filter(Boolean);
95
+ s.length > 0 && o.push(`Contact: ${s.join("; ")}`);
96
+ }
97
+ return t.ecommerce?.enabled && o.push(`Ecommerce: yes${t.ecommerce.products_summary ? ` — ${t.ecommerce.products_summary}` : ""}`), t.keep_existing_content && o.push("Keep existing content: yes (do not wipe pages or posts)."), t.notes && o.push(`Notes: ${t.notes}`), o.push(`Brief status: ${t.status}.`), o.join(`
98
+ `);
99
+ }
100
+ export {
101
+ h as CORTEX_AI_BUILD_SESSION_SETTING_KEY,
102
+ d as CORTEX_AI_SITE_BRIEF_SETTING_KEY,
103
+ x as cortexBuildSessionSchema,
104
+ _ as cortexSiteBriefInputSchema,
105
+ l as cortexSiteBriefSchema,
106
+ $ as formatCortexSiteBriefForPrompt,
107
+ f as isCortexBuildSessionUsable,
108
+ y as safeParseCortexSiteBrief
109
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextblock-cms/cortex",
3
- "version": "0.17.6",
3
+ "version": "0.17.8",
4
4
  "license": "AGPL-3.0-or-later",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -28,9 +28,9 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@ai-sdk/openai-compatible": "^2.0.42",
31
- "@nextblock-cms/db": "^0.17.6",
32
- "@nextblock-cms/ecommerce": "npm:@nextblock-cms/ecom@^0.17.6",
33
- "@nextblock-cms/utils": "^0.17.6",
31
+ "@nextblock-cms/db": "^0.17.8",
32
+ "@nextblock-cms/ecommerce": "npm:@nextblock-cms/ecom@^0.17.8",
33
+ "@nextblock-cms/utils": "^0.17.8",
34
34
  "ai": "^6.0.170",
35
35
  "zod": "^4.3.6"
36
36
  },