@nextblock-cms/cortex 0.18.1 → 0.18.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.cjs.js +1 -1
- package/index.es.js +207 -204
- package/lib/ai-global-agent-site-tools.cjs.js +3 -2
- package/lib/ai-global-agent-site-tools.d.ts +12 -4
- package/lib/ai-global-agent-site-tools.es.js +411 -369
- package/lib/ai-model-registry.cjs.js +2 -3
- package/lib/ai-model-registry.d.ts +29 -6
- package/lib/ai-model-registry.es.js +152 -107
- package/lib/mcp-tool-registry.cjs.js +3 -3
- package/lib/mcp-tool-registry.es.js +5 -5
- package/lib/site-brief.cjs.js +2 -2
- package/lib/site-brief.d.ts +9 -0
- package/lib/site-brief.es.js +10 -6
- package/package.json +4 -4
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const P=require("ai"),ae=require("./ai-global-agent-theming-tools.cjs.js"),g=require("./site-brief.cjs.js");require("./zod-config.cjs.js");const d=require("zod"),Z=new Set(["0098e4f0-e5f3-4e28-acfc-bb9fdb3a4a6b","50adeea5-5040-4ae7-bacd-992647920982","e3b28669-7e38-47bd-9189-7db2353b52dc","8cd91839-518d-4b57-87db-441e59a71a95","b5eb82c7-95d6-4308-82d8-3ac2816f5d46","de8b8593-1ef6-4e66-8d1f-8b3e7ffb811d","461cde55-74cb-4112-8c3d-a25882eeedce","23773a34-e54b-40c6-89b5-b09ead17ae60","669489e7-5900-496e-8b73-2aba7514a71b","c0d3f1a2-8b47-4e19-9a52-7f6b1d4e8c30","a7c4e2d1-3b58-4f96-9e0a-6d2c8b1f5a73","dbe2a06f-1a9d-48ba-bc35-0f6ef4869156"]),G=new Set(["images/NBcover.webp","images/commerce-plan.webp","images/commerce-wide.webp","images/cortex-ai.webp","images/cortex_post.webp","images/extensibility.webp","images/included.webp","images/nextblock-logo-button-tiny.png","images/nextblock-logo-small.webp","images/programmer-upscaled.webp","images/update_nextblock.webp"]),we="NextBlock™ CMS",ve="nextblock cms",A="home",ke=["active_logo_id","footer_copyright","footer_show_attribution","site_description","site_keywords","site_title"];function M(t){if(!t?.supabase)throw new Error("No database connection is available for this tool.");return t.supabase}function p(t){return t?t instanceof Error?t.message:typeof t=="object"&&"message"in t?String(t.message):String(t):"unknown error"}function ie(t){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(",")}}`}function Te(t){let i=2166136261;const s=ie(t);for(let o=0;o<s.length;o++)i^=s.charCodeAt(o),i=Math.imul(i,16777619);return(i>>>0).toString(16).padStart(8,"0")}function te(t){return t.replace(/\s+/g," ").trim().toUpperCase()}function se(t){if(t.context?.skipConfirmation)return null;const i=`${te(`CONFIRM ${t.action} ${t.subject}`)} #${Te(t.payload)}`;return te(t.context?.latestUserMessage||"").includes(te(i))?null:{confirmationPhrase:i,mutationExecuted:!1,preview:t.preview,requiresConfirmation:!0,success:!0}}function Ee(){return globalThis.crypto?.randomUUID?.()||`id-${Date.now()}-${Math.random().toString(36).slice(2)}`}function me(t,i=200){const s=[];for(let o=0;o<t.length;o+=i)s.push(t.slice(o,o+i));return s}function oe(t,i=[]){let s=t?.revalidatePath;if(!s)try{s=require("next/cache").revalidatePath}catch{return}try{s("/","layout");for(const o of i)s(o)}catch{}}async function Ie(t,i){const{data:s,error:o}=await t.from("site_settings").select("key, value").in("key",[...i]);if(o)throw new Error(`Could not read the site settings: ${p(o)}`);return new Map((Array.isArray(s)?s:[]).map(n=>[n.key,n.value]))}async function k(t,i,s){const{error:o}=await t.from("site_settings").upsert({key:i,value:s});if(o)throw new Error(`Could not save the "${i}" setting: ${p(o)}`)}async function B(t,i,s,o){let n=0;for(const a of me(o)){const{error:r}=await t.from(i).delete().in(s,a);if(r)throw new Error(`Could not delete from ${i}: ${p(r)}`);n+=a.length}return n}function Ce(t){return!t||typeof t!="object"?!1:Object.values(t).some(i=>typeof i=="string"&&i.toLowerCase().includes(ve))}async function ee(t){const{data:i,error:s}=await t.from("site_settings").select("value").eq("key",g.CORTEX_AI_SITE_BRIEF_SETTING_KEY).maybeSingle();return s?null:g.safeParseCortexSiteBrief(i?.value)}const ne=d.z.strictObject({brief:g.cortexSiteBriefInputSchema.describe('The fields you learned. With mode "merge" (default) they are layered over the saved brief, so you can save after every answer.'),mode:d.z.enum(["merge","replace"]).default("merge")});async function Y(t,i){const s=ne.parse(t),o=M(i),n=s.mode==="merge"?await ee(o):null,a=new Set(Object.keys(t.brief??{})),r=Object.fromEntries(Object.entries(s.brief).filter(([m])=>a.has(m))),u=g.cortexSiteBriefSchema.parse({...n??{},...r,updated_at:new Date().toISOString()});return await k(o,g.CORTEX_AI_SITE_BRIEF_SETTING_KEY,u),{brief:u,created:!n,mutationExecuted:!0,success:!0}}async function re(t){const{data:i,error:s}=await t.from("site_settings").select("value").eq("key",g.CORTEX_AI_BUILD_SESSION_SETTING_KEY).maybeSingle();if(s)return null;const o=g.cortexBuildSessionSchema.safeParse(i?.value);return o.success?o.data:null}async function xe(t,i){const s=await re(t);return g.isCortexBuildSessionUsable(s,{actorUserId:i})?s:null}async function Ne(t){const{error:i}=await t.from("site_settings").delete().eq("key",g.CORTEX_AI_BUILD_SESSION_SETTING_KEY);if(i)throw new Error(`Could not close the build session: ${p(i)}`)}const de=d.z.strictObject({includeMedia:d.z.boolean().default(!1).describe("Also list the most recent media library entries (id, file name, alt text)."),includeNavigation:d.z.boolean().default(!0),limit:d.z.number().int().min(10).max(300).default(120).describe("Maximum pages, posts, and products to list.")});function Be(t,i){const s={footer:{},header:{}},o=new Map;for(const a of t){const r=String(a.menu_key).toUpperCase()==="FOOTER"?"footer":"header";o.set(Number(a.id),{children:[],id:Number(a.id),label:String(a.label??""),languageCode:i.get(Number(a.language_id))??String(a.language_id),menu:r,order:Number(a.order??0),parentId:a.parent_id===null||a.parent_id===void 0?null:Number(a.parent_id),url:String(a.url??"")})}const n=[...o.values()].sort((a,r)=>a.order-r.order);for(const a of n){const r=a.parentId===null?null:o.get(a.parentId);if(r){r.children.push({children:a.children,id:a.id,label:a.label,url:a.url});continue}(s[a.menu][a.languageCode]??=[]).push({children:a.children,id:a.id,label:a.label,url:a.url})}return s}async function fe(t,i){const s=de.parse(t),o=M(i),[n,a,r,u,m,b,f]=await Promise.all([o.from("languages").select("id, code, name, is_default, is_active").order("id"),Ie(o,[...ke,g.CORTEX_AI_SITE_BRIEF_SETTING_KEY,g.CORTEX_AI_BUILD_SESSION_SETTING_KEY]),o.from("pages").select("id, slug, title, status, language_id, translation_group_id, author_id, updated_at").order("id").limit(s.limit),o.from("posts").select("id, slug, title, status, language_id, translation_group_id, author_id, published_at").order("id").limit(s.limit),o.from("site_themes").select("slug, name, is_default, is_active, color_scheme").order("sort_order"),o.from("custom_block_definitions").select("slug, name, fields").order("name"),o.from("content_drafts").select("parent_type, parent_id, updated_at")]);for(const[e,l]of[["languages",n],["pages",r],["posts",u],["themes",m],["custom blocks",b],["drafts",f]])if(l?.error)throw new Error(`Could not read the ${e}: ${p(l.error)}`);const h=n.data??[],S=new Map(h.map(e=>[Number(e.id),String(e.code)])),z=h.find(e=>e.is_default)??h[0]??null;let T=[];try{const{data:e}=await o.from("products").select("id, slug, title, status, language_id").order("created_at",{ascending:!1}).limit(s.limit);T=Array.isArray(e)?e:[]}catch{T=[]}const E=new Map;{const{data:e}=await o.from("blocks").select("page_id, post_id").limit(5e3);for(const l of Array.isArray(e)?e:[]){const _=l.page_id?`page:${l.page_id}`:l.post_id?`post:${l.post_id}`:null;_&&E.set(_,(E.get(_)??0)+1)}}let F=null;if(s.includeNavigation){const{data:e,error:l}=await o.from("navigation_items").select("id, label, url, menu_key, language_id, parent_id, order, page_id").order("order");if(l)throw new Error(`Could not read the navigation: ${p(l)}`);F=Be(Array.isArray(e)?e:[],S)}const{data:H}=await o.from("media").select("id, object_key, file_name, description, created_at").order("created_at",{ascending:!1}).limit(500),y=Array.isArray(H)?H:[],I=y.filter(e=>G.has(String(e.object_key))).length,{data:C}=await o.from("logos").select("id, name, media_id, created_at").order("created_at",{ascending:!1}),j=Array.isArray(C)?C:[],K=typeof a.get("active_logo_id")=="string"?a.get("active_logo_id"):null,v=(K?j.find(e=>e.id===K):null)??j[0]??null,D=v?y.find(e=>e.id===v.media_id)??null:null,$=D?String(D.object_key):null,R=(r.data??[]).map(e=>({blockCount:E.get(`page:${e.id}`)??0,id:Number(e.id),isHome:e.slug===A&&(!z||Number(e.language_id)===Number(z.id)),isSeeded:Z.has(String(e.translation_group_id)),languageCode:S.get(Number(e.language_id))??String(e.language_id),slug:String(e.slug),status:String(e.status),title:String(e.title??""),translationGroupId:e.translation_group_id?String(e.translation_group_id):null})),O=(u.data??[]).map(e=>({blockCount:E.get(`post:${e.id}`)??0,id:Number(e.id),isSeeded:Z.has(String(e.translation_group_id)),languageCode:S.get(Number(e.language_id))??String(e.language_id),publishedAt:e.published_at??null,slug:String(e.slug),status:String(e.status),title:String(e.title??""),translationGroupId:e.translation_group_id?String(e.translation_group_id):null})),U=typeof a.get("site_title")=="string"?a.get("site_title"):"",W=a.get("footer_copyright"),c={copyright:Ce(W),logo:!!($&&G.has($)),media:I,pages:R.filter(e=>e.isSeeded).length,posts:O.filter(e=>e.isSeeded).length,siteTitle:U.trim()===we},J=c.copyright||c.logo||c.media>0||c.pages>0||c.posts>0||c.siteTitle,q=g.safeParseCortexSiteBrief(a.get(g.CORTEX_AI_SITE_BRIEF_SETTING_KEY)),Q=g.cortexBuildSessionSchema.safeParse(a.get(g.CORTEX_AI_BUILD_SESSION_SETTING_KEY)),x=Q.success?Q.data:null,L=R.find(e=>e.isHome)??null,w=[];return L?L.blockCount===0&&w.push("The home page exists but has no blocks. Fill it with rewrite_page_draft then publish_content_draft."):w.push(`There is no "${A}" page in the default language, so "/" returns 404. Create one with create_cms_page (slug "${A}", status "published").`),J&&w.push(`NextBlock demo content is still present (${c.pages} seeded pages, ${c.posts} seeded posts, ${c.media} bundled images${c.logo?", the NextBlock logo":""}${c.siteTitle?", the default site title":""}${c.copyright?", the default copyright":""}). Remove it with reset_site_content, or pass reset to start_site_build.`),q||w.push("No site brief is saved yet. Interview the client and record the answers with save_site_brief."),{brief:q,buildSession:x?{expiresAt:x.expiresAt,id:x.id,summary:x.summary}:null,counts:{customBlocks:(b.data??[]).length,drafts:(f.data??[]).length,media:y.length,pages:R.length,posts:O.length,products:T.length},customBlocks:(b.data??[]).map(e=>({fieldCount:Array.isArray(e.fields)?e.fields.length:0,fields:Array.isArray(e.fields)?e.fields.map(l=>({key:l?.key,required:l?.required===!0,type:l?.type})):[],name:String(e.name??""),slug:String(e.slug)})),drafts:(f.data??[]).map(e=>({parentId:Number(e.parent_id),parentType:String(e.parent_type)})),homePage:L,identity:{activeLogo:v?{id:String(v.id),isSeeded:c.logo,name:String(v.name??""),objectKey:$}:null,footerCopyright:W??null,footerShowAttribution:a.get("footer_show_attribution")!==!1,siteDescription:typeof a.get("site_description")=="string"?a.get("site_description"):"",siteKeywords:typeof a.get("site_keywords")=="string"?a.get("site_keywords"):"",siteTitle:U},languages:h.map(e=>({code:String(e.code),id:Number(e.id),isActive:e.is_active!==!1,isDefault:e.is_default===!0,name:String(e.name??"")})),media:{count:y.length,sample:s.includeMedia?y.slice(0,40).map(e=>({altText:e.description??null,fileName:e.file_name??null,id:String(e.id),isSeeded:G.has(String(e.object_key))})):void 0,seededCount:I},navigation:F,nextSteps:w,pages:R,posts:O,products:T.map(e=>({id:String(e.id),languageCode:S.get(Number(e.language_id))??String(e.language_id),slug:String(e.slug),status:String(e.status),title:String(e.title??"")})),seeded:{...c,anyPresent:J},success:!0,themes:(m.data??[]).map(e=>({colorScheme:String(e.color_scheme??"light"),isActive:e.is_active!==!1,isDefault:e.is_default===!0,name:String(e.name??""),slug:String(e.slug)}))}}const le=d.z.strictObject({active_logo_id:d.z.string().uuid().nullable().optional().describe("Pin a logos.id as the active logo, or null to fall back to the newest logo."),footer_copyright:d.z.record(d.z.string().trim().min(2).max(12),d.z.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" }.'),footer_show_attribution:d.z.boolean().optional().describe('Whether the "Published with NextBlock" credit shows in the footer. Set false when the client wants no NextBlock mention.'),site_description:d.z.string().trim().max(500).optional().describe("Default meta description and header tagline."),site_keywords:d.z.string().trim().max(500).optional().describe("Comma-separated SEO keywords."),site_title:d.z.string().trim().min(1).max(160).optional().describe("The brand name shown in the header and <title>.")}).refine(t=>Object.values(t).some(i=>i!==void 0),{message:"Provide at least one identity field to update."});async function he(t,i){const s=le.parse(t);await ae.requireActorRole(i,["ADMIN","WRITER"],"Updating the site identity");const o=M(i),n=Object.entries(s).filter(([,r])=>r!==void 0),a=se({action:"UPDATE SITE IDENTITY",context:i,payload:{input:s,tool:"update_site_identity"},preview:{changes:Object.fromEntries(n),summary:`Update ${n.map(([r])=>r).join(", ")} for the whole site.`},subject:n.map(([r])=>r).join(" ")});if(a)return a;for(const[r,u]of n)await k(o,r,u);return oe(i,["/cms/settings/logos","/cms/settings/copyright"]),{mutationExecuted:!0,success:!0,updatedKeys:n.map(([r])=>r)}}const Ae=d.z.strictObject({customBlocks:d.z.boolean().default(!1).describe("Delete every custom block definition."),identity:d.z.boolean().default(!0).describe("Clear the NextBlock site title, description, keywords, copyright line, and the seeded logo."),navigation:d.z.boolean().default(!0).describe("Delete every header and footer navigation item."),pages:d.z.boolean().default(!0).describe("Delete pages (all languages) except keepPageSlugs."),posts:d.z.boolean().default(!0).describe("Delete every blog post."),products:d.z.boolean().default(!1).describe("Delete every product, with its variants, images, and drafts."),seededMedia:d.z.boolean().default(!0).describe("Delete the bundled NextBlock demo images from the media library.")}).partial(),X=d.z.strictObject({clearKeptPageBlocks:d.z.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.'),dryRun:d.z.boolean().default(!1).describe("Report what would be removed without changing anything. No confirmation needed."),keepLanguages:d.z.array(d.z.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."),keepPageSlugs:d.z.array(d.z.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.'),onlySeeded:d.z.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."),scope:Ae.default({})});async function _e(t,i){const s={customBlocks:t.scope.customBlocks??!1,identity:t.scope.identity??!0,navigation:t.scope.navigation??!0,pages:t.scope.pages??!0,posts:t.scope.posts??!0,products:t.scope.products??!1,seededMedia:t.scope.seededMedia??!0},{data:o,error:n}=await i.from("languages").select("id, code, name, is_default, is_active");if(n)throw new Error(`Could not read the languages: ${p(n)}`);const a=Array.isArray(o)?o:[],r=new Map(a.map(e=>[Number(e.id),String(e.code).toLowerCase()])),u=t.keepLanguages?new Set(t.keepLanguages.map(e=>e.toLowerCase())):null;if(u){const e=new Set(r.values()),l=[...u].filter(_=>!e.has(_));if(l.length>0)throw new Error(`Unknown language code(s) in keepLanguages: ${l.join(", ")}. Existing: ${[...e].join(", ")}.`)}const m=new Set(a.filter(e=>!u||u.has(String(e.code).toLowerCase())).map(e=>Number(e.id))),b=a.filter(e=>u&&!u.has(String(e.code).toLowerCase())&&e.is_active!==!1).map(e=>({code:String(e.code),id:Number(e.id)})),f=a.find(e=>e.is_default===!0),h=u&&f&&!u.has(String(f.code).toLowerCase())?(()=>{const e=a.find(l=>u.has(String(l.code).toLowerCase()));return e?{code:String(e.code),id:Number(e.id)}:null})():null,S=e=>Z.has(String(e.translation_group_id)),{data:z,error:T}=await i.from("pages").select("id, slug, language_id, translation_group_id, author_id");if(T)throw new Error(`Could not read the pages: ${p(T)}`);const E=Array.isArray(z)?z:[],F=new Set(t.keepPageSlugs.map(e=>e.toLowerCase())),H=new Set(E.filter(e=>F.has(String(e.slug).toLowerCase())).map(e=>String(e.translation_group_id))),y=e=>({id:Number(e.id),languageCode:r.get(Number(e.language_id))??String(e.language_id),slug:String(e.slug)}),I=[],C=[];for(const e of E){const l=H.has(String(e.translation_group_id)),_=m.has(Number(e.language_id)),V=!t.onlySeeded||S(e);l&&_?C.push(y(e)):s.pages&&V||!_?I.push(y(e)):C.push(y(e))}const{data:j,error:K}=await i.from("posts").select("id, slug, language_id, translation_group_id, author_id");if(K)throw new Error(`Could not read the posts: ${p(K)}`);const v=(Array.isArray(j)?j:[]).filter(e=>{const l=m.has(Number(e.language_id)),_=!t.onlySeeded||S(e);return s.posts&&_||!l}).map(y);let D=[];if(s.products)try{const{data:e}=await i.from("products").select("id");D=(Array.isArray(e)?e:[]).map(l=>String(l.id))}catch{D=[]}const $=new Set(I.map(e=>e.id)),R=new Set(I.map(e=>`/${e.slug}`)),{data:O,error:U}=await i.from("navigation_items").select("id, url, page_id");if(U)throw new Error(`Could not read the navigation: ${p(U)}`);const W=(Array.isArray(O)?O:[]).filter(e=>s.navigation||e.page_id!==null&&e.page_id!==void 0&&$.has(Number(e.page_id))||R.has(String(e.url))).map(e=>Number(e.id)),c=C.map(e=>e.id),J=new Set(v.map(e=>e.id)),{data:q}=await i.from("content_drafts").select("id, parent_type, parent_id"),Q=(Array.isArray(q)?q:[]).filter(e=>{const l=Number(e.parent_id);return e.parent_type==="page"?$.has(l)||t.clearKeptPageBlocks&&c.includes(l):e.parent_type==="post"&&J.has(l)}).map(e=>({parentId:Number(e.parent_id),parentType:String(e.parent_type)}));let x=[];if(s.customBlocks){const{data:e}=await i.from("custom_block_definitions").select("slug");x=(Array.isArray(e)?e:[]).map(l=>String(l.slug))}let L=[],w=[];if(s.seededMedia||s.identity){const{data:e}=await i.from("media").select("id, object_key"),l=(Array.isArray(e)?e:[]).filter(N=>G.has(String(N.object_key))),_=new Set(l.map(N=>String(N.id))),{data:V}=await i.from("logos").select("id, media_id");w=(Array.isArray(V)?V:[]).filter(N=>_.has(String(N.media_id))).map(N=>String(N.id)),L=s.seededMedia?[..._]:[],s.identity||(w=[])}return{blocksToClearPageIds:t.clearKeptPageBlocks?c:[],customBlockSlugs:x,draftsToDelete:Q,identityKeys:s.identity?["site_title","site_description","site_keywords","footer_copyright"]:[],languagesToDeactivate:b,logosToDelete:w,mediaToDelete:L,navigationToDelete:W,newDefaultLanguage:h,pagesToDelete:I,pagesToKeep:C,postsToDelete:v,productsToDelete:D}}function be(t){return{blocksClearedOnPages:t.blocksToClearPageIds.length,customBlocks:t.customBlockSlugs.length,drafts:t.draftsToDelete.length,identityCleared:t.identityKeys.length>0,languagesDeactivated:t.languagesToDeactivate.map(i=>i.code),logos:t.logosToDelete.length,media:t.mediaToDelete.length,navigationItems:t.navigationToDelete.length,pages:t.pagesToDelete.length,pagesKept:t.pagesToKeep.map(i=>`${i.slug} (${i.languageCode})`),posts:t.postsToDelete.length,products:t.productsToDelete.length}}async function De(t,i,s){for(const o of t.draftsToDelete){const{error:n}=await i.from("content_drafts").delete().eq("parent_type",o.parentType).eq("parent_id",o.parentId);if(n)throw new Error(`Could not delete a draft: ${p(n)}`)}if(await B(i,"navigation_items","id",t.navigationToDelete),t.blocksToClearPageIds.length>0)for(const o of me(t.blocksToClearPageIds)){const{error:n}=await i.from("blocks").delete().in("page_id",o);if(n)throw new Error(`Could not clear the kept pages: ${p(n)}`)}if(await B(i,"pages","id",t.pagesToDelete.map(o=>o.id)),await B(i,"posts","id",t.postsToDelete.map(o=>o.id)),await B(i,"products","id",t.productsToDelete),await B(i,"custom_block_definitions","slug",t.customBlockSlugs),await B(i,"logos","id",t.logosToDelete),await B(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(i,"footer_copyright",Object.fromEntries(s.map(o=>[o,"© {year}"])))),t.logosToDelete.length>0){const{data:o}=await i.from("site_settings").select("value").eq("key","active_logo_id").maybeSingle();typeof o?.value=="string"&&t.logosToDelete.includes(o.value)&&await k(i,"active_logo_id",null)}for(const o of t.languagesToDeactivate){const{error:n}=await i.from("languages").update({is_active:!1,is_default:!1}).eq("id",o.id);if(n)throw new Error(`Could not deactivate the "${o.code}" language: ${p(n)}`)}if(t.newDefaultLanguage){const{error:o}=await i.from("languages").update({is_default:!1}).neq("id",t.newDefaultLanguage.id);if(o)throw new Error(`Could not change the default language: ${p(o)}`);const{error:n}=await i.from("languages").update({is_active:!0,is_default:!0}).eq("id",t.newDefaultLanguage.id);if(n)throw new Error(`Could not change the default language: ${p(n)}`)}}function pe(t){const i=[],s=t.pagesToKeep.some(o=>o.slug===A);return s&&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.'):s||i.push(`No "${A}" page remains, so "/" is a 404 until you create_cms_page with slug "${A}" 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}async function ue(t,i){const s=X.parse(t);await ae.requireActorRole(i,["ADMIN"],"Resetting the site content");const o=M(i),n=await _e(s,o),a=be(n),r=a.pages+a.posts+a.products+a.navigationItems+a.customBlocks+a.media+a.logos;if(s.dryRun)return{dryRun:!0,mutationExecuted:!1,nextSteps:pe(n),success:!0,summary:a,wouldDelete:{pages:n.pagesToDelete,posts:n.postsToDelete}};const u=se({action:"RESET SITE CONTENT",context:i,payload:{input:s,summary:a,tool:"reset_site_content"},preview:{irreversible:!0,summary:`Permanently delete ${a.pages} page(s), ${a.posts} post(s), ${a.products} product(s), ${a.navigationItems} navigation item(s), ${a.customBlocks} custom block definition(s), ${a.media} demo image(s) and ${a.logos} logo(s)${a.identityCleared?", clear the site identity":""}${a.languagesDeactivated.length?`, deactivate ${a.languagesDeactivated.join(", ")}`:""}. Kept: ${a.pagesKept.join(", ")||"nothing"}. This cannot be undone — take a backup first at /cms/settings/backup-restore if the content matters.`,...a},subject:`${r} items`});if(u)return u;const m=s.keepLanguages?.map(f=>f.toLowerCase())??[],b=m.length>0?m:await(async()=>{const{data:f}=await o.from("languages").select("code, is_active");return(Array.isArray(f)?f:[]).filter(h=>h.is_active!==!1).map(h=>String(h.code).toLowerCase())})();return await De(n,o,b),oe(i,["/cms/pages","/cms/posts","/cms/navigation","/cms/media","/cms/custom-blocks","/cms/products"]),{deleted:a,mutationExecuted:!0,nextSteps:pe(n),success:!0}}const $e=60,ce=d.z.strictObject({brief:g.cortexSiteBriefInputSchema.optional().describe("The brief as agreed with the client. Merged over the saved brief and marked confirmed."),durationMinutes:d.z.number().int().min(15).max(180).default($e).describe("How long the unattended build session stays open."),reset:X.optional().describe("Remove the seeded NextBlock demo content as the first step of the build. Omit when the client keeps existing content."),summary:d.z.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.")});async function ye(t,i){const s=ce.parse(t);await ae.requireActorRole(i,["ADMIN"],"Starting a site build");const o=M(i),n=i?.actorUserId;let a=null,r=null;s.reset&&(r=X.parse({...s.reset,dryRun:!1}),a=be(await _e(r,o)));const u=se({action:"START SITE BUILD",context:i,payload:{brief:s.brief??null,durationMinutes:s.durationMinutes,reset:r,summary:s.summary,tool:"start_site_build"},preview:{durationMinutes:s.durationMinutes,plan:s.summary,reset:a,summary:`${s.summary}${a?` First, permanently delete ${a.pages} page(s), ${a.posts} post(s), ${a.navigationItems} navigation item(s), ${a.media} demo image(s)${a.identityCleared?" and the NextBlock site identity":""} (kept: ${a.pagesKept.join(", ")||"nothing"}).`:""} After you confirm, Cortex applies every step of this plan without asking again, for up to ${s.durationMinutes} minutes. You can stop it at any time.`},subject:"plan"});if(u)return u;let m=null;s.brief?m=(await Y({brief:{...s.brief,status:"confirmed"},mode:"merge"},{...i})).brief:await ee(o)&&(m=(await Y({brief:{status:"confirmed"},mode:"merge"},{...i})).brief);let b=null;r&&(b=await ue(r,{...i,skipConfirmation:!0}));const f=new Date,h={actorUserId:n,createdAt:f.toISOString(),expiresAt:new Date(f.getTime()+s.durationMinutes*6e4).toISOString(),id:Ee(),summary:s.summary};return await k(o,g.CORTEX_AI_BUILD_SESSION_SETTING_KEY,h),{brief:m,buildSession:{expiresAt:h.expiresAt,id:h.id},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.",mutationExecuted:!0,resetResult:b,success:!0}}const ge=d.z.strictObject({outcome:d.z.enum(["built","stopped"]).default("built").describe('"built" when the plan was completed; "stopped" when the operator interrupted it.'),summary:d.z.string().trim().max(4e3).optional().describe("What was built, for the record.")});async function Se(t,i){const s=ge.parse(t),o=M(i),n=await re(o);await Ne(o);let a=null;if(s.outcome==="built"){const r=await ee(o);r&&(a=(await Y({brief:{status:"built",...s.summary?{notes:`${r.notes?`${r.notes}
|
|
2
|
-
`:""}Built: ${s.summary}`}:{}},mode:"merge"},{...i})).brief)}return oe(i,["/cms/dashboard"]),{brief:a,buildSessionEnded:!0,hadSession:!!n,mutationExecuted:!0,outcome:s.outcome,success:!0}}function Re(t){return{get_site_overview:P.tool({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.',execute:i=>fe(i,t),inputSchema:de,strict:!0}),update_site_identity:P.tool({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.',execute:i=>he(i,t),inputSchema:le,strict:!0}),save_site_brief:P.tool({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.',execute:i=>Y(i,t),inputSchema:ne,strict:!0}),reset_site_content:P.tool({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.',execute:i=>ue(i,t),inputSchema:X,strict:!0}),start_site_build:P.tool({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.",execute:i=>ye(i,t),inputSchema:ce,strict:!0}),finish_site_build:P.tool({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.',execute:i=>Se(i,t),inputSchema:ge,strict:!0})}}exports.NEXTBLOCK_SEED_MEDIA_OBJECT_KEYS=G;exports.NEXTBLOCK_SEED_TRANSLATION_GROUP_IDS=Z;exports.createCortexSiteTools=Re;exports.executeFinishSiteBuild=Se;exports.executeGetSiteOverview=fe;exports.executeResetSiteContent=ue;exports.executeSaveSiteBrief=Y;exports.executeStartSiteBuild=ye;exports.executeUpdateSiteIdentity=he;exports.finishSiteBuildInputSchema=ge;exports.getSiteOverviewInputSchema=de;exports.readCortexBuildSession=re;exports.readCortexSiteBrief=ee;exports.resetSiteContentInputSchema=X;exports.resolveCortexBuildSession=xe;exports.saveSiteBriefInputSchema=ne;exports.startSiteBuildInputSchema=ce;exports.updateSiteIdentityInputSchema=le;
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const P=require("ai"),se=require("./ai-global-agent-theming-tools.cjs.js"),f=require("./site-brief.cjs.js");require("./zod-config.cjs.js");const l=require("zod"),Z=new Set(["0098e4f0-e5f3-4e28-acfc-bb9fdb3a4a6b","50adeea5-5040-4ae7-bacd-992647920982","e3b28669-7e38-47bd-9189-7db2353b52dc","8cd91839-518d-4b57-87db-441e59a71a95","b5eb82c7-95d6-4308-82d8-3ac2816f5d46","de8b8593-1ef6-4e66-8d1f-8b3e7ffb811d","461cde55-74cb-4112-8c3d-a25882eeedce","23773a34-e54b-40c6-89b5-b09ead17ae60","669489e7-5900-496e-8b73-2aba7514a71b","c0d3f1a2-8b47-4e19-9a52-7f6b1d4e8c30","a7c4e2d1-3b58-4f96-9e0a-6d2c8b1f5a73","dbe2a06f-1a9d-48ba-bc35-0f6ef4869156"]),G=new Set(["images/NBcover.webp","images/commerce-plan.webp","images/commerce-wide.webp","images/cortex-ai.webp","images/cortex_post.webp","images/extensibility.webp","images/included.webp","images/nextblock-logo-button-tiny.png","images/nextblock-logo-small.webp","images/programmer-upscaled.webp","images/update_nextblock.webp"]),we="NextBlock™ CMS",ve="nextblock cms",N="home",ke=["active_logo_id","footer_copyright","footer_show_attribution","site_description","site_keywords","site_title"];function M(t){if(!t?.supabase)throw new Error("No database connection is available for this tool.");return t.supabase}function _(t){return t?t instanceof Error?t.message:typeof t=="object"&&"message"in t?String(t.message):String(t):"unknown error"}function ie(t){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(",")}}`}function Te(t){let i=2166136261;const a=ie(t);for(let s=0;s<a.length;s++)i^=a.charCodeAt(s),i=Math.imul(i,16777619);return(i>>>0).toString(16).padStart(8,"0")}function te(t){return t.replace(/\s+/g," ").trim().toUpperCase()}function ae(t){if(t.context?.skipConfirmation)return null;const i=`${te(`CONFIRM ${t.action} ${t.subject}`)} #${Te(t.payload)}`;return te(t.context?.latestUserMessage||"").includes(te(i))?null:{confirmationPhrase:i,mutationExecuted:!1,preview:t.preview,requiresConfirmation:!0,success:!0}}function Ee(){return globalThis.crypto?.randomUUID?.()||`id-${Date.now()}-${Math.random().toString(36).slice(2)}`}function me(t,i=200){const a=[];for(let s=0;s<t.length;s+=i)a.push(t.slice(s,s+i));return a}function oe(t,i=[]){let a=t?.revalidatePath;if(!a)try{a=require("next/cache").revalidatePath}catch{return}try{a("/","layout");for(const s of i)a(s)}catch{}}async function Ie(t,i){const{data:a,error:s}=await t.from("site_settings").select("key, value").in("key",[...i]);if(s)throw new Error(`Could not read the site settings: ${_(s)}`);return new Map((Array.isArray(a)?a:[]).map(n=>[n.key,n.value]))}async function k(t,i,a){const{error:s}=await t.from("site_settings").upsert({key:i,value:a});if(s)throw new Error(`Could not save the "${i}" setting: ${_(s)}`)}async function B(t,i,a,s){let n=0;for(const o of me(s)){const{error:r}=await t.from(i).delete().in(a,o);if(r)throw new Error(`Could not delete from ${i}: ${_(r)}`);n+=o.length}return n}function Ce(t){return!t||typeof t!="object"?!1:Object.values(t).some(i=>typeof i=="string"&&i.toLowerCase().includes(ve))}async function ee(t){const{data:i,error:a}=await t.from("site_settings").select("value").eq("key",f.CORTEX_AI_SITE_BRIEF_SETTING_KEY).maybeSingle();return a?null:f.safeParseCortexSiteBrief(i?.value)}const ne=l.z.strictObject({brief:f.cortexSiteBriefInputSchema.describe('The fields you learned. With mode "merge" (default) they are layered over the saved brief, so you can save after every answer.'),mode:l.z.enum(["merge","replace"]).default("merge")});async function Y(t,i){const a=ne.parse(t),s=M(i),n=a.mode==="merge"?await ee(s):null,o=new Set(Object.keys(t.brief??{})),r=Object.fromEntries(Object.entries(a.brief).filter(([h])=>o.has(h))),d=f.cortexSiteBriefSchema.parse({...n??{},...r,updated_at:new Date().toISOString()});return await k(s,f.CORTEX_AI_SITE_BRIEF_SETTING_KEY,d),{brief:d,created:!n,mutationExecuted:!0,success:!0}}async function re(t){const{data:i,error:a}=await t.from("site_settings").select("value").eq("key",f.CORTEX_AI_BUILD_SESSION_SETTING_KEY).maybeSingle();if(a)return null;const s=f.cortexBuildSessionSchema.safeParse(i?.value);return s.success?s.data:null}async function $e(t,i){const a=await re(t);return f.isCortexBuildSessionUsable(a,{actorUserId:i})?a:null}async function xe(t){const{error:i}=await t.from("site_settings").delete().eq("key",f.CORTEX_AI_BUILD_SESSION_SETTING_KEY);if(i)throw new Error(`Could not close the build session: ${_(i)}`)}const le=l.z.strictObject({includeMedia:l.z.boolean().default(!1).describe("Also list the most recent media library entries (id, file name, alt text)."),includeNavigation:l.z.boolean().default(!0),limit:l.z.number().int().min(10).max(300).default(120).describe("Maximum pages, posts, and products to list.")});function Be(t,i){const a={footer:{},header:{}},s=new Map;for(const o of t){const r=String(o.menu_key).toUpperCase()==="FOOTER"?"footer":"header";s.set(Number(o.id),{children:[],id:Number(o.id),label:String(o.label??""),languageCode:i.get(Number(o.language_id))??String(o.language_id),menu:r,order:Number(o.order??0),parentId:o.parent_id===null||o.parent_id===void 0?null:Number(o.parent_id),url:String(o.url??"")})}const n=[...s.values()].sort((o,r)=>o.order-r.order);for(const o of n){const r=o.parentId===null?null:s.get(o.parentId);if(r){r.children.push({children:o.children,id:o.id,label:o.label,url:o.url});continue}(a[o.menu][o.languageCode]??=[]).push({children:o.children,id:o.id,label:o.label,url:o.url})}return a}async function fe(t,i){const a=le.parse(t),s=M(i),[n,o,r,d,h,c,g]=await Promise.all([s.from("languages").select("id, code, name, is_default, is_active").order("id"),Ie(s,[...ke,f.CORTEX_AI_SITE_BRIEF_SETTING_KEY,f.CORTEX_AI_BUILD_SESSION_SETTING_KEY]),s.from("pages").select("id, slug, title, status, language_id, translation_group_id, author_id, updated_at",{count:"exact"}).order("id").limit(a.limit),s.from("posts").select("id, slug, title, status, language_id, translation_group_id, author_id, published_at",{count:"exact"}).order("id").limit(a.limit),s.from("site_themes").select("slug, name, is_default, is_active, color_scheme").order("sort_order"),s.from("custom_block_definitions").select("slug, name, fields").order("name"),s.from("content_drafts").select("parent_type, parent_id, updated_at")]);for(const[e,u]of[["languages",n],["pages",r],["posts",d],["themes",h],["custom blocks",c],["drafts",g]])if(u?.error)throw new Error(`Could not read the ${e}: ${_(u.error)}`);const p=n.data??[],S=new Map(p.map(e=>[Number(e.id),String(e.code)])),j=p.find(e=>e.is_default)??p[0]??null;let T=[];try{const{data:e}=await s.from("products").select("id, slug, title, status, language_id").order("created_at",{ascending:!1}).limit(a.limit);T=Array.isArray(e)?e:[]}catch{T=[]}const E=new Map;{const{data:e}=await s.from("blocks").select("page_id, post_id").limit(5e3);for(const u of Array.isArray(e)?e:[]){const b=u.page_id?`page:${u.page_id}`:u.post_id?`post:${u.post_id}`:null;b&&E.set(b,(E.get(b)??0)+1)}}let F=null;if(a.includeNavigation){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");if(u)throw new Error(`Could not read the navigation: ${_(u)}`);F=Be(Array.isArray(e)?e:[],S)}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:[],I=y.filter(e=>G.has(String(e.object_key))).length,{data:C}=await s.from("logos").select("id, name, media_id, created_at").order("created_at",{ascending:!1}),z=Array.isArray(C)?C:[],K=typeof o.get("active_logo_id")=="string"?o.get("active_logo_id"):null,v=(K?z.find(e=>e.id===K):null)??z[0]??null,A=v?y.find(e=>e.id===v.media_id)??null:null,D=A?String(A.object_key):null,O=(r.data??[]).map(e=>({blockCount:E.get(`page:${e.id}`)??0,id:Number(e.id),isHome:e.slug===N&&(!j||Number(e.language_id)===Number(j.id)),isSeeded:Z.has(String(e.translation_group_id)),languageCode:S.get(Number(e.language_id))??String(e.language_id),slug:String(e.slug),status:String(e.status),title:String(e.title??""),translationGroupId:e.translation_group_id?String(e.translation_group_id):null})),R=(d.data??[]).map(e=>({blockCount:E.get(`post:${e.id}`)??0,id:Number(e.id),isSeeded:Z.has(String(e.translation_group_id)),languageCode:S.get(Number(e.language_id))??String(e.language_id),publishedAt:e.published_at??null,slug:String(e.slug),status:String(e.status),title:String(e.title??""),translationGroupId:e.translation_group_id?String(e.translation_group_id):null})),U=typeof o.get("site_title")=="string"?o.get("site_title"):"",W=o.get("footer_copyright"),m={copyright:Ce(W),logo:!!(D&&G.has(D)),media:I,pages:O.filter(e=>e.isSeeded).length,posts:R.filter(e=>e.isSeeded).length,siteTitle:U.trim()===we},J=m.copyright||m.logo||m.media>0||m.pages>0||m.posts>0||m.siteTitle,q=f.safeParseCortexSiteBrief(o.get(f.CORTEX_AI_SITE_BRIEF_SETTING_KEY)),V=f.cortexBuildSessionSchema.safeParse(o.get(f.CORTEX_AI_BUILD_SESSION_SETTING_KEY)),$=V.success?V.data:null,L=O.find(e=>e.isHome)??null,w=[];return L?L.blockCount===0&&w.push("The home page exists but has no blocks. Fill it with rewrite_page_draft then publish_content_draft."):w.push(`There is no "${N}" page in the default language, so "/" returns 404. Create one with create_cms_page (slug "${N}", status "published").`),J&&w.push(`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.`),q||w.push("No site brief is saved yet. Interview the client and record the answers with save_site_brief."),{brief:q,buildSession:$?{expiresAt:$.expiresAt,id:$.id,summary:$.summary}:null,counts:{customBlocks:(c.data??[]).length,drafts:(g.data??[]).length,media:y.length,pages:typeof r.count=="number"?r.count:O.length,posts:typeof d.count=="number"?d.count:R.length,products:T.length},customBlocks:(c.data??[]).map(e=>({fieldCount:Array.isArray(e.fields)?e.fields.length:0,fields:Array.isArray(e.fields)?e.fields.map(u=>({key:u?.key,required:u?.required===!0,type:u?.type})):[],name:String(e.name??""),slug:String(e.slug)})),drafts:(g.data??[]).map(e=>({parentId:Number(e.parent_id),parentType:String(e.parent_type)})),homePage:L,identity:{activeLogo:v?{id:String(v.id),isSeeded:m.logo,name:String(v.name??""),objectKey:D}:null,footerCopyright:W??null,footerShowAttribution:o.get("footer_show_attribution")!==!1,siteDescription:typeof o.get("site_description")=="string"?o.get("site_description"):"",siteKeywords:typeof o.get("site_keywords")=="string"?o.get("site_keywords"):"",siteTitle:U},languages:p.map(e=>({code:String(e.code),id:Number(e.id),isActive:e.is_active!==!1,isDefault:e.is_default===!0,name:String(e.name??"")})),media:{count:y.length,sample:a.includeMedia?y.slice(0,40).map(e=>({altText:e.description??null,fileName:e.file_name??null,id:String(e.id),isSeeded:G.has(String(e.object_key))})):void 0,seededCount:I},navigation:F,nextSteps:w,pages:O,posts:R,products:T.map(e=>({id:String(e.id),languageCode:S.get(Number(e.language_id))??String(e.language_id),slug:String(e.slug),status:String(e.status),title:String(e.title??"")})),seeded:{...m,anyPresent:J},success:!0,themes:(h.data??[]).map(e=>({colorScheme:String(e.color_scheme??"light"),isActive:e.is_active!==!1,isDefault:e.is_default===!0,name:String(e.name??""),slug:String(e.slug)}))}}const Ne=30;function Ae(t){const{counts:i,seeded:a}=t,s=[];s.push(`Counts: ${i.pages} pages, ${i.posts} posts, ${i.products} products, ${i.media} media, ${i.customBlocks} custom blocks, ${i.drafts} drafts.`);const n=t.languages.filter(c=>c.isActive),o=(n.length>0?n:t.languages).map(c=>`${c.code}${c.isDefault?" (default)":""}`).join(", ");if(s.push(`Languages: ${o||"none"}.`),a.anyPresent){const c=[a.pages>0?`${a.pages} demo pages`:null,a.posts>0?`${a.posts} demo posts`:null,a.media>0?`${a.media} bundled images`:null,a.logo?"the NextBlock logo":null,a.siteTitle?"the default site title":null,a.copyright?"the default copyright":null].filter(g=>!!g);s.push(`NextBlock demo content: present (${c.join(", ")}).`)}else s.push("NextBlock demo content: none detected.");const r=t.homePage;s.push(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).');const d=t.identity,h=d.activeLogo?`logo "${d.activeLogo.name||d.activeLogo.id}"${d.activeLogo.isSeeded?" (NextBlock demo logo)":""}`:"no logo";if(s.push(`Site title: ${d.siteTitle?`"${d.siteTitle}"`:"not set"}; ${h}.`),t.themes.length>0&&s.push(`Themes: ${t.themes.map(c=>`${c.name||c.slug} (${c.colorScheme}${c.isDefault?", default":""}${c.isActive?"":", inactive"})`).join("; ")}.`),s.push(t.brief?`Site brief: saved (status ${t.brief.status}).`:"Site brief: none saved."),s.push(t.buildSession?`Build session: active until ${t.buildSession.expiresAt}.`:"Build session: none."),t.pages.length>0){const c=t.pages.slice(0,Ne),g=Math.max(i.pages,t.pages.length)-c.length;s.push(`Pages: ${c.map(p=>`${p.title||p.slug} (/${p.slug}) [${p.languageCode}]`).join(", ")}${g>0?`, +${g} more`:""}.`)}else s.push("Pages: none.");return s.join(`
|
|
2
|
+
`)}const de=l.z.strictObject({active_logo_id:l.z.string().uuid().nullable().optional().describe("Pin a logos.id as the active logo, or null to fall back to the newest logo."),footer_copyright:l.z.record(l.z.string().trim().min(2).max(12),l.z.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" }.'),footer_show_attribution:l.z.boolean().optional().describe('Whether the "Published with NextBlock" credit shows in the footer. Set false when the client wants no NextBlock mention.'),site_description:l.z.string().trim().max(500).optional().describe("Default meta description and header tagline."),site_keywords:l.z.string().trim().max(500).optional().describe("Comma-separated SEO keywords."),site_title:l.z.string().trim().min(1).max(160).optional().describe("The brand name shown in the header and <title>.")}).refine(t=>Object.values(t).some(i=>i!==void 0),{message:"Provide at least one identity field to update."});async function he(t,i){const a=de.parse(t);await se.requireActorRole(i,["ADMIN","WRITER"],"Updating the site identity");const s=M(i),n=Object.entries(a).filter(([,r])=>r!==void 0),o=ae({action:"UPDATE SITE IDENTITY",context:i,payload:{input:a,tool:"update_site_identity"},preview:{changes:Object.fromEntries(n),summary:`Update ${n.map(([r])=>r).join(", ")} for the whole site.`},subject:n.map(([r])=>r).join(" ")});if(o)return o;for(const[r,d]of n)await k(s,r,d);return oe(i,["/cms/settings/logos","/cms/settings/copyright"]),{mutationExecuted:!0,success:!0,updatedKeys:n.map(([r])=>r)}}const De=l.z.strictObject({customBlocks:l.z.boolean().default(!1).describe("Delete every custom block definition."),identity:l.z.boolean().default(!0).describe("Clear the NextBlock site title, description, keywords, copyright line, and the seeded logo."),navigation:l.z.boolean().default(!0).describe("Delete every header and footer navigation item."),pages:l.z.boolean().default(!0).describe("Delete pages (all languages) except keepPageSlugs."),posts:l.z.boolean().default(!0).describe("Delete every blog post."),products:l.z.boolean().default(!1).describe("Delete every product, with its variants, images, and drafts."),seededMedia:l.z.boolean().default(!0).describe("Delete the bundled NextBlock demo images from the media library.")}).partial(),X=l.z.strictObject({clearKeptPageBlocks:l.z.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.'),dryRun:l.z.boolean().default(!1).describe("Report what would be removed without changing anything. No confirmation needed."),keepLanguages:l.z.array(l.z.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."),keepPageSlugs:l.z.array(l.z.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.'),onlySeeded:l.z.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."),scope:De.default({})});async function _e(t,i){const a={customBlocks:t.scope.customBlocks??!1,identity:t.scope.identity??!0,navigation:t.scope.navigation??!0,pages:t.scope.pages??!0,posts:t.scope.posts??!0,products:t.scope.products??!1,seededMedia:t.scope.seededMedia??!0},{data:s,error:n}=await i.from("languages").select("id, code, name, is_default, is_active");if(n)throw new Error(`Could not read the languages: ${_(n)}`);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;if(d){const e=new Set(r.values()),u=[...d].filter(b=>!e.has(b));if(u.length>0)throw new Error(`Unknown language code(s) in keepLanguages: ${u.join(", ")}. Existing: ${[...e].join(", ")}.`)}const h=new Set(o.filter(e=>!d||d.has(String(e.code).toLowerCase())).map(e=>Number(e.id))),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())?(()=>{const e=o.find(u=>d.has(String(u.code).toLowerCase()));return e?{code:String(e.code),id:Number(e.id)}:null})():null,S=e=>Z.has(String(e.translation_group_id)),{data:j,error:T}=await i.from("pages").select("id, slug, language_id, translation_group_id, author_id");if(T)throw new Error(`Could not read the pages: ${_(T)}`);const E=Array.isArray(j)?j:[],F=new Set(t.keepPageSlugs.map(e=>e.toLowerCase())),H=new Set(E.filter(e=>F.has(String(e.slug).toLowerCase())).map(e=>String(e.translation_group_id))),y=e=>({id:Number(e.id),languageCode:r.get(Number(e.language_id))??String(e.language_id),slug:String(e.slug)}),I=[],C=[];for(const e of E){const u=H.has(String(e.translation_group_id)),b=h.has(Number(e.language_id)),Q=!t.onlySeeded||S(e);u&&b?C.push(y(e)):a.pages&&Q||!b?I.push(y(e)):C.push(y(e))}const{data:z,error:K}=await i.from("posts").select("id, slug, language_id, translation_group_id, author_id");if(K)throw new Error(`Could not read the posts: ${_(K)}`);const v=(Array.isArray(z)?z:[]).filter(e=>{const u=h.has(Number(e.language_id)),b=!t.onlySeeded||S(e);return a.posts&&b||!u}).map(y);let A=[];if(a.products)try{const{data:e}=await i.from("products").select("id");A=(Array.isArray(e)?e:[]).map(u=>String(u.id))}catch{A=[]}const D=new Set(I.map(e=>e.id)),O=new Set(I.map(e=>`/${e.slug}`)),{data:R,error:U}=await i.from("navigation_items").select("id, url, page_id");if(U)throw new Error(`Could not read the navigation: ${_(U)}`);const W=(Array.isArray(R)?R:[]).filter(e=>a.navigation||e.page_id!==null&&e.page_id!==void 0&&D.has(Number(e.page_id))||O.has(String(e.url))).map(e=>Number(e.id)),m=C.map(e=>e.id),J=new Set(v.map(e=>e.id)),{data:q}=await i.from("content_drafts").select("id, parent_type, parent_id"),V=(Array.isArray(q)?q:[]).filter(e=>{const u=Number(e.parent_id);return e.parent_type==="page"?D.has(u)||t.clearKeptPageBlocks&&m.includes(u):e.parent_type==="post"&&J.has(u)}).map(e=>({parentId:Number(e.parent_id),parentType:String(e.parent_type)}));let $=[];if(a.customBlocks){const{data:e}=await i.from("custom_block_definitions").select("slug");$=(Array.isArray(e)?e:[]).map(u=>String(u.slug))}let L=[],w=[];if(a.seededMedia||a.identity){const{data:e}=await i.from("media").select("id, object_key"),u=(Array.isArray(e)?e:[]).filter(x=>G.has(String(x.object_key))),b=new Set(u.map(x=>String(x.id))),{data:Q}=await i.from("logos").select("id, media_id");w=(Array.isArray(Q)?Q:[]).filter(x=>b.has(String(x.media_id))).map(x=>String(x.id)),L=a.seededMedia?[...b]:[],a.identity||(w=[])}return{blocksToClearPageIds:t.clearKeptPageBlocks?m:[],customBlockSlugs:$,draftsToDelete:V,identityKeys:a.identity?["site_title","site_description","site_keywords","footer_copyright"]:[],languagesToDeactivate:c,logosToDelete:w,mediaToDelete:L,navigationToDelete:W,newDefaultLanguage:p,pagesToDelete:I,pagesToKeep:C,postsToDelete:v,productsToDelete:A}}function be(t){return{blocksClearedOnPages:t.blocksToClearPageIds.length,customBlocks:t.customBlockSlugs.length,drafts:t.draftsToDelete.length,identityCleared:t.identityKeys.length>0,languagesDeactivated:t.languagesToDeactivate.map(i=>i.code),logos:t.logosToDelete.length,media:t.mediaToDelete.length,navigationItems:t.navigationToDelete.length,pages:t.pagesToDelete.length,pagesKept:t.pagesToKeep.map(i=>`${i.slug} (${i.languageCode})`),posts:t.postsToDelete.length,products:t.productsToDelete.length}}async function Oe(t,i,a){for(const s of t.draftsToDelete){const{error:n}=await i.from("content_drafts").delete().eq("parent_type",s.parentType).eq("parent_id",s.parentId);if(n)throw new Error(`Could not delete a draft: ${_(n)}`)}if(await B(i,"navigation_items","id",t.navigationToDelete),t.blocksToClearPageIds.length>0)for(const s of me(t.blocksToClearPageIds)){const{error:n}=await i.from("blocks").delete().in("page_id",s);if(n)throw new Error(`Could not clear the kept pages: ${_(n)}`)}if(await B(i,"pages","id",t.pagesToDelete.map(s=>s.id)),await B(i,"posts","id",t.postsToDelete.map(s=>s.id)),await B(i,"products","id",t.productsToDelete),await B(i,"custom_block_definitions","slug",t.customBlockSlugs),await B(i,"logos","id",t.logosToDelete),await B(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(i,"footer_copyright",Object.fromEntries(a.map(s=>[s,"© {year}"])))),t.logosToDelete.length>0){const{data:s}=await i.from("site_settings").select("value").eq("key","active_logo_id").maybeSingle();typeof s?.value=="string"&&t.logosToDelete.includes(s.value)&&await k(i,"active_logo_id",null)}for(const s of t.languagesToDeactivate){const{error:n}=await i.from("languages").update({is_active:!1,is_default:!1}).eq("id",s.id);if(n)throw new Error(`Could not deactivate the "${s.code}" language: ${_(n)}`)}if(t.newDefaultLanguage){const{error:s}=await i.from("languages").update({is_default:!1}).neq("id",t.newDefaultLanguage.id);if(s)throw new Error(`Could not change the default language: ${_(s)}`);const{error:n}=await i.from("languages").update({is_active:!0,is_default:!0}).eq("id",t.newDefaultLanguage.id);if(n)throw new Error(`Could not change the default language: ${_(n)}`)}}function pe(t){const i=[],a=t.pagesToKeep.some(s=>s.slug===N);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}async function ue(t,i){const a=X.parse(t);await se.requireActorRole(i,["ADMIN"],"Resetting the site content");const s=M(i),n=await _e(a,s),o=be(n),r=o.pages+o.posts+o.products+o.navigationItems+o.customBlocks+o.media+o.logos;if(a.dryRun)return{dryRun:!0,mutationExecuted:!1,nextSteps:pe(n),success:!0,summary:o,wouldDelete:{pages:n.pagesToDelete,posts:n.postsToDelete}};const d=ae({action:"RESET SITE CONTENT",context:i,payload:{input:a,summary:o,tool:"reset_site_content"},preview:{irreversible:!0,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.`,...o},subject:`${r} items`});if(d)return d;const h=a.keepLanguages?.map(g=>g.toLowerCase())??[],c=h.length>0?h:await(async()=>{const{data:g}=await s.from("languages").select("code, is_active");return(Array.isArray(g)?g:[]).filter(p=>p.is_active!==!1).map(p=>String(p.code).toLowerCase())})();return await Oe(n,s,c),oe(i,["/cms/pages","/cms/posts","/cms/navigation","/cms/media","/cms/custom-blocks","/cms/products"]),{deleted:o,mutationExecuted:!0,nextSteps:pe(n),success:!0}}const Re=60,ce=l.z.strictObject({brief:f.cortexSiteBriefInputSchema.optional().describe("The brief as agreed with the client. Merged over the saved brief and marked confirmed."),durationMinutes:l.z.number().int().min(15).max(180).default(Re).describe("How long the unattended build session stays open."),reset:X.optional().describe("Remove the seeded NextBlock demo content as the first step of the build. Omit when the client keeps existing content."),summary:l.z.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.")});async function ye(t,i){const a=ce.parse(t);await se.requireActorRole(i,["ADMIN"],"Starting a site build");const s=M(i),n=i?.actorUserId;let o=null,r=null;a.reset&&(r=X.parse({...a.reset,dryRun:!1}),o=be(await _e(r,s)));const d=ae({action:"START SITE BUILD",context:i,payload:{brief:a.brief??null,durationMinutes:a.durationMinutes,reset:r,summary:a.summary,tool:"start_site_build"},preview:{durationMinutes:a.durationMinutes,plan:a.summary,reset:o,summary:`${a.summary}${o?` First, permanently delete ${o.pages} page(s), ${o.posts} post(s), ${o.navigationItems} navigation item(s), ${o.media} demo image(s)${o.identityCleared?" and the NextBlock site identity":""} (kept: ${o.pagesKept.join(", ")||"nothing"}).`:""} After you confirm, Cortex applies every step of this plan without asking again, for up to ${a.durationMinutes} minutes. You can stop it at any time.`},subject:"plan"});if(d)return d;let h=null;a.brief?h=(await Y({brief:{...a.brief,status:"confirmed"},mode:"merge"},{...i})).brief:await ee(s)&&(h=(await Y({brief:{status:"confirmed"},mode:"merge"},{...i})).brief);let c=null;r&&(c=await ue(r,{...i,skipConfirmation:!0}));const g=new Date,p={actorUserId:n,createdAt:g.toISOString(),expiresAt:new Date(g.getTime()+a.durationMinutes*6e4).toISOString(),id:Ee(),summary:a.summary};return await k(s,f.CORTEX_AI_BUILD_SESSION_SETTING_KEY,p),{brief:h,buildSession:{expiresAt:p.expiresAt,id:p.id},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.",mutationExecuted:!0,resetResult:c,success:!0}}const ge=l.z.strictObject({outcome:l.z.enum(["built","stopped"]).default("built").describe('"built" when the plan was completed; "stopped" when the operator interrupted it.'),summary:l.z.string().trim().max(4e3).optional().describe("What was built, for the record.")});async function Se(t,i){const a=ge.parse(t),s=M(i),n=await re(s);await xe(s);let o=null;if(a.outcome==="built"){const r=await ee(s);r&&(o=(await Y({brief:{status:"built",...a.summary?{notes:`${r.notes?`${r.notes}
|
|
3
|
+
`:""}Built: ${a.summary}`}:{}},mode:"merge"},{...i})).brief)}return oe(i,["/cms/dashboard"]),{brief:o,buildSessionEnded:!0,hadSession:!!n,mutationExecuted:!0,outcome:a.outcome,success:!0}}function Le(t){return{get_site_overview:P.tool({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.',execute:i=>fe(i,t),inputSchema:le,strict:!0}),update_site_identity:P.tool({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.',execute:i=>he(i,t),inputSchema:de,strict:!0}),save_site_brief:P.tool({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.',execute:i=>Y(i,t),inputSchema:ne,strict:!0}),reset_site_content:P.tool({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.',execute:i=>ue(i,t),inputSchema:X,strict:!0}),start_site_build:P.tool({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.",execute:i=>ye(i,t),inputSchema:ce,strict:!0}),finish_site_build:P.tool({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.',execute:i=>Se(i,t),inputSchema:ge,strict:!0})}}exports.NEXTBLOCK_SEED_MEDIA_OBJECT_KEYS=G;exports.NEXTBLOCK_SEED_TRANSLATION_GROUP_IDS=Z;exports.createCortexSiteTools=Le;exports.executeFinishSiteBuild=Se;exports.executeGetSiteOverview=fe;exports.executeResetSiteContent=ue;exports.executeSaveSiteBrief=Y;exports.executeStartSiteBuild=ye;exports.executeUpdateSiteIdentity=he;exports.finishSiteBuildInputSchema=ge;exports.formatCortexSiteOverviewForPrompt=Ae;exports.getSiteOverviewInputSchema=le;exports.readCortexBuildSession=re;exports.readCortexSiteBrief=ee;exports.resetSiteContentInputSchema=X;exports.resolveCortexBuildSession=$e;exports.saveSiteBriefInputSchema=ne;exports.startSiteBuildInputSchema=ce;exports.updateSiteIdentityInputSchema=de;
|
|
@@ -222,8 +222,8 @@ export declare function executeGetSiteOverview(input: GetSiteOverviewInput, cont
|
|
|
222
222
|
customBlocks: number;
|
|
223
223
|
drafts: number;
|
|
224
224
|
media: number;
|
|
225
|
-
pages:
|
|
226
|
-
posts:
|
|
225
|
+
pages: any;
|
|
226
|
+
posts: any;
|
|
227
227
|
products: number;
|
|
228
228
|
};
|
|
229
229
|
customBlocks: {
|
|
@@ -326,6 +326,14 @@ export declare function executeGetSiteOverview(input: GetSiteOverviewInput, cont
|
|
|
326
326
|
slug: string;
|
|
327
327
|
}[];
|
|
328
328
|
}>;
|
|
329
|
+
export type CortexSiteOverview = Awaited<ReturnType<typeof executeGetSiteOverview>>;
|
|
330
|
+
/**
|
|
331
|
+
* Compact, prompt-ready rendering of a site overview (≈ 1200 characters at most on a
|
|
332
|
+
* typical install). The chat route fetches the overview itself when the site
|
|
333
|
+
* builder opens, so the model's first turn can be the plan instead of a tool call
|
|
334
|
+
* followed by a questionnaire. Facts only, no JSON.
|
|
335
|
+
*/
|
|
336
|
+
export declare function formatCortexSiteOverviewForPrompt(overview: CortexSiteOverview): string;
|
|
329
337
|
export declare const updateSiteIdentityInputSchema: z.ZodObject<{
|
|
330
338
|
active_logo_id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
331
339
|
footer_copyright: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
@@ -680,8 +688,8 @@ export declare function createCortexSiteTools(context?: SiteToolContext): {
|
|
|
680
688
|
customBlocks: number;
|
|
681
689
|
drafts: number;
|
|
682
690
|
media: number;
|
|
683
|
-
pages:
|
|
684
|
-
posts:
|
|
691
|
+
pages: any;
|
|
692
|
+
posts: any;
|
|
685
693
|
products: number;
|
|
686
694
|
};
|
|
687
695
|
customBlocks: {
|