diff --git a/Spectre.Docs/Components/Layouts/MainLayout.razor b/Spectre.Docs/Components/Layouts/MainLayout.razor
index cc711c4..c5100d1 100644
--- a/Spectre.Docs/Components/Layouts/MainLayout.razor
+++ b/Spectre.Docs/Components/Layouts/MainLayout.razor
@@ -43,9 +43,9 @@
diff --git a/Spectre.Docs/Components/Pages/Blog.razor.metadata.yml b/Spectre.Docs/Components/Pages/Blog.razor.metadata.yml
new file mode 100644
index 0000000..6268d34
--- /dev/null
+++ b/Spectre.Docs/Components/Pages/Blog.razor.metadata.yml
@@ -0,0 +1,4 @@
+# Metadata sidecar for the Razor landing page at "/blog/" so it projects a content record
+# and gets a social card + og:image / description meta tags.
+title: Blog
+description: "News, updates, and insights from the Spectre.Console team."
diff --git a/Spectre.Docs/Components/Pages/CliIndex.razor.metadata.yml b/Spectre.Docs/Components/Pages/CliIndex.razor.metadata.yml
new file mode 100644
index 0000000..dd1e788
--- /dev/null
+++ b/Spectre.Docs/Components/Pages/CliIndex.razor.metadata.yml
@@ -0,0 +1,4 @@
+# Metadata sidecar for the Razor landing page at "/cli/" so it projects a content record
+# and gets a social card + og:image / description meta tags.
+title: Spectre.Console.Cli Documentation
+description: "Build powerful command-line applications with Spectre.Console.Cli - type-safe argument parsing, nested commands, and dependency injection for .NET"
diff --git a/Spectre.Docs/Components/Pages/ConsoleIndex.razor.metadata.yml b/Spectre.Docs/Components/Pages/ConsoleIndex.razor.metadata.yml
new file mode 100644
index 0000000..9202baa
--- /dev/null
+++ b/Spectre.Docs/Components/Pages/ConsoleIndex.razor.metadata.yml
@@ -0,0 +1,4 @@
+# Metadata sidecar for the Razor landing page at "/console/" so it projects a content record
+# and gets a social card + og:image / description meta tags.
+title: Spectre.Console Documentation
+description: "Create beautiful, cross-platform console applications with Spectre.Console - tables, progress bars, charts, and rich text formatting for .NET"
diff --git a/Spectre.Docs/Components/Pages/Home.razor.metadata.yml b/Spectre.Docs/Components/Pages/Home.razor.metadata.yml
new file mode 100644
index 0000000..5149bb2
--- /dev/null
+++ b/Spectre.Docs/Components/Pages/Home.razor.metadata.yml
@@ -0,0 +1,4 @@
+# Metadata sidecar for the Razor landing page at "/". Makes the home page project a content
+# record so it gets a social card (/social-cards/index.png) and the og:image / description meta tags.
+title: Spectre.Console
+description: "Beautiful, cross-platform console applications for .NET - tables, charts, prompts, and live displays."
diff --git a/Spectre.Docs/Program.cs b/Spectre.Docs/Program.cs
index 5fc6e50..140975d 100644
--- a/Spectre.Docs/Program.cs
+++ b/Spectre.Docs/Program.cs
@@ -1,7 +1,11 @@
+using System.Reflection;
+using Ashcroft;
using Mdazor;
using Pennington.ApiMetadata.Reflection;
using Pennington.Infrastructure;
+using Pennington.LlmsTxt;
using Pennington.MonorailCss;
+using Pennington.SocialCards;
using Pennington.TreeSitter;
using Pennington.UI.Components;
using Spectre.Console;
@@ -9,12 +13,19 @@
using Spectre.Docs.Components.Reference;
using Spectre.Docs.Components.Shared;
using Spectre.Docs.Services;
+using Spectre.Docs.SocialCardAssets;
using ColorName = Pennington.MonorailCss.ColorName;
using IContentService = Pennington.Content.IContentService;
using IContentRenderer = Pennington.Pipeline.IContentRenderer;
var builder = WebApplication.CreateBuilder(args);
+// The documented subject's version for the /llms.txt front door. Resolved from the referenced
+// Spectre.Console assembly so it tracks package bumps; the + SourceLink suffix is stripped.
+var spectreVersion = typeof(AnsiConsole).Assembly
+ .GetCustomAttribute()?
+ .InformationalVersion?.Split('+', 2)[0];
+
builder.Services.AddRazorComponents();
// Typed views over the Pennington content pipeline that preserve the ergonomics the Razor
@@ -43,7 +54,23 @@
{
penn.SiteTitle = "Spectre.Console Documentation";
penn.SiteDescription = "Beautiful console applications with Spectre.Console";
+ penn.SiteVersion = spectreVersion; // → llms.txt `version:` (replaces penningtonVersion:)
+ penn.CanonicalBaseUrl = "https://spectreconsole.net";
penn.ContentRootPath = "Content";
+ penn.SiteProjection.ContentSelector = "article";
+
+ // OpenGraph / Twitter social cards. Pennington discovers one card per page, auto-maps the
+ // /social-cards/{**slug}.png route (via UsePennington), and emits the og:image / twitter:image
+ // meta tags; Ashcroft does the drawing — the branded PNG as the background with the page
+ // title set in JetBrains Mono, anchored bottom-left clear of the watermark on the right.
+ penn.SocialCards = new SocialCardOptions
+ {
+ Render = (request, sp, _) =>
+ {
+ var environment = sp.GetRequiredService();
+ return SocialCardGenerator.Build(request, environment);
+ },
+ };
penn.AddMarkdownContent(md =>
{
@@ -64,6 +91,8 @@
md.ContentPath = "Content/blog";
md.BasePageUrl = "/blog";
});
+
+ penn.AddLlmsTxt();
});
// Reflection-backed API metadata providers, one keyed registration per reference area.
@@ -80,6 +109,13 @@
builder.Services.AddApiMetadataFromCompiledAssembly("cli", opts =>
opts.FromPackageReference("Spectre.Console.Cli"));
+builder.Services.AddLlmsSubtree(new LlmsSubtree(
+ "/cli/reference/api/", "CLI reference",
+ "Spectre.Console.Cli — command, configuration, and settings types."));
+builder.Services.AddLlmsSubtree(new LlmsSubtree(
+ "/console/reference/api/", "Console reference",
+ "Spectre.Console — widgets, prompts, rendering, and markup types."));
+
builder.Services.AddScoped();
// Supplies API route discovery (so the static build emits the pages) and the sidebar
@@ -114,11 +150,7 @@
PrimaryColorName = ColorName.Sky,
AccentColorName = ColorName.Amber,
BaseColorName = ColorName.Neutral,
- AdditionalMappings =
- {
- ["tertiary-one"] = ColorName.Emerald,
- ["tertiary-two"] = ColorName.Violet,
- },
+ AdditionalMappings = { ["tertiary-one"] = ColorName.Emerald, ["tertiary-two"] = ColorName.Violet, },
},
});
@@ -130,4 +162,4 @@
app.UsePennington();
app.UseMonorailCss();
-await app.RunOrBuildAsync(args);
+await app.RunOrBuildAsync(args);
\ No newline at end of file
diff --git a/Spectre.Docs/Services/ApiReferenceContentService.cs b/Spectre.Docs/Services/ApiReferenceContentService.cs
index d4df642..8076a5c 100644
--- a/Spectre.Docs/Services/ApiReferenceContentService.cs
+++ b/Spectre.Docs/Services/ApiReferenceContentService.cs
@@ -65,8 +65,10 @@ public async Task> GetIndexableEntriesAsync()
// The default would index only the nav entries (the two index pages), so a type that
// appears nowhere else — e.g. IAnsiConsole — is unreachable via search, and types that do
// appear only match the big index listing rather than their own page. Add one searchable
- // entry per type so each type page surfaces directly. Kept out of nav (SearchOnly) and out
- // of llms.txt (ExcludeFromLlms) to avoid bloating either with the full type list.
+ // entry per type so each type page surfaces directly. Kept out of nav (SearchOnly); they
+ // still feed llms.txt, where the per-area `/{area}/reference/api/` subtree (declared via
+ // AddLlmsSubtree in Program.cs) splits them into a dedicated {prefix}llms.txt rather than
+ // bloating the front door.
var builder = ImmutableList.CreateBuilder();
builder.AddRange(await GetContentTocEntriesAsync());
@@ -82,7 +84,7 @@ public async Task> GetIndexableEntriesAsync()
int.MaxValue,
[area, "reference", "api"],
area,
- null) { Description = type.Summary, SearchOnly = true, ExcludeFromLlms = true });
+ null) { Description = type.Summary, SearchOnly = true });
}
}
return builder.ToImmutable();
diff --git a/Spectre.Docs/SocialCardAssets/JetBrainsMono[wght].ttf b/Spectre.Docs/SocialCardAssets/JetBrainsMono[wght].ttf
new file mode 100644
index 0000000..b60e77f
Binary files /dev/null and b/Spectre.Docs/SocialCardAssets/JetBrainsMono[wght].ttf differ
diff --git a/Spectre.Docs/SocialCardAssets/SocialCardGenerator.cs b/Spectre.Docs/SocialCardAssets/SocialCardGenerator.cs
new file mode 100644
index 0000000..219bc37
--- /dev/null
+++ b/Spectre.Docs/SocialCardAssets/SocialCardGenerator.cs
@@ -0,0 +1,44 @@
+using Ashcroft;
+using Pennington.SocialCards;
+
+namespace Spectre.Docs.SocialCardAssets;
+
+public static class SocialCardGenerator
+{
+ // Social-card assets live in the project's SocialCardAssets folder. Resolved against the
+ // content root, so the paths are correct under both `dotnet run` and `dotnet run -- build`
+ // (the static-build crawler fetches /social-cards/*.png from the in-memory host).
+ public static Task Build(SocialCardRequest socialCardRequest, IWebHostEnvironment environment)
+ {
+ var socialCardAssetsPath = Path.Combine(environment.ContentRootPath, "SocialCardAssets");
+ var cardBackgroundPath = Path.Combine(socialCardAssetsPath, "spectre-og-card.png");
+ var cardFontPath = Path.Combine(socialCardAssetsPath, "JetBrainsMono[wght].ttf");
+
+ var card = SocialCard.Create(socialCardRequest.Width, socialCardRequest.Height)
+ .Background(cardBackgroundPath)
+ .Theme(new Theme { FontPath = cardFontPath })
+ .At(Anchor.BottomLeft, stack =>
+ {
+ stack.MaxWidth(960);
+
+ stack.Text(socialCardRequest.Title, new TextStyle { Size = 72, Weight = 800, MaxLines = 2, ShrinkToFit = true });
+ if (!string.IsNullOrWhiteSpace(socialCardRequest.Description))
+ {
+ stack.Text(socialCardRequest.Description,
+ new TextStyle
+ {
+ MaxLines = 2,
+ ShrinkToFit = true,
+ Size = 30,
+ Weight = 200,
+ LetterSpacing = -0.5f
+ });
+ }
+
+ stack.Spacer(8);
+ stack.Meta(socialCardRequest.SiteTitle, color: "#38bdf8");
+ });
+
+ return Task.FromResult(card.ToBytes());
+ }
+}
\ No newline at end of file
diff --git a/Spectre.Docs/SocialCardAssets/spectre-og-card.png b/Spectre.Docs/SocialCardAssets/spectre-og-card.png
new file mode 100644
index 0000000..aadeffa
Binary files /dev/null and b/Spectre.Docs/SocialCardAssets/spectre-og-card.png differ
diff --git a/Spectre.Docs/Spectre.Docs.csproj b/Spectre.Docs/Spectre.Docs.csproj
index d5580ce..33ec865 100644
--- a/Spectre.Docs/Spectre.Docs.csproj
+++ b/Spectre.Docs/Spectre.Docs.csproj
@@ -6,15 +6,24 @@
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
+
-
-
+
+
\ No newline at end of file