@nextblock-cms/cortex 0.20.1 → 0.21.0

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.
Files changed (71) hide show
  1. package/client.cjs.js +1 -1
  2. package/client.es.js +3 -7
  3. package/index.cjs.js +1 -1
  4. package/index.es.js +24 -277
  5. package/lib/ai-block-generation.cjs.js +3 -4
  6. package/lib/ai-block-generation.es.js +143 -182
  7. package/lib/ai-client.cjs.js +1 -1
  8. package/lib/ai-client.d.ts +1 -1
  9. package/lib/ai-client.es.js +101 -135
  10. package/lib/ai-config.cjs.js +1 -1
  11. package/lib/ai-config.es.js +89 -108
  12. package/lib/ai-cortex-widget-builder.cjs.js +1 -1
  13. package/lib/ai-cortex-widget-builder.es.js +56 -70
  14. package/lib/ai-global-agent-content-ops-tools.cjs.js +1 -1
  15. package/lib/ai-global-agent-content-ops-tools.d.ts +1943 -68
  16. package/lib/ai-global-agent-content-ops-tools.es.js +185 -199
  17. package/lib/ai-global-agent-custom-block-tools.cjs.js +1 -1
  18. package/lib/ai-global-agent-custom-block-tools.d.ts +2160 -95
  19. package/lib/ai-global-agent-custom-block-tools.es.js +226 -223
  20. package/lib/ai-global-agent-db-tools.cjs.js +1 -1
  21. package/lib/ai-global-agent-db-tools.d.ts +2708 -98
  22. package/lib/ai-global-agent-db-tools.es.js +1013 -986
  23. package/lib/ai-global-agent-ecommerce.cjs.js +1 -1
  24. package/lib/ai-global-agent-ecommerce.es.js +3 -7
  25. package/lib/ai-global-agent-site-tools.cjs.js +2 -3
  26. package/lib/ai-global-agent-site-tools.d.ts +10695 -500
  27. package/lib/ai-global-agent-site-tools.es.js +750 -757
  28. package/lib/ai-global-agent-theming-tools.cjs.js +1 -3
  29. package/lib/ai-global-agent-theming-tools.d.ts +2905 -105
  30. package/lib/ai-global-agent-theming-tools.es.js +326 -396
  31. package/lib/ai-global-agent-tools.cjs.js +2 -2
  32. package/lib/ai-global-agent-tools.d.ts +4 -2230
  33. package/lib/ai-global-agent-tools.es.js +3899 -4328
  34. package/lib/ai-key-crypto.cjs.js +1 -1
  35. package/lib/ai-key-crypto.es.js +63 -82
  36. package/lib/ai-key-verification.cjs.js +1 -1
  37. package/lib/ai-key-verification.es.js +74 -63
  38. package/lib/ai-model-catalog.cjs.js +1 -1
  39. package/lib/ai-model-catalog.es.js +18 -22
  40. package/lib/ai-model-registry.cjs.js +2 -2
  41. package/lib/ai-model-registry.es.js +246 -309
  42. package/lib/ai-seo-metadata.cjs.js +2 -3
  43. package/lib/ai-seo-metadata.es.js +143 -185
  44. package/lib/ai-vision.cjs.js +2 -2
  45. package/lib/ai-vision.d.ts +6 -6
  46. package/lib/ai-vision.es.js +161 -215
  47. package/lib/block-content-schemas.cjs.js +1 -1
  48. package/lib/block-content-schemas.es.js +324 -254
  49. package/lib/cortex-widget-registry.cjs.js +1 -1
  50. package/lib/cortex-widget-registry.es.js +35 -43
  51. package/lib/cortex-widget-schema.cjs.js +2 -3
  52. package/lib/cortex-widget-schema.es.js +280 -309
  53. package/lib/editor-document-from-html.cjs.js +1 -1
  54. package/lib/editor-document-from-html.es.js +334 -278
  55. package/lib/fetch-host-guard.cjs.js +1 -0
  56. package/lib/fetch-host-guard.d.ts +2 -0
  57. package/lib/fetch-host-guard.es.js +30 -0
  58. package/lib/mcp-server.cjs.js +1 -1
  59. package/lib/mcp-server.es.js +155 -173
  60. package/lib/mcp-tokens.cjs.js +1 -1
  61. package/lib/mcp-tokens.es.js +95 -107
  62. package/lib/mcp-tool-registry.cjs.js +5 -6
  63. package/lib/mcp-tool-registry.es.js +533 -411
  64. package/lib/site-brief.cjs.js +2 -2
  65. package/lib/site-brief.es.js +102 -108
  66. package/lib/vision-image-download.cjs.js +1 -0
  67. package/lib/vision-image-download.d.ts +43 -0
  68. package/lib/vision-image-download.es.js +120 -0
  69. package/lib/zod-config.cjs.js +1 -1
  70. package/lib/zod-config.es.js +5 -6
  71. package/package.json +8 -8
@@ -1,2 +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){return!!(t&&t.business_name.trim()&&t.description?.trim())}function f(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 z(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=z;exports.isCortexBuildSessionUsable=f;exports.isCortexSiteBriefComplete=x;exports.safeParseCortexSiteBrief=_;
1
+ require("./zod-config.cjs.js");let e=require("zod");var t=`cortex_ai_site_brief`,n=`cortex_ai_build_session`,r=e.z.string().trim().min(2).max(10),i=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)}),a=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(),o=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(),s=e.z.strictObject({audience:e.z.string().trim().max(1e3).optional().describe(`Who the site is for.`),brand:a.optional(),business_name:e.z.string().trim().min(1).max(160),contact:o.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(r).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(i).max(30).default([]).describe(`The pages to build. A one-page landing site is a single "home" entry.`),primary_language:r.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()}),c=s.partial(),l=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 u(e){let t=s.safeParse(e);return t.success?t.data:null}function d(e){return!!(e&&e.business_name.trim()&&e.description?.trim())}function f(e,t){if(!e||!t.actorUserId||e.actorUserId!==t.actorUserId)return!1;let n=Date.parse(e.expiresAt),r=(t.now??new Date).getTime();return Number.isFinite(n)&&n>r}function p(e){let t=[`Business: ${e.business_name}${e.tagline?` — ${e.tagline}`:``}`];e.description&&t.push(`What they do: ${e.description}`),e.audience&&t.push(`Audience: ${e.audience}`),e.goals?.length&&t.push(`Goals: ${e.goals.join(`; `)}`),t.push(`Site type: ${e.site_type}. Languages: ${e.languages.join(`, `)} (primary ${e.primary_language}).`),e.pages.length>0&&t.push(`Pages: ${e.pages.map(e=>`${e.title} (/${e.slug})${e.purpose?` — ${e.purpose}`:``}`).join(`; `)}`);let n=e.brand;if(n&&Object.keys(n).length>0){let e=[n.primary_color?`primary ${n.primary_color}`:null,n.secondary_color?`secondary ${n.secondary_color}`:null,n.accent_color?`accent ${n.accent_color}`:null,n.color_scheme?`${n.color_scheme} scheme`:null,n.font_style?`fonts: ${n.font_style}`:null,n.tone?`tone: ${n.tone}`:null,n.style_keywords?.length?`style: ${n.style_keywords.join(`, `)}`:null,n.logo_notes?`logo: ${n.logo_notes}`:null].filter(Boolean);e.length>0&&t.push(`Brand: ${e.join(`; `)}`)}let r=e.contact;if(r&&Object.keys(r).length>0){let e=[r.email?`email ${r.email}`:null,r.phone?`phone ${r.phone}`:null,r.address?`address ${r.address}`:null,r.hours?`hours ${r.hours}`:null,r.social?.length?`social: ${r.social.map(e=>`${e.platform} ${e.url}`).join(`, `)}`:null].filter(Boolean);e.length>0&&t.push(`Contact: ${e.join(`; `)}`)}return e.ecommerce?.enabled&&t.push(`Ecommerce: yes${e.ecommerce.products_summary?` — ${e.ecommerce.products_summary}`:``}`),e.keep_existing_content&&t.push(`Keep existing content: yes (do not wipe pages or posts).`),e.notes&&t.push(`Notes: ${e.notes}`),t.push(`Brief status: ${e.status}.`),t.join(`
2
+ `)}exports.CORTEX_AI_BUILD_SESSION_SETTING_KEY=n,exports.CORTEX_AI_SITE_BRIEF_SETTING_KEY=t,exports.cortexBuildSessionSchema=l,exports.cortexSiteBriefInputSchema=c,exports.cortexSiteBriefSchema=s,exports.formatCortexSiteBriefForPrompt=p,exports.isCortexBuildSessionUsable=f,exports.isCortexSiteBriefComplete=d,exports.safeParseCortexSiteBrief=u;
@@ -1,113 +1,107 @@
1
- import "./zod-config.es.js";
2
- import { z as e } from "zod";
3
- const g = "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)
1
+ import { z as e } from "./zod-config.es.js";
2
+ //#region libs/cortex/src/lib/site-brief.ts
3
+ var t = "cortex_ai_site_brief", n = "cortex_ai_build_session", r = e.string().trim().min(2).max(10), i = 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
+ }), a = 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(), o = 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(e.strictObject({
23
+ platform: e.string().trim().min(1).max(60),
24
+ url: e.string().trim().min(1).max(2048)
25
+ })).max(12).optional()
26
+ }).partial(), s = e.strictObject({
27
+ audience: e.string().trim().max(1e3).optional().describe("Who the site is for."),
28
+ brand: a.optional(),
29
+ business_name: e.string().trim().min(1).max(160),
30
+ contact: o.optional(),
31
+ description: e.string().trim().max(3e3).optional().describe("What the business does, in the client's own words where possible."),
32
+ ecommerce: e.strictObject({
33
+ enabled: e.boolean().default(!1),
34
+ products_summary: e.string().trim().max(1e3).optional()
35
+ }).optional(),
36
+ 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\"]."),
37
+ keep_existing_content: e.boolean().default(!1).describe("true when the client wants existing pages/posts kept rather than replaced."),
38
+ languages: e.array(r).min(1).max(10).default(["en"]).describe("Locale codes the public site must offer, primary first."),
39
+ notes: e.string().trim().max(4e3).optional().describe("Anything else the client said that matters."),
40
+ pages: e.array(i).max(30).default([]).describe("The pages to build. A one-page landing site is a single \"home\" entry."),
41
+ primary_language: r.default("en"),
42
+ site_type: e.enum([
43
+ "landing-page",
44
+ "multi-page",
45
+ "blog",
46
+ "store",
47
+ "portfolio",
48
+ "other"
49
+ ]).default("multi-page"),
50
+ status: e.enum([
51
+ "draft",
52
+ "confirmed",
53
+ "built"
54
+ ]).default("draft").describe("draft while interviewing, confirmed once the client approved the plan, built when the build finished."),
55
+ tagline: e.string().trim().max(200).optional(),
56
+ updated_at: e.string().max(80).optional()
57
+ }), c = s.partial(), l = e.strictObject({
58
+ actorUserId: e.string().min(1),
59
+ createdAt: e.string().min(1),
60
+ expiresAt: e.string().min(1),
61
+ id: e.string().min(1),
62
+ summary: e.string().max(4e3)
54
63
  });
55
- function y(t) {
56
- const o = l.safeParse(t);
57
- return o.success ? o.data : null;
64
+ function u(e) {
65
+ let t = s.safeParse(e);
66
+ return t.success ? t.data : null;
58
67
  }
59
- function f(t) {
60
- return !!(t && t.business_name.trim() && t.description?.trim());
68
+ function d(e) {
69
+ return !!(e && e.business_name.trim() && e.description?.trim());
61
70
  }
62
- function $(t, o) {
63
- if (!t || !o.actorUserId || t.actorUserId !== o.actorUserId)
64
- return !1;
65
- const i = Date.parse(t.expiresAt), n = (o.now ?? /* @__PURE__ */ new Date()).getTime();
66
- return Number.isFinite(i) && i > n;
71
+ function f(e, t) {
72
+ if (!e || !t.actorUserId || e.actorUserId !== t.actorUserId) return !1;
73
+ let n = Date.parse(e.expiresAt), r = (t.now ?? /* @__PURE__ */ new Date()).getTime();
74
+ return Number.isFinite(n) && n > r;
67
75
  }
68
- function b(t) {
69
- const o = [
70
- `Business: ${t.business_name}${t.tagline ? ` — ${t.tagline}` : ""}`
71
- ];
72
- 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(
73
- `Pages: ${t.pages.map((s) => `${s.title} (/${s.slug})${s.purpose ? ` — ${s.purpose}` : ""}`).join("; ")}`
74
- );
75
- const i = t.brand;
76
- if (i && Object.keys(i).length > 0) {
77
- const s = [
78
- i.primary_color ? `primary ${i.primary_color}` : null,
79
- i.secondary_color ? `secondary ${i.secondary_color}` : null,
80
- i.accent_color ? `accent ${i.accent_color}` : null,
81
- i.color_scheme ? `${i.color_scheme} scheme` : null,
82
- i.font_style ? `fonts: ${i.font_style}` : null,
83
- i.tone ? `tone: ${i.tone}` : null,
84
- i.style_keywords?.length ? `style: ${i.style_keywords.join(", ")}` : null,
85
- i.logo_notes ? `logo: ${i.logo_notes}` : null
86
- ].filter(Boolean);
87
- s.length > 0 && o.push(`Brand: ${s.join("; ")}`);
88
- }
89
- const n = t.contact;
90
- if (n && Object.keys(n).length > 0) {
91
- const s = [
92
- n.email ? `email ${n.email}` : null,
93
- n.phone ? `phone ${n.phone}` : null,
94
- n.address ? `address ${n.address}` : null,
95
- n.hours ? `hours ${n.hours}` : null,
96
- n.social?.length ? `social: ${n.social.map((a) => `${a.platform} ${a.url}`).join(", ")}` : null
97
- ].filter(Boolean);
98
- s.length > 0 && o.push(`Contact: ${s.join("; ")}`);
99
- }
100
- 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(`
101
- `);
76
+ function p(e) {
77
+ let t = [`Business: ${e.business_name}${e.tagline ? ` — ${e.tagline}` : ""}`];
78
+ e.description && t.push(`What they do: ${e.description}`), e.audience && t.push(`Audience: ${e.audience}`), e.goals?.length && t.push(`Goals: ${e.goals.join("; ")}`), t.push(`Site type: ${e.site_type}. Languages: ${e.languages.join(", ")} (primary ${e.primary_language}).`), e.pages.length > 0 && t.push(`Pages: ${e.pages.map((e) => `${e.title} (/${e.slug})${e.purpose ? ` — ${e.purpose}` : ""}`).join("; ")}`);
79
+ let n = e.brand;
80
+ if (n && Object.keys(n).length > 0) {
81
+ let e = [
82
+ n.primary_color ? `primary ${n.primary_color}` : null,
83
+ n.secondary_color ? `secondary ${n.secondary_color}` : null,
84
+ n.accent_color ? `accent ${n.accent_color}` : null,
85
+ n.color_scheme ? `${n.color_scheme} scheme` : null,
86
+ n.font_style ? `fonts: ${n.font_style}` : null,
87
+ n.tone ? `tone: ${n.tone}` : null,
88
+ n.style_keywords?.length ? `style: ${n.style_keywords.join(", ")}` : null,
89
+ n.logo_notes ? `logo: ${n.logo_notes}` : null
90
+ ].filter(Boolean);
91
+ e.length > 0 && t.push(`Brand: ${e.join("; ")}`);
92
+ }
93
+ let r = e.contact;
94
+ if (r && Object.keys(r).length > 0) {
95
+ let e = [
96
+ r.email ? `email ${r.email}` : null,
97
+ r.phone ? `phone ${r.phone}` : null,
98
+ r.address ? `address ${r.address}` : null,
99
+ r.hours ? `hours ${r.hours}` : null,
100
+ r.social?.length ? `social: ${r.social.map((e) => `${e.platform} ${e.url}`).join(", ")}` : null
101
+ ].filter(Boolean);
102
+ e.length > 0 && t.push(`Contact: ${e.join("; ")}`);
103
+ }
104
+ return e.ecommerce?.enabled && t.push(`Ecommerce: yes${e.ecommerce.products_summary ? ` — ${e.ecommerce.products_summary}` : ""}`), e.keep_existing_content && t.push("Keep existing content: yes (do not wipe pages or posts)."), e.notes && t.push(`Notes: ${e.notes}`), t.push(`Brief status: ${e.status}.`), t.join("\n");
102
105
  }
103
- export {
104
- h as CORTEX_AI_BUILD_SESSION_SETTING_KEY,
105
- g as CORTEX_AI_SITE_BRIEF_SETTING_KEY,
106
- x as cortexBuildSessionSchema,
107
- _ as cortexSiteBriefInputSchema,
108
- l as cortexSiteBriefSchema,
109
- b as formatCortexSiteBriefForPrompt,
110
- $ as isCortexBuildSessionUsable,
111
- f as isCortexSiteBriefComplete,
112
- y as safeParseCortexSiteBrief
113
- };
106
+ //#endregion
107
+ export { n as CORTEX_AI_BUILD_SESSION_SETTING_KEY, t as CORTEX_AI_SITE_BRIEF_SETTING_KEY, l as cortexBuildSessionSchema, c as cortexSiteBriefInputSchema, s as cortexSiteBriefSchema, p as formatCortexSiteBriefForPrompt, f as isCortexBuildSessionUsable, d as isCortexSiteBriefComplete, u as safeParseCortexSiteBrief };
@@ -0,0 +1 @@
1
+ const e=require("./fetch-host-guard.cjs.js");let t=require("node:dns"),n=require("node:http"),r=require("node:https"),i=require("node:net");var a=20971520,o=5,s=2e4,c=(()=>{let e=new i.BlockList;for(let[t,n]of[[`0.0.0.0`,8],[`10.0.0.0`,8],[`100.64.0.0`,10],[`127.0.0.0`,8],[`169.254.0.0`,16],[`172.16.0.0`,12],[`192.0.0.0`,24],[`192.0.2.0`,24],[`192.168.0.0`,16],[`198.18.0.0`,15],[`198.51.100.0`,24],[`203.0.113.0`,24],[`224.0.0.0`,4],[`240.0.0.0`,4]])e.addSubnet(t,n,`ipv4`);for(let[t,n]of[[`::`,128],[`::1`,128],[`64:ff9b::`,96],[`100::`,64],[`2001:db8::`,32],[`fc00::`,7],[`fe80::`,10],[`ff00::`,8]])e.addSubnet(t,n,`ipv6`);return e})();function l(e){let t=(0,i.isIP)(e);return t===4?c.check(e,`ipv4`):t!==6||c.check(e,`ipv6`)}var u=(e,n)=>(0,t.lookup)(e,{all:!0},(e,t)=>n(e,t??[]));function d(e=u){return(t,n,r)=>{e(t,(e,i)=>{if(e){r(e,``);return}let a=i.find(e=>l(e.address));if(i.length===0||a){r(Error(a?`Refusing to download the image: ${t} resolves to a private address (${a.address}).`:`Refusing to download the image: ${t} did not resolve to any address.`),``);return}if(n.all){r(null,i);return}let[o]=i;r(null,o.address,o.family)})}}function f(e,t,i){return new Promise((a,o)=>{let s=(e.protocol===`https:`?r.request:n.request)(e,{headers:{accept:`image/*`,"user-agent":`NextBlock-Cortex-AI`},lookup:t,method:`GET`,signal:i});s.on(`response`,a),s.on(`error`,o),s.end()})}async function p(e,t){let n=[],r=0;for await(let i of e){let a=i;if(r+=a.length,r>t)throw e.destroy(),Error(`The image is larger than the ${t}-byte limit for alt-text generation.`);n.push(a)}return new Uint8Array(Buffer.concat(n))}function m(e){let t=t=>e[t];if(e.length>=8&&t(0)===137&&t(1)===80&&t(2)===78&&t(3)===71)return`image/png`;if(e.length>=3&&t(0)===255&&t(1)===216&&t(2)===255)return`image/jpeg`;if(e.length>=6&&t(0)===71&&t(1)===73&&t(2)===70&&t(3)===56)return`image/gif`;if(e.length>=12&&t(0)===82&&t(1)===73&&t(2)===70&&t(3)===70&&t(8)===87&&t(9)===69&&t(10)===66&&t(11)===80)return`image/webp`;if(e.length>=12&&t(4)===102&&t(5)===116&&t(6)===121&&t(7)===112){let t=String.fromCharCode(...e.subarray(8,12));if(t===`avif`||t===`avis`)return`image/avif`}return null}async function h(t,n={}){let r=n.lookup??d(),a=n.maxBytes??20971520,c=n.abortSignal?AbortSignal.any([n.abortSignal,AbortSignal.timeout(s)]):AbortSignal.timeout(s),u=t;for(let t=0;;t++){if(u.protocol!==`https:`&&u.protocol!==`http:`)throw Error(`Refusing to download the image: ${u.protocol} URLs are not allowed.`);let n=u.hostname.replace(/^\[|\]$/g,``);if(e.isBlockedFetchHost(n)||(0,i.isIP)(n)!==0&&l(n))throw Error(`Refusing to download the image from ${u.hostname}: private or local host.`);let s=await f(u,r,c),d=s.statusCode??0;if(d>=300&&d<400&&s.headers.location){if(s.resume(),t>=o)throw Error(`Too many redirects while downloading the image.`);u=new URL(s.headers.location,u);continue}if(d<200||d>=300)throw s.resume(),Error(`The image URL answered HTTP ${d}.`);let h=await p(s,a),[g=``]=String(s.headers[`content-type`]??``).split(`;`),_=g.trim().toLowerCase(),v=_.startsWith(`image/`)?_:m(h);if(!v)throw Error(`The URL did not return an image (Content-Type: ${_||`none`}).`);return{data:h,mediaType:v}}}exports.CORTEX_AI_VISION_MAX_IMAGE_BYTES=a,exports.createPinnedLookup=d,exports.downloadCortexVisionImage=h,exports.isPrivateAddress=l,exports.sniffImageMediaType=m;
@@ -0,0 +1,43 @@
1
+ import { LookupAddress } from 'node:dns';
2
+ import { LookupFunction } from 'node:net';
3
+ /**
4
+ * Download the image Cortex AI describes for alt text, so the AI SDK never has to.
5
+ *
6
+ * AI SDK 7 downloads a URL file part itself through a DNS-pinned undici Agent that
7
+ * @ai-sdk/provider-utils loads at runtime with `createRequire(...)('undici')`. That dynamic
8
+ * require is invisible to Next's output file tracer, so undici was missing from Vercel
9
+ * functions and from standalone builds, and every alt-text request threw there. Tracing it
10
+ * in next.config.js is not a fix for this repo: the glob that reaches the monorepo root
11
+ * breaks a scaffold's Turbopack build, and `npm run update` never rewrites an existing
12
+ * project's next.config.js. So the image is fetched here with Node's own http/https and
13
+ * handed to the model as bytes.
14
+ *
15
+ * The SSRF protection matches the SDK's: the hostname is checked first, and the DNS lookup
16
+ * is PINNED (the address that is checked is the address the socket connects to, so a
17
+ * rebinding hostname cannot swap in a private address afterwards). Redirects are followed
18
+ * by hand and every hop is checked the same way. The body is capped, and the media type
19
+ * comes from Content-Type or, when that is not an image type, from the file signature.
20
+ */
21
+ /** Largest image accepted for alt-text generation. The model request inlines it as base64. */
22
+ export declare const CORTEX_AI_VISION_MAX_IMAGE_BYTES: number;
23
+ export declare function isPrivateAddress(address: string): boolean;
24
+ type ResolveAll = (hostname: string, callback: (error: NodeJS.ErrnoException | null, addresses: LookupAddress[]) => void) => void;
25
+ /**
26
+ * A `lookup` for http(s).request that refuses any answer containing a private address.
27
+ * `resolveAll` is injectable so tests can simulate DNS without the network.
28
+ */
29
+ export declare function createPinnedLookup(resolveAll?: ResolveAll): LookupFunction;
30
+ export type DownloadedVisionImage = {
31
+ data: Uint8Array;
32
+ mediaType: string;
33
+ };
34
+ export type DownloadVisionImageOptions = {
35
+ abortSignal?: AbortSignal;
36
+ /** Override for tests. Defaults to the pinned DNS lookup. */
37
+ lookup?: LookupFunction;
38
+ maxBytes?: number;
39
+ };
40
+ /** The media type from the first bytes, for servers that send no or a generic Content-Type. */
41
+ export declare function sniffImageMediaType(bytes: Uint8Array): string | null;
42
+ export declare function downloadCortexVisionImage(url: URL, options?: DownloadVisionImageOptions): Promise<DownloadedVisionImage>;
43
+ export {};
@@ -0,0 +1,120 @@
1
+ import { isBlockedFetchHost as e } from "./fetch-host-guard.es.js";
2
+ import { lookup as t } from "node:dns";
3
+ import { request as n } from "node:http";
4
+ import { request as r } from "node:https";
5
+ import { BlockList as i, isIP as a } from "node:net";
6
+ //#region libs/cortex/src/lib/vision-image-download.ts
7
+ var o = 20971520, s = 5, c = 2e4, l = (() => {
8
+ let e = new i();
9
+ for (let [t, n] of [
10
+ ["0.0.0.0", 8],
11
+ ["10.0.0.0", 8],
12
+ ["100.64.0.0", 10],
13
+ ["127.0.0.0", 8],
14
+ ["169.254.0.0", 16],
15
+ ["172.16.0.0", 12],
16
+ ["192.0.0.0", 24],
17
+ ["192.0.2.0", 24],
18
+ ["192.168.0.0", 16],
19
+ ["198.18.0.0", 15],
20
+ ["198.51.100.0", 24],
21
+ ["203.0.113.0", 24],
22
+ ["224.0.0.0", 4],
23
+ ["240.0.0.0", 4]
24
+ ]) e.addSubnet(t, n, "ipv4");
25
+ for (let [t, n] of [
26
+ ["::", 128],
27
+ ["::1", 128],
28
+ ["64:ff9b::", 96],
29
+ ["100::", 64],
30
+ ["2001:db8::", 32],
31
+ ["fc00::", 7],
32
+ ["fe80::", 10],
33
+ ["ff00::", 8]
34
+ ]) e.addSubnet(t, n, "ipv6");
35
+ return e;
36
+ })();
37
+ function u(e) {
38
+ let t = a(e);
39
+ return t === 4 ? l.check(e, "ipv4") : t !== 6 || l.check(e, "ipv6");
40
+ }
41
+ var d = (e, n) => t(e, { all: !0 }, (e, t) => n(e, t ?? []));
42
+ function f(e = d) {
43
+ return (t, n, r) => {
44
+ e(t, (e, i) => {
45
+ if (e) {
46
+ r(e, "");
47
+ return;
48
+ }
49
+ let a = i.find((e) => u(e.address));
50
+ if (i.length === 0 || a) {
51
+ r(/* @__PURE__ */ Error(a ? `Refusing to download the image: ${t} resolves to a private address (${a.address}).` : `Refusing to download the image: ${t} did not resolve to any address.`), "");
52
+ return;
53
+ }
54
+ if (n.all) {
55
+ r(null, i);
56
+ return;
57
+ }
58
+ let [o] = i;
59
+ r(null, o.address, o.family);
60
+ });
61
+ };
62
+ }
63
+ function p(e, t, i) {
64
+ return new Promise((a, o) => {
65
+ let s = (e.protocol === "https:" ? r : n)(e, {
66
+ headers: {
67
+ accept: "image/*",
68
+ "user-agent": "NextBlock-Cortex-AI"
69
+ },
70
+ lookup: t,
71
+ method: "GET",
72
+ signal: i
73
+ });
74
+ s.on("response", a), s.on("error", o), s.end();
75
+ });
76
+ }
77
+ async function m(e, t) {
78
+ let n = [], r = 0;
79
+ for await (let i of e) {
80
+ let a = i;
81
+ if (r += a.length, r > t) throw e.destroy(), Error(`The image is larger than the ${t}-byte limit for alt-text generation.`);
82
+ n.push(a);
83
+ }
84
+ return new Uint8Array(Buffer.concat(n));
85
+ }
86
+ function h(e) {
87
+ let t = (t) => e[t];
88
+ if (e.length >= 8 && t(0) === 137 && t(1) === 80 && t(2) === 78 && t(3) === 71) return "image/png";
89
+ if (e.length >= 3 && t(0) === 255 && t(1) === 216 && t(2) === 255) return "image/jpeg";
90
+ if (e.length >= 6 && t(0) === 71 && t(1) === 73 && t(2) === 70 && t(3) === 56) return "image/gif";
91
+ if (e.length >= 12 && t(0) === 82 && t(1) === 73 && t(2) === 70 && t(3) === 70 && t(8) === 87 && t(9) === 69 && t(10) === 66 && t(11) === 80) return "image/webp";
92
+ if (e.length >= 12 && t(4) === 102 && t(5) === 116 && t(6) === 121 && t(7) === 112) {
93
+ let t = String.fromCharCode(...e.subarray(8, 12));
94
+ if (t === "avif" || t === "avis") return "image/avif";
95
+ }
96
+ return null;
97
+ }
98
+ async function g(t, n = {}) {
99
+ let r = n.lookup ?? f(), i = n.maxBytes ?? 20971520, o = n.abortSignal ? AbortSignal.any([n.abortSignal, AbortSignal.timeout(c)]) : AbortSignal.timeout(c), l = t;
100
+ for (let t = 0;; t++) {
101
+ if (l.protocol !== "https:" && l.protocol !== "http:") throw Error(`Refusing to download the image: ${l.protocol} URLs are not allowed.`);
102
+ let n = l.hostname.replace(/^\[|\]$/g, "");
103
+ if (e(n) || a(n) !== 0 && u(n)) throw Error(`Refusing to download the image from ${l.hostname}: private or local host.`);
104
+ let c = await p(l, r, o), d = c.statusCode ?? 0;
105
+ if (d >= 300 && d < 400 && c.headers.location) {
106
+ if (c.resume(), t >= s) throw Error("Too many redirects while downloading the image.");
107
+ l = new URL(c.headers.location, l);
108
+ continue;
109
+ }
110
+ if (d < 200 || d >= 300) throw c.resume(), Error(`The image URL answered HTTP ${d}.`);
111
+ let f = await m(c, i), [g = ""] = String(c.headers["content-type"] ?? "").split(";"), _ = g.trim().toLowerCase(), v = _.startsWith("image/") ? _ : h(f);
112
+ if (!v) throw Error(`The URL did not return an image (Content-Type: ${_ || "none"}).`);
113
+ return {
114
+ data: f,
115
+ mediaType: v
116
+ };
117
+ }
118
+ }
119
+ //#endregion
120
+ export { o as CORTEX_AI_VISION_MAX_IMAGE_BYTES, f as createPinnedLookup, g as downloadCortexVisionImage, u as isPrivateAddress, h as sniffImageMediaType };
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("zod");e.z.config({jitless:!0});Object.defineProperty(exports,"z",{enumerable:!0,get:()=>e.z});
1
+ let e=require("zod");e.z.config({jitless:!0}),exports.z=e.z;
@@ -1,6 +1,5 @@
1
- import { z as o } from "zod";
2
- import { z as f } from "zod";
3
- o.config({ jitless: !0 });
4
- export {
5
- f as z
6
- };
1
+ import { z as e } from "zod";
2
+ //#region libs/cortex/src/lib/zod-config.ts
3
+ e.config({ jitless: !0 });
4
+ //#endregion
5
+ export { e as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextblock-cms/cortex",
3
- "version": "0.20.1",
3
+ "version": "0.21.0",
4
4
  "license": "AGPL-3.0-or-later",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -27,14 +27,14 @@
27
27
  "./package.json": "./package.json"
28
28
  },
29
29
  "dependencies": {
30
- "@ai-sdk/openai-compatible": "^2.0.42",
31
- "@nextblock-cms/db": "^0.20.1",
32
- "@nextblock-cms/ecommerce": "npm:@nextblock-cms/ecom@^0.20.1",
33
- "@nextblock-cms/utils": "^0.20.1",
34
- "ai": "^6.0.170",
35
- "zod": "^4.3.6"
30
+ "@ai-sdk/openai-compatible": "^3.0.53",
31
+ "@nextblock-cms/db": "^0.21.0",
32
+ "@nextblock-cms/ecommerce": "npm:@nextblock-cms/ecom@^0.21.0",
33
+ "@nextblock-cms/utils": "^0.21.0",
34
+ "ai": "^7.0.107",
35
+ "zod": "^4.6.5"
36
36
  },
37
37
  "peerDependencies": {
38
- "next": "^16.2.7"
38
+ "next": "^16.3.5"
39
39
  }
40
40
  }