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
1 change: 1 addition & 0 deletions docs/api/README.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const SpecLinks = () => {
- New to the API? **[Quickstart](./quickstart)** — mint a key, make your first call, round-trip a resource. ~10 minutes.
- Already know the shape? **[Interactive reference](/api)** — every endpoint, rendered from the OpenAPI spec that ships with each release.
- Generating a client? Point your generator at <SpecLinks />. The same URLs feed the [API client guide](./postman) (Postman, Insomnia, Bruno, Hoppscotch).
- Static fallback for Markdown consumers: production specs are `https://app.trakrf.id/api/openapi.json` and `https://app.trakrf.id/api/openapi.yaml`; preview deployments use the matching `app.preview.trakrf.id` host.

## Concept guides

Expand Down
27 changes: 27 additions & 0 deletions docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,33 @@ const config: Config = {
],
},
],
[
"docusaurus-plugin-llms",
{
generateLLMsTxt: true,
generateLLMsFullTxt: true,
generateMarkdownFiles: true,
preserveDirectoryStructure: true,
addMdExtension: true,
excludeImports: true,
removeDuplicateHeadings: true,
rootContent: [
"This file links every documentation page to its Markdown version. Fetching a single page's `.md` is the most reliable way for an agent to read it.",
"",
`- [Full documentation in one file](${docsHost}/llms-full.txt): every page's content. Large; older changelog entries are linked rather than included.`,
`- [API documentation in one file](${docsHost}/llms-api.txt): the API concept guides only.`,
].join("\n"),
customLLMFiles: [
{
filename: "llms-api.txt",
includePatterns: ["api/**/*.md", "api/**/*.mdx"],
fullContent: true,
title: "TrakRF API Documentation",
description: "Integrator-focused API reference and conventions",
},
],
},
],
],

presets: [
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"scripts": {
"dev": "docusaurus start --host 0.0.0.0",
"prebuild": "node scripts/write-health-json.mjs",
"build": "docusaurus build",
"build": "docusaurus build && node scripts/trim-llms-changelog.mjs && node scripts/check-agent-docs.mjs",
"serve": "docusaurus serve --host 0.0.0.0",
"typecheck": "tsc",
"lint": "prettier --check . && node scripts/check-contributing.mjs",
Expand All @@ -14,7 +14,8 @@
"swizzle": "docusaurus swizzle",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids",
"check:contributing": "node scripts/check-contributing.mjs"
"check:contributing": "node scripts/check-contributing.mjs",
"check:agent-docs": "node scripts/check-agent-docs.mjs"
},
"dependencies": {
"@docusaurus/core": "3.9.2",
Expand All @@ -32,6 +33,7 @@
"@docusaurus/module-type-aliases": "3.9.2",
"@docusaurus/tsconfig": "3.9.2",
"@docusaurus/types": "3.9.2",
"docusaurus-plugin-llms": "0.6.0",
"prettier": "^3.0.0",
"typescript": "~5.6.2"
},
Expand Down
31 changes: 31 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

83 changes: 83 additions & 0 deletions scripts/check-agent-docs.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { readFile } from "node:fs/promises";
import { resolve } from "node:path";

const buildDir = resolve("build");
const requiredFiles = ["llms.txt", "llms-full.txt", "llms-api.txt"];
const markdownFiles = [
"docs/api/authentication.md",
"docs/api/errors.md",
"docs/api/design-notes.md",
"docs/api/quickstart.md",
];

async function readBuildFile(file) {
try {
return await readFile(resolve(buildDir, file), "utf8");
} catch (error) {
throw new Error(`Missing build/${file}: ${error.message}`);
}
}

function assert(condition, message) {
if (!condition) throw new Error(message);
}

const files = new Map(
await Promise.all(
[...requiredFiles, ...markdownFiles].map(async (file) => [
file,
await readBuildFile(file),
]),
),
);

const llms = files.get("llms.txt");
const api = files.get("llms-api.txt");

assert(
/\[[^\]]+\]\([^\s)]+\.md\)/.test(llms),
"llms.txt does not link to Markdown mirrors",
);
assert(api.includes("TrakRF API"), "llms-api.txt does not contain API content");
assert(
!/\/docs\/(?:user-guide|app-tour)\//.test(api),
"llms-api.txt includes user-guide or app-tour content",
);

for (const file of markdownFiles) {
assert(
!/^\s*<!doctype html/i.test(files.get(file)),
`build/${file} is an HTML document, not Markdown`,
);
}

// llms-full.txt is only found by agents that already know the convention
// unless the index points to it.
for (const name of ["llms-full.txt", "llms-api.txt"]) {
assert(llms.includes(`/${name}`), `llms.txt does not link to ${name}`);
}

// The changelog is trimmed in the full-content files, never in its own mirror.
const changelog = await readBuildFile("docs/api/changelog.md");
const sourceChangelog = await readFile(
resolve("docs/api/changelog.md"),
"utf8",
);
const lastSourceEntry = [...sourceChangelog.matchAll(/^### (.+)$/gm)].at(-1)[1];
assert(
changelog.includes(lastSourceEntry.replace(/\s*\{#[^}]+\}$/, "")),
"build/docs/api/changelog.md is missing older entries; the mirror must stay complete",
);
for (const name of ["llms-full.txt", "llms-api.txt"]) {
const text = files.get(name);
assert(
!text.includes(lastSourceEntry.replace(/\s*\{#[^}]+\}$/, "")),
`${name} still carries the full changelog history`,
);
assert(
/Older changelog entries:.*\/docs\/api\/changelog\.md/.test(text),
`${name} does not point to the full changelog`,
);
}

console.log("Agent documentation build output is complete and API-scoped.");
43 changes: 43 additions & 0 deletions scripts/trim-llms-changelog.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env node
// Trims the API changelog inside the single-file agent docs after build.
//
// The changelog is the largest page on the site, and almost all of it is
// pre-launch history. Agents loading llms-full.txt or llms-api.txt get the
// newest entries and a link to the full changelog; the per-page Markdown
// mirror (build/docs/api/changelog.md) is left complete. The llms plugin has
// no per-page trimming, hence a post-build step.

import { readFile, writeFile } from "node:fs/promises";
import { resolve } from "node:path";

const KEEP_ENTRIES = 8;
const buildDir = resolve("build");
const targets = ["llms-full.txt", "llms-api.txt"];

const index = await readFile(resolve(buildDir, "llms.txt"), "utf8");
const changelogUrl = index.match(/\]\((\S+\/docs\/api\/changelog\.md)\)/)?.[1];
if (!changelogUrl) throw new Error("llms.txt has no link to the changelog");

for (const name of targets) {
const path = resolve(buildDir, name);
const text = await readFile(path, "utf8");
const start = text.search(/^## Changelog$/m);
if (start === -1) throw new Error(`${name} has no "## Changelog" section`);
const sepAt = text.indexOf("\n---\n", start);
const end = sepAt === -1 ? text.length : sepAt;
const section = text.slice(start, end);

// Entry headings are level 3 and deeper inside the section, newest first.
const headings = [...section.matchAll(/^#{3,} .*$/gm)];
if (headings.length <= KEEP_ENTRIES) continue;
const cut = headings[KEEP_ENTRIES].index;
const dropped = headings.length - KEEP_ENTRIES;
const trimmed =
section.slice(0, cut).trimEnd() +
`\n\nOlder changelog entries: ${changelogUrl} (${dropped} more entries).\n`;

await writeFile(path, text.slice(0, start) + trimmed + text.slice(end));
console.log(
`${name}: changelog trimmed to ${KEEP_ENTRIES} entries (${dropped} linked).`,
);
}
5 changes: 5 additions & 0 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ function HomepageHeader() {
{siteConfig.title}
</Heading>
<p className="hero__subtitle">{siteConfig.tagline}</p>
<p>
Coding with an agent? Browse our machine-readable documentation at{" "}
<a href="/llms.txt">/llms.txt</a>, or load it all at once from{" "}
<a href="/llms-full.txt">/llms-full.txt</a>.
</p>
<div style={{ marginTop: "1.5rem" }}>
<Link
className="button button--primary button--lg"
Expand Down
Loading