mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-08-13 14:37:26 +00:00
feat(web): AEO foundation — robots, JSON-LD, meta, llms.txt, security headers
Implements the high-signal, content-independent fixes flagged across the multi-engine AEO audit: - robots.txt (app/robots.ts): allow mainstream + AI crawlers (GPTBot, ClaudeBot, PerplexityBot, Google-Extended, …), disallow /api, link sitemap. - Organization + WebSite JSON-LD on the root layout; BlogPosting JSON-LD on /blog/[slug]. - Richer metadata: descriptive default title, Open Graph + Twitter cards, canonical, icons, metadataBase. - Per-route titles/descriptions for catch-all routes (docs, about, hire-us, agent-swarm, …) instead of the generic "LogicSRC". - /llms.txt (llmstxt.org) and /skill.md capability manifest. - /.well-known/security.txt (RFC 9116). - Security headers via next.config: HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy (CSP intentionally deferred to avoid breaking inline/stats/CoinPay scripts). Verified in a running build: all routes serve correctly and headers are set. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
ce79f02211
commit
6518dfb4a9
8 changed files with 285 additions and 19 deletions
|
|
@ -6,7 +6,19 @@ import type { NextConfig } from "next";
|
|||
// webhooks) are filesystem routes and match before these afterFiles rewrites.
|
||||
const commandboardApiUrl = process.env.COMMANDBOARD_API_URL;
|
||||
|
||||
const securityHeaders = [
|
||||
// HSTS — site is HTTPS-only behind Railway. No `preload` (irreversible).
|
||||
{ key: "Strict-Transport-Security", value: "max-age=31536000; includeSubDomains" },
|
||||
{ key: "X-Content-Type-Options", value: "nosniff" },
|
||||
{ key: "X-Frame-Options", value: "SAMEORIGIN" },
|
||||
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
|
||||
{ key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
|
||||
];
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
async headers() {
|
||||
return [{ source: "/:path*", headers: securityHeaders }];
|
||||
},
|
||||
async rewrites() {
|
||||
if (!commandboardApiUrl) return [];
|
||||
const base = commandboardApiUrl.replace(/\/$/, "");
|
||||
|
|
|
|||
17
apps/logicsrc-web/src/app/.well-known/security.txt/route.ts
Normal file
17
apps/logicsrc-web/src/app/.well-known/security.txt/route.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
const SITE_URL = (process.env.PUBLIC_URL ?? "https://logicsrc.com").replace(/\/$/, "");
|
||||
|
||||
// GET /.well-known/security.txt (RFC 9116).
|
||||
export function GET(): Response {
|
||||
const expires = new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString();
|
||||
const body = `Contact: mailto:security@profullstack.com
|
||||
Expires: ${expires}
|
||||
Preferred-Languages: en
|
||||
Canonical: ${SITE_URL}/.well-known/security.txt
|
||||
`;
|
||||
return new Response(body, {
|
||||
headers: {
|
||||
"content-type": "text/plain; charset=utf-8",
|
||||
"cache-control": "public, max-age=86400",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { notFound } from "next/navigation";
|
||||
import type { Metadata } from "next";
|
||||
import type { ReactNode } from "react";
|
||||
import { renderPageMarkup } from "@/lib/page-markup";
|
||||
import { HomeInteractivity } from "@/components/home-interactivity";
|
||||
|
|
@ -7,18 +8,57 @@ import { HomeInteractivity } from "@/components/home-interactivity";
|
|||
// scrolled to the matching section. We preserve those URLs (they are canonical
|
||||
// in sitemap.xml) by rendering the same page for each known route and 404ing
|
||||
// anything else.
|
||||
const KNOWN_ROUTES = new Set([
|
||||
"docs",
|
||||
"blog",
|
||||
"openspec",
|
||||
"credential-sharing",
|
||||
"hire-us",
|
||||
"about",
|
||||
"terms",
|
||||
"privacy",
|
||||
"agent-swarm",
|
||||
"agentbyte"
|
||||
]);
|
||||
const ROUTE_META: Record<string, { title: string; description: string }> = {
|
||||
docs: {
|
||||
title: "Docs · LogicSRC",
|
||||
description: "LogicSRC specification guides, schemas, and CLI conventions for human–AI agent coordination.",
|
||||
},
|
||||
openspec: {
|
||||
title: "LogicSRC vs OpenSpec.dev · LogicSRC",
|
||||
description: "How LogicSRC's coordination standard compares with OpenSpec.dev, including MCP and agent support.",
|
||||
},
|
||||
"credential-sharing": {
|
||||
title: "Credential Sharing · LogicSRC",
|
||||
description: "Source/target credential diffs, approval, sync, rollback, and audit across .env, Doppler, Railway, and GitHub Secrets.",
|
||||
},
|
||||
"hire-us": {
|
||||
title: "Hire Us · LogicSRC",
|
||||
description: "Implementation help for LogicSRC, AgentSwarm, and Credential Sharing at $250/week for accepted work, paid via CoinPay.",
|
||||
},
|
||||
about: {
|
||||
title: "About · LogicSRC",
|
||||
description: "LogicSRC is the Profullstack, Inc. open-specification project for human and AI agent coordination.",
|
||||
},
|
||||
terms: { title: "Terms · LogicSRC", description: "LogicSRC terms of use." },
|
||||
privacy: { title: "Privacy · LogicSRC", description: "LogicSRC privacy notes." },
|
||||
"agent-swarm": {
|
||||
title: "AgentSwarm · LogicSRC",
|
||||
description: "Provider-neutral agent orchestration with model routing, cost controls, and GitHub integration.",
|
||||
},
|
||||
agentbyte: {
|
||||
title: "AgentByte · LogicSRC",
|
||||
description: "Agent screening sessions, AI-assisted humans, policy events, and APIs.",
|
||||
},
|
||||
};
|
||||
|
||||
const KNOWN_ROUTES = new Set(Object.keys(ROUTE_META));
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug?: string[] }>;
|
||||
}): Promise<Metadata> {
|
||||
const { slug } = await params;
|
||||
const key = slug?.[0];
|
||||
if (key && ROUTE_META[key]) {
|
||||
return {
|
||||
title: ROUTE_META[key].title,
|
||||
description: ROUTE_META[key].description,
|
||||
alternates: { canonical: `/${key}` },
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
export default async function Page({
|
||||
params
|
||||
|
|
|
|||
|
|
@ -14,13 +14,16 @@ type PostRow = {
|
|||
html: string;
|
||||
featured_image: { url?: string } | null;
|
||||
published_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
const SITE_URL = (process.env.PUBLIC_URL ?? "https://logicsrc.com").replace(/\/$/, "");
|
||||
|
||||
async function loadPost(slug: string): Promise<PostRow | null> {
|
||||
const supabase = publicClient();
|
||||
const { data } = await supabase
|
||||
.from("blog_posts")
|
||||
.select("slug, title, excerpt, html, featured_image, published_at")
|
||||
.select("slug, title, excerpt, html, featured_image, published_at, updated_at")
|
||||
.eq("slug", slug)
|
||||
.eq("status", "published")
|
||||
.maybeSingle();
|
||||
|
|
@ -65,8 +68,25 @@ export default async function BlogPostPage({
|
|||
const post = await loadPost(slug);
|
||||
if (!post) notFound();
|
||||
|
||||
const jsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BlogPosting",
|
||||
headline: post.title,
|
||||
description: post.excerpt ?? undefined,
|
||||
image: post.featured_image?.url ? [post.featured_image.url] : undefined,
|
||||
datePublished: post.published_at,
|
||||
dateModified: post.updated_at,
|
||||
url: `${SITE_URL}/blog/${post.slug}`,
|
||||
mainEntityOfPage: `${SITE_URL}/blog/${post.slug}`,
|
||||
publisher: { "@id": `${SITE_URL}/#organization` },
|
||||
};
|
||||
|
||||
return (
|
||||
<SiteShell active="Blog">
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<article className="band" style={{ maxWidth: "48rem" }}>
|
||||
<p style={{ marginBottom: "1.5rem" }}>
|
||||
<Link href="/blog" style={{ color: "#5b6b7a", textDecoration: "none" }}>
|
||||
|
|
|
|||
|
|
@ -3,23 +3,91 @@ import type { ReactNode } from "react";
|
|||
import "../styles.css";
|
||||
import Script from "next/script";
|
||||
|
||||
const SITE_URL = (process.env.PUBLIC_URL ?? "https://logicsrc.com").replace(/\/$/, "");
|
||||
const DESCRIPTION =
|
||||
"Open schemas, primitives, and conventions for coordination between humans, AI agents, plugins, payment systems, and hosted products.";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "LogicSRC",
|
||||
description:
|
||||
"Open schemas, primitives, and conventions for coordination between humans, AI agents, plugins, payment systems, and hosted products.",
|
||||
manifest: "/manifest.webmanifest"
|
||||
metadataBase: new URL(SITE_URL),
|
||||
title: "LogicSRC — Open Coordination Standards for Humans & AI Agents",
|
||||
description: DESCRIPTION,
|
||||
applicationName: "LogicSRC",
|
||||
manifest: "/manifest.webmanifest",
|
||||
alternates: { canonical: "/" },
|
||||
icons: {
|
||||
icon: [{ url: "/icon.svg", type: "image/svg+xml" }],
|
||||
apple: "/icon.svg",
|
||||
},
|
||||
openGraph: {
|
||||
type: "website",
|
||||
siteName: "LogicSRC",
|
||||
url: SITE_URL,
|
||||
title: "LogicSRC — Open Coordination Standards for Humans & AI Agents",
|
||||
description: DESCRIPTION,
|
||||
images: ["/icon.svg"],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary",
|
||||
title: "LogicSRC — Open Coordination Standards",
|
||||
description: DESCRIPTION,
|
||||
images: ["/icon.svg"],
|
||||
},
|
||||
};
|
||||
|
||||
export const viewport: Viewport = {
|
||||
themeColor: "#101418",
|
||||
width: "device-width",
|
||||
initialScale: 1
|
||||
initialScale: 1,
|
||||
};
|
||||
|
||||
// Organization + WebSite JSON-LD so answer engines can resolve LogicSRC as a
|
||||
// distinct entity without guessing.
|
||||
const jsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@graph": [
|
||||
{
|
||||
"@type": "Organization",
|
||||
"@id": `${SITE_URL}/#organization`,
|
||||
name: "LogicSRC",
|
||||
url: SITE_URL,
|
||||
logo: `${SITE_URL}/icon.svg`,
|
||||
description: DESCRIPTION,
|
||||
parentOrganization: {
|
||||
"@type": "Organization",
|
||||
name: "Profullstack, Inc.",
|
||||
url: "https://profullstack.com",
|
||||
},
|
||||
sameAs: [
|
||||
"https://github.com/profullstack/logicsrc",
|
||||
"https://profullstack.com",
|
||||
],
|
||||
},
|
||||
{
|
||||
"@type": "WebSite",
|
||||
"@id": `${SITE_URL}/#website`,
|
||||
url: SITE_URL,
|
||||
name: "LogicSRC",
|
||||
description: DESCRIPTION,
|
||||
inLanguage: "en",
|
||||
publisher: { "@id": `${SITE_URL}/#organization` },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }: { children: ReactNode }): ReactNode {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children} <Script data-site="56a0c760-e6cb-4875-844e-8b8aaa80b59b" src="https://crawlproof.com/stats.js" strategy="afterInteractive" />
|
||||
<body>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
{children}
|
||||
<Script
|
||||
data-site="56a0c760-e6cb-4875-844e-8b8aaa80b59b"
|
||||
src="https://crawlproof.com/stats.js"
|
||||
strategy="afterInteractive"
|
||||
/>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
|
|
|||
37
apps/logicsrc-web/src/app/llms.txt/route.ts
Normal file
37
apps/logicsrc-web/src/app/llms.txt/route.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
const SITE_URL = (process.env.PUBLIC_URL ?? "https://logicsrc.com").replace(/\/$/, "");
|
||||
|
||||
// GET /llms.txt — concise, link-rich orientation for LLM crawlers
|
||||
// (https://llmstxt.org spec).
|
||||
export function GET(): Response {
|
||||
const body = `# LogicSRC
|
||||
|
||||
> Open schemas, primitives, and conventions for coordination between humans, AI agents, plugins, payment systems, and hosted products. LogicSRC defines the shared language; products can implement it without owning the standard. A Profullstack, Inc. open-specification project.
|
||||
|
||||
## Core
|
||||
|
||||
- [Home](${SITE_URL}/): Overview, standards surface, schemas, CLI, and reference implementations.
|
||||
- [Docs](${SITE_URL}/docs): Specification guides and conventions.
|
||||
- [OpenSpec](${SITE_URL}/openspec): LogicSRC vs OpenSpec.dev comparison and compatibility mode.
|
||||
- [Blog](${SITE_URL}/blog): Project notes and release announcements.
|
||||
- [Blog RSS](${SITE_URL}/blog/rss.xml): Machine-readable feed of posts.
|
||||
|
||||
## Standards & products
|
||||
|
||||
- [AgentSwarm](${SITE_URL}/agent-swarm): Provider-neutral agent orchestration, model routing, and cost controls.
|
||||
- [AgentByte](${SITE_URL}/agentbyte): Agent screening sessions, policy events, and APIs.
|
||||
- [Credential Sharing](${SITE_URL}/credential-sharing): Source/target credential diffs, approval, sync, rollback, and audit.
|
||||
|
||||
## Company & legal
|
||||
|
||||
- [About](${SITE_URL}/about): What LogicSRC is and who maintains it (Profullstack, Inc.).
|
||||
- [Hire Us](${SITE_URL}/hire-us): Implementation help at $250/week for accepted LogicSRC work.
|
||||
- [Terms](${SITE_URL}/terms)
|
||||
- [Privacy](${SITE_URL}/privacy)
|
||||
`;
|
||||
return new Response(body, {
|
||||
headers: {
|
||||
"content-type": "text/plain; charset=utf-8",
|
||||
"cache-control": "public, max-age=3600",
|
||||
},
|
||||
});
|
||||
}
|
||||
30
apps/logicsrc-web/src/app/robots.ts
Normal file
30
apps/logicsrc-web/src/app/robots.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import type { MetadataRoute } from "next";
|
||||
|
||||
const SITE_URL = (process.env.PUBLIC_URL ?? "https://logicsrc.com").replace(/\/$/, "");
|
||||
|
||||
// Welcome mainstream + AI crawlers; keep them out of the API surface.
|
||||
export default function robots(): MetadataRoute.Robots {
|
||||
return {
|
||||
rules: [
|
||||
{
|
||||
userAgent: [
|
||||
"*",
|
||||
"GPTBot",
|
||||
"OAI-SearchBot",
|
||||
"ChatGPT-User",
|
||||
"ClaudeBot",
|
||||
"Claude-Web",
|
||||
"anthropic-ai",
|
||||
"PerplexityBot",
|
||||
"Google-Extended",
|
||||
"Applebot-Extended",
|
||||
"CCBot",
|
||||
],
|
||||
allow: "/",
|
||||
disallow: ["/api/", "/health"],
|
||||
},
|
||||
],
|
||||
sitemap: `${SITE_URL}/sitemap.xml`,
|
||||
host: SITE_URL,
|
||||
};
|
||||
}
|
||||
42
apps/logicsrc-web/src/app/skill.md/route.ts
Normal file
42
apps/logicsrc-web/src/app/skill.md/route.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
const SITE_URL = (process.env.PUBLIC_URL ?? "https://logicsrc.com").replace(/\/$/, "");
|
||||
|
||||
// GET /skill.md — capability manifest for agents discovering what this site
|
||||
// exposes.
|
||||
export function GET(): Response {
|
||||
const body = `# LogicSRC Skill
|
||||
|
||||
LogicSRC publishes open coordination standards (schemas, primitives, and
|
||||
conventions) for humans, AI agents, plugins, payment systems, and hosted
|
||||
products. CommandBoard.run is the reference implementation.
|
||||
|
||||
Base URL: ${SITE_URL}
|
||||
|
||||
## Resources
|
||||
|
||||
- Specification & docs: ${SITE_URL}/docs
|
||||
- OpenSpec comparison: ${SITE_URL}/openspec
|
||||
- Schemas, CLI, SDK, TUI, and reference implementations: ${SITE_URL}/
|
||||
- Blog feed: ${SITE_URL}/blog/rss.xml
|
||||
- Sitemap: ${SITE_URL}/sitemap.xml
|
||||
- LLM orientation: ${SITE_URL}/llms.txt
|
||||
|
||||
## What you can do here
|
||||
|
||||
- Read the LogicSRC coordination schemas and conventions.
|
||||
- Compare LogicSRC with OpenSpec.dev.
|
||||
- Request paid implementation help via the Hire Us flow (${SITE_URL}/hire-us),
|
||||
billed at $250/week for accepted work and paid through CoinPay.
|
||||
|
||||
## Notes
|
||||
|
||||
- Public marketing/spec content is open to crawl. The /api/ surface is for
|
||||
application use, not crawling.
|
||||
- Contact: implementation requests via the Hire Us form at ${SITE_URL}/hire-us.
|
||||
`;
|
||||
return new Response(body, {
|
||||
headers: {
|
||||
"content-type": "text/markdown; charset=utf-8",
|
||||
"cache-control": "public, max-age=3600",
|
||||
},
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue