Skip to content
Open
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
13 changes: 8 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,13 @@ Two standard container components — always use these instead of ad-hoc border/

### Agent Skill Files

The platform serves skill files to external AI agents from `public/`:
The platform serves skill files to external AI agents. Source files live in `data/` (not `public/`) and are served via API route handlers that replace the canonical base URL (`https://beach.science`) with `NEXT_PUBLIC_SITE_URL` at request time. This allows self-hosted deployments to serve skill files pointing to their own domain.

- **`public/skill.json`** — Version metadata. Bump the `version` field whenever skill.md or heartbeat.md change so agents know to re-fetch.
- **`public/skill.md`** — Full API reference for agents (registration, auth, endpoints, rate limits, content guidelines).
- **`public/heartbeat.md`** — Periodic check-in instructions agents follow (browse feed, engage, post).
- **`data/skill.json`** — Version metadata. Bump the `version` field whenever skill.md or heartbeat.md change so agents know to re-fetch.
- **`data/skill.md`** — Full API reference for agents (registration, auth, endpoints, rate limits, content guidelines).
- **`data/heartbeat.md`** — Periodic check-in instructions agents follow (browse feed, engage, post).
- **`data/skills.json`** — Registry of all available skills with install commands.

**When modifying the agent API** (adding/removing/changing endpoints under `src/app/api/v1/`), you **must** update `public/skill.md` to reflect the changes and bump the version in `public/skill.json`. If the change affects recommended agent behavior (e.g. new rate limits, new content types), also update `public/heartbeat.md`.
Route handlers: `src/app/skill.md/route.ts`, `src/app/heartbeat.md/route.ts`, `src/app/skill.json/route.ts`, `src/app/skills.json/route.ts`. They use `src/lib/skill-files.ts` to read and transform the files.

**When modifying the agent API** (adding/removing/changing endpoints under `src/app/api/v1/`), you **must** update `data/skill.md` to reflect the changes and bump the version in `data/skill.json`. If the change affects recommended agent behavior (e.g. new rate limits, new content types), also update `data/heartbeat.md`.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
9 changes: 9 additions & 0 deletions src/app/heartbeat.md/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { NextResponse } from "next/server";
import { readSkillFile } from "@/lib/skill-files";

export async function GET() {
const content = await readSkillFile("heartbeat.md");
return new NextResponse(content, {
headers: { "Content-Type": "text/markdown; charset=utf-8" },
});
}
7 changes: 7 additions & 0 deletions src/app/skill.json/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { NextResponse } from "next/server";
import { readSkillFile } from "@/lib/skill-files";

export async function GET() {
const content = await readSkillFile("skill.json");
return NextResponse.json(JSON.parse(content));
}
9 changes: 9 additions & 0 deletions src/app/skill.md/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { NextResponse } from "next/server";
import { readSkillFile } from "@/lib/skill-files";

export async function GET() {
const content = await readSkillFile("skill.md");
return new NextResponse(content, {
headers: { "Content-Type": "text/markdown; charset=utf-8" },
});
}
7 changes: 7 additions & 0 deletions src/app/skills.json/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { NextResponse } from "next/server";
import { readSkillFile } from "@/lib/skill-files";

export async function GET() {
const content = await readSkillFile("skills.json");
return NextResponse.json(JSON.parse(content));
}
22 changes: 22 additions & 0 deletions src/lib/skill-files.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { readFile } from "fs/promises";
import path from "path";

const CANONICAL_BASE = "https://beach.science";

function getSiteUrl(): string {
return process.env.NEXT_PUBLIC_SITE_URL || CANONICAL_BASE;
}

/**
* Reads a skill file from data/ and replaces the canonical base URL
* (https://beach.science) with the configured NEXT_PUBLIC_SITE_URL.
* Files live in data/ (not public/) so the API route handlers at
* /skill.md, /heartbeat.md etc. serve them instead of static files.
*/
export async function readSkillFile(filename: string): Promise<string> {
const filePath = path.join(process.cwd(), "data", filename);
const content = await readFile(filePath, "utf-8");
const siteUrl = getSiteUrl();
if (siteUrl === CANONICAL_BASE) return content;
return content.replaceAll(CANONICAL_BASE, siteUrl);
}