@stack-mode/site-manager-kit 1.44.1 → 1.45.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.
package/README.md CHANGED
@@ -194,6 +194,18 @@ export default function Robots() {
194
194
  }
195
195
  ```
196
196
 
197
+ robots.txt also keeps out the AI crawlers the site blocks in Site Manager (Search Settings > Indexing): AI search tools,
198
+ AI training crawlers, or both.
199
+
200
+ `app/llms.txt/route.ts` (optional), the site's pages with their titles and descriptions for AI tools that read
201
+ `/llms.txt`. It answers 404 while llms.txt is off for the site (Search Settings > Tools):
202
+
203
+ ```ts
204
+ import { llmsRoute } from "site-manager-kit/llms-route";
205
+
206
+ export const GET = llmsRoute();
207
+ ```
208
+
197
209
  `src/proxy.ts` (or `proxy.ts` at the root when there's no `src` folder):
198
210
 
199
211
  ```ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stack-mode/site-manager-kit",
3
- "version": "1.44.1",
3
+ "version": "1.45.0",
4
4
  "description": "Reads a site's content from Site Manager and builds its metadata, sitemap, robots, redirects, forms and refreshes. For Next.js App Router sites on Vercel.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -28,6 +28,7 @@
28
28
  "./attribution": "./src/attribution.tsx",
29
29
  "./phone": "./src/phone-input.tsx",
30
30
  "./rss-route": "./src/rss-route.ts",
31
+ "./llms-route": "./src/llms-route.ts",
31
32
  "./visual": "./src/visual.ts",
32
33
  "./visual-tags": "./src/visual-tags.ts",
33
34
  "./visual-accents": "./src/visual-accents.ts",
@@ -0,0 +1,45 @@
1
+ import { BlueprintError, bp, tagsFor } from "./client";
2
+
3
+ export type LlmsData = {
4
+ name: string;
5
+ summary: string | null;
6
+ sections: { title: string; pages: { url: string; title: string; description: string | null }[] }[];
7
+ };
8
+
9
+ const line = (s: string) => s.replace(/\s+/g, " ").trim();
10
+ const linkText = (s: string) => line(s).replace(/[[\]]/g, "");
11
+
12
+ /** Pure: the llms.txt text (llmstxt.org): the business, its summary, then each section's pages as links. */
13
+ export function buildLlmsTxt(d: LlmsData): string {
14
+ const out = [`# ${line(d.name)}`, ""];
15
+ if (d.summary) out.push(`> ${line(d.summary)}`, "");
16
+ for (const s of d.sections) {
17
+ if (!s.pages.length) continue;
18
+ out.push(`## ${line(s.title)}`, "");
19
+ for (const p of s.pages) out.push(`- [${linkText(p.title)}](${p.url})${p.description ? `: ${line(p.description)}` : ""}`);
20
+ out.push("");
21
+ }
22
+ return out.join("\n");
23
+ }
24
+
25
+ /**
26
+ * /llms.txt, from the site's pages in Site Manager. Put in app/llms.txt/route.ts:
27
+ *
28
+ * import { llmsRoute } from "site-manager-kit/llms-route";
29
+ * export const GET = llmsRoute();
30
+ *
31
+ * Answers 404 when llms.txt is off for the site in Site Manager, or the Search section is.
32
+ */
33
+ export function llmsRoute() {
34
+ return async function GET(): Promise<Response> {
35
+ let data: LlmsData | null;
36
+ try {
37
+ data = await bp<LlmsData>("/llms", { tags: tagsFor("seo") });
38
+ } catch (e) {
39
+ if (e instanceof BlueprintError && (e.code === "module_off" || e.code === "feature_off")) return new Response("Not found", { status: 404 });
40
+ throw e;
41
+ }
42
+ if (!data) return new Response("Not found", { status: 404 });
43
+ return new Response(buildLlmsTxt(data), { headers: { "content-type": "text/plain; charset=utf-8", "cache-control": "public, max-age=0, s-maxage=3600, stale-while-revalidate=86400" } });
44
+ };
45
+ }
package/src/robots.ts CHANGED
@@ -10,7 +10,11 @@ import type { Robots } from "./types";
10
10
  export function buildRobots(r: Robots | null, websiteUrl: string | null): MetadataRoute.Robots {
11
11
  if (!r || !r.indexable) return { rules: [{ userAgent: "*", disallow: "/" }] };
12
12
  return {
13
- rules: [{ userAgent: "*", allow: "/", ...(r.disallow.length ? { disallow: r.disallow } : {}) }],
13
+ rules: [
14
+ { userAgent: "*", allow: "/", ...(r.disallow.length ? { disallow: r.disallow } : {}) },
15
+ // A crawler named in its own group ignores the "*" group, so the AI crawlers the site keeps out are listed apart.
16
+ ...(r.blockedBots?.length ? [{ userAgent: r.blockedBots, disallow: "/" }] : []),
17
+ ],
14
18
  ...(websiteUrl ? { sitemap: `${websiteUrl.replace(/\/+$/, "")}/sitemap.xml` } : {}),
15
19
  };
16
20
  }
package/src/types.ts CHANGED
@@ -312,7 +312,8 @@ export type PageMeta = {
312
312
  maxImagePreview?: "large" | "standard" | "none";
313
313
  };
314
314
  export type SitemapEntry = { url: string; lastModified: string };
315
- export type Robots = { indexable: boolean; disallow: string[] };
315
+ /** blockedBots: AI crawlers the site keeps out, by user agent (Site Manager 2026-11, optional for older servers). */
316
+ export type Robots = { indexable: boolean; disallow: string[]; blockedBots?: string[] };
316
317
  export type Redirect = { from: string; to: string; status: 301 | 302 | 308 };
317
318
 
318
319
  // Real estate (Site Manager's Listings and Team sections).
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const KIT_VERSION = "1.44.1";
1
+ export const KIT_VERSION = "1.45.0";