@sonordev/site-kit 8.2.0 → 8.4.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 (198) hide show
  1. package/AGENTS.md +13 -1
  2. package/CHANGELOG.md +12 -0
  3. package/README.md +9 -5
  4. package/agent-manifest.json +53 -8
  5. package/dist/{AnalyticsProvider-6TAA3XYB.js → AnalyticsProvider-D6GWEJSB.js} +6 -6
  6. package/dist/{ArticleFaqSection-3Q26DP7P.js → ArticleFaqSection-DEWE5B4Q.js} +1 -1
  7. package/dist/{ArticleViewTracker-H4N7YCLC.js → ArticleViewTracker-YZ76HM64.js} +17 -6
  8. package/dist/{BlocksPopup-FJT5YQD3.js → BlocksPopup-UJXZ4AXG.js} +6 -6
  9. package/dist/{ChatWidget-THQHHTAA.js → ChatWidget-H4EOSH5I.js} +6 -6
  10. package/dist/{FileField-OZXXJGUM.js → FileField-CKBYGCNT.js} +4 -4
  11. package/dist/{FormSpotlight-MLDJS246.js → FormSpotlight-CGF5ZE6S.js} +1 -1
  12. package/dist/{FormStage-6RVY7M3R.js → FormStage-PQPUPQWW.js} +1 -1
  13. package/dist/{ManagedForm-3VV54RVH.js → ManagedForm-YOEZ7CFS.js} +7 -7
  14. package/dist/{ManagedNewsletterForm-RTJDGMGG.js → ManagedNewsletterForm-EUWJ2HCA.js} +25 -11
  15. package/dist/{SignalCore-T7ZOCA44.js → SignalCore-JZSU7A44.js} +14 -8
  16. package/dist/SiteChat-OAZH34W7.js +6 -0
  17. package/dist/SiteDesignReporter-IWBMZJYU.js +12 -0
  18. package/dist/{SitePopups-K6ADAWFK.js → SitePopups-MLAT35UN.js} +6 -6
  19. package/dist/SitemapSync-6KQLKFO2.js +9 -0
  20. package/dist/_client/booking-widget.js +6 -6
  21. package/dist/_client/testimonial-section.js +3 -3
  22. package/dist/affiliates/index.js +4 -4
  23. package/dist/analytics/index.js +6 -6
  24. package/dist/analytics/publication-cta.d.ts +3 -0
  25. package/dist/{article-styles-GFXJJDLE.js → article-styles-AOOVVKXH.js} +7 -5
  26. package/dist/article-tables-6XHVULPU.js +2 -0
  27. package/dist/articles/Article.d.ts +2 -89
  28. package/dist/articles/ArticleFAQ.d.ts +2 -41
  29. package/dist/articles/ArticleFaqSection.d.ts +2 -2
  30. package/dist/articles/ArticleList.d.ts +2 -79
  31. package/dist/articles/ArticleViewTracker.d.ts +2 -15
  32. package/dist/articles/AuthorCard.d.ts +2 -10
  33. package/dist/articles/AuthorPage.d.ts +2 -8
  34. package/dist/articles/ClusterLandingPage.d.ts +2 -40
  35. package/dist/articles/ClusterNavigation.d.ts +2 -37
  36. package/dist/articles/ManagedNewsletterForm.d.ts +3 -12
  37. package/dist/articles/NewsletterShell.d.ts +2 -17
  38. package/dist/articles/NewsletterWidget.d.ts +2 -30
  39. package/dist/articles/PublicationLayout.d.ts +2 -67
  40. package/dist/articles/PublicationSidebar.d.ts +2 -36
  41. package/dist/articles/RelatedPosts.d.ts +2 -22
  42. package/dist/articles/ServiceCallout.d.ts +2 -56
  43. package/dist/articles/TableOfContents.d.ts +2 -6
  44. package/dist/articles/article-photos.d.ts +2 -9
  45. package/dist/articles/article-styles.d.ts +2 -21
  46. package/dist/articles/article-tables.d.ts +2 -12
  47. package/dist/articles/artwork.d.ts +2 -13
  48. package/dist/articles/author-schema.d.ts +2 -72
  49. package/dist/articles/author-social.d.ts +2 -14
  50. package/dist/articles/excerpt.d.ts +2 -17
  51. package/dist/articles/index.d.ts +2 -79
  52. package/dist/articles/index.js +16 -11
  53. package/dist/articles/news-sitemap.d.ts +2 -46
  54. package/dist/articles/processArticleHtml.d.ts +2 -34
  55. package/dist/articles/public-reads.d.ts +2 -26
  56. package/dist/articles/reading-time.d.ts +2 -9
  57. package/dist/articles/routes.d.ts +2 -52
  58. package/dist/articles/server-core.d.ts +2 -461
  59. package/dist/articles/server-ui.d.ts +2 -30
  60. package/dist/articles/server-ui.js +72 -62
  61. package/dist/articles/server.d.ts +2 -461
  62. package/dist/articles/server.js +14 -5
  63. package/dist/articles/types.d.ts +2 -316
  64. package/dist/articles/widget-styles.d.ts +2 -8
  65. package/dist/chat/index.js +8 -8
  66. package/dist/chunk-34ZYJTQR.js +1 -0
  67. package/dist/{chunk-727R7C5R.js → chunk-3VSFOD2Z.js} +2 -2
  68. package/dist/{chunk-JC2VS2SB.js → chunk-44GVU5UH.js} +1 -1
  69. package/dist/{chunk-WYZ4OUON.js → chunk-4HYIDQJJ.js} +1 -1
  70. package/dist/{chunk-WMVK77ND.js → chunk-4K735O3J.js} +1 -1
  71. package/dist/{chunk-L5UOHS6E.js → chunk-5D7ZISUP.js} +1 -1
  72. package/dist/{chunk-Q72M5KBS.js → chunk-5S7YTOCB.js} +1 -1
  73. package/dist/{chunk-WSNG2K3T.js → chunk-5TDZHIG6.js} +3 -3
  74. package/dist/{chunk-6CJGZFOU.js → chunk-A4MPHIVP.js} +1 -1
  75. package/dist/chunk-B7T2SFUF.js +1 -0
  76. package/dist/{chunk-FV2VD7WM.js → chunk-BC7JMY4Z.js} +3 -3
  77. package/dist/chunk-BLCHQIWR.js +319 -0
  78. package/dist/{chunk-NNEIBXGU.js → chunk-BLENFNBW.js} +2 -2
  79. package/dist/{chunk-NFRJTOTB.js → chunk-BSQP6PRA.js} +1 -1
  80. package/dist/{chunk-GKGSXUVQ.js → chunk-BVB5FBAA.js} +1 -1
  81. package/dist/{chunk-WACWV5EP.js → chunk-CE7VPAEH.js} +3 -3
  82. package/dist/{chunk-GYF2ULVO.js → chunk-DJAYFHVF.js} +1 -1
  83. package/dist/{chunk-WBC5B3JD.js → chunk-DKKYZLKR.js} +3 -3
  84. package/dist/{chunk-CAH4Y4PY.js → chunk-DLM4FFNH.js} +6 -2
  85. package/dist/{chunk-575L73NT.js → chunk-DS6LECIX.js} +1 -1
  86. package/dist/{chunk-Y3INC4QJ.js → chunk-E4PTMORJ.js} +1 -1
  87. package/dist/chunk-H72ZFO3F.js +1 -0
  88. package/dist/{chunk-WK3N3Y7V.js → chunk-HH2GOZTO.js} +2 -2
  89. package/dist/{chunk-JBEUZJS3.js → chunk-ITY4UZEJ.js} +3 -3
  90. package/dist/{chunk-GWP2VBFH.js → chunk-J4KX2D3Q.js} +3 -3
  91. package/dist/{chunk-57K4GW3D.js → chunk-JCSU6EJI.js} +1 -1
  92. package/dist/{chunk-SC4MZGWZ.js → chunk-JHTPHA2D.js} +6 -3
  93. package/dist/{chunk-YBAFUM3Z.js → chunk-JV5YWTJN.js} +13 -4
  94. package/dist/{chunk-I6QFPSUU.js → chunk-KASKB7UK.js} +11 -10
  95. package/dist/{chunk-SMYX3AAO.js → chunk-LEGMPX6N.js} +1 -1
  96. package/dist/{chunk-ZM2FNWIQ.js → chunk-LPVEBQVL.js} +1 -1
  97. package/dist/{chunk-HUBMKTPW.js → chunk-M5SF7NCC.js} +1 -1
  98. package/dist/{chunk-EBILJHUF.js → chunk-MTV5LYXN.js} +31 -2
  99. package/dist/{chunk-5ZCY36TY.js → chunk-MZX4EN6Z.js} +1 -1
  100. package/dist/{chunk-7FSB2EXS.js → chunk-OKITZFML.js} +1 -1
  101. package/dist/{chunk-H4R2GPAB.js → chunk-PEI6DRSK.js} +2 -2
  102. package/dist/{chunk-FVXFLVXF.js → chunk-QD73RO67.js} +3 -3
  103. package/dist/{chunk-66G47P5C.js → chunk-QKOVP7WO.js} +2 -2
  104. package/dist/{chunk-3EU6S5YC.js → chunk-RBNHRZGD.js} +1 -1
  105. package/dist/{chunk-XWIRU6UT.js → chunk-S6QDNXDK.js} +2 -2
  106. package/dist/{chunk-FQUGGPBM.js → chunk-TY6WNQO3.js} +124 -41
  107. package/dist/{chunk-QXMIKDOL.js → chunk-UVYUTBT4.js} +14 -4
  108. package/dist/{chunk-3G7YABRA.js → chunk-W7OPEMGJ.js} +4 -1
  109. package/dist/{chunk-JIH32RH2.js → chunk-XA7Q2VQA.js} +1 -1
  110. package/dist/{chunk-X7VKRHFF.js → chunk-XENW66OF.js} +1 -1
  111. package/dist/chunk-XI2XM7UG.js +14 -0
  112. package/dist/{chunk-BQZPHICL.js → chunk-YM5KMNTU.js} +1 -1
  113. package/dist/client/index.js +4 -16
  114. package/dist/fleet/index.js +6 -6
  115. package/dist/forms/index.js +9 -325
  116. package/dist/forms/server.js +2 -2
  117. package/dist/forms/types.d.ts +5 -0
  118. package/dist/forms/useForm.d.ts +4 -0
  119. package/dist/images/index.js +4 -4
  120. package/dist/index.js +1 -1
  121. package/dist/layout/client.js +9 -9
  122. package/dist/layout/index.js +10 -10
  123. package/dist/llms/index.js +8 -8
  124. package/dist/maps/index.js +4 -4
  125. package/dist/mcp/sonor.d.ts +46 -3
  126. package/dist/mcp/sonor.js +51 -29
  127. package/dist/mcp/transport.d.ts +1 -1
  128. package/dist/press-kit/articles/Article.d.ts +89 -0
  129. package/dist/press-kit/articles/ArticleFAQ.d.ts +41 -0
  130. package/dist/press-kit/articles/ArticleFaqSection.d.ts +2 -0
  131. package/dist/press-kit/articles/ArticleList.d.ts +79 -0
  132. package/dist/press-kit/articles/ArticleViewTracker.d.ts +15 -0
  133. package/dist/press-kit/articles/AuthorCard.d.ts +10 -0
  134. package/dist/press-kit/articles/AuthorPage.d.ts +8 -0
  135. package/dist/press-kit/articles/ClusterLandingPage.d.ts +40 -0
  136. package/dist/press-kit/articles/ClusterNavigation.d.ts +37 -0
  137. package/dist/press-kit/articles/ManagedNewsletterForm.d.ts +12 -0
  138. package/dist/press-kit/articles/NewsletterShell.d.ts +17 -0
  139. package/dist/press-kit/articles/NewsletterWidget.d.ts +30 -0
  140. package/dist/press-kit/articles/PublicationLayout.d.ts +67 -0
  141. package/dist/press-kit/articles/PublicationSidebar.d.ts +36 -0
  142. package/dist/press-kit/articles/RelatedPosts.d.ts +22 -0
  143. package/dist/press-kit/articles/ServiceCallout.d.ts +56 -0
  144. package/dist/press-kit/articles/TableOfContents.d.ts +6 -0
  145. package/dist/press-kit/articles/article-photos.d.ts +9 -0
  146. package/dist/press-kit/articles/article-styles.d.ts +21 -0
  147. package/dist/press-kit/articles/article-tables.d.ts +12 -0
  148. package/dist/press-kit/articles/artwork.d.ts +16 -0
  149. package/dist/press-kit/articles/author-schema.d.ts +72 -0
  150. package/dist/press-kit/articles/author-social.d.ts +14 -0
  151. package/dist/press-kit/articles/excerpt.d.ts +17 -0
  152. package/dist/press-kit/articles/index.d.ts +80 -0
  153. package/dist/press-kit/articles/news-sitemap.d.ts +40 -0
  154. package/dist/press-kit/articles/processArticleHtml.d.ts +34 -0
  155. package/dist/press-kit/articles/public-reads.d.ts +42 -0
  156. package/dist/press-kit/articles/publication-definition.d.ts +41 -0
  157. package/dist/press-kit/articles/publication-reads.d.ts +54 -0
  158. package/dist/press-kit/articles/reading-time.d.ts +9 -0
  159. package/dist/press-kit/articles/routes.d.ts +52 -0
  160. package/dist/press-kit/articles/server-core.d.ts +479 -0
  161. package/dist/press-kit/articles/server-ui.d.ts +31 -0
  162. package/dist/press-kit/articles/types.d.ts +339 -0
  163. package/dist/press-kit/articles/widget-styles.d.ts +8 -0
  164. package/dist/reputation/index.js +3 -3
  165. package/dist/reputation/server.js +2 -2
  166. package/dist/revalidate/index.js +2 -2
  167. package/dist/runtime/index.js +5 -4
  168. package/dist/seo/client.js +6 -6
  169. package/dist/seo/index.js +7 -7
  170. package/dist/seo/llms.js +8 -8
  171. package/dist/seo/register-sitemap-cli.js +1 -1
  172. package/dist/seo/sitemap.js +6 -6
  173. package/dist/server/index.js +3 -2
  174. package/dist/shared/build-entries.d.ts +2 -1
  175. package/dist/shared/clientApiConfig.d.ts +2 -0
  176. package/dist/shared/import-specifiers.d.ts +1 -1
  177. package/dist/shared/index.d.ts +6 -0
  178. package/dist/shared/index.js +8 -0
  179. package/dist/shared/reporting-gate.d.ts +2 -0
  180. package/dist/shared/version.d.ts +1 -1
  181. package/dist/signal/index.js +2 -2
  182. package/dist/sitemap/index.js +6 -6
  183. package/dist/sync/index.js +6 -6
  184. package/dist/website/images.js +4 -4
  185. package/dist/website/index.js +7 -7
  186. package/dist/website/popups.js +6 -6
  187. package/dist/{writeLLMsTxt-DGKLYXIW.js → writeLLMsTxt-N4Y4YC2H.js} +5 -5
  188. package/docs/MIGRATING-TO-PRESS-KIT.md +83 -0
  189. package/docs.json +145 -29
  190. package/package.json +15 -5
  191. package/src/articles/README.md +5 -360
  192. package/src/mcp/README.md +16 -0
  193. package/src/og/README.md +1 -1
  194. package/src/runtime/README.md +4 -0
  195. package/dist/SiteChat-JSHPGAE3.js +0 -6
  196. package/dist/SiteDesignReporter-L2JBAKKK.js +0 -12
  197. package/dist/SitemapSync-VV2YSMIW.js +0 -9
  198. package/dist/article-tables-AXSZOGNW.js +0 -2
package/dist/mcp/sonor.js CHANGED
@@ -1,9 +1,9 @@
1
- import { fetchReviews, fetchReviewStats } from '../chunk-NFRJTOTB.js';
1
+ import { fetchReviews, fetchReviewStats } from '../chunk-BSQP6PRA.js';
2
2
  import { toolErrorResult } from '../chunk-FYAB6CXS.js';
3
- import { fetchBookingTypes, fetchAvailability } from '../chunk-XWIRU6UT.js';
4
- import { getLLMsData, getServices, getFAQItems, getPageSummaries } from '../chunk-JIH32RH2.js';
5
- import { getFormConfig } from '../chunk-BQZPHICL.js';
6
- import '../chunk-SMYX3AAO.js';
3
+ import { fetchBookingTypes, fetchAvailability } from '../chunk-S6QDNXDK.js';
4
+ import { getLLMsData, getServices, getFAQItems, getPageSummaries } from '../chunk-XA7Q2VQA.js';
5
+ import { getFormConfig } from '../chunk-YM5KMNTU.js';
6
+ import '../chunk-LEGMPX6N.js';
7
7
  import '../chunk-C2FZBUSS.js';
8
8
  import '../chunk-YC7ELZS3.js';
9
9
  import '../chunk-LNSF6BJC.js';
@@ -12,23 +12,18 @@ import '../chunk-TYAVHRWH.js';
12
12
  import '../chunk-5SQK3D53.js';
13
13
  import '../chunk-HW7B43E5.js';
14
14
  import '../chunk-NJCTQH2P.js';
15
- import { getAllArticles, getArticle } from '../chunk-FQUGGPBM.js';
16
- import '../chunk-HUBMKTPW.js';
17
- import '../chunk-Y3INC4QJ.js';
18
- import '../chunk-QXMIKDOL.js';
19
- import '../chunk-WMVK77ND.js';
15
+ import { resolveArticleUrl } from '../chunk-4K735O3J.js';
20
16
  import { resolveFetch } from '../chunk-UAYAJPEP.js';
21
17
  import '../chunk-NZ4RXN3G.js';
22
- import '../chunk-VWWITFWM.js';
23
18
  import '../chunk-MJP63SSS.js';
24
19
  import { resolveServerApiConfig } from '../chunk-C7TLGL3R.js';
25
20
  import '../chunk-MH2DSGC6.js';
26
21
  import { resolveSiteHost } from '../chunk-FESKKJAS.js';
27
22
  import '../chunk-A4CC4ZG3.js';
28
- import '../chunk-3G7YABRA.js';
29
- import '../chunk-727R7C5R.js';
30
- import '../chunk-3EU6S5YC.js';
31
- import { SITE_KIT_VERSION } from '../chunk-Q72M5KBS.js';
23
+ import '../chunk-3VSFOD2Z.js';
24
+ import '../chunk-RBNHRZGD.js';
25
+ import '../chunk-W7OPEMGJ.js';
26
+ import { SITE_KIT_VERSION } from '../chunk-5S7YTOCB.js';
32
27
  import '../chunk-PKBMQBKP.js';
33
28
  import 'server-only';
34
29
  import { after } from 'next/server.js';
@@ -74,6 +69,10 @@ function reportToolCallsToSonor(options = {}) {
74
69
  }
75
70
 
76
71
  // src/mcp/sonor.ts
72
+ var legacyArticleReaders = {
73
+ readMany: async (options) => (await import('../articles/server.js')).getAllArticles(options),
74
+ readOne: async (slug, options) => (await import('../articles/server.js')).getArticle(slug, options)
75
+ };
77
76
  var SEARCH_LIMIT_MAX = 20;
78
77
  var ARTICLE_CONTENT_MAX = 12e3;
79
78
  var REPORTED_BY = "from the business\u2019s Sonor workspace";
@@ -199,55 +198,71 @@ function findPages(ctx) {
199
198
  }
200
199
  };
201
200
  }
202
- function listArticles(ctx, basePath) {
201
+ function articleUrl(ctx, basePath, post) {
202
+ const articles = ctx.options.articles;
203
+ return resolveArticleUrl({ slug: post.slug, canonical_url: post.canonical_url, category: post.category }, {
204
+ ...articles && articles.routing,
205
+ basePath,
206
+ siteUrl: (linkTo(ctx, "/") || "").replace(/\/$/, "")
207
+ });
208
+ }
209
+ function listArticles(ctx, basePath, readers) {
203
210
  return {
204
211
  name: "list_articles",
205
212
  title: "List articles",
206
213
  description: `${ctx.options.businessName}'s published articles, newest first, optionally matching a topic. Call get_article for the full text.`,
207
- inputSchema: { type: "object", properties: { query: QUERY, limit: LIMIT }, additionalProperties: false },
214
+ inputSchema: { type: "object", properties: { query: QUERY, limit: LIMIT, offset: { type: "integer", minimum: 0, description: "Start at this result offset when browsing the archive." } }, additionalProperties: false },
208
215
  annotations: READ,
209
216
  handler: async (args) => {
210
- const posts = await getAllArticles({ maxPosts: 100, site: ctx.site });
211
- const picked = rankByQuery(posts, text(args.query, 200), (p) => [p.title, [p.excerpt, nameOf(p.category)].filter(Boolean).join(" ")], limitOf(args.limit, 5));
217
+ const posts = await readers.readMany({ maxPosts: Infinity, site: ctx.site });
218
+ const picked = rankByQuery(posts, text(args.query, 200), (p) => [p.title, [p.excerpt, nameOf(p.category), p.content_html || p.content].filter(Boolean).join(" ")], posts.length);
219
+ const offset = Math.max(0, Math.floor(Number(args.offset) || 0));
220
+ const limit = limitOf(args.limit, 5);
212
221
  return {
213
- articles: picked.map((p) => ({
222
+ total: picked.length,
223
+ next_offset: offset + limit < picked.length ? offset + limit : null,
224
+ articles: picked.slice(offset, offset + limit).map((p) => ({
214
225
  slug: p.slug,
215
226
  title: p.title,
216
227
  excerpt: p.excerpt,
217
228
  category: nameOf(p.category),
218
229
  published_at: p.published_at,
219
- url: linkTo(ctx, `${basePath}/${p.slug}`)
230
+ url: articleUrl(ctx, basePath, p)
220
231
  }))
221
232
  };
222
233
  }
223
234
  };
224
235
  }
225
- function getArticleTool(ctx, basePath) {
236
+ function getArticleTool(ctx, basePath, readers) {
226
237
  return {
227
238
  name: "get_article",
228
239
  title: "Read an article",
229
240
  description: "One article in full, with approved supporting photo descriptions and section placements, by the slug list_articles returned. Cite it by its URL. Photo captions describe context; they do not verify claims beyond the article.",
230
241
  inputSchema: {
231
242
  type: "object",
232
- properties: { slug: { type: "string", description: "The article slug from list_articles." } },
243
+ properties: { slug: { type: "string", description: "The article slug from list_articles." }, offset: { type: "integer", minimum: 0, description: "Character offset. Follow next_offset until null to read the entire article." } },
233
244
  required: ["slug"],
234
245
  additionalProperties: false
235
246
  },
236
247
  annotations: READ,
237
248
  handler: async (args) => {
238
249
  const slug = text(args.slug, 200).replace(/^\/+|\/+$/g, "").split("/").pop() ?? "";
239
- const post = slug ? await getArticle(slug, { site: ctx.site }) : null;
250
+ const post = slug ? await readers.readOne(slug, { site: ctx.site }) : null;
240
251
  if (!post) return { found: false, note: `No published article with the slug "${slug}". Call list_articles for the slugs that exist.` };
241
- const content = post.content ?? "";
252
+ const content = post.content_html || post.content || "";
253
+ const offset = Math.max(0, Math.floor(Number(args.offset) || 0));
242
254
  return {
243
255
  found: true,
244
256
  title: post.title,
245
- url: linkTo(ctx, `${basePath}/${post.slug}`),
257
+ url: articleUrl(ctx, basePath, post),
246
258
  author: nameOf(post.author),
247
259
  category: nameOf(post.category),
248
260
  published_at: post.published_at,
249
- content: content.slice(0, ARTICLE_CONTENT_MAX),
250
- truncated: content.length > ARTICLE_CONTENT_MAX,
261
+ content: content.slice(offset, offset + ARTICLE_CONTENT_MAX),
262
+ offset,
263
+ total_length: content.length,
264
+ next_offset: offset + ARTICLE_CONTENT_MAX < content.length ? offset + ARTICLE_CONTENT_MAX : null,
265
+ truncated: offset + ARTICLE_CONTENT_MAX < content.length,
251
266
  faq: post.faq_items,
252
267
  editorial_photos: post.editorial_photos?.slice(0, 8).map(({ url, alt, caption, width, height, placement }) => ({ url, alt, caption, width, height, placement }))
253
268
  };
@@ -445,6 +460,13 @@ function sendInquiry(ctx, form, purpose) {
445
460
  }
446
461
  };
447
462
  }
463
+ function articleReaders(articles) {
464
+ if (!articles || articles.readMany === void 0 && articles.readOne === void 0) return legacyArticleReaders;
465
+ if (typeof articles.readMany !== "function" || typeof articles.readOne !== "function") {
466
+ throw new Error("sonorMcpServer: pass both articles.readMany and articles.readOne: getAllArticles and getArticle from '@sonordev/press-kit/articles/server'.");
467
+ }
468
+ return { readMany: articles.readMany, readOne: articles.readOne };
469
+ }
448
470
  function offeringsReader(offerings) {
449
471
  if (typeof offerings.read !== "function") {
450
472
  throw new Error(
@@ -462,7 +484,7 @@ function sonorMcpTools(options) {
462
484
  listServices(ctx),
463
485
  searchFaq(ctx),
464
486
  findPages(ctx),
465
- ...articlesPath !== null ? [listArticles(ctx, articlesPath), getArticleTool(ctx, articlesPath)] : [],
487
+ ...articlesPath !== null ? [listArticles(ctx, articlesPath, articleReaders(options.articles || void 0)), getArticleTool(ctx, articlesPath, articleReaders(options.articles || void 0))] : [],
466
488
  ...options.reviews === false ? [] : [getReviews(ctx)],
467
489
  ...options.offerings ? [listOfferings(ctx, (options.offerings.path ?? "/shop").replace(/\/+$/, ""), offeringsReader(options.offerings))] : [],
468
490
  ...options.booking ? [checkAvailability(ctx, options.booking.path)] : [],
@@ -126,7 +126,7 @@ export declare function protectMcpHandlers(handlers: McpRouteHandlerSet, options
126
126
  * @example
127
127
  * ```ts
128
128
  * // app/api/mcp-internal/route.ts
129
- * import * as mcp from '../mcp/route'
129
+ * import * as mcp from './route'
130
130
  * export const { POST, GET, DELETE, OPTIONS } = createMcpInternalRoute(mcp)
131
131
  * ```
132
132
  */
@@ -0,0 +1,89 @@
1
+ /**
2
+ * @sonordev/press-kit/articles - Article Component
3
+ *
4
+ * Fetches and displays a single article with full content, table of contents,
5
+ * author info, related posts, and SEO metadata support.
6
+ *
7
+ * Ships with world-class default styling via scoped `.sk-article-*` classes.
8
+ * No configuration needed — just `<Article slug={slug} />`.
9
+ *
10
+ * Override with `styles` prop (per-element class names) or `unstyled` for
11
+ * complete control. The `children` render prop gives full data access.
12
+ */
13
+ import React from 'react';
14
+ import type { Article as ArticleType, TocItem } from './types';
15
+ import { type PublicationRoutingOptions } from './routes';
16
+ export interface ArticleStyles {
17
+ article?: string;
18
+ header?: string;
19
+ breadcrumb?: string;
20
+ breadcrumbLink?: string;
21
+ title?: string;
22
+ subtitle?: string;
23
+ meta?: string;
24
+ metaItem?: string;
25
+ featuredImageWrapper?: string;
26
+ featuredImage?: string;
27
+ contentLayout?: string;
28
+ content?: string;
29
+ tocSidebar?: string;
30
+ tocTitle?: string;
31
+ tocList?: string;
32
+ tocItem?: string;
33
+ tocLink?: string;
34
+ tags?: string;
35
+ tag?: string;
36
+ faqSection?: string;
37
+ faqTitle?: string;
38
+ faqItem?: string;
39
+ faqQuestion?: string;
40
+ faqAnswer?: string;
41
+ authorCard?: string;
42
+ relatedSection?: string;
43
+ relatedTitle?: string;
44
+ relatedGrid?: string;
45
+ relatedCard?: string;
46
+ notFound?: string;
47
+ }
48
+ export interface ArticleServerProps {
49
+ apiUrl?: string;
50
+ apiKey?: string;
51
+ slug: string;
52
+ showToc?: boolean;
53
+ showRelated?: boolean;
54
+ relatedCount?: number;
55
+ showAuthor?: boolean;
56
+ basePath?: string;
57
+ routing?: PublicationRoutingOptions;
58
+ /** The publication's name in the breadcrumb (e.g. "The Forge"). Default "Articles". */
59
+ publicationName?: string;
60
+ className?: string;
61
+ styles?: ArticleStyles;
62
+ unstyled?: boolean;
63
+ siteUrl?: string;
64
+ /**
65
+ * Sub-site host for multi-site projects (e.g. `charlotte.example.com`). Scopes
66
+ * the post, related-posts and view-count calls to that host. Omit to resolve
67
+ * from NEXT_PUBLIC_SITE_URL. Separate from `siteUrl`, which only decides
68
+ * which links in the body open in a new tab.
69
+ */
70
+ site?: string;
71
+ /**
72
+ * What to render when the post doesn't exist. Default true: call Next's
73
+ * `notFound()`, so the route answers 404 with `noindex` instead of a
74
+ * "Post Not Found" message on a 200 (a soft 404). False renders that
75
+ * message, for a page that wants its own missing-post state.
76
+ *
77
+ * For a guaranteed 404 status, also call `requireArticle(slug)` from
78
+ * `@sonordev/press-kit/articles/server` in `generateMetadata`: metadata resolves
79
+ * before anything streams. Thrown from inside a Suspense boundary that has
80
+ * already streamed, `notFound()` can only add `noindex` to a 200.
81
+ */
82
+ notFound?: boolean;
83
+ children?: (props: {
84
+ post: ArticleType;
85
+ tableOfContents: TocItem[];
86
+ relatedPosts: ArticleType[];
87
+ }) => React.ReactNode;
88
+ }
89
+ export declare function Article({ apiUrl, apiKey, slug, showToc, showRelated, relatedCount, showAuthor, basePath, publicationName, routing, className, styles, unstyled, siteUrl, site, notFound, children, }: ArticleServerProps): Promise<React.JSX.Element | null>;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @sonordev/press-kit/articles - Article FAQ Component
3
+ *
4
+ * Renders FAQ items with collapsible accordion behavior.
5
+ * Content is always in the DOM (not display:none) for SEO crawlability.
6
+ * Uses CSS max-height transitions — no JS animation library required.
7
+ *
8
+ * Pairs with FAQPage schema (generated by Signal AI in the publication pipeline)
9
+ * for Google's "People Also Ask" featured snippets.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * <ArticleFAQ items={post.faqItems} />
14
+ * ```
15
+ */
16
+ import React from 'react';
17
+ export interface FaqItem {
18
+ question: string;
19
+ answer: string;
20
+ }
21
+ export interface ArticleFAQProps {
22
+ items: FaqItem[];
23
+ /** Section title */
24
+ title?: string;
25
+ /** Allow multiple items open simultaneously */
26
+ allowMultiple?: boolean;
27
+ /** Override styles with Tailwind classes */
28
+ styles?: {
29
+ section?: string;
30
+ title?: string;
31
+ list?: string;
32
+ item?: string;
33
+ question?: string;
34
+ questionText?: string;
35
+ icon?: string;
36
+ answer?: string;
37
+ answerInner?: string;
38
+ };
39
+ unstyled?: boolean;
40
+ }
41
+ export declare function ArticleFAQ({ items, title, allowMultiple, styles, unstyled, }: ArticleFAQProps): React.JSX.Element | null;
@@ -0,0 +1,2 @@
1
+ import type { ArticleFAQProps } from './ArticleFAQ';
2
+ export declare function ArticleFaqSection(props: ArticleFAQProps): import("react").JSX.Element;
@@ -0,0 +1,79 @@
1
+ /**
2
+ * @sonordev/press-kit/articles - Article List Component
3
+ *
4
+ * Fetches and displays a list of articles with pagination, filtering, and sorting.
5
+ *
6
+ * Ships with world-class default styling via scoped `.sk-article-list-*` classes.
7
+ * No configuration needed — just `<ArticleList basePath="/article" />`.
8
+ *
9
+ * Override with `styles` prop (per-element class names) or `unstyled` for
10
+ * complete control. The `children` render prop gives full data access.
11
+ */
12
+ import React from 'react';
13
+ import type { ArticleListResult, Article, ArticleCategory } from './types';
14
+ import { type PublicationRoutingOptions } from './routes';
15
+ export interface ArticleListStyles {
16
+ container?: string;
17
+ categoryNav?: string;
18
+ categoryLink?: string;
19
+ categoryLinkActive?: string;
20
+ categoryLinkInactive?: string;
21
+ grid?: string;
22
+ card?: string;
23
+ cardImageWrapper?: string;
24
+ cardImage?: string;
25
+ cardBody?: string;
26
+ cardMeta?: string;
27
+ cardCategory?: string;
28
+ cardDate?: string;
29
+ cardTitle?: string;
30
+ cardTitleLink?: string;
31
+ cardExcerpt?: string;
32
+ cardFooter?: string;
33
+ cardAuthor?: string;
34
+ cardReadingTime?: string;
35
+ cardReadMore?: string;
36
+ pagination?: string;
37
+ paginationLink?: string;
38
+ paginationInfo?: string;
39
+ emptyState?: string;
40
+ }
41
+ export interface ArticleListServerProps {
42
+ apiUrl?: string;
43
+ apiKey?: string;
44
+ category?: string;
45
+ tag?: string;
46
+ /** Author slug (preferred) or display name; slug filtering requires the current Sonor API */
47
+ author?: string;
48
+ featured?: boolean;
49
+ search?: string;
50
+ /**
51
+ * Topic cluster slug — pairs with getTopicCluster. Note: an unknown slug
52
+ * yields an UNfiltered list (pre-existing API behavior).
53
+ */
54
+ cluster?: string;
55
+ page?: number;
56
+ perPage?: number;
57
+ orderBy?: 'published_at' | 'title' | 'view_count';
58
+ order?: 'asc' | 'desc';
59
+ /**
60
+ * Sub-site host for multi-site projects (e.g. `charlotte.example.com`): the list
61
+ * shows that host's posts plus project-wide ones. Omit to resolve from
62
+ * NEXT_PUBLIC_SITE_URL.
63
+ */
64
+ site?: string;
65
+ showCategoryFilter?: boolean;
66
+ showPagination?: boolean;
67
+ className?: string;
68
+ basePath?: string;
69
+ routing?: PublicationRoutingOptions;
70
+ styles?: ArticleListStyles;
71
+ unstyled?: boolean;
72
+ renderPost?: (post: Article) => React.ReactNode;
73
+ children?: (props: {
74
+ posts: Article[];
75
+ pagination: ArticleListResult['pagination'];
76
+ categories: ArticleCategory[];
77
+ }) => React.ReactNode;
78
+ }
79
+ export declare function ArticleList({ apiUrl, apiKey, category, tag, author, featured, search, cluster, page, perPage, orderBy, order, site, showCategoryFilter, showPagination, className, basePath, routing, styles, unstyled, renderPost, children, }: ArticleListServerProps): Promise<string | number | bigint | boolean | React.JSX.Element | Iterable<React.ReactNode> | null | undefined>;
@@ -0,0 +1,15 @@
1
+ export interface ArticleViewTrackerProps {
2
+ slug: string;
3
+ /**
4
+ * Sub-site host the view counts toward. Omit to use the host SiteKitLayout
5
+ * published (`__SITE_KIT_SITE__`, the same one analytics tags page views
6
+ * with), then NEXT_PUBLIC_SITE_URL, then `window.location.host`.
7
+ */
8
+ site?: string;
9
+ }
10
+ /**
11
+ * The effect body, exported for tests (the package's vitest runs in node, so
12
+ * the component's mount path can't be exercised directly).
13
+ */
14
+ export declare function trackArticleView(slug: string, site?: string): void;
15
+ export declare function ArticleViewTracker({ slug, site }: ArticleViewTrackerProps): null;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @sonordev/press-kit/articles - Author Card Component
3
+ */
4
+ import React from 'react';
5
+ import type { AuthorCardProps, ArticleAuthor } from './types';
6
+ declare function TrustBlock({ author }: {
7
+ author: ArticleAuthor;
8
+ }): React.JSX.Element | null;
9
+ export declare function AuthorCard({ author, showBio, showSocial, showTrustBlock, className }: AuthorCardProps): React.JSX.Element;
10
+ export { TrustBlock };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @sonordev/press-kit/articles - Author Page Component (Server Component)
3
+ *
4
+ * Full author profile page with hero, trust block, social links, and articles grid.
5
+ */
6
+ import React from 'react';
7
+ import type { AuthorPageProps } from './types';
8
+ export declare function AuthorPage({ author, posts, basePath, routing, siteUrl, siteName, jsonLd }: AuthorPageProps): React.JSX.Element;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @sonordev/press-kit/articles - Cluster Landing Page Component
3
+ *
4
+ * Pre-built page component for `/article/cluster/[slug]` routes.
5
+ * Renders a topic cluster overview with:
6
+ * - Cluster hero (name, topic, description)
7
+ * - Pillar article (featured, large card)
8
+ * - Support articles (grid, categorized by article_type or funnel_stage)
9
+ *
10
+ * Server component — fetches cluster data from Sonor API.
11
+ * Ships with `.sk-cluster-landing-*` scoped styles.
12
+ */
13
+ import React from 'react';
14
+ import { type PublicationRoutingOptions } from './routes';
15
+ import type { TopicCluster } from './types';
16
+ export interface ClusterLandingPageProps {
17
+ /** Cluster slug to fetch */
18
+ slug: string;
19
+ /** Sub-site host for multi-site projects. Omit to resolve from NEXT_PUBLIC_SITE_URL. */
20
+ site?: string;
21
+ /** Base path for article URLs (default: /article) */
22
+ basePath?: string;
23
+ routing?: PublicationRoutingOptions;
24
+ /**
25
+ * Include each article's own category segment in its URL:
26
+ * {basePath}/{category}/{slug}. Enable on sites whose article routes are
27
+ * category-segmented (e.g. /insights/{category}/{slug}) —
28
+ * without it, links point at {basePath}/{slug} and 404 on such sites.
29
+ */
30
+ categoryInPath?: boolean;
31
+ /** Disable default styles */
32
+ unstyled?: boolean;
33
+ /** Custom class name */
34
+ className?: string;
35
+ /** Render prop for full data access */
36
+ children?: (props: {
37
+ cluster: TopicCluster;
38
+ }) => React.ReactNode;
39
+ }
40
+ export declare function ClusterLandingPage({ slug, site, basePath, routing, categoryInPath, unstyled, className, children, }: ClusterLandingPageProps): Promise<string | number | bigint | boolean | React.JSX.Element | Iterable<React.ReactNode> | null | undefined>;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @sonordev/press-kit/articles - Cluster Navigation Component
3
+ *
4
+ * Renders cluster context on article pages:
5
+ * - "Full Guide" pillar backlink (on support articles only)
6
+ * - Sibling article list with article_type badges
7
+ *
8
+ * Adapts to light/dark themes automatically using the same --sk-* variables
9
+ * as the rest of site-kit, with currentColor-based fallbacks.
10
+ */
11
+ import React from 'react';
12
+ import type { ClusterNavigation as ClusterNavigationType } from './types';
13
+ import { type PublicationRoutingOptions } from './routes';
14
+ export interface ClusterNavigationProps {
15
+ /** Cluster navigation data */
16
+ navigation: ClusterNavigationType;
17
+ /** Base path for article URLs (default: /article) */
18
+ basePath?: string;
19
+ routing?: PublicationRoutingOptions;
20
+ /**
21
+ * Fallback category slug for URL building (optional). Each linked article's
22
+ * OWN category takes precedence when present — applying one category to
23
+ * every sibling produced wrong-category URLs (duplicate content / redirect
24
+ * chains) on category-segmented sites whenever a cluster spans categories.
25
+ */
26
+ category?: string;
27
+ /**
28
+ * Append a trailing slash to article URLs. Defaults to the site's
29
+ * next.config `trailingSlash`, so it rarely needs setting.
30
+ */
31
+ trailingSlash?: boolean;
32
+ /** Disable default styles */
33
+ unstyled?: boolean;
34
+ /** Custom class name */
35
+ className?: string;
36
+ }
37
+ export declare function ClusterNavigation({ navigation, basePath, routing, category, trailingSlash: trailingSlashProp, unstyled, className, }: ClusterNavigationProps): React.JSX.Element | null;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Managed-forms mode of NewsletterWidget — its own module, loaded via
3
+ * React.lazy, so the useForm graph (forms engine, sonorFetch, site token)
4
+ * stays out of the publication entry's eager bundle. Article consumers only pay for
5
+ * it when a formSlug newsletter actually mounts (enforced by the
6
+ * integration harness's bundle budget, which measures the eager
7
+ * static-import graph).
8
+ */
9
+ import React from 'react';
10
+ import { type NewsletterShellProps } from './NewsletterShell';
11
+ import type { NewsletterWidgetProps } from './NewsletterWidget';
12
+ export default function ManagedNewsletterForm({ formSlug, title, description, buttonText, successMessage, }: Required<Pick<NewsletterWidgetProps, 'formSlug'>> & Pick<NewsletterShellProps, 'title' | 'description' | 'buttonText' | 'successMessage'>): React.JSX.Element | null;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Presentational shell shared by NewsletterWidget's two modes. No data
3
+ * imports — keep it that way, it renders in the eager article bundle.
4
+ */
5
+ import React from 'react';
6
+ export interface NewsletterShellProps {
7
+ title: string;
8
+ description: string;
9
+ buttonText: string;
10
+ email: string;
11
+ onEmailChange: (value: string) => void;
12
+ onFormSubmit: (e: React.FormEvent) => void;
13
+ status: 'idle' | 'submitting' | 'success' | 'error';
14
+ successMessage: string;
15
+ errorMessage?: string | null;
16
+ }
17
+ export declare function NewsletterShell({ title, description, buttonText, email, onEmailChange, onFormSubmit, status, successMessage, errorMessage, }: NewsletterShellProps): React.JSX.Element;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @sonordev/site-kit article — newsletter signup widget
3
+ *
4
+ * Two working modes (the old version rendered a form whose submit handler
5
+ * discarded the email):
6
+ *
7
+ * - `onSubmit`: the site owns delivery — we await the callback.
8
+ * - `formSlug`: the widget drives the managed-forms rail (useForm), so
9
+ * submissions inherit Sonor's newsletter routing (email_subscribers),
10
+ * honeypot, reCAPTCHA, and attribution for free.
11
+ *
12
+ * `onSubmit` wins when both are set. With neither, the widget renders
13
+ * nothing and warns — a dead form that swallows emails must not resurrect.
14
+ *
15
+ * Lives in its own module (not PublicationSidebar.tsx) on purpose: it needs hooks,
16
+ * and PublicationSidebar is also bundled into the un-stamped server entry
17
+ * articles/server-ui, where hooks would enter the react-server graph.
18
+ */
19
+ import React from 'react';
20
+ export interface NewsletterWidgetProps {
21
+ title?: string;
22
+ description?: string;
23
+ buttonText?: string;
24
+ /** Site-owned delivery. Wins over formSlug when both are set. */
25
+ onSubmit?: (email: string) => Promise<void>;
26
+ /** Slug of a managed Sonor form (newsletter routing) to submit through. */
27
+ formSlug?: string;
28
+ successMessage?: string;
29
+ }
30
+ export declare function NewsletterWidget({ title, description, buttonText, onSubmit, formSlug, successMessage, }: NewsletterWidgetProps): React.JSX.Element | null;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * @sonordev/press-kit/articles - Article Layout Component
3
+ *
4
+ * A complete article layout with sidebar, responsive design, and optional hero.
5
+ * Use this to wrap your article pages for consistent styling.
6
+ */
7
+ import React from 'react';
8
+ import { PublicationSidebarProps } from './PublicationSidebar';
9
+ import type { PublicationRoutingOptions } from './routes';
10
+ export interface PublicationLayoutProps {
11
+ /** Sonor API URL */
12
+ apiUrl?: string;
13
+ /** Project API key */
14
+ apiKey?: string;
15
+ /** Sub-site host for multi-site projects, passed to the sidebar. Omit to resolve from NEXT_PUBLIC_SITE_URL. */
16
+ site?: string;
17
+ /** Layout style: 'sidebar-right', 'sidebar-left', 'full-width' */
18
+ layout?: 'sidebar-right' | 'sidebar-left' | 'full-width';
19
+ /** Show sidebar */
20
+ showSidebar?: boolean;
21
+ /** Sidebar props */
22
+ sidebarProps?: Partial<PublicationSidebarProps>;
23
+ /** Hero section config */
24
+ hero?: {
25
+ title?: string;
26
+ subtitle?: string;
27
+ backgroundImage?: string;
28
+ backgroundColor?: string;
29
+ };
30
+ /** Max width for content */
31
+ maxWidth?: number;
32
+ /** Base URL for article links */
33
+ basePath?: string;
34
+ routing?: PublicationRoutingOptions;
35
+ /** Custom class name */
36
+ className?: string;
37
+ /** Current category (for sidebar highlighting) */
38
+ currentCategory?: string;
39
+ /** Main content */
40
+ children: React.ReactNode;
41
+ }
42
+ export declare function PublicationLayout({ apiUrl, apiKey, site, layout, showSidebar, sidebarProps, hero, maxWidth, basePath, routing, className, currentCategory, children, }: PublicationLayoutProps): Promise<React.JSX.Element>;
43
+ /**
44
+ * Convenience wrapper for a complete publication index page
45
+ */
46
+ export interface PublicationPageProps extends Omit<PublicationLayoutProps, 'children'> {
47
+ /** List component to render */
48
+ listComponent: React.ReactNode;
49
+ }
50
+ export declare function PublicationPage({ listComponent, ...layoutProps }: PublicationPageProps): Promise<React.JSX.Element>;
51
+ /**
52
+ * Convenience wrapper for a single article page
53
+ */
54
+ export interface ArticlePageProps extends Omit<PublicationLayoutProps, 'children'> {
55
+ /** Post component to render */
56
+ postComponent: React.ReactNode;
57
+ }
58
+ export declare function ArticlePage({ postComponent, ...layoutProps }: ArticlePageProps): Promise<React.JSX.Element>;
59
+ export interface CategoryPageProps extends PublicationLayoutProps {
60
+ /** Category slug being viewed */
61
+ category: string;
62
+ /** Category display name */
63
+ categoryName?: string;
64
+ /** List component */
65
+ listComponent: React.ReactNode;
66
+ }
67
+ export declare function CategoryPage({ category, categoryName, listComponent, hero, ...layoutProps }: CategoryPageProps): Promise<React.JSX.Element>;