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
143 changes: 143 additions & 0 deletions bin/check-seo-metadata.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { readFileSync, readdirSync, statSync } from "node:fs";
import { dirname, join, relative } from "node:path";
import { fileURLToPath } from "node:url";

const MIN_DESCRIPTION_LENGTH = 150;
const MAX_DESCRIPTION_LENGTH = 160;
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
const fern = join(root, "fern");
const errors = [];
const descriptions = new Map();

function checkDescription(label, value) {
if (value == null || value === "") {
errors.push(`${label}: missing description`);
return;
}

const length = [...value].length;
if (length < MIN_DESCRIPTION_LENGTH || length > MAX_DESCRIPTION_LENGTH) {
errors.push(
`${label}: description is ${length} characters; expected ${MIN_DESCRIPTION_LENGTH}-${MAX_DESCRIPTION_LENGTH}`,
);
}

const matches = descriptions.get(value) ?? [];
matches.push(label);
descriptions.set(value, matches);
}

function frontmatterDescription(file) {
const source = readFileSync(file, "utf8");
if (!source.startsWith("---\n")) return undefined;
const end = source.indexOf("\n---\n", 4);
if (end === -1) return undefined;
const match = source.slice(4, end).match(/^description:\s*(.+)$/m);
if (!match) return undefined;
const raw = match[1].trim();
if (raw.startsWith('"')) {
try {
return JSON.parse(raw);
} catch {
return undefined;
}
}
return raw.replace(/^['"]|['"]$/g, "");
}

function walkYaml(directory) {
return readdirSync(directory).flatMap((entry) => {
const file = join(directory, entry);
return statSync(file).isDirectory()
? walkYaml(file)
: file.endsWith(".yml")
? [file]
: [];
});
}

function followingDocs(lines, start, itemIndent) {
const nextItem = new RegExp(`^ {${itemIndent}}[A-Za-z0-9_-]+:$`);
const docs = new RegExp(`^ {${itemIndent + 2}}docs:(?:\\s+(.*))?$`);
for (let index = start + 1; index < lines.length; index += 1) {
if (nextItem.test(lines[index])) return undefined;
const match = lines[index].match(docs);
if (!match) continue;
if (match[1] && match[1] !== "|") return match[1].trim();
for (let content = index + 1; content < lines.length; content += 1) {
const value = lines[content].trim();
if (value) return value;
}
return undefined;
}
return undefined;
}

const docsPath = join(fern, "docs.yml");
const docsSource = readFileSync(docsPath, "utf8");
const activeMdx = [
...docsSource.matchAll(/^\s+(?:path|summary): (.+\.mdx)$/gm),
].map((match) => match[1]);

for (const mdx of activeMdx) {
checkDescription(mdx, frontmatterDescription(join(fern, mdx)));
}

const globalDescription = docsSource.match(
/^\s+og:description:\s*"([^"]+)"$/m,
)?.[1];
checkDescription("docs.yml metadata.og:description", globalDescription);
checkDescription(
"changelog/overview.mdx",
frontmatterDescription(join(fern, "changelog", "overview.mdx")),
);

for (const file of walkYaml(join(fern, "definition"))) {
const lines = readFileSync(file, "utf8").split("\n");
const label = relative(root, file);
for (let index = 0; index < lines.length; index += 1) {
const endpoint = lines[index].match(/^ {6}display-name:\s*(.+)$/);
if (endpoint) {
checkDescription(
`${label}: ${endpoint[1]}`,
followingDocs(lines, index, 4),
);
continue;
}

const webhook = lines[index].match(/^ {4}display-name:\s*(.+)$/);
if (file.endsWith("/webhooks/events.yml") && webhook) {
checkDescription(
`${label}: ${webhook[1]}`,
followingDocs(lines, index, 2),
);
}
}
}

const channelSource = readFileSync(
join(fern, "definition", "websockets.yml"),
"utf8",
);
checkDescription(
"fern/definition/websockets.yml: Connect",
channelSource.match(/^\s{2}docs:\s*(.+)$/m)?.[1],
);

for (const [description, labels] of descriptions) {
if (labels.length > 1) {
errors.push(
`duplicate description for ${labels.join(", ")}: ${description}`,
);
}
}

if (errors.length > 0) {
console.error(errors.join("\n"));
process.exit(1);
}

console.log(
`SEO metadata check passed: ${activeMdx.length} MDX pages, ` +
`${descriptions.size - activeMdx.length - 2} generated API pages, and site defaults.`,
);
4 changes: 4 additions & 0 deletions fern/changelog/overview.mdx
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
description: "Follow AgentMail API, SDK, webhook, WebSocket, and platform updates, including new capabilities, behavior changes, bug fixes, and clear migration guidance."
---

# AgentMail Changelog

Latest API and SDK updates. [Subscribe via RSS](https://docs.agentmail.to/changelog.rss) · [Discord](https://discord.gg/hTYatWYWBc)
4 changes: 4 additions & 0 deletions fern/definition/agent.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ service:
path: /sign-up
display-name: Sign Up
docs: |
Use the AgentMail API to create an agent organization, inbox, and API key. Review authentication, parameters, response fields, errors, and usage details.

Create a new agent organization with an inbox and API key. This endpoint is for signing up for the first time. If you've already signed up, you're all set — just use your existing API key.

A 6-digit OTP is sent to the human's email for verification.
Expand All @@ -87,6 +89,8 @@ service:
display-name: Verify
auth: true
docs: |
Use the AgentMail API to verify a new agent organization with its one-time passcode. Review authentication, parameters, responses, errors, and usage details.

Verify an agent organization using the 6-digit OTP sent to the human's email during sign-up.

On success, the organization is upgraded from `agent_unverified` to `agent_verified`, the send allowlist is removed, and free plan entitlements are applied.
Expand Down
24 changes: 20 additions & 4 deletions fern/definition/api-keys.yml
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,8 @@ service:
path: ""
display-name: List API Keys
docs: |
Use the AgentMail API to list API keys for the authenticated organization. Review authentication, parameters, response fields, errors, and usage details.

**CLI:**
```bash
agentmail api-keys list
Expand All @@ -349,6 +351,8 @@ service:
path: ""
display-name: Create API Key
docs: |
Use the AgentMail API to create an API key for the authenticated organization. Review authentication, parameters, response fields, errors, and usage details.

**CLI:**
```bash
agentmail api-keys create --name "My Key"
Expand All @@ -363,6 +367,8 @@ service:
path: /{api_key_id}
display-name: Delete API Key
docs: |
Use the AgentMail API to delete an API key for the authenticated organization. Review authentication, parameters, response fields, errors, and usage details.

**CLI:**
```bash
agentmail api-keys delete --api-key-id <api_key_id>
Expand All @@ -377,7 +383,9 @@ service:
path: /public-keys
display-name: List Public-Key Credentials
docs: |
List only public-key credentials visible to the bearer caller's scope.
List public-key credentials visible to the bearer API key scope, with pagination details, while excluding bearer credentials from every returned result.

Only public-key credentials visible to the bearer caller's scope are returned.
Bearer credentials are never returned, even though both credential types
share storage and pagination indexes. Requires `api_key_read`.
request:
Expand All @@ -393,7 +401,9 @@ service:
path: /public-keys
display-name: Register Public-Key Credential
docs: |
Register a public P-256 JWK using an existing AgentMail bearer API key
Register a scoped P-256 public JWK for AgentID sign-in using an authorized bearer API key, while keeping all private key material entirely outside AgentMail.

Register the JWK using an existing AgentMail bearer API key
with `api_key_create`. Re-registering the same JWK creates a new
credential ID; it does not replace or recover an earlier credential.
The private key must never be sent to AgentMail.
Expand All @@ -408,7 +418,9 @@ service:
path: /public-keys/{api_key_id}
display-name: Rename Public-Key Credential
docs: |
Rename the credential. All security-relevant fields are immutable.
Rename an existing public-key credential while preserving its immutable key material, identifier, scope, permissions, generation, and expiration settings.

All security-relevant fields are immutable.
Requires `api_key_update`.
path-parameters:
api_key_id:
Expand All @@ -425,7 +437,9 @@ service:
path: /public-keys/{api_key_id}
display-name: Revoke Public-Key Credential
docs: |
Permanently revoke one public-key credential. This hard-deletes the
Permanently revoke and delete one public-key credential by its ID, requiring API key deletion permission and returning not found if the request repeats.

This hard-deletes the
credential; repeating the request returns not found. Requires
`api_key_delete`.
path-parameters:
Expand All @@ -442,6 +456,8 @@ service:
path: /public-keys/agentid-sign-in/revoke-all
display-name: Revoke All AgentID Sign-In Keys
docs: |
Revoke every current AgentID public-key sign-in credential in an organization with a required idempotency key and a permanent generation-change receipt.

Invalidate every current public-key credential in the caller's
organization by advancing its AgentID key generation. The caller must be
organization-scoped and either have `api_key_delete` or, for a verified
Expand Down
2 changes: 2 additions & 0 deletions fern/definition/auth.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ service:
path: /me
display-name: Who Am I
docs: |
Use the AgentMail API to inspect the identity and scope of the current credential. Review authentication, parameters, responses, errors, and usage details.

Returns the identity and scope of the authenticated credential. Useful when a client holds a pod-scoped or inbox-scoped API key and needs to discover the parent organization, pod, or inbox without prior knowledge.

**CLI:**
Expand Down
14 changes: 14 additions & 0 deletions fern/definition/domains.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ service:
path: ""
display-name: List Domains
docs: |
Use the AgentMail API to list domains for the authenticated organization. Review authentication, parameters, response fields, errors, and usage details.

**CLI:**
```bash
agentmail domains list
Expand All @@ -159,6 +161,8 @@ service:
path: /{domain_id}
display-name: Get Domain
docs: |
Use the AgentMail API to get a domain for the authenticated organization. Review authentication, parameters, response fields, errors, and usage details.

**CLI:**
```bash
agentmail domains get --domain-id <domain_id>
Expand All @@ -174,6 +178,8 @@ service:
path: /{domain_id}/zone-file
display-name: Get Zone File
docs: |
Use the AgentMail API to get a zone file for the authenticated organization. Review authentication, parameters, response fields, errors, and usage details.

**CLI:**
```bash
agentmail domains get-zone-file --domain-id <domain_id>
Expand All @@ -189,6 +195,8 @@ service:
path: ""
display-name: Create Domain
docs: |
Use the AgentMail API to create a domain for the authenticated organization. Review authentication, parameters, response fields, errors, and usage details.

**CLI:**
```bash
agentmail domains create --domain example.com
Expand All @@ -203,6 +211,8 @@ service:
path: /{domain_id}
display-name: Update Domain
docs: |
Use the AgentMail API to update a domain for the authenticated organization. Review authentication, parameters, response fields, errors, and usage details.

**CLI:**
```bash
agentmail domains update --domain-id <domain_id>
Expand All @@ -219,6 +229,8 @@ service:
path: /{domain_id}
display-name: Delete Domain
docs: |
Use the AgentMail API to delete a domain for the authenticated organization. Review authentication, parameters, response fields, errors, and usage details.

**CLI:**
```bash
agentmail domains delete --domain-id <domain_id>
Expand All @@ -233,6 +245,8 @@ service:
path: /{domain_id}/verify
display-name: Verify Domain
docs: |
Use the AgentMail API to verify a domain for the authenticated organization. Review authentication, parameters, response fields, errors, and usage details.

**CLI:**
```bash
agentmail domains verify --domain-id <domain_id>
Expand Down
6 changes: 6 additions & 0 deletions fern/definition/drafts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@ service:
path: ""
display-name: List Drafts
docs: |
Use the AgentMail API to list drafts for the authenticated organization. Review authentication, parameters, response fields, errors, and usage details.

**CLI:**
```bash
agentmail drafts list
Expand All @@ -230,6 +232,8 @@ service:
path: /{draft_id}
display-name: Get Draft
docs: |
Use the AgentMail API to get a draft for the authenticated organization. Review authentication, parameters, response fields, errors, and usage details.

**CLI:**
```bash
agentmail drafts get --draft-id <draft_id>
Expand All @@ -245,6 +249,8 @@ service:
path: /{draft_id}/attachments/{attachment_id}
display-name: Get Attachment
docs: |
Use the AgentMail API to get an attachment from a draft for the authenticated organization. Review authentication, parameters, responses, and error behavior.

**CLI:**
```bash
agentmail drafts get-attachment --draft-id <draft_id> --attachment-id <attachment_id>
Expand Down
10 changes: 10 additions & 0 deletions fern/definition/inboxes/__package__.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ service:
path: ""
display-name: List Inboxes
docs: |
Use the AgentMail API to list inboxes for the authenticated organization. Review authentication, parameters, response fields, errors, and usage details.

**CLI:**
```bash
agentmail inboxes list
Expand All @@ -138,6 +140,8 @@ service:
path: /{inbox_id}
display-name: Get Inbox
docs: |
Use the AgentMail API to get an inbox for the authenticated organization. Review authentication, parameters, response fields, errors, and usage details.

**CLI:**
```bash
agentmail inboxes get --inbox-id <inbox_id>
Expand All @@ -153,6 +157,8 @@ service:
path: ""
display-name: Create Inbox
docs: |
Use the AgentMail API to create an inbox for the authenticated organization. Review authentication, parameters, response fields, errors, and usage details.

**CLI:**
```bash
agentmail inboxes create --display-name "My Agent" --username myagent --domain agentmail.to
Expand All @@ -168,6 +174,8 @@ service:
path: /{inbox_id}
display-name: Update Inbox
docs: |
Use the AgentMail API to update an inbox for the authenticated organization. Review authentication, parameters, response fields, errors, and usage details.

**CLI:**
```bash
agentmail inboxes update --inbox-id <inbox_id> --display-name "Updated Name"
Expand All @@ -188,6 +196,8 @@ service:
path: /{inbox_id}
display-name: Delete Inbox
docs: |
Use the AgentMail API to delete an inbox for the authenticated organization. Review authentication, parameters, response fields, errors, and usage details.

**CLI:**
```bash
agentmail inboxes delete --inbox-id <inbox_id>
Expand Down
Loading
Loading