Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 152 additions & 2 deletions agent-edge.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const AGENT_SURFACE = {
"name": "RolePatch",
"url": "https://rolepatch.com",
"llmsFullTxt": "# RolePatch — full agent brief\n\nAI-powered resume tailoring. Score fit against a job description, rewrite bullets for the role, and prep interviews.\n\n## Index\n\n# RolePatch\n\nAI-powered resume tailoring and job-application assistant.\n\n## What it is\n\n- Score resume fit against a job description\n- Rewrite bullets for the role\n- Cover letters, company research, STAR prep\n\n## Who it's for\n\nJob seekers who want reviewed, role-specific application materials — not generic AI fluff.\n\n## Agent entrypoints\n\n- https://rolepatch.com/llms.txt\n- https://rolepatch.com/api/ai\n- https://rolepatch.com/index.md\n\nDashboard routes require auth and are not agent-indexed.\n\n## Product links\n\n- Home: https://rolepatch.com/ — Product landing\n- Pricing: https://rolepatch.com/pricing — Plans and limits\n- Tools: https://rolepatch.com/tools — Public tools\n\n## Machine surfaces\n\n- https://rolepatch.com/llms.txt\n- https://rolepatch.com/llms-full.txt\n- https://rolepatch.com/api/ai\n- https://rolepatch.com/index.md\n- https://rolepatch.com/sitemap.xml\n- https://rolepatch.com/robots.txt\n\n## Contact\n\n- Owner: https://sarthakagrawal.dev\n- Agent email for directory verification: sarthakagrawal@agentmail.to\n",
"llmsTxt": "# RolePatch\n\n> AI-powered resume tailoring. Score fit against a job description, rewrite bullets for the role, and prep interviews.\n\n## Product\n\n- [Home](https://rolepatch.com/): Product landing\n- [Pricing](https://rolepatch.com/pricing): Plans and limits\n- [Tools](https://rolepatch.com/tools): Public tools\n\n## Machine surfaces\n\n- [Agent catalog](https://rolepatch.com/api/ai): JSON inventory of public surfaces\n- [Homepage markdown](https://rolepatch.com/index.md): Product brief without JS\n- [This index](https://rolepatch.com/llms.txt)\n",
"llmsTxt": "# RolePatch\n\n> AI-powered resume tailoring. Score fit against a job description, rewrite bullets for the role, and prep interviews.\n\n## Product\n\n- [Home](https://rolepatch.com/): Product landing\n- [Pricing](https://rolepatch.com/pricing): Plans and limits\n- [Tools](https://rolepatch.com/tools): Public tools\n\n## When to use this\n\n- Tailoring a resume to a specific job description before applying\n- Scoring fit between a resume and a job posting to prioritize applications\n- Generating role-specific cover letters and STAR interview prep stories\n- Discovering public tools for job seekers (fit scoring, resume analysis)\n\n## Machine surfaces\n\n- [Agent catalog](https://rolepatch.com/api/ai): JSON inventory of public surfaces\n- [OpenAPI spec](https://rolepatch.com/openapi.json): Machine-readable API description\n- [Homepage markdown](https://rolepatch.com/index.md): Product brief without JS\n- [This index](https://rolepatch.com/llms.txt)\n",
"indexMd": "# RolePatch\n\nAI-powered resume tailoring and job-application assistant.\n\n## What it is\n\n- Score resume fit against a job description\n- Rewrite bullets for the role\n- Cover letters, company research, STAR prep\n\n## Who it's for\n\nJob seekers who want reviewed, role-specific application materials — not generic AI fluff.\n\n## Agent entrypoints\n\n- https://rolepatch.com/llms.txt\n- https://rolepatch.com/api/ai\n- https://rolepatch.com/index.md\n\nDashboard routes require auth and are not agent-indexed.\n",
"catalog": {
"name": "RolePatch",
Expand All @@ -24,6 +24,7 @@ const AGENT_SURFACE = {
"llmsFull": "https://rolepatch.com/llms-full.txt",
"sitemap": "https://rolepatch.com/sitemap.xml",
"robots": "https://rolepatch.com/robots.txt",
"openapi": "https://rolepatch.com/openapi.json",
"markdown": {
"suffix": ".md",
"negotiation": true
Expand Down Expand Up @@ -58,6 +59,136 @@ const AGENT_SURFACE = {
}
};

const OPENAPI_SPEC = {
openapi: '3.1.0',
info: {
title: 'RolePatch public API',
version: '1.0.0',
description:
'RolePatch is an AI-powered resume tailoring and job-application assistant. The public web API exposes read-only agent surfaces: the agent catalog, llms.txt, sitemap, and markdown alternates.',
contact: { name: 'RolePatch', url: 'https://rolepatch.com' },
},
servers: [{ url: 'https://rolepatch.com' }],
tags: [{ name: 'agent-surfaces', description: 'Machine-readable public surfaces' }],
paths: {
'/api/ai': {
get: {
operationId: 'getAgentCatalog',
tags: ['agent-surfaces'],
summary: 'Agent catalog',
description:
'JSON inventory of public agent surfaces: llms.txt, llms-full.txt, sitemap, robots, and per-page markdown alternates.',
responses: {
200: {
description: 'Agent catalog',
content: {
'application/json': { schema: { $ref: '#/components/schemas/AgentCatalog' } },
},
},
},
},
},
'/llms.txt': {
get: {
operationId: 'getLlmsTxt',
tags: ['agent-surfaces'],
summary: 'llms.txt index',
description: 'Compact agent index following the llms.txt convention.',
responses: { 200: { description: 'Markdown index', content: { 'text/plain': {} } } },
},
},
'/llms-full.txt': {
get: {
operationId: 'getLlmsFullTxt',
tags: ['agent-surfaces'],
summary: 'Full agent brief',
description:
'Full canonical agent brief with product, architecture, and surface inventory.',
responses: { 200: { description: 'Markdown brief', content: { 'text/plain': {} } } },
},
},
'/sitemap.xml': {
get: {
operationId: 'getSitemap',
tags: ['agent-surfaces'],
summary: 'Sitemap',
responses: { 200: { description: 'XML sitemap', content: { 'application/xml': {} } } },
},
},
'/openapi.json': {
get: {
operationId: 'getOpenApiSpec',
tags: ['agent-surfaces'],
summary: 'OpenAPI specification',
description: 'This document.',
responses: {
200: { description: 'OpenAPI 3.1 spec', content: { 'application/json': {} } },
},
},
},
},
components: {
schemas: {
AgentCatalog: {
type: 'object',
properties: {
name: { type: 'string' },
version: { type: 'string' },
url: { type: 'string', format: 'uri' },
llms: { type: 'string', format: 'uri' },
llmsFull: { type: 'string', format: 'uri' },
sitemap: { type: 'string', format: 'uri' },
robots: { type: 'string', format: 'uri' },
openapi: { type: 'string', format: 'uri' },
markdown: {
type: 'object',
properties: {
suffix: { type: 'string' },
negotiation: { type: 'boolean' },
},
},
surfaces: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string' },
url: { type: 'string', format: 'uri' },
md: { type: 'string', format: 'uri', nullable: true },
kind: { type: 'string' },
description: { type: 'string' },
},
},
},
},
},
},
},
};

function markdown404(pathname, origin) {
const body = `# 404 — Not Found

\`${pathname}\` does not exist on ${origin}.

## Where to look next

- [Home](${origin}/)
- [Sitemap](${origin}/sitemap.xml)
- [Agent index](${origin}/llms.txt)
- [Full agent brief](${origin}/llms-full.txt)
- [Agent catalog (JSON)](${origin}/api/ai)
`;
return new Response(body, {
status: 404,
headers: {
'content-type': 'text/markdown; charset=utf-8',
'cache-control': 'no-store',
'x-content-type-options': 'nosniff',
},
});
}

/**
* @param {Request} request
* @returns {Response | null}
Expand All @@ -67,6 +198,18 @@ export function handleAgentEdge(request) {
const url = new URL(request.url);
const path = url.pathname === '' ? '/' : url.pathname;

// /openapi.json — serve the spec directly.
if (path === '/openapi.json' || path === '/openapi.yaml') {
return new Response(JSON.stringify(OPENAPI_SPEC, null, 2), {
status: 200,
headers: {
'content-type': 'application/json; charset=utf-8',
'access-control-allow-origin': '*',
'cache-control': 'public, max-age=3600',
},
});
}

if (path === '/llms.txt') {
return text(AGENT_SURFACE.llmsTxt, 'text/plain; charset=utf-8');
}
Expand All @@ -86,6 +229,7 @@ export function handleAgentEdge(request) {
sitemap: AGENT_SURFACE.catalog.sitemap
? String(AGENT_SURFACE.catalog.sitemap).replace(AGENT_SURFACE.url, url.origin)
: `${url.origin}/sitemap.xml`,
openapi: `${url.origin}/openapi.json`,
surfaces: (AGENT_SURFACE.catalog.surfaces || []).map((s) => ({
...s,
url: s.url ? String(s.url).replace(AGENT_SURFACE.url, url.origin) : s.url,
Expand All @@ -99,10 +243,16 @@ export function handleAgentEdge(request) {
if ((path === '/' || path === '') && wantsMarkdown(request)) {
return text(AGENT_SURFACE.indexMd, 'text/markdown; charset=utf-8', {
Link: '</index.md>; rel="alternate"; type="text/markdown"',
Vary: 'Accept',
Vary: 'Accept, Accept-Encoding',
});
}

// Agent-friendly 404: return a markdown recovery body for unknown paths
// when the client asks for markdown.
if (wantsMarkdown(request) && !path.includes('.') && !path.startsWith('/api/')) {
return markdown404(path, url.origin);
}

return null;
}

Expand Down
2 changes: 1 addition & 1 deletion next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const securityHeaders = [
// unsafe-eval required by Next.js dev + some libs.
// static.cloudflareinsights.com is the Cloudflare Web Analytics beacon
// that Cloudflare injects automatically on the rolepatch.com zone.
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://static.cloudflareinsights.com https://us-assets.i.posthog.com",
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://static.cloudflareinsights.com https://us-assets.i.posthog.com https://www.clarity.ms",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"font-src 'self'",
Expand Down
1 change: 1 addition & 0 deletions public/api-ai.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"llmsFull": "https://rolepatch.com/llms-full.txt",
"sitemap": "https://rolepatch.com/sitemap.xml",
"robots": "https://rolepatch.com/robots.txt",
"openapi": "https://rolepatch.com/openapi.json",
"markdown": {
"suffix": ".md",
"negotiation": true
Expand Down
8 changes: 8 additions & 0 deletions public/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,16 @@
- [Pricing](https://rolepatch.com/pricing): Plans and limits
- [Tools](https://rolepatch.com/tools): Public tools

## When to use this

- Tailoring a resume to a specific job description before applying
- Scoring fit between a resume and a job posting to prioritize applications
- Generating role-specific cover letters and STAR interview prep stories
- Discovering public tools for job seekers (fit scoring, resume analysis)

## Machine surfaces

- [Agent catalog](https://rolepatch.com/api/ai): JSON inventory of public surfaces
- [OpenAPI spec](https://rolepatch.com/openapi.json): Machine-readable API description
- [Homepage markdown](https://rolepatch.com/index.md): Product brief without JS
- [This index](https://rolepatch.com/llms.txt)
6 changes: 6 additions & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ export default function RootLayout({
}}
/>
{/* fleet-jsonld:end */}
{/* Microsoft Clarity — session replay + heatmaps (additive; PostHog stays) */}
<script
dangerouslySetInnerHTML={{
__html: `(function(c,l,a,r,i,t,y){c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/y6buz6rkot";y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);})(window,document,"clarity","script","y6buz6rkot");window.clarity("set","project_id","rolepatch");`,
}}
/>
</head>
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
<AnalyticsProvider>
Expand Down
Loading